From 2a0571ba87183023b1073af1badb5fb91da763cf Mon Sep 17 00:00:00 2001
From: ConfuSomu
Date: Sat, 27 Feb 2021 13:29:37 -0500
Subject: Use an Api class for abstraction

It is currently very basic as functions will be added to it when the time
comes. The idea is to have a method to, for example, show notifications
(and store them in a list with metadata) to the user or also to easily
show a message box overlaying the current display.

Private class members will permit encapsulations of variables related
to these features and limit the privilage that each app has on the
device.
---
 pico-watch.cpp | 25 ++++++++++++++-----------
 1 file changed, 14 insertions(+), 11 deletions(-)

(limited to 'pico-watch.cpp')

diff --git a/pico-watch.cpp b/pico-watch.cpp
index 243f1e1..e888ba1 100644
--- a/pico-watch.cpp
+++ b/pico-watch.cpp
@@ -6,49 +6,51 @@
 #include "oled/ss_oled.h"
 
 #include "init.hpp"
+#include "api.hpp"
 #include "buttons.hpp"
 #include "apps/main_clock.hpp"
 #include "apps/home_menu.hpp"
 
 int current_app = 0;
+Api app_api;
 
 #define NUMBER_OF_APPS 2
 #define APP_DATA_BUFFER_LEN 256
-int (*APPS_FUNC_INIT[NUMBER_OF_APPS])(SSOLED *oled) = {app_home_menu::init, app_main_clock::init};
-int (*APPS_FUNC_RENDER[NUMBER_OF_APPS])(SSOLED *oled) = {app_home_menu::render, app_main_clock::render};
-int (*APPS_FUNC_BTNPRESS[NUMBER_OF_APPS])(SSOLED *oled, uint gpio) = {app_home_menu::btnpressed, app_main_clock::btnpressed};
-int (*APPS_FUNC_BGREFRESH[NUMBER_OF_APPS])(SSOLED *oled, char in_foreground) = {app_home_menu::bgrefresh, app_main_clock::bgrefresh};
-int (*APPS_FUNC_DESTROY[NUMBER_OF_APPS])(SSOLED *oled) = {app_home_menu::destroy, app_main_clock::destroy};
+int (*APPS_FUNC_INIT[NUMBER_OF_APPS])(Api *app_api) = {app_home_menu::init, app_main_clock::init};
+int (*APPS_FUNC_RENDER[NUMBER_OF_APPS])(Api *app_api) = {app_home_menu::render, app_main_clock::render};
+int (*APPS_FUNC_BTNPRESS[NUMBER_OF_APPS])(Api *app_api, uint gpio) = {app_home_menu::btnpressed, app_main_clock::btnpressed};
+int (*APPS_FUNC_BGREFRESH[NUMBER_OF_APPS])(Api *app_api, char in_foreground) = {app_home_menu::bgrefresh, app_main_clock::bgrefresh};
+int (*APPS_FUNC_DESTROY[NUMBER_OF_APPS])(Api *app_api) = {app_home_menu::destroy, app_main_clock::destroy};
 int APPS_DESTROY_ON_EXIT[NUMBER_OF_APPS] = {0, 1};
 int APPS_IS_INIT[NUMBER_OF_APPS] = {0, 0}; // Only run in background if init
 
 int app_init(int app_id) {
-    oledFill(&oled, 0,1); // Clear OLED
+    app_api.dispFill(0,1); // Clear OLED
     if (!APPS_IS_INIT[app_id]) {
         APPS_IS_INIT[app_id] = 1;
-        return (*APPS_FUNC_INIT[app_id])(&oled);
+        return (*APPS_FUNC_INIT[app_id])(&app_api);
     }
 }
 
 int app_render(int app_id) {
-    return (*APPS_FUNC_RENDER[app_id])(&oled);
+    return (*APPS_FUNC_RENDER[app_id])(&app_api);
 }
 
 int app_btnpressed(int app_id, uint gpio) {
-    return (*APPS_FUNC_BTNPRESS[app_id])(&oled, gpio);
+    return (*APPS_FUNC_BTNPRESS[app_id])(&app_api, gpio);
 }
 
 int app_destroy(int app_id) {
     // TODO: reset APPS_DATA for the app
     if (APPS_IS_INIT[app_id]) {
         APPS_IS_INIT[app_id] = 0;
-        return (*APPS_FUNC_DESTROY[app_id])(&oled);
+        return (*APPS_FUNC_DESTROY[app_id])(&app_api);
     }
 }
 
 int app_bgrefresh(int app_id) {
     if (APPS_IS_INIT[app_id])
-        return (*APPS_FUNC_BGREFRESH[app_id])(&oled, app_id==current_app);
+        return (*APPS_FUNC_BGREFRESH[app_id])(&app_api, app_id==current_app);
 }
 
 bool apps_bgrefresh(struct repeating_timer *t) {  // TODO: Refresh done on core1
@@ -69,6 +71,7 @@ void app_switch(int old_appid, int new_appid) {
 int main() {
     init_all();
     init_buttons();
+    app_api.init();
     struct repeating_timer timer;
     add_repeating_timer_ms(250, apps_bgrefresh, NULL, &timer);
 
-- 
cgit v1.2.3-54-g00ecf


From ccf1d4f74eaf9c56fe2ea620ddc6e12746a8b108 Mon Sep 17 00:00:00 2001
From: ConfuSomu
Date: Sat, 27 Feb 2021 15:11:38 -0500
Subject: Remove unnecessary includes

---
 api.hpp             | 1 -
 apps/home_menu.cpp  | 1 -
 apps/main_clock.cpp | 1 -
 pico-watch.cpp      | 1 -
 4 files changed, 4 deletions(-)

(limited to 'pico-watch.cpp')

diff --git a/api.hpp b/api.hpp
index 0e4992f..735d55b 100644
--- a/api.hpp
+++ b/api.hpp
@@ -1,7 +1,6 @@
 #ifndef __API_H__
 #define __API_H__
 
-#include "pico/util/datetime.h"
 #include "oled/ss_oled.h"
 
 class Api {
diff --git a/apps/home_menu.cpp b/apps/home_menu.cpp
index c261611..8f0ee8b 100644
--- a/apps/home_menu.cpp
+++ b/apps/home_menu.cpp
@@ -4,7 +4,6 @@ extern "C" {
 #include "hardware/rtc.h"
 }
 #include "pico/util/datetime.h"
-#include "../oled/ss_oled.h"
 
 #include "home_menu.hpp"
 #include "../api.hpp"
diff --git a/apps/main_clock.cpp b/apps/main_clock.cpp
index db8e39e..9939e4e 100644
--- a/apps/main_clock.cpp
+++ b/apps/main_clock.cpp
@@ -4,7 +4,6 @@ extern "C" {
 #include "hardware/rtc.h"
 }
 #include "pico/util/datetime.h"
-#include "../oled/ss_oled.h"
 
 #include "main_clock.hpp"
 #include "../api.hpp"
diff --git a/pico-watch.cpp b/pico-watch.cpp
index e888ba1..181d708 100644
--- a/pico-watch.cpp
+++ b/pico-watch.cpp
@@ -3,7 +3,6 @@
 #include "hardware/i2c.h"
 #include "hardware/rtc.h"
 #include "pico/util/datetime.h"
-#include "oled/ss_oled.h"
 
 #include "init.hpp"
 #include "api.hpp"
-- 
cgit v1.2.3-54-g00ecf


From 85f79065384ba77614ddb0212d22704253321587 Mon Sep 17 00:00:00 2001
From: ConfuSomu
Date: Sat, 27 Feb 2021 18:06:46 -0500
Subject: Refactor API function names

---
 api.cpp             |  6 +++---
 api.hpp             |  6 +++---
 apps/home_menu.cpp  |  8 ++++----
 apps/main_clock.cpp | 10 +++++-----
 pico-watch.cpp      |  2 +-
 5 files changed, 16 insertions(+), 16 deletions(-)

(limited to 'pico-watch.cpp')

diff --git a/api.cpp b/api.cpp
index 98b10f1..4be8457 100644
--- a/api.cpp
+++ b/api.cpp
@@ -23,14 +23,14 @@ void Api::init_display() {
     //oledSetTextWrap(&oled, true);
 }
 
-int Api::dispWriteString(int iScrollX, int x, int y, char *szMsg, int iSize, int bInvert, int bRender) {
+int Api::display_write_string(int iScrollX, int x, int y, char *szMsg, int iSize, int bInvert, int bRender) {
     return oledWriteString(&m_oled, iScrollX, x, y, szMsg, iSize, bInvert, bRender);
 }
 
-void Api::dispFill(unsigned char ucData, int bRender) {
+void Api::display_fill(unsigned char ucData, int bRender) {
     oledFill(&m_oled, ucData, bRender);
 }
 
-bool Api::getDatetime(datetime_t *t) {
+bool Api::datetime_get(datetime_t *t) {
     return rtc_get_datetime(t);
 }
diff --git a/api.hpp b/api.hpp
index 3863388..9616a8d 100644
--- a/api.hpp
+++ b/api.hpp
@@ -11,9 +11,9 @@ class Api {
         void init_display();
     public:
         void init();
-        int dispWriteString(int iScrollX, int x, int y, char *szMsg, int iSize, int bInvert, int bRender);
-        void dispFill(unsigned char ucData, int bRender);
-        bool getDatetime(datetime_t *t);
+        int display_write_string(int iScrollX, int x, int y, char *szMsg, int iSize, int bInvert, int bRender);
+        void display_fill(unsigned char ucData, int bRender);
+        bool datetime_get(datetime_t *t);
 };
 
 #endif
\ No newline at end of file
diff --git a/apps/home_menu.cpp b/apps/home_menu.cpp
index 97d4423..a3a0691 100644
--- a/apps/home_menu.cpp
+++ b/apps/home_menu.cpp
@@ -28,18 +28,18 @@ namespace app_home_menu {
         char datetime_buf[256];
         char *datetime_str = &datetime_buf[0];
         datetime_t t;
-        app_api->getDatetime(&t);
+        app_api->datetime_get(&t);
 
         // title with time
         title_str(datetime_str, sizeof(datetime_buf), &t);
-        app_api->dispWriteString(0,0,0, datetime_str, FONT_8x8, 0, 1);
+        app_api->display_write_string(0,0,0, datetime_str, FONT_8x8, 0, 1);
     }
 
     // Rendering of app
     int render(Api *app_api) {
         show_title(app_api);
-        app_api->dispWriteString(0,0,2, pressed_button, FONT_6x8, 0, 1);
-        app_api->dispWriteString(0,5,3, APPS_NAME[*selected_app], FONT_12x16, 0, 1);
+        app_api->display_write_string(0,0,2, pressed_button, FONT_6x8, 0, 1);
+        app_api->display_write_string(0,5,3, APPS_NAME[*selected_app], FONT_12x16, 0, 1);
         return 0;
     }
 
diff --git a/apps/main_clock.cpp b/apps/main_clock.cpp
index 298fed0..9933775 100644
--- a/apps/main_clock.cpp
+++ b/apps/main_clock.cpp
@@ -39,22 +39,22 @@ namespace app_main_clock {
         char datetime_buf[256];
         char *datetime_str = &datetime_buf[0];
         datetime_t t;
-        app_api->getDatetime(&t);
+        app_api->datetime_get(&t);
 
         // time
         time_as_str(datetime_str, sizeof(datetime_buf), &t);
-        app_api->dispWriteString(0,10,3, datetime_str, FONT_12x16, 0, 1);
+        app_api->display_write_string(0,10,3, datetime_str, FONT_12x16, 0, 1);
 
         // date
         date_as_str(datetime_str, sizeof(datetime_buf), &t);
-        app_api->dispWriteString(0,0,7, datetime_str, FONT_8x8, 0, 1);
+        app_api->display_write_string(0,0,7, datetime_str, FONT_8x8, 0, 1);
     }
 
     // Rendering of the app
     int render(Api *app_api) {
-        app_api->dispWriteString(0,15,0, (char *)"Test clock", FONT_8x8, 0, 1);
+        app_api->display_write_string(0,15,0, (char *)"Test clock", FONT_8x8, 0, 1);
         show_datetime(app_api);
-        //app_api->dispWriteString(0,0,0, &data[0], FONT_6x8, 0, 1);
+        //app_api->display_write_string(0,0,0, &data[0], FONT_6x8, 0, 1);
         return 0;
     }
 
diff --git a/pico-watch.cpp b/pico-watch.cpp
index 181d708..ebc3f12 100644
--- a/pico-watch.cpp
+++ b/pico-watch.cpp
@@ -24,7 +24,7 @@ int APPS_DESTROY_ON_EXIT[NUMBER_OF_APPS] = {0, 1};
 int APPS_IS_INIT[NUMBER_OF_APPS] = {0, 0}; // Only run in background if init
 
 int app_init(int app_id) {
-    app_api.dispFill(0,1); // Clear OLED
+    app_api.display_fill(0,1); // Clear OLED
     if (!APPS_IS_INIT[app_id]) {
         APPS_IS_INIT[app_id] = 1;
         return (*APPS_FUNC_INIT[app_id])(&app_api);
-- 
cgit v1.2.3-54-g00ecf


From 03c6be69d3ba5ef7070cf857063d2bcfe12d9a3b Mon Sep 17 00:00:00 2001
From: ConfuSomu
Date: Mon, 1 Mar 2021 15:13:17 -0500
Subject: Wait for button press then hide popup

---
 api.cpp            | 19 +++++++++++++++++--
 api.hpp            | 10 +++++++++-
 apps/home_menu.cpp |  1 +
 buttons.cpp        |  4 +++-
 pico-watch.cpp     |  4 +++-
 5 files changed, 33 insertions(+), 5 deletions(-)

(limited to 'pico-watch.cpp')

diff --git a/api.cpp b/api.cpp
index e333272..16d79ad 100644
--- a/api.cpp
+++ b/api.cpp
@@ -54,6 +54,9 @@ int Api::display_write_pixel(int x, int y, unsigned char ucColor, int bRender) {
 
 bool Api::gui_popup_text(std::string title, std::string body){
 #define CHARS_PER_LINE 13
+    m_button_last_pressed = 0;
+    m_send_button_press_to_app = false;
+
     oledRectangle(&m_oled, 9,7, 119,63, 0, 1); // Background
     oledRectangle(&m_oled, 9,7, 119,63, 1, 0); // Popup border
     oledRectangle(&m_oled, 9,7, 119,16, 1, 1); // Title background
@@ -67,7 +70,7 @@ bool Api::gui_popup_text(std::string title, std::string body){
 
     // Make body fit by adding '\n' at a regular interval
     int since_nl = 0; // Characters since newline
-    for(std::string::size_type i = 0; i < body.size(); ++i) {
+    for (std::string::size_type i = 0; i < body.size(); ++i) {
         if (body[i] == '\n')
             since_nl = 0;
         else if (since_nl++ == CHARS_PER_LINE) {
@@ -78,9 +81,21 @@ bool Api::gui_popup_text(std::string title, std::string body){
     // See https://stackoverflow.com/questions/1986966/does-s0-point-to-contiguous-characters-in-a-stdstring
     oledWriteString(&m_oled, 0, 15,1, &title[0], FONT_8x8, 1, 1); // Draw title
     oledWriteString(&m_oled, 0, 13,2, &body[0], FONT_8x8, 0, 1); // Draw body
-    while(1) {;} // TODO: change, this is temporary, only for this commit!
+    while (m_button_last_pressed != BUTTON_SELECT)
+        sleep_ms(50);
+    // Give back control to running app
+    oledFill(&m_oled, 0, 1);
+    m_send_button_press_to_app = true;
 }
 
 bool Api::datetime_get(datetime_t *t) {
     return rtc_get_datetime(t);
 }
+
+uint Api::button_last_get() {
+    return m_button_last_pressed;
+}
+
+void Api::button_last_set(uint gpio) {
+    m_button_last_pressed = gpio;
+}
diff --git a/api.hpp b/api.hpp
index 2738688..2fc3bba 100644
--- a/api.hpp
+++ b/api.hpp
@@ -5,13 +5,17 @@
 #include "pico/util/datetime.h"
 #include "oled/ss_oled.h"
 
+#include "buttons.hpp"
+
 class Api {
     private:
         SSOLED m_oled;
         u_char m_init_done = 0;
         uint8_t m_ucBuffer[1024] = {0};
+        uint m_button_last_pressed = 0;
         void init_display();
     public:
+        bool m_send_button_press_to_app = true;
         void init();
         int display_write_string(int iScrollX, int x, int y, char *szMsg, int iSize, int bInvert, int bRender);
         void display_fill(unsigned char ucData, int bRender);
@@ -20,13 +24,17 @@ class Api {
         void display_draw_ellipse(int iCenterX, int iCenterY, int32_t iRadiusX, int32_t iRadiusY, uint8_t ucColor, uint8_t bFilled);
         void display_write_buffer(uint8_t *pBuffer);
         int display_write_pixel(int x, int y, unsigned char ucColor, int bRender);
-        // Display a popup over the current view.
+        // Display a popup over the current view and wait for select button to be pressed.
         // This is a blocking function and should be used only in the app's render method.
         // \param title Popup's title, length should not exceed 13 characters.
         // \param body String containing the popup's body. The zone has a size of 13×6 characters, so body should not be longer than 78 characters. Newline allows going to the next line and the text is automatically wrapped.
         // \note Strings longer than 13 and 78 respectively will be truncated.
         bool gui_popup_text(std::string title, std::string body);
         bool datetime_get(datetime_t *t);
+        // Get last button pressed, see buttons.hpp for values
+        uint button_last_get();
+        // Set last button pressed, should only be called by button gpio interrupt.
+        void button_last_set(uint gpio);
 };
 
 #endif
\ No newline at end of file
diff --git a/apps/home_menu.cpp b/apps/home_menu.cpp
index 996b713..b2dddb7 100644
--- a/apps/home_menu.cpp
+++ b/apps/home_menu.cpp
@@ -40,6 +40,7 @@ namespace app_home_menu {
         show_title(app_api);
         app_api->display_write_string(0,0,2, pressed_button, FONT_6x8, 0, 1);
         app_api->display_write_string(0,5,3, APPS_NAME[*selected_app], FONT_12x16, 0, 1);
+        sleep_ms(500);
         app_api->gui_popup_text("Title is a string", "Body text, and new line. And this is a pretty long string.\nIterating is very fun!");
         return 0;
     }
diff --git a/buttons.cpp b/buttons.cpp
index 8ba7b81..cd94504 100644
--- a/buttons.cpp
+++ b/buttons.cpp
@@ -2,10 +2,12 @@
 #include "pico/stdlib.h"
 
 #include "buttons.hpp"
+#include "api.hpp"
 // From pico-watch.c:
 extern int app_btnpressed(int app_id, uint gpio);
 extern void app_switch(int old_appid, int new_appid);
 extern int current_app;
+extern Api app_api;
 
 // Debounce control
 // See https://www.raspberrypi.org/forums/viewtopic.php?f=145&t=301522#p1812063
@@ -21,7 +23,7 @@ void gpio_interrupt_cb(uint gpio, uint32_t events) {
             app_switch(current_app, 0);
         else
             app_btnpressed(current_app, gpio);
-        
+        app_api.button_last_set(gpio);
         button_time = to_ms_since_boot(get_absolute_time());
     }
 }
diff --git a/pico-watch.cpp b/pico-watch.cpp
index ebc3f12..7c59d98 100644
--- a/pico-watch.cpp
+++ b/pico-watch.cpp
@@ -36,7 +36,9 @@ int app_render(int app_id) {
 }
 
 int app_btnpressed(int app_id, uint gpio) {
-    return (*APPS_FUNC_BTNPRESS[app_id])(&app_api, gpio);
+    if (app_api.m_send_button_press_to_app)
+        return (*APPS_FUNC_BTNPRESS[app_id])(&app_api, gpio);
+    return 2;
 }
 
 int app_destroy(int app_id) {
-- 
cgit v1.2.3-54-g00ecf


From 09f67190a934230853e1dddaf65a14c4e9a50999 Mon Sep 17 00:00:00 2001
From: ConfuSomu
Date: Mon, 1 Mar 2021 22:31:20 -0500
Subject: Implement API to set/get render interval

---
 api.cpp        | 10 ++++++++++
 api.hpp        |  8 ++++++++
 pico-watch.cpp |  2 +-
 3 files changed, 19 insertions(+), 1 deletion(-)

(limited to 'pico-watch.cpp')

diff --git a/api.cpp b/api.cpp
index 1200f16..2d63506 100644
--- a/api.cpp
+++ b/api.cpp
@@ -134,6 +134,16 @@ bool Api::datetime_get(datetime_t *t) {
     return rtc_get_datetime(t);
 }
 
+int Api::performance_render_interval_get() {
+    return m_app_render_interval;
+}
+
+void Api::performance_render_interval_set(int interval) {
+    if (interval < 10)
+        interval = 10;
+    m_app_render_interval = interval;
+}
+
 uint Api::button_last_get() {
     return m_button_last_pressed;
 }
diff --git a/api.hpp b/api.hpp
index f5de57e..20f5c34 100644
--- a/api.hpp
+++ b/api.hpp
@@ -13,6 +13,7 @@ class Api {
         u_char m_init_done = 0;
         uint8_t m_ucBuffer[1024] = {0};
         uint m_button_last_pressed = 0;
+        int m_app_render_interval = 500;
         void init_display();
     public:
         bool m_send_button_press_to_app = true;
@@ -55,7 +56,14 @@ class Api {
         // \param perf See Api::perf_modes enum for possible values
         bool performance_set(int perf);
         bool datetime_get(datetime_t *t);
+        // Get app's current render interval
+        // \return Value in millisec
+        int performance_render_interval_get();
+        // Same spirit as performance_set, the app should use the smallest value possible that does not cause a lack of responsiveness in the app's UI.
+        // \param interval Time to wait in millisec between calls of the app's render function.
+        void performance_render_interval_set(int interval);
         // Get last button pressed, see buttons.hpp for values
+        // \return Last button pressed
         uint button_last_get();
         // Set last button pressed, should only be called by button gpio interrupt.
         void button_last_set(uint gpio);
diff --git a/pico-watch.cpp b/pico-watch.cpp
index 7c59d98..adfe2fd 100644
--- a/pico-watch.cpp
+++ b/pico-watch.cpp
@@ -80,7 +80,7 @@ int main() {
 
     while (1) {
         app_render(current_app);
-        sleep_ms(500);
+        sleep_ms(app_api.performance_render_interval_get());
     }
     return 0;
 }
-- 
cgit v1.2.3-54-g00ecf


From b9971cb3b7779d31889379c56afeddc24ff50d04 Mon Sep 17 00:00:00 2001
From: ConfuSomu
Date: Mon, 1 Mar 2021 23:01:44 -0500
Subject: Fix race condition when refresh interval is small

Possible fix
---
 pico-watch.cpp | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

(limited to 'pico-watch.cpp')

diff --git a/pico-watch.cpp b/pico-watch.cpp
index adfe2fd..9df6799 100644
--- a/pico-watch.cpp
+++ b/pico-watch.cpp
@@ -11,6 +11,7 @@
 #include "apps/home_menu.hpp"
 
 int current_app = 0;
+bool app_ready = true;
 Api app_api;
 
 #define NUMBER_OF_APPS 2
@@ -25,6 +26,7 @@ int APPS_IS_INIT[NUMBER_OF_APPS] = {0, 0}; // Only run in background if init
 
 int app_init(int app_id) {
     app_api.display_fill(0,1); // Clear OLED
+    app_api.performance_render_interval_set(500); // Reset interval
     if (!APPS_IS_INIT[app_id]) {
         APPS_IS_INIT[app_id] = 1;
         return (*APPS_FUNC_INIT[app_id])(&app_api);
@@ -42,7 +44,6 @@ int app_btnpressed(int app_id, uint gpio) {
 }
 
 int app_destroy(int app_id) {
-    // TODO: reset APPS_DATA for the app
     if (APPS_IS_INIT[app_id]) {
         APPS_IS_INIT[app_id] = 0;
         return (*APPS_FUNC_DESTROY[app_id])(&app_api);
@@ -62,11 +63,13 @@ bool apps_bgrefresh(struct repeating_timer *t) {  // TODO: Refresh done on core1
 }
 
 void app_switch(int old_appid, int new_appid) {
+    app_ready = false;
     if (APPS_DESTROY_ON_EXIT[old_appid]) {
         app_destroy(old_appid);
     }
     app_init(new_appid);
     current_app = new_appid;
+    app_ready = true;
 }
 
 int main() {
@@ -79,7 +82,8 @@ int main() {
     app_init(current_app);
 
     while (1) {
-        app_render(current_app);
+        if (app_ready)
+            app_render(current_app); // FIXME: This may cause race conditions when switching app
         sleep_ms(app_api.performance_render_interval_get());
     }
     return 0;
-- 
cgit v1.2.3-54-g00ecf