From dd6b9fe68509cd58efafefb0b39f6ebe327fa09a Mon Sep 17 00:00:00 2001 From: akasaka Date: Wed, 17 Jul 2024 21:02:38 +0900 Subject: [PATCH 01/21] Add keypad to big clock --- include/devices/big_clock.h | 11 ++++++++- include/devices/smol_clock.h | 2 +- include/graphics/framebuffer.h | 1 + include/input/keypad.h | 7 ++++++ src/app/alarming.cpp | 2 +- src/display/md_plasma.cpp | 4 ++-- src/input/keypad.cpp | 44 ++++++++++++++++++++++++++++++++++ src/input/keys.cpp | 4 ++-- src/main.cpp | 4 ++++ 9 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 include/input/keypad.h create mode 100644 src/input/keypad.cpp diff --git a/include/devices/big_clock.h b/include/devices/big_clock.h index 414f85b..cf9735f 100644 --- a/include/devices/big_clock.h +++ b/include/devices/big_clock.h @@ -1,12 +1,14 @@ #pragma once #include +#include #define HAS_OUTPUT_MD_PLASMA #define HAS_VARYING_BRIGHTNESS #define HAS_LIGHT_SENSOR #define HAS_TEMP_SENSOR #define HAS_MOTION_SENSOR +#define HAS_KEYPAD // Plasma Information System OS (not DOS, there's no disk in it!) #define PRODUCT_NAME "PIS-OS" @@ -47,4 +49,11 @@ const gpio_num_t HWCONF_I2C_SDA_GPIO = GPIO_NUM_26; const gpio_num_t HWCONF_I2C_SCL_GPIO = GPIO_NUM_25; // ---- TBD: Connection of buttons ---- -// Free GPIOS: 36, 39, 34, 27 \ No newline at end of file +// Free GPIOS: 36, 39, 34, 27 + +const keypad_definition_t HWCONF_KEYPAD = { + {GPIO_NUM_27, KEY_LEFT}, + {GPIO_NUM_39, KEY_RIGHT}, + {GPIO_NUM_34, KEY_UP}, + {GPIO_NUM_36, KEY_DOWN}, +}; diff --git a/include/devices/smol_clock.h b/include/devices/smol_clock.h index bc2f668..ff81155 100644 --- a/include/devices/smol_clock.h +++ b/include/devices/smol_clock.h @@ -39,7 +39,7 @@ const gpio_num_t HWCONF_WS0010_EN_GPIO = GPIO_NUM_18; // ---- Connection to touch plane ---- const touch_plane_definition_t HWCONF_TOUCH_PLANE = { // Screen panel - {/*GPIO_NUM_32*/ TOUCH_PAD_NUM9, {.key = KEY_RIGHT, .press_threshold = 7, .release_threshold = -3}}, + {/*GPIO_NUM_32*/ TOUCH_PAD_NUM9, {.key = KEY_RIGHT, .press_threshold = 8, .release_threshold = -4}}, {/*GPIO_NUM_33*/ TOUCH_PAD_NUM8, {.key = KEY_UP, .press_threshold = 7, .release_threshold = -2}}, {/*GPIO_NUM_27*/ TOUCH_PAD_NUM7, {.key = KEY_DOWN, .press_threshold = 7, .release_threshold = -2}}, {/*GPIO_NUM_14*/ TOUCH_PAD_NUM6, {.key = KEY_LEFT, .press_threshold = 7, .release_threshold = -2}}, diff --git a/include/graphics/framebuffer.h b/include/graphics/framebuffer.h index 7479109..51ff3a1 100644 --- a/include/graphics/framebuffer.h +++ b/include/graphics/framebuffer.h @@ -5,6 +5,7 @@ #include #include #include +#include /// @brief A framebuffer for driving the plasma display class DisplayFramebuffer { diff --git a/include/input/keypad.h b/include/input/keypad.h new file mode 100644 index 0000000..4806268 --- /dev/null +++ b/include/input/keypad.h @@ -0,0 +1,7 @@ +#pragma once +#include "keys.h" +#include +#include "esp32-hal-gpio.h" + +typedef std::vector> keypad_definition_t; +void keypad_start(); diff --git a/src/app/alarming.cpp b/src/app/alarming.cpp index 0d64fa5..deee2d2 100644 --- a/src/app/alarming.cpp +++ b/src/app/alarming.cpp @@ -87,7 +87,7 @@ void app_alarming_draw(FantaManipulator* fb) { } if(framecount == 255) { - #if HAS(TOUCH_PLANE) + #if HAS(TOUCH_PLANE) || HAS(KEYPAD) arrows->left = true; arrows->top = false; arrows->bottom = false; diff --git a/src/display/md_plasma.cpp b/src/display/md_plasma.cpp index 229c92b..8cd7f4a 100644 --- a/src/display/md_plasma.cpp +++ b/src/display/md_plasma.cpp @@ -84,9 +84,9 @@ void MorioDenkiPlasmaDriver::set_databus(uint8_t data) { void MorioDenkiPlasmaDriver::pulse_clock() { gpio_set_level(clk_gpio, 0); - delayMicroseconds(5); + delayMicroseconds(1); gpio_set_level(clk_gpio, 1); - delayMicroseconds(5); + delayMicroseconds(1); } void MorioDenkiPlasmaDriver::set_show(bool show) { diff --git a/src/input/keypad.cpp b/src/input/keypad.cpp new file mode 100644 index 0000000..f2495b3 --- /dev/null +++ b/src/input/keypad.cpp @@ -0,0 +1,44 @@ +#include +#include +#include + +static char LOG_TAG[] = "KEYP"; + +static TaskHandle_t hTask; + +static void keypad_task(void*) { + while(1) { + for(auto i: HWCONF_KEYPAD) { + int lvl = gpio_get_level(i.first); + hid_set_key_state(i.second, lvl > 0); + } + + vTaskDelay(pdMS_TO_TICKS(10)); + } +} + +void keypad_start() { +#if HAS(KEYPAD) + gpio_config_t io_conf = { + .mode = GPIO_MODE_INPUT, + .pull_up_en = gpio_pullup_t::GPIO_PULLUP_DISABLE, + .pull_down_en = gpio_pulldown_t::GPIO_PULLDOWN_DISABLE + }; + + for(auto i: HWCONF_KEYPAD) { + io_conf.pin_bit_mask |= 1ULL << i.first; + } + + ESP_ERROR_CHECK(gpio_config(&io_conf)); + + ESP_LOGI(LOG_TAG, "Creating task"); + xTaskCreate( + keypad_task, + "KEYPAD", + 4096, + nullptr, + configMAX_PRIORITIES - 3, + &hTask + ); +#endif +} \ No newline at end of file diff --git a/src/input/keys.cpp b/src/input/keys.cpp index 3d49140..b541971 100644 --- a/src/input/keys.cpp +++ b/src/input/keys.cpp @@ -61,11 +61,11 @@ static key_state_t min_state_of_mask(key_bitmask_t keys, bool peek = false) { } void hid_set_key_state(key_id_t key, bool state) { - if(state) { + if(state && (active_keys & KEY_ID_TO_BIT(key)) == 0) { keypress_started_at[key] = xTaskGetTickCount(); keypress_repeated_at[key] = xTaskGetTickCount(); active_keys |= KEY_ID_TO_BIT(key); - } else { + } else if(!state && (active_keys & KEY_ID_TO_BIT(key)) != 0) { active_keys &= ~KEY_ID_TO_BIT(key); } } diff --git a/src/main.cpp b/src/main.cpp index 804fb64..10b44b1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -153,6 +154,9 @@ void bringup_hid() { beepola->beep_blocking(CHANNEL_SYSTEM, 500, 125); } #endif +#if HAS(KEYPAD) + keypad_start(); +#endif } void setup() { From 62747735f27919d91a3e96a3fd8583bb7117f4cc Mon Sep 17 00:00:00 2001 From: akasaka Date: Wed, 17 Jul 2024 21:31:40 +0900 Subject: [PATCH 02/21] Don't continuously call the stop functions when stopping the alarm --- src/app/alarming.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/alarming.cpp b/src/app/alarming.cpp index deee2d2..2e17388 100644 --- a/src/app/alarming.cpp +++ b/src/app/alarming.cpp @@ -26,6 +26,8 @@ typedef enum alarming_state { SNOOZE_HOLD_COUNTDOWN, SNOOZING, STOP_HOLD_COUNTDOWN, + + STOPPED } alarming_state_t; static SimpleClock *clockView = nullptr; @@ -195,6 +197,7 @@ void begin_snoozing() { } void stop_alarm() { + state = STOPPED; clear_triggered_alarm(); seq->stop_sequence(); power_mgmt_resume(); From fd4da9aa1b339c7f85f8da7618f921980e9fec74 Mon Sep 17 00:00:00 2001 From: akasaka Date: Thu, 18 Jul 2024 10:39:28 +0900 Subject: [PATCH 03/21] Fix build error; don't blink when waking up the device by buttons without triggering motion --- include/devices/smol_clock.h | 2 +- src/input/keypad.cpp | 2 ++ src/service/power_management.cpp | 11 +++++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/include/devices/smol_clock.h b/include/devices/smol_clock.h index ff81155..72bcc7e 100644 --- a/include/devices/smol_clock.h +++ b/include/devices/smol_clock.h @@ -39,7 +39,7 @@ const gpio_num_t HWCONF_WS0010_EN_GPIO = GPIO_NUM_18; // ---- Connection to touch plane ---- const touch_plane_definition_t HWCONF_TOUCH_PLANE = { // Screen panel - {/*GPIO_NUM_32*/ TOUCH_PAD_NUM9, {.key = KEY_RIGHT, .press_threshold = 8, .release_threshold = -4}}, + {/*GPIO_NUM_32*/ TOUCH_PAD_NUM9, {.key = KEY_RIGHT, .press_threshold = 10, .release_threshold = -6}}, {/*GPIO_NUM_33*/ TOUCH_PAD_NUM8, {.key = KEY_UP, .press_threshold = 7, .release_threshold = -2}}, {/*GPIO_NUM_27*/ TOUCH_PAD_NUM7, {.key = KEY_DOWN, .press_threshold = 7, .release_threshold = -2}}, {/*GPIO_NUM_14*/ TOUCH_PAD_NUM6, {.key = KEY_LEFT, .press_threshold = 7, .release_threshold = -2}}, diff --git a/src/input/keypad.cpp b/src/input/keypad.cpp index f2495b3..1fb535b 100644 --- a/src/input/keypad.cpp +++ b/src/input/keypad.cpp @@ -7,6 +7,7 @@ static char LOG_TAG[] = "KEYP"; static TaskHandle_t hTask; static void keypad_task(void*) { +#if HAS(KEYPAD) while(1) { for(auto i: HWCONF_KEYPAD) { int lvl = gpio_get_level(i.first); @@ -15,6 +16,7 @@ static void keypad_task(void*) { vTaskDelay(pdMS_TO_TICKS(10)); } +#endif } void keypad_start() { diff --git a/src/service/power_management.cpp b/src/service/power_management.cpp index 646808e..b400961 100644 --- a/src/service/power_management.cpp +++ b/src/service/power_management.cpp @@ -27,7 +27,7 @@ static int lightnessThreshDown; extern "C" void PMTaskFunction( void * pvParameter ); -static void wake_up() { +static void wake_up(TickType_t now) { // There was some motion, reenable the display if(isDisplayOff) { ESP_LOGI(LOG_TAG, "Start display"); @@ -51,7 +51,10 @@ static void wake_up() { #endif } - lastMotionTime = xTaskGetTickCount(); + // cannot use getTickCount here, because then lastMotionTime will be in the future + // and thus further conditions will think it's way off in the past (due to ticktype_t being unsigned) + // which causes blinking when waking up the device with buttons without triggering motion somehow + lastMotionTime = now; } void PMTaskFunction( void * pvParameter ) @@ -92,7 +95,7 @@ void PMTaskFunction( void * pvParameter ) if(hid_info) { // wake up on keypress if(hid_info->last_result > 0) { - wake_up(); + wake_up(now); } } @@ -102,7 +105,7 @@ void PMTaskFunction( void * pvParameter ) if(motion_info != nullptr) { if(motion_info->last_result > 0) { // There was some motion, reenable the display - wake_up(); + wake_up(now); } else { if(now - lastMotionTime >= pdMS_TO_TICKS(motionlessTimeOff) && !isDisplayOff) { // No motion for a while, turn off display, first only logically From 1f5d5918bc7b7d986a32a94b7f00d17f0dbef334 Mon Sep 17 00:00:00 2001 From: akasaka Date: Thu, 18 Jul 2024 10:45:16 +0900 Subject: [PATCH 04/21] get rid of ALARM_IS_ENABLED / ALARM_GLOBAL_ENABLE in favor of a bitfield --- include/service/alarm.h | 6 +++--- src/app/alarm_editor.cpp | 30 +++++++++++++----------------- src/network/admin_panel.cpp | 2 +- src/service/alarm.cpp | 6 +++--- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/include/service/alarm.h b/include/service/alarm.h index ff9aba5..369e2ee 100644 --- a/include/service/alarm.h +++ b/include/service/alarm.h @@ -7,11 +7,11 @@ #define ALARM_DAY_OF_WEEK(d) (1 << d) #define ALARM_ON_DAY(a,d) (((a).days & ALARM_DAY_OF_WEEK(d)) != 0) -#define ALARM_IS_ENABLED(x) (((x).days & ALARM_DAY_GLOBAL_ENABLE) != 0) -typedef struct alarm_setting { +typedef struct __attribute__((__packed__)) alarm_setting { + bool enabled : 1; /// @brief LSB is Sunday, B1 is Monday, and so on. Disabled altogether when MSB not set.. - uint8_t days; + uint8_t days : 7; int hour; int minute; int melody_no; diff --git a/src/app/alarm_editor.cpp b/src/app/alarm_editor.cpp index 03a8ee6..e5b2287 100644 --- a/src/app/alarm_editor.cpp +++ b/src/app/alarm_editor.cpp @@ -30,8 +30,8 @@ class AlarmItemView: public Renderable { void render(FantaManipulator *fb) { // box around number - fb->rect(0, 2, 10 + ALARM_IS_ENABLED(setting), 12, ALARM_IS_ENABLED(setting)); - fb->put_string(&keyrus0808_font, index_str, 2, 4, ALARM_IS_ENABLED(setting)); + fb->rect(0, 2, 10 + setting.enabled, 12, setting.enabled); + fb->put_string(&keyrus0808_font, index_str, 2, 4, setting.enabled); fb->put_string(&keyrus0808_font, time_buf, 14, 0); @@ -62,10 +62,10 @@ class AlarmItemView: public Renderable { class AlarmDaySelectorView: public Composite { public: - AlarmDaySelectorView(const char * title, uint8_t * day_list, std::function onActivated): + AlarmDaySelectorView(const char * title, alarm_setting_t * setting, std::function onActivated): isActive {false}, label(new StringScroll(&keyrus0808_font, title)), - dayList(day_list), + setting(setting), cursor { 0 }, onActivated(onActivated) { add_subrenderable(label); @@ -87,10 +87,10 @@ class AlarmDaySelectorView: public Composite { if(cursor < 0) cursor = 6; } else if(hid_test_key_state(KEY_RIGHT) == KEYSTATE_HIT) { uint8_t day_mask = ALARM_DAY_OF_WEEK(cursor); - if((*dayList & day_mask) != 0) { - *dayList &= ~day_mask; + if((setting->days & day_mask) != 0) { + setting->days &= ~day_mask; } else { - *dayList |= day_mask; + setting->days |= day_mask; } } else if(hid_test_key_state(KEY_LEFT)) { onActivated(false, this); @@ -112,10 +112,10 @@ class AlarmDaySelectorView: public Composite { framecount = 0; } - if(isActive || (*dayList != 0 && *dayList != ALARM_DAY_GLOBAL_ENABLE)) { + if(isActive || setting->days != 0) { for(int d = 0; d < 7; d++) { int ltr_x = fb->get_width() - 70 + d * (keyrus0808_font.width + 2); - bool lit_up = (*dayList & ALARM_DAY_OF_WEEK(d)) != 0; + bool lit_up = (setting->days & ALARM_DAY_OF_WEEK(d)) != 0; if(isActive && d == cursor && cursorShows) { fb->rect(ltr_x - 2, 7, ltr_x + 8, 15, lit_up); @@ -133,7 +133,7 @@ class AlarmDaySelectorView: public Composite { bool isActive; bool cursorShows; StringScroll * label; - uint8_t * dayList; + alarm_setting_t * setting; int cursor; std::function onActivated; }; @@ -148,15 +148,11 @@ class AppShimAlarmEditor::AlarmEditorView: public ListView { pushPop(false, ts_view); }); - add_view(new MenuBooleanSelectorView("Enabled", ALARM_IS_ENABLED(*alm), [alm](bool newEnabled) { - if(newEnabled) { - alm->days |= ALARM_DAY_GLOBAL_ENABLE; - } else { - alm->days &= ~ALARM_DAY_GLOBAL_ENABLE; - } + add_view(new MenuBooleanSelectorView("Enabled", alm->enabled, [alm](bool newEnabled) { + alm->enabled = newEnabled; })); - add_view(new AlarmDaySelectorView("Days", &alm->days, activation)); + add_view(new AlarmDaySelectorView("Days", alm, activation)); add_view(new MenuActionItemView("Time", [pushPop, this]() { pushPop(true, ts_view); }, nullptr, time_str)); add_view(new MenuMelodySelectorView(b, "Melody", alm->melody_no, activation, [alm](int newMelodyNo) { alm->melody_no = newMelodyNo; })); } diff --git a/src/network/admin_panel.cpp b/src/network/admin_panel.cpp index 52c6fe2..ee91e04 100644 --- a/src/network/admin_panel.cpp +++ b/src/network/admin_panel.cpp @@ -117,7 +117,7 @@ static void render_alarms() { tmp = String("alm_") + i + "_"; GP.LABEL("Enabled: "); - GP.CHECK(tmp + "enable", ALARM_IS_ENABLED(a)); + GP.CHECK(tmp + "enable", a.enabled); GP.BREAK(); GP.TABLE_BEGIN(); diff --git a/src/service/alarm.cpp b/src/service/alarm.cpp index 4a8a231..0deb261 100644 --- a/src/service/alarm.cpp +++ b/src/service/alarm.cpp @@ -69,15 +69,15 @@ static void alarm_task(void*) { for(int i = 0; i < ALARM_LIST_SIZE; i++) { alarm_setting_t alarm = alarms[i]; - if(ALARM_IS_ENABLED(alarm)) { - if(alarm.days == ALARM_DAY_GLOBAL_ENABLE || ALARM_ON_DAY(alarm, today.dayOfWeek)){ + if(alarm.enabled) { + if(alarm.days == 0 || ALARM_ON_DAY(alarm, today.dayOfWeek)){ if(alarm.hour == now.hour && alarm.minute == now.minute) { ESP_LOGI(LOG_TAG, "Triggering alarm at index %i", i); triggered_alarm = &alarms[i]; memcpy(&last_alarmed, &now, sizeof(tk_time_of_day_t)); memcpy(&last_alarmed_day, &today, sizeof(tk_date_t)); - if(alarm.days == ALARM_DAY_GLOBAL_ENABLE) { + if(alarm.days == 0) { ESP_LOGI(LOG_TAG, "Alarm %i was singular, turning OFF!", i); alarm.days = 0; set_alarm(i, alarm); From 923b4f7772925b3206e3de5d76b3d3c193ef73e9 Mon Sep 17 00:00:00 2001 From: akasaka Date: Thu, 18 Jul 2024 15:41:14 +0900 Subject: [PATCH 05/21] (untested) smart alarm and countdown view for alarms --- include/service/alarm.h | 12 ++- include/service/prefs.h | 1 + include/service/time.h | 5 + include/views/idle_screens/next_alarm.h | 19 ++++ src/app/alarm_editor.cpp | 12 ++- src/app/idle.cpp | 23 ++--- src/app/menu.cpp | 1 + src/main.cpp | 2 +- src/network/admin_panel.cpp | 15 ++- src/service/alarm.cpp | 120 +++++++++++++++++++----- src/service/time.cpp | 49 ++++++++++ src/views/idle_screens/next_alarm.cpp | 79 ++++++++++++++++ 12 files changed, 293 insertions(+), 45 deletions(-) create mode 100644 include/views/idle_screens/next_alarm.h create mode 100644 src/views/idle_screens/next_alarm.cpp diff --git a/include/service/alarm.h b/include/service/alarm.h index 369e2ee..44ca602 100644 --- a/include/service/alarm.h +++ b/include/service/alarm.h @@ -1,26 +1,32 @@ #pragma once #include "time.h" #include +#include #define ALARM_LIST_SIZE 9 #define ALARM_DAY_GLOBAL_ENABLE (1 << 7) -#define ALARM_DAY_OF_WEEK(d) (1 << d) +#define ALARM_DAY_OF_WEEK(d) (1 << (d)) -#define ALARM_ON_DAY(a,d) (((a).days & ALARM_DAY_OF_WEEK(d)) != 0) +#define ALARM_ON_DAY(a,d) (((a).days & ALARM_DAY_OF_WEEK((d))) != 0) typedef struct __attribute__((__packed__)) alarm_setting { bool enabled : 1; /// @brief LSB is Sunday, B1 is Monday, and so on. Disabled altogether when MSB not set.. uint8_t days : 7; + int hour; int minute; int melody_no; + + bool smart : 1; + uint8_t smart_margin_minutes : 7; } alarm_setting_t; -void alarm_init(); +void alarm_init(SensorPool*); const alarm_setting_t * get_alarm_list(); void set_alarm(uint8_t idx, alarm_setting_t setting); const alarm_setting_t* get_triggered_alarm(); +const alarm_setting_t* get_upcoming_alarm(); void clear_triggered_alarm(); \ No newline at end of file diff --git a/include/service/prefs.h b/include/service/prefs.h index 4a9e44c..69cce8c 100644 --- a/include/service/prefs.h +++ b/include/service/prefs.h @@ -53,6 +53,7 @@ static constexpr prefs_key_t PREFS_KEY_SCRN_TIME_REMOTE_WEATHER_SECONDS = "s_rem static constexpr prefs_key_t PREFS_KEY_SCRN_TIME_OUTDOOR_SECONDS = "s_outside_s"; static constexpr prefs_key_t PREFS_KEY_SCRN_TIME_WORD_OF_THE_DAY_SECONDS = "s_wotd_s"; static constexpr prefs_key_t PREFS_KEY_SCRN_TIME_FOOBAR_SECONDS = "s_foo_s"; +static constexpr prefs_key_t PREFS_KEY_SCRN_TIME_NEXT_ALARM_SECONDS = "s_alm_s"; static constexpr prefs_key_t PREFS_KEY_HOURLY_CHIME_ON = "h_chime_on"; static constexpr prefs_key_t PREFS_KEY_HOURLY_CHIME_START_HOUR = "h_chime_start"; diff --git a/include/service/time.h b/include/service/time.h index 1fa202e..1814ec4 100644 --- a/include/service/time.h +++ b/include/service/time.h @@ -32,3 +32,8 @@ tk_date_t get_current_date(); void set_current_time(tk_time_of_day_t); /// @brief Set the current date without changing the timezone et al void set_current_date(tk_date_t); + +tk_time_of_day operator -(const tk_time_of_day_t& a, const tk_time_of_day_t& b); +bool operator==(const tk_time_of_day_t& a, const tk_time_of_day_t& b); +bool operator<(const tk_time_of_day_t& a, const tk_time_of_day_t& b); +bool operator>(const tk_time_of_day_t& a, const tk_time_of_day_t& b); diff --git a/include/views/idle_screens/next_alarm.h b/include/views/idle_screens/next_alarm.h new file mode 100644 index 0000000..e088fea --- /dev/null +++ b/include/views/idle_screens/next_alarm.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include +#include + +/// @brief A view that shows the time remaining until the next alarm. +class NextAlarmView: public Screen { +public: + NextAlarmView(); + void prepare(); + void render(FantaManipulator*); + int desired_display_time(); + +private: + tk_time_of_day_t time_remaining; + bool show_alarm; + const font_definition_t * font; +}; + diff --git a/src/app/alarm_editor.cpp b/src/app/alarm_editor.cpp index e5b2287..aea2e52 100644 --- a/src/app/alarm_editor.cpp +++ b/src/app/alarm_editor.cpp @@ -45,7 +45,11 @@ class AlarmItemView: public Renderable { } void step() { - snprintf(time_buf, 6, "%02d:%02d", setting.hour, setting.minute); + if(!setting.smart || setting.smart_margin_minutes == 0) { + snprintf(time_buf, 16, "%02d:%02d", setting.hour, setting.minute); + } else { + snprintf(time_buf, 16, "%02d:%02d (-%02d)", setting.hour, setting.minute, setting.smart_margin_minutes); + } if(hid_test_key_state(KEY_RIGHT) == KEYSTATE_HIT) { _action(index); @@ -57,7 +61,7 @@ class AlarmItemView: public Renderable { alarm_setting_t setting; int index; char index_str[4]; - char time_buf[6]; + char time_buf[16]; }; class AlarmDaySelectorView: public Composite { @@ -154,6 +158,10 @@ class AppShimAlarmEditor::AlarmEditorView: public ListView { add_view(new AlarmDaySelectorView("Days", alm, activation)); add_view(new MenuActionItemView("Time", [pushPop, this]() { pushPop(true, ts_view); }, nullptr, time_str)); + #if HAS(MOTION_SENSOR) // CBA to inject SensorPool which would only be used to check for existence of the sensor + add_view(new MenuBooleanSelectorView("Smart Alarm", alm->smart, [alm](bool newSmart) { alm->smart = newSmart; })); + add_view(new MenuNumberSelectorView("Smart margin", 1, 30, 1, alm->smart_margin_minutes, activation, [alm](int newMargin) { alm->smart_margin_minutes = newMargin; })); + #endif add_view(new MenuMelodySelectorView(b, "Melody", alm->melody_no, activation, [alm](int newMelodyNo) { alm->melody_no = newMelodyNo; })); } diff --git a/src/app/idle.cpp b/src/app/idle.cpp index 5e39eb5..946bc8e 100644 --- a/src/app/idle.cpp +++ b/src/app/idle.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,7 @@ static char LOG_TAG[] = "APL_IDLE"; typedef enum MainViewId: uint16_t { VIEW_CLOCK = 0, + VIEW_NEXT_ALARM, #if HAS(TEMP_SENSOR) VIEW_INDOOR_WEATHER, #endif @@ -41,20 +43,7 @@ typedef enum MainViewId: uint16_t { VIEW_MAX } MainViewId_t; -static int screen_times_ms[VIEW_MAX] = { - 30000, // VIEW_CLOCK -#if HAS(TEMP_SENSOR) - 10000, // VIEW_INDOOR_WEATHER -#endif -#if HAS(SWITCHBOT_METER_INTEGRATION) - 10000, // VIEW_REMOTE_WEATHER, -#endif - 25000, // VIEW_OUTDOOR_WEATHER -#if HAS(WORDNIK_API) - 25000, // VIEW_WORD_OF_THE_DAY -#endif - 0, // VIEW_FB2K -}; +static int screen_times_ms[VIEW_MAX] = {0}; int current_screen_time_ms = 0; @@ -67,6 +56,7 @@ static SensorPool * sensors; static Renderable * mainView; static SimpleClock * clockView; +static NextAlarmView * nextAlarmView; static RainOverlay * rain; static ThunderOverlay * thunder; @@ -191,6 +181,7 @@ void app_idle_prepare(SensorPool* s, Beeper* b) { hourly_chime_on = prefs_get_bool(PREFS_KEY_HOURLY_CHIME_ON); screen_times_ms[VIEW_CLOCK] = prefs_get_int(PREFS_KEY_SCRN_TIME_CLOCK_SECONDS) * 1000; + screen_times_ms[VIEW_NEXT_ALARM] = prefs_get_int(PREFS_KEY_SCRN_TIME_NEXT_ALARM_SECONDS) * 1000; #if HAS(TEMP_SENSOR) screen_times_ms[VIEW_INDOOR_WEATHER] = prefs_get_int(PREFS_KEY_SCRN_TIME_INDOOR_SECONDS) * 1000; #endif @@ -222,6 +213,7 @@ void app_idle_prepare(SensorPool* s, Beeper* b) { signalIndicator = new SignalStrengthIcon(sensors); weatherView = new CurrentWeatherView(); fb2kView = new Fb2kView(); + nextAlarmView = new NextAlarmView(); touchArrows = new TouchArrowOverlay(); touchArrows->bottom = true; @@ -234,6 +226,7 @@ void app_idle_prepare(SensorPool* s, Beeper* b) { slideShow = new ViewMultiplexor(); slideShow->add_view(thunderClock, VIEW_CLOCK); + slideShow->add_view(nextAlarmView, VIEW_NEXT_ALARM); #if HAS(TEMP_SENSOR) indoorView = new IndoorView(sensors); slideShow->add_view(indoorView, VIEW_INDOOR_WEATHER); @@ -337,4 +330,4 @@ void app_idle_process() { tick_tock_enable = prefs_get_bool(PREFS_KEY_TICKING_SOUND); hourly_chime_on = prefs_get_bool(PREFS_KEY_HOURLY_CHIME_ON); -} \ No newline at end of file +} diff --git a/src/app/menu.cpp b/src/app/menu.cpp index 249ef7e..e3209f1 100644 --- a/src/app/menu.cpp +++ b/src/app/menu.cpp @@ -59,6 +59,7 @@ AppShimMenu::AppShimMenu(Beeper *b): ProtoShimNavMenu::ProtoShimNavMenu() { static ListView * screen_times = new ListView(); display_menu->add_view(new MenuActionItemView("Screen Times", [this](){ push_submenu(screen_times); })); screen_times->add_view(new MenuNumberSelectorPreferenceView("Clock", PREFS_KEY_SCRN_TIME_CLOCK_SECONDS, 0, 3600, 1, normalActivationFunction)); + screen_times->add_view(new MenuNumberSelectorPreferenceView("Alarm countdown", PREFS_KEY_SCRN_TIME_NEXT_ALARM_SECONDS, 0, 3600, 1, normalActivationFunction)); #if HAS(TEMP_SENSOR) screen_times->add_view(new MenuNumberSelectorPreferenceView("Thermometer", PREFS_KEY_SCRN_TIME_INDOOR_SECONDS, 0, 3600, 1, normalActivationFunction)); #endif diff --git a/src/main.cpp b/src/main.cpp index 10b44b1..ed307e2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -242,7 +242,7 @@ void setup() { appHost->add_view(new AppShimTimerEditor(beepola), STATE_TIMER_EDITOR); change_state(startup_state); - alarm_init(); + alarm_init(sensors); } void loop() { diff --git a/src/network/admin_panel.cpp b/src/network/admin_panel.cpp index ee91e04..987753e 100644 --- a/src/network/admin_panel.cpp +++ b/src/network/admin_panel.cpp @@ -145,6 +145,13 @@ static void render_alarms() { GP.TABLE_END(); GP.BREAK(); + if (sensors->exists(SENSOR_ID_MOTION)) { + GP.LABEL("Smart: "); + GP.CHECK(tmp + "smart", a.smart); + GP.NUMBER(tmp + "margin", "10", a.smart_margin_minutes); + GP.BREAK(); + } + GP.SELECT(tmp + "melo", all_chime_names_csv, a.melody_no); } @@ -159,9 +166,8 @@ static void save_alarms() { alarm_setting_t a = {0}; String tmp = String("alm_") + i + "_"; - if(ui.getBool(tmp + "enable")) { - a.days |= ALARM_DAY_GLOBAL_ENABLE; - } + a.enabled = ui.getBool(tmp + "enable"); + a.smart = ui.getBool(tmp + "smart"); for(int d = 0; d < 7; d++) { if(ui.getBool(tmp + days[d])) { @@ -173,6 +179,7 @@ static void save_alarms() { a.hour = ui.getInt(tmp + "h"); a.minute = ui.getInt(tmp + "m"); a.melody_no = ui.getInt(tmp + "melo"); + a.smart_margin_minutes = ui.getInt(tmp + "margin"); set_alarm(i, a); } @@ -227,6 +234,8 @@ void build() { GP.SPOILER_BEGIN("Display", GP_BLUE); render_int("Show clock for [s]:", PREFS_KEY_SCRN_TIME_CLOCK_SECONDS); GP.BREAK(); + render_int("Show next alarm countdown for [s]:", PREFS_KEY_SCRN_TIME_NEXT_ALARM_SECONDS); + GP.BREAK(); #if HAS(TEMP_SENSOR) render_int("Show temperature for [s]:", PREFS_KEY_SCRN_TIME_INDOOR_SECONDS); GP.BREAK(); diff --git a/src/service/alarm.cpp b/src/service/alarm.cpp index 0deb261..6984f4c 100644 --- a/src/service/alarm.cpp +++ b/src/service/alarm.cpp @@ -10,8 +10,9 @@ typedef alarm_setting_t alarm_settings_list_t[ALARM_LIST_SIZE]; static TaskHandle_t hTask; static alarm_settings_list_t alarms = { 0 }; static bool alarm_list_loaded = false; +static SensorPool * sensors = nullptr; -static tk_time_of_day_t last_alarmed = { 0 }; +static alarm_setting_t last_alarmed = { 0 }; // <- if we were to just save the index, reusing the same alarm slot on the same day would not work. static tk_date_t last_alarmed_day = { 0 }; static const alarm_setting_t * triggered_alarm = nullptr; @@ -40,7 +41,7 @@ void set_alarm(uint8_t idx, alarm_setting_t setting) { _load_alarm_list_if_needed(); alarms[idx] = setting; _save_alarm_list(); - ESP_LOGI(LOG_TAG, "Set alarm %i: days 0x%2.x, melody %i, %2.i:%2.i", idx, setting.days, setting.melody_no, setting.hour, setting.minute); + ESP_LOGI(LOG_TAG, "Set alarm %i: days 0x%2.x, melody %i, %2.i:%2.i, smart %i (margin %i)", idx, setting.days, setting.melody_no, setting.hour, setting.minute, setting.smart, setting.smart_margin_minutes); } const alarm_setting_t* get_triggered_alarm() { @@ -51,6 +52,29 @@ void clear_triggered_alarm() { triggered_alarm = nullptr; } +static void _trigger_alarm_if_needed(tk_date& today, int index) { + alarm_setting_t alarm = alarms[index]; + // a hash is probably in order here, but CBA. + if(memcmp(&alarm, &last_alarmed, sizeof(alarm_setting_t)) == 0 && + memcmp(&today, &last_alarmed_day, sizeof(tk_date_t)) == 0) { + // already alarmed this alarm today, nothing to do + return; + } + + ESP_LOGI(LOG_TAG, "Triggering alarm at index %i", index); + triggered_alarm = &alarms[index]; + memcpy(&last_alarmed, &alarm, sizeof(alarm_setting_t)); + memcpy(&last_alarmed_day, &today, sizeof(tk_date_t)); + + if(alarm.days == 0) { + ESP_LOGI(LOG_TAG, "Alarm %i was singular, turning OFF!", index); + alarm.enabled = false; + set_alarm(index, alarm); + } + + push_state(STATE_ALARMING, TRANSITION_WIPE); +} + static void alarm_task(void*) { ESP_LOGI(LOG_TAG, "Task started"); while(1) { @@ -61,30 +85,39 @@ static void alarm_task(void*) { now.millisecond = 0; now.second = 0; - if(memcmp(&now, &last_alarmed, sizeof(tk_time_of_day_t)) == 0 && - memcmp(&today, &last_alarmed_day, sizeof(tk_date_t)) == 0) { - // already alarmed at this time - continue; - } - for(int i = 0; i < ALARM_LIST_SIZE; i++) { alarm_setting_t alarm = alarms[i]; if(alarm.enabled) { if(alarm.days == 0 || ALARM_ON_DAY(alarm, today.dayOfWeek)){ if(alarm.hour == now.hour && alarm.minute == now.minute) { - ESP_LOGI(LOG_TAG, "Triggering alarm at index %i", i); - triggered_alarm = &alarms[i]; - memcpy(&last_alarmed, &now, sizeof(tk_time_of_day_t)); - memcpy(&last_alarmed_day, &today, sizeof(tk_date_t)); - - if(alarm.days == 0) { - ESP_LOGI(LOG_TAG, "Alarm %i was singular, turning OFF!", i); - alarm.days = 0; - set_alarm(i, alarm); - } - - push_state(STATE_ALARMING, TRANSITION_WIPE); + ESP_LOGI(LOG_TAG, "Triggering alarm %i due to TIME reasons", i); + _trigger_alarm_if_needed(today, i); break; + } else if(alarm.smart && alarm.smart_margin_minutes > 0 + && sensors && sensors->exists(SENSOR_ID_MOTION) + && now.hour <= alarm.hour && now.minute < alarm.minute) { + // Simple smart alarm logic based on motion sensing + tk_time_of_day_t margin_as_time = { .hour = 0, .minute = alarm.smart_margin_minutes, .second = 0, .millisecond = 0 }; + tk_time_of_day_t alarm_time = { .hour = alarm.hour, .minute = alarm.minute, .second = 0, .millisecond = 0 }; + tk_time_of_day_t earliest_time = alarm_time - margin_as_time; + if ( + (now.hour >= earliest_time.hour && now.minute >= earliest_time.minute) || + // edge case if the time wraps around midnight: + (now.hour == 0 && earliest_time.hour == 23 && now.minute < earliest_time.minute) + ) { + // We are now within the alarm margin, so in "armed" state + sensor_info_t * motn = sensors->get_info(SENSOR_ID_MOTION); + if(motn) { + ESP_LOGV(LOG_TAG, "Alarm %i is ARMED for smart mode", i); + // NB: check if last motion sensor poll was recent enough? + if(motn->last_result) { + // there was motion, kick off the alarm + ESP_LOGI(LOG_TAG, "Triggering alarm %i due to MOTION reasons", i); + _trigger_alarm_if_needed(today, i); + break; + } + } + } } } } @@ -94,7 +127,52 @@ static void alarm_task(void*) { } } -void alarm_init() { +const alarm_setting_t* get_upcoming_alarm() { + _load_alarm_list_if_needed(); + + tk_time_of_day_t now = get_current_time_coarse(); + tk_date today = get_current_date(); + + tk_time_of_day_t next_alarm_time = { .hour = INT_MAX, .minute = INT_MAX, .second = INT_MAX, .millisecond = INT_MAX }; + const alarm_setting_t * next_alarm = nullptr; + + // Look for any alarms in today + for (int i = 0; i < ALARM_LIST_SIZE; i++) { + const alarm_setting_t * alarm = &alarms[i]; + if (alarm->enabled) { + if (alarm->days == 0 || ALARM_ON_DAY(*alarm, today.dayOfWeek)) { + tk_time_of_day_t alarm_time = { .hour = alarm->hour, .minute = alarm->minute, .second = 0, .millisecond = 0 }; + if (alarm_time > now) { + if (alarm_time < next_alarm_time) { + next_alarm_time = alarm_time; + next_alarm = alarm; + } + } + } + } + } + + // If none today, look for alarms in tomorrow + if(next_alarm == nullptr) { + for (int i = 0; i < ALARM_LIST_SIZE; i++) { + const alarm_setting_t * alarm = &alarms[i]; + if (alarm->enabled) { + if (alarm->days == 0 || ALARM_ON_DAY(*alarm, (today.dayOfWeek + 1) % 7)) { + tk_time_of_day_t alarm_time = { .hour = alarm->hour, .minute = alarm->minute, .second = 0, .millisecond = 0 }; + if (alarm_time < next_alarm_time) { + next_alarm_time = alarm_time; + next_alarm = alarm; + } + } + } + } + } + + return next_alarm; +} + +void alarm_init(SensorPool* s) { + sensors = s; _load_alarm_list_if_needed(); xTaskCreate( alarm_task, diff --git a/src/service/time.cpp b/src/service/time.cpp index b4485cb..2991633 100644 --- a/src/service/time.cpp +++ b/src/service/time.cpp @@ -124,3 +124,52 @@ void set_current_date(tk_date_t date) { time = get_current_time_coarse(); ESP_LOGI(LOG_TAG, "Changed date, current: %04d/%02d/%02d %02d:%02d:%02d", date.year, date.month, date.day, time.hour, time.minute, time.second); } + +tk_time_of_day operator -(const tk_time_of_day_t& a, const tk_time_of_day_t& b) { + tk_time_of_day result = { 0 }; + + int64_t a_ms = (a.hour * 60 * 60 + a.minute * 60 + a.second) * 1000 + a.millisecond; + int64_t b_ms = (b.hour * 60 * 60 + b.minute * 60 + b.second) * 1000 + b.millisecond; + + int64_t diff_ms = a_ms - b_ms; + + if (diff_ms < 0) { + // wrap around so that e.g 00:05 - 0h10min returns 23:55 + diff_ms += 24 * 60 * 60 * 1000; + } + + result.hour = diff_ms / (60 * 60 * 1000); + diff_ms %= (60 * 60 * 1000); + result.minute = diff_ms / (60 * 1000); + diff_ms %= (60 * 1000); + result.second = diff_ms / 1000; + result.millisecond = diff_ms % 1000; + + return result; +} + + +bool operator==(const tk_time_of_day_t& a, const tk_time_of_day_t& b) { + return a.hour == b.hour && a.minute == b.minute && a.second == b.second && a.millisecond == b.millisecond; +} + +bool operator<(const tk_time_of_day_t& a, const tk_time_of_day_t& b) { + if (a.hour < b.hour) { + return true; + } else if (a.hour == b.hour) { + if (a.minute < b.minute) { + return true; + } else if (a.minute == b.minute) { + if (a.second < b.second) { + return true; + } else if (a.second == b.second) { + return a.millisecond < b.millisecond; + } + } + } + return false; +} + +bool operator>(const tk_time_of_day_t& a, const tk_time_of_day_t& b) { + return !(a < b || a == b); +} diff --git a/src/views/idle_screens/next_alarm.cpp b/src/views/idle_screens/next_alarm.cpp new file mode 100644 index 0000000..b4947e5 --- /dev/null +++ b/src/views/idle_screens/next_alarm.cpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include + +static const uint8_t sleep_icns_data[] = { + // By PiiXL + 0x00, 0x00, 0x00, 0x1e, 0x1f, 0xc4, 0x3f, 0xc8, 0x7f, 0xde, 0xff, 0xe0, 0xff, 0xfc, 0xdb, 0x6c, + 0xe7, 0x9c, 0xff, 0xfc, 0xff, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0x7f, 0xf8, 0x3f, 0xf0, 0x1f, 0xe0, +}; + +static const sprite_t sleep_icns = { + .width = 16, .height = 16, + .data = sleep_icns_data, + .mask = nullptr +}; + +inline void itoa_padded(uint i, char * a) { + a[0] = '0' + (i / 10); + a[1] = '0' + (i % 10); + a[2] = 0; +} + +NextAlarmView::NextAlarmView() { + show_alarm = false; + time_remaining = {0}; + font = &xnu_font; +} + +void NextAlarmView::prepare() { + const alarm_setting_t * alarm = get_upcoming_alarm(); + if(alarm != nullptr) { + tk_time_of_day_t now = get_current_time_coarse(); + tk_time_of_day_t alarm_time = { 0 }; + alarm_time.hour = alarm->hour; + alarm_time.minute = alarm->minute; + + time_remaining = alarm_time - now; + + show_alarm = time_remaining.hour < 8; + } else { + show_alarm = false; + } +} + +void NextAlarmView::render(FantaManipulator *fb) { + fb->put_sprite(&sleep_icns, 0, 0); + + int char_count = 5; // XX:XX + int text_width = char_count * font->width; + int left_offset = (fb->get_width() - alarm_icns.width) / 2 - text_width / 2 + alarm_icns.width; + + char hour_str[3] = { 0 }; + char minute_str[3] = { 0 }; + if(time_remaining.hour > 0) { + itoa_padded(time_remaining.hour, hour_str); + itoa_padded(time_remaining.minute, minute_str); + } else { + itoa_padded(time_remaining.minute, hour_str); + itoa_padded(time_remaining.second, minute_str); + } + + fb->put_string(font, hour_str, left_offset, 0); + left_offset += 2 * font->width; + + fb->put_glyph(font, ':', left_offset, 0); + left_offset += font->width; + + fb->put_string(font, minute_str, left_offset, 0); +} + +int NextAlarmView::desired_display_time() { + prepare(); + if(show_alarm && time_remaining.hour < 8) { + return DISP_TIME_NO_OVERRIDE; + } else { + return DISP_TIME_DONT_SHOW; + } +} From fbfcce6786161dbdd849fdee3926ba2bfe76eb69 Mon Sep 17 00:00:00 2001 From: akasaka Date: Thu, 18 Jul 2024 16:31:11 +0900 Subject: [PATCH 06/21] animate the upcoming alarm screen --- include/views/idle_screens/next_alarm.h | 9 +++- src/views/idle_screens/next_alarm.cpp | 55 ++++++++++++++++++------- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/include/views/idle_screens/next_alarm.h b/include/views/idle_screens/next_alarm.h index e088fea..e254f84 100644 --- a/include/views/idle_screens/next_alarm.h +++ b/include/views/idle_screens/next_alarm.h @@ -2,18 +2,25 @@ #include #include #include +#include /// @brief A view that shows the time remaining until the next alarm. -class NextAlarmView: public Screen { +class NextAlarmView: public Screen, DroppingDigits { public: NextAlarmView(); void prepare(); void render(FantaManipulator*); + void step(); int desired_display_time(); private: + void recalculate(); tk_time_of_day_t time_remaining; bool show_alarm; + + // For dropping digits + int disp_h, next_h, disp_m, next_m; + int phase; const font_definition_t * font; }; diff --git a/src/views/idle_screens/next_alarm.cpp b/src/views/idle_screens/next_alarm.cpp index b4947e5..47a6b45 100644 --- a/src/views/idle_screens/next_alarm.cpp +++ b/src/views/idle_screens/next_alarm.cpp @@ -21,16 +21,27 @@ inline void itoa_padded(uint i, char * a) { a[2] = 0; } -NextAlarmView::NextAlarmView() { +NextAlarmView::NextAlarmView(): DroppingDigits() { show_alarm = false; time_remaining = {0}; font = &xnu_font; + phase = -1; + disp_h = 0; + disp_m = 0; + next_h = 0; + next_m = 0; } void NextAlarmView::prepare() { + recalculate(); + disp_h = next_h; + disp_m = next_m; +} + +void NextAlarmView::recalculate() { const alarm_setting_t * alarm = get_upcoming_alarm(); if(alarm != nullptr) { - tk_time_of_day_t now = get_current_time_coarse(); + tk_time_of_day_t now = get_current_time_precise(); tk_time_of_day_t alarm_time = { 0 }; alarm_time.hour = alarm->hour; alarm_time.minute = alarm->minute; @@ -43,6 +54,30 @@ void NextAlarmView::prepare() { } } +void NextAlarmView::step() { + recalculate(); + if(time_remaining.hour > 0) { + next_h = time_remaining.hour; + next_m = time_remaining.minute; + } else { + next_h = time_remaining.minute; + next_m = time_remaining.second; + } + + // this pattern actually makes sense, extract it into DroppingDigits? + if((next_h != disp_h || next_m != disp_m) && phase == -1) { + phase = 0; + } else if (phase < 17 && phase > -1) { + phase++; + } + + if(phase == 17) { + disp_h = next_h; + disp_m = next_m; + phase = -1; + } +} + void NextAlarmView::render(FantaManipulator *fb) { fb->put_sprite(&sleep_icns, 0, 0); @@ -50,27 +85,17 @@ void NextAlarmView::render(FantaManipulator *fb) { int text_width = char_count * font->width; int left_offset = (fb->get_width() - alarm_icns.width) / 2 - text_width / 2 + alarm_icns.width; - char hour_str[3] = { 0 }; - char minute_str[3] = { 0 }; - if(time_remaining.hour > 0) { - itoa_padded(time_remaining.hour, hour_str); - itoa_padded(time_remaining.minute, minute_str); - } else { - itoa_padded(time_remaining.minute, hour_str); - itoa_padded(time_remaining.second, minute_str); - } - - fb->put_string(font, hour_str, left_offset, 0); + draw_dropping_number(fb, disp_h, next_h, phase == -1 ? 0 : phase, left_offset); left_offset += 2 * font->width; fb->put_glyph(font, ':', left_offset, 0); left_offset += font->width; - fb->put_string(font, minute_str, left_offset, 0); + draw_dropping_number(fb, disp_m, next_m, phase == -1 ? 0 : phase, left_offset); } int NextAlarmView::desired_display_time() { - prepare(); + recalculate(); if(show_alarm && time_remaining.hour < 8) { return DISP_TIME_NO_OVERRIDE; } else { From 643555a58b8713bee6b0b6f69635270606965cba Mon Sep 17 00:00:00 2001 From: akasaka Date: Thu, 18 Jul 2024 17:35:34 +0900 Subject: [PATCH 07/21] duty cycle changes in sequencer (gimmick) --- src/sound/beeper.cpp | 36 ++++++++++++++++++++++++++++++++---- src/sound/melodies.cpp | 16 ++++++++++++++++ src/sound/sequencer.cpp | 13 +++++++++---- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/sound/beeper.cpp b/src/sound/beeper.cpp index b93abca..f4c0989 100644 --- a/src/sound/beeper.cpp +++ b/src/sound/beeper.cpp @@ -1,4 +1,32 @@ #include +#include +#include + +static void ledcWriteToneD(uint8_t chan, uint32_t freq, uint32_t duty) +{ + if(!freq){ + ledcWrite(chan, 0); + return; + } + + uint8_t group=(chan/8), timer=((chan/2)%4); + + ledc_timer_config_t ledc_timer = { + .speed_mode = (ledc_mode_t) group, + .duty_resolution = (ledc_timer_bit_t) 10, + .timer_num = (ledc_timer_t) timer, + .freq_hz = freq, + .clk_cfg = (ledc_clk_cfg_t) 0 + }; + + if(ledc_timer_config(&ledc_timer) != ESP_OK) + { + log_e("ledcSetup failed!"); + return; + } + + ledcWrite(chan, duty); +} static portMUX_TYPE _spinlock = portMUX_INITIALIZER_UNLOCKED; @@ -27,12 +55,12 @@ bool Beeper::is_channel_enabled(beeper_channel_t ch) { return (channel_status & (1 << ch)) > 0; } -void Beeper::start_tone(beeper_channel_t ch, uint freq) { +void Beeper::start_tone(beeper_channel_t ch, uint freq, uint16_t duty) { if(active_channel > ch) return; if(!is_channel_enabled(ch)) return; if(active_channel < ch) active_channel = ch; - ledcWriteTone(pwm_channel, freq); + ledcWriteToneD(pwm_channel, freq, duty); } void Beeper::stop_tone(beeper_channel_t ch) { @@ -41,9 +69,9 @@ void Beeper::stop_tone(beeper_channel_t ch) { active_channel = -1; } -void Beeper::beep_blocking(beeper_channel_t ch, uint freq, uint len) { +void Beeper::beep_blocking(beeper_channel_t ch, uint freq, uint len, uint16_t duty) { taskENTER_CRITICAL(&_spinlock); - start_tone(ch, freq); + start_tone(ch, freq, duty); delayMicroseconds(len * 1000); stop_tone(ch); taskEXIT_CRITICAL(&_spinlock); diff --git a/src/sound/melodies.cpp b/src/sound/melodies.cpp index 6708dd8..c08c93a 100644 --- a/src/sound/melodies.cpp +++ b/src/sound/melodies.cpp @@ -82,6 +82,7 @@ const melody_sequence_t arise = MELODY_OF(arise_data); // Caramell - Caramelldansen static const melody_item_t caramell_data[] = { + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {880, 181}, {0, 181}, {1479, 181}, @@ -191,6 +192,7 @@ const melody_sequence_t duvet = MELODY_OF(duvet_data); // T-Square - Truth static const melody_item_t truth_data[] = { + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {932, 371}, {0, 12}, {698, 182}, @@ -394,16 +396,19 @@ static const melody_item_t hishoku_data[] = { const melody_sequence_t hishoku = MELODY_OF(hishoku_data); static const melody_item_t bouken_data[] = { + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {880, 392}, {987, 196}, {1046, 196}, {1174, 196}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, {1318, 294}, {0, 98}, {880, 294}, {0, 98}, {1318, 392}, {880, 196}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {1318, 392}, {1174, 196}, {1318, 196}, @@ -452,6 +457,7 @@ static const melody_item_t bouken_data[] = { const melody_sequence_t bouken = MELODY_OF(bouken_data); static const melody_item_t gentle_jena_data[] = { + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {932, 775}, {0, 66}, {1479, 845}, @@ -535,6 +541,7 @@ static const melody_item_t gentle_jena_data[] = { const melody_sequence_t gentle_jena = MELODY_OF(gentle_jena_data); static const melody_item_t gammapolisz_data[] = { + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {880, 1299}, {0, 293}, {1046, 779}, @@ -589,18 +596,21 @@ static const melody_item_t gammapolisz_data[] = { const melody_sequence_t gammapolisz = MELODY_OF(gammapolisz_data); static const melody_item_t like_the_wind_data[] = { + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {783, 199}, {0, 30}, {783, 199}, {0, 30}, {783, 315}, {0, 146}, + {0x2FF, SEQ_LEN_FLAG_TIMBRE_SET}, {523, 199}, {0, 30}, {523, 199}, {0, 30}, {523, 315}, {0, 146}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, {391, 199}, {0, 30}, {523, 199}, @@ -618,18 +628,21 @@ static const melody_item_t like_the_wind_data[] = { {587, 115}, {523, 892}, {0, 261}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {783, 199}, {0, 30}, {783, 199}, {0, 30}, {783, 315}, {0, 146}, + {0x2FF, SEQ_LEN_FLAG_TIMBRE_SET}, {523, 199}, {0, 30}, {523, 199}, {0, 30}, {523, 315}, {0, 146}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, {391, 199}, {0, 30}, {523, 199}, @@ -642,6 +655,7 @@ static const melody_item_t like_the_wind_data[] = { {0, 30}, {622, 430}, {0, 30}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {587, 1123}, }; const melody_sequence_t like_the_wind = MELODY_OF(like_the_wind_data); @@ -676,6 +690,8 @@ static const melody_item_t waiting_freqs_data[] = { const melody_sequence_t waiting_freqs = MELODY_OF(waiting_freqs_data); static const melody_item_t the_way_data[] = { + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {932, 100}, {0, 20}, {1244, 108}, diff --git a/src/sound/sequencer.cpp b/src/sound/sequencer.cpp index 572a25f..2adf942 100644 --- a/src/sound/sequencer.cpp +++ b/src/sound/sequencer.cpp @@ -28,6 +28,7 @@ BeepSequencer::~BeepSequencer() { void BeepSequencer::play_sequence(const melody_sequence_t melody, beeper_channel_t ch, int repeat) { pointer = 0; + timbre = DUTY_SQUARE; current_sequence = melody; current_channel = ch; repetitions = repeat; @@ -70,13 +71,17 @@ void BeepSequencer::stop_sequence() { void BeepSequencer::task() { while(is_running) { melody_item_t cur = current_sequence.array[pointer]; - if(cur.frequency == 0) { - beeper->stop_tone(current_channel); + if(cur.length == SEQ_LEN_FLAG_TIMBRE_SET) { + timbre = cur.frequency; } else { - beeper->start_tone(current_channel, cur.frequency); + if(cur.frequency == 0) { + beeper->stop_tone(current_channel); + } else { + beeper->start_tone(current_channel, cur.frequency, timbre); + } + vTaskDelay(cur.length); } - vTaskDelay(cur.length); pointer ++; if(pointer >= current_sequence.count) { From 1eb1f9dd76c2872464c64ee7e70001fb6e58ff9c Mon Sep 17 00:00:00 2001 From: akasaka Date: Thu, 18 Jul 2024 17:35:45 +0900 Subject: [PATCH 08/21] max alarming duration --- include/service/prefs.h | 1 + include/sound/beeper.h | 6 ++++-- include/sound/sequencer.h | 3 +++ src/app/alarm_editor.cpp | 6 +++++- src/app/alarming.cpp | 13 +++++++++++++ src/network/admin_panel.cpp | 2 ++ 6 files changed, 28 insertions(+), 3 deletions(-) diff --git a/include/service/prefs.h b/include/service/prefs.h index 69cce8c..6ccc20c 100644 --- a/include/service/prefs.h +++ b/include/service/prefs.h @@ -75,6 +75,7 @@ static constexpr prefs_key_t PREFS_KEY_SWITCHBOT_EMULATES_LOCAL = "wos_emus"; static constexpr prefs_key_t PREFS_KEY_ALARM_LIST = "alarms"; static constexpr prefs_key_t PREFS_KEY_ALARM_SNOOZE_MINUTES = "snz_minutes"; +static constexpr prefs_key_t PREFS_KEY_ALARM_MAX_DURATION_MINUTES = "alm_dur"; static constexpr prefs_key_t PREFS_KEY_TIMER_TIME_SECONDS = "timer_time"; static constexpr prefs_key_t PREFS_KEY_TIMER_MELODY = "timer_melo"; diff --git a/include/sound/beeper.h b/include/sound/beeper.h index 760ee4b..bdc7b8b 100644 --- a/include/sound/beeper.h +++ b/include/sound/beeper.h @@ -15,17 +15,19 @@ typedef enum beeper_channels { CHANNEL_SYSTEM = 7 } beeper_channel_t; +static const uint16_t DUTY_SQUARE = 0x1FF; + class Beeper { public: Beeper(gpio_num_t pin, uint8_t ledcChannel); void set_channel_state(beeper_channel_t, bool); bool is_channel_enabled(beeper_channel_t); - void start_tone(beeper_channel_t, uint); + void start_tone(beeper_channel_t, uint, uint16_t duty = DUTY_SQUARE); void stop_tone(beeper_channel_t); /// @brief Play a tone for a precise amount of milliseconds. Blocks the whole tasks and might block neighboring tasks, so use sparingly. - void beep_blocking(beeper_channel_t, uint freq, uint len); + void beep_blocking(beeper_channel_t, uint freq, uint len, uint16_t duty = DUTY_SQUARE); private: uint8_t channel_status; int active_channel; diff --git a/include/sound/sequencer.h b/include/sound/sequencer.h index 46355a1..9b64a57 100644 --- a/include/sound/sequencer.h +++ b/include/sound/sequencer.h @@ -1,6 +1,8 @@ #pragma once #include +#define SEQ_LEN_FLAG_TIMBRE_SET (INT_MAX - 1) + typedef struct melody_item { /// @brief Frequency of the note in Hz. 0 for a pause. int frequency; @@ -28,6 +30,7 @@ class BeepSequencer { void task(); private: + uint32_t timbre; Beeper * beeper; melody_sequence_t current_sequence; beeper_channel_t current_channel; diff --git a/src/app/alarm_editor.cpp b/src/app/alarm_editor.cpp index aea2e52..c2e9e0f 100644 --- a/src/app/alarm_editor.cpp +++ b/src/app/alarm_editor.cpp @@ -48,7 +48,10 @@ class AlarmItemView: public Renderable { if(!setting.smart || setting.smart_margin_minutes == 0) { snprintf(time_buf, 16, "%02d:%02d", setting.hour, setting.minute); } else { - snprintf(time_buf, 16, "%02d:%02d (-%02d)", setting.hour, setting.minute, setting.smart_margin_minutes); + tk_time_of_day_t alarm_time = { .hour = setting.hour, .minute = setting.minute, .second = 0, .millisecond = 0 }; + tk_time_of_day_t margin = { .hour = 0, .minute = setting.smart_margin_minutes, .second = 0, .millisecond = 0 }; + tk_time_of_day_t early_time = alarm_time - margin; + snprintf(time_buf, 16, "%02d:%02d-%02d:%02d", early_time.hour, early_time.minute, setting.hour, setting.minute); } if(hid_test_key_state(KEY_RIGHT) == KEYSTATE_HIT) { @@ -211,6 +214,7 @@ AppShimAlarmEditor::AppShimAlarmEditor(Beeper *b): ProtoShimNavMenu::ProtoShimNa main_menu->add_view(new AlarmItemView(i, beginEditing)); } main_menu->add_view(new MenuNumberSelectorPreferenceView("Snooze time", PREFS_KEY_ALARM_SNOOZE_MINUTES, 0, 30, 1, normalActivationFunction)); + main_menu->add_view(new MenuNumberSelectorPreferenceView("Max beeping time, minutes", PREFS_KEY_ALARM_MAX_DURATION_MINUTES, 0, 120, 1, normalActivationFunction)); } void AppShimAlarmEditor::pop_renderable(transition_type_t transition) { diff --git a/src/app/alarming.cpp b/src/app/alarming.cpp index 2e17388..4753489 100644 --- a/src/app/alarming.cpp +++ b/src/app/alarming.cpp @@ -42,6 +42,9 @@ static int snooze_minutes = 0; static bool is_snoozing = false; static melody_sequence_t melody; +static TickType_t startedAt; +static TickType_t maxDur; + void app_alarming_prepare(Beeper* beeper) { if(seq) { seq->stop_sequence(); @@ -64,6 +67,8 @@ void app_alarming_prepare(Beeper* beeper) { melody = melody_from_no(alarm->melody_no); seq->play_sequence(melody, CHANNEL_ALARM, SEQUENCER_REPEAT_INDEFINITELY); } + startedAt = xTaskGetTickCount(); + maxDur = pdMS_TO_TICKS( prefs_get_int(PREFS_KEY_ALARM_MAX_DURATION_MINUTES) * 60000 ); power_mgmt_pause(); } @@ -242,6 +247,14 @@ void app_alarming_process() { state = STOP_HOLD_COUNTDOWN; #endif } + + if(maxDur > 0) { + // there is a max set limit + if(xTaskGetTickCount() - startedAt > maxDur) { + stop_alarm(); + return; + } + } } break; diff --git a/src/network/admin_panel.cpp b/src/network/admin_panel.cpp index 987753e..09dedf5 100644 --- a/src/network/admin_panel.cpp +++ b/src/network/admin_panel.cpp @@ -227,6 +227,7 @@ void build() { GP.SPOILER_BEGIN("Alarms", GP_BLUE); render_int("Snooze minutes:", PREFS_KEY_ALARM_SNOOZE_MINUTES); + render_int("Max duration [m]:", PREFS_KEY_ALARM_MAX_DURATION_MINUTES); render_alarms(); GP.SPOILER_END(); GP.BREAK(); @@ -466,6 +467,7 @@ void action() { save_alarms(); if(ui.click()) { save_int(PREFS_KEY_ALARM_SNOOZE_MINUTES, 0, 30); + save_int(PREFS_KEY_ALARM_MAX_DURATION_MINUTES, 0, 120); save_string(PREFS_KEY_WIFI_SSID); save_string(PREFS_KEY_WIFI_PASS); save_bool(PREFS_KEY_BLINK_SEPARATORS); From b279140e48a8e5a8f05d72151874758429de2779 Mon Sep 17 00:00:00 2001 From: akasaka Date: Thu, 18 Jul 2024 22:13:46 +0900 Subject: [PATCH 09/21] small-ish bug fixes --- include/app/alarm_editor.h | 1 + src/app/alarm_editor.cpp | 6 +++++- src/app/alarming.cpp | 10 ++++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/include/app/alarm_editor.h b/include/app/alarm_editor.h index 2480f82..d73d059 100644 --- a/include/app/alarm_editor.h +++ b/include/app/alarm_editor.h @@ -16,4 +16,5 @@ class AppShimAlarmEditor: public ProtoShimNavMenu { int current_editing_idx; alarm_setting_t current_editing_setting; AlarmEditorView * current_editor; + bool edit_flag; }; \ No newline at end of file diff --git a/src/app/alarm_editor.cpp b/src/app/alarm_editor.cpp index c2e9e0f..56e6a5a 100644 --- a/src/app/alarm_editor.cpp +++ b/src/app/alarm_editor.cpp @@ -189,6 +189,7 @@ AppShimAlarmEditor::AppShimAlarmEditor(Beeper *b): ProtoShimNavMenu::ProtoShimNa beeper = b; current_editor = nullptr; current_editing_idx = 0; + edit_flag = false; std::function normalActivationFunction = [this](bool isActive, Renderable* instance) { if(isActive) push_renderable(instance, TRANSITION_NONE); else pop_renderable(TRANSITION_NONE); @@ -203,6 +204,7 @@ AppShimAlarmEditor::AppShimAlarmEditor(Beeper *b): ProtoShimNavMenu::ProtoShimNa if(current_editor != nullptr) { delete current_editor; } + edit_flag = true; current_editing_idx = editIdx; const alarm_setting_t * settings = get_alarm_list(); current_editing_setting = settings[editIdx]; @@ -225,7 +227,9 @@ void AppShimAlarmEditor::pop_renderable(transition_type_t transition) { if(back_stack.size() == 1) { // returning to alarm list, likely from editing screen - if(current_editor != nullptr) { + if(current_editor != nullptr && edit_flag) { + // cannot delete current_editor here because it's still used by the transition coordinator for animation + edit_flag = false; set_alarm(current_editing_idx, current_editing_setting); main_menu->get_view(current_editing_idx)->prepare(); } diff --git a/src/app/alarming.cpp b/src/app/alarming.cpp index 4753489..e885095 100644 --- a/src/app/alarming.cpp +++ b/src/app/alarming.cpp @@ -46,6 +46,11 @@ static TickType_t startedAt; static TickType_t maxDur; void app_alarming_prepare(Beeper* beeper) { + // do this prior to changing state because apparently this may get called from a separate thread, so step() goes ahead of prepare() and thus exits the alarm right away? + // TODO: somehow stick the state switching to the UI thread!! and all that + startedAt = xTaskGetTickCount(); + maxDur = pdMS_TO_TICKS( prefs_get_int(PREFS_KEY_ALARM_MAX_DURATION_MINUTES) * 60000 ); + if(seq) { seq->stop_sequence(); free(seq); @@ -57,8 +62,6 @@ void app_alarming_prepare(Beeper* beeper) { arrows->prepare(); arrows->active = true; clockView->prepare(); - - state = BLINKERING; framecount = 0; snooze_minutes = prefs_get_int(PREFS_KEY_ALARM_SNOOZE_MINUTES); @@ -67,9 +70,8 @@ void app_alarming_prepare(Beeper* beeper) { melody = melody_from_no(alarm->melody_no); seq->play_sequence(melody, CHANNEL_ALARM, SEQUENCER_REPEAT_INDEFINITELY); } - startedAt = xTaskGetTickCount(); - maxDur = pdMS_TO_TICKS( prefs_get_int(PREFS_KEY_ALARM_MAX_DURATION_MINUTES) * 60000 ); power_mgmt_pause(); + state = BLINKERING; } void app_alarming_draw(FantaManipulator* fb) { From 8af8ed61ad4ac60e728ef46db11b7320856bc75a Mon Sep 17 00:00:00 2001 From: akasaka Date: Fri, 19 Jul 2024 08:43:44 +0900 Subject: [PATCH 10/21] fix out of bounds write in plot_pixel causing a bootloop when it's raining --- src/graphics/fanta_manipulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphics/fanta_manipulator.cpp b/src/graphics/fanta_manipulator.cpp index c46f199..239e519 100644 --- a/src/graphics/fanta_manipulator.cpp +++ b/src/graphics/fanta_manipulator.cpp @@ -47,7 +47,7 @@ void FantaManipulator::clear() { } void FantaManipulator::plot_pixel(int x, int y, bool state) { - if(x < 0 || y < 0 || x > buffer_size/2 || y > height) { + if(x < 0 || y < 0 || x >= width || y >= height) { return; } From b05836273a4a2b8c7628ad6204b74934933dde40 Mon Sep 17 00:00:00 2001 From: akasaka Date: Fri, 19 Jul 2024 09:23:22 +0900 Subject: [PATCH 11/21] Fix fuzzy alarm logic, bump version --- include/devices/big_clock.h | 2 +- include/devices/smol_clock.h | 2 +- include/service/time.h | 2 ++ src/service/alarm.cpp | 14 +++++--------- src/service/time.cpp | 15 +++++++++++++++ 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/include/devices/big_clock.h b/include/devices/big_clock.h index cf9735f..a79dcf8 100644 --- a/include/devices/big_clock.h +++ b/include/devices/big_clock.h @@ -12,7 +12,7 @@ // Plasma Information System OS (not DOS, there's no disk in it!) #define PRODUCT_NAME "PIS-OS" -#define PRODUCT_VERSION "2.0" +#define PRODUCT_VERSION "2.1" // ---- Connection to DISP BOARD ---- const gpio_num_t HWCONF_PLASMA_DATABUS_GPIOS[] = { diff --git a/include/devices/smol_clock.h b/include/devices/smol_clock.h index 72bcc7e..2b838b2 100644 --- a/include/devices/smol_clock.h +++ b/include/devices/smol_clock.h @@ -9,7 +9,7 @@ // Plasma Information System OS (not DOS, there's no disk in it!) #define PRODUCT_NAME "microPIS-OS" -#define PRODUCT_VERSION "2.0" +#define PRODUCT_VERSION "2.1" // ---- Connection to beeper ---- const gpio_num_t HWCONF_BEEPER_GPIO = GPIO_NUM_12; diff --git a/include/service/time.h b/include/service/time.h index 1814ec4..731d0ee 100644 --- a/include/service/time.h +++ b/include/service/time.h @@ -37,3 +37,5 @@ tk_time_of_day operator -(const tk_time_of_day_t& a, const tk_time_of_day_t& b); bool operator==(const tk_time_of_day_t& a, const tk_time_of_day_t& b); bool operator<(const tk_time_of_day_t& a, const tk_time_of_day_t& b); bool operator>(const tk_time_of_day_t& a, const tk_time_of_day_t& b); + +bool time_is_within_hour_from(const tk_time_of_day_t& start_of_hour, const tk_time_of_day_t& now); diff --git a/src/service/alarm.cpp b/src/service/alarm.cpp index 6984f4c..a5649b0 100644 --- a/src/service/alarm.cpp +++ b/src/service/alarm.cpp @@ -87,24 +87,20 @@ static void alarm_task(void*) { for(int i = 0; i < ALARM_LIST_SIZE; i++) { alarm_setting_t alarm = alarms[i]; + tk_time_of_day_t alarm_time = { .hour = alarm.hour, .minute = alarm.minute, .second = 0, .millisecond = 0 }; if(alarm.enabled) { if(alarm.days == 0 || ALARM_ON_DAY(alarm, today.dayOfWeek)){ - if(alarm.hour == now.hour && alarm.minute == now.minute) { + if(now == alarm_time) { ESP_LOGI(LOG_TAG, "Triggering alarm %i due to TIME reasons", i); _trigger_alarm_if_needed(today, i); break; } else if(alarm.smart && alarm.smart_margin_minutes > 0 - && sensors && sensors->exists(SENSOR_ID_MOTION) - && now.hour <= alarm.hour && now.minute < alarm.minute) { + && sensors && sensors->exists(SENSOR_ID_MOTION)) { // Simple smart alarm logic based on motion sensing tk_time_of_day_t margin_as_time = { .hour = 0, .minute = alarm.smart_margin_minutes, .second = 0, .millisecond = 0 }; - tk_time_of_day_t alarm_time = { .hour = alarm.hour, .minute = alarm.minute, .second = 0, .millisecond = 0 }; tk_time_of_day_t earliest_time = alarm_time - margin_as_time; - if ( - (now.hour >= earliest_time.hour && now.minute >= earliest_time.minute) || - // edge case if the time wraps around midnight: - (now.hour == 0 && earliest_time.hour == 23 && now.minute < earliest_time.minute) - ) { + // no upper boundary 'cause that is covered by the outer condition (exact cutoff) + if (time_is_within_hour_from(earliest_time, now)) { // We are now within the alarm margin, so in "armed" state sensor_info_t * motn = sensors->get_info(SENSOR_ID_MOTION); if(motn) { diff --git a/src/service/time.cpp b/src/service/time.cpp index 2991633..2fc7c58 100644 --- a/src/service/time.cpp +++ b/src/service/time.cpp @@ -173,3 +173,18 @@ bool operator<(const tk_time_of_day_t& a, const tk_time_of_day_t& b) { bool operator>(const tk_time_of_day_t& a, const tk_time_of_day_t& b) { return !(a < b || a == b); } + +bool time_is_within_hour_from(const tk_time_of_day_t& start_of_hour, const tk_time_of_day_t& now) { + // Calculate the time difference in minutes + int64_t start_minutes = start_of_hour.hour * 60 + start_of_hour.minute; + int64_t now_minutes = now.hour * 60 + now.minute; + int64_t diff_minutes = now_minutes - start_minutes; + + // Handle wrapping around midnight + if (diff_minutes < 0) { + diff_minutes += 24 * 60; + } + + // Check if the difference is within 1 hour (60 minutes) + return diff_minutes <= 60; +} \ No newline at end of file From 725f402c5ad750d96f8ebe710b2008a6313c9835 Mon Sep 17 00:00:00 2001 From: akasaka Date: Fri, 19 Jul 2024 09:54:02 +0900 Subject: [PATCH 12/21] i take back my words about ai, it's misleading, useless and should burn in hell; use your dam brain instead --- include/service/time.h | 6 ++++-- src/service/alarm.cpp | 3 +-- src/service/time.cpp | 46 +++++++++++++++--------------------------- 3 files changed, 21 insertions(+), 34 deletions(-) diff --git a/include/service/time.h b/include/service/time.h index 731d0ee..7fa4271 100644 --- a/include/service/time.h +++ b/include/service/time.h @@ -11,6 +11,8 @@ typedef struct tk_time_of_day { int millisecond; } tk_time_of_day_t; +static const tk_time_of_day_t ONE_HOUR = { .hour = 1, .minute = 0, .second = 0, .millisecond = 0 }; + typedef struct tk_date { int year; int month; @@ -36,6 +38,6 @@ void set_current_date(tk_date_t); tk_time_of_day operator -(const tk_time_of_day_t& a, const tk_time_of_day_t& b); bool operator==(const tk_time_of_day_t& a, const tk_time_of_day_t& b); bool operator<(const tk_time_of_day_t& a, const tk_time_of_day_t& b); +bool operator<=(const tk_time_of_day_t& a, const tk_time_of_day_t& b); bool operator>(const tk_time_of_day_t& a, const tk_time_of_day_t& b); - -bool time_is_within_hour_from(const tk_time_of_day_t& start_of_hour, const tk_time_of_day_t& now); +bool operator>=(const tk_time_of_day_t& a, const tk_time_of_day_t& b); diff --git a/src/service/alarm.cpp b/src/service/alarm.cpp index a5649b0..4453ffa 100644 --- a/src/service/alarm.cpp +++ b/src/service/alarm.cpp @@ -99,8 +99,7 @@ static void alarm_task(void*) { // Simple smart alarm logic based on motion sensing tk_time_of_day_t margin_as_time = { .hour = 0, .minute = alarm.smart_margin_minutes, .second = 0, .millisecond = 0 }; tk_time_of_day_t earliest_time = alarm_time - margin_as_time; - // no upper boundary 'cause that is covered by the outer condition (exact cutoff) - if (time_is_within_hour_from(earliest_time, now)) { + if ((now - earliest_time <= ONE_HOUR) && (alarm_time - now <= ONE_HOUR)) { // We are now within the alarm margin, so in "armed" state sensor_info_t * motn = sensors->get_info(SENSOR_ID_MOTION); if(motn) { diff --git a/src/service/time.cpp b/src/service/time.cpp index 2fc7c58..62b6442 100644 --- a/src/service/time.cpp +++ b/src/service/time.cpp @@ -150,41 +150,27 @@ tk_time_of_day operator -(const tk_time_of_day_t& a, const tk_time_of_day_t& b) bool operator==(const tk_time_of_day_t& a, const tk_time_of_day_t& b) { - return a.hour == b.hour && a.minute == b.minute && a.second == b.second && a.millisecond == b.millisecond; + int64_t a_ms = (a.hour * 60 * 60 + a.minute * 60 + a.second) * 1000 + a.millisecond; + int64_t b_ms = (b.hour * 60 * 60 + b.minute * 60 + b.second) * 1000 + b.millisecond; + return a_ms == b_ms; } bool operator<(const tk_time_of_day_t& a, const tk_time_of_day_t& b) { - if (a.hour < b.hour) { - return true; - } else if (a.hour == b.hour) { - if (a.minute < b.minute) { - return true; - } else if (a.minute == b.minute) { - if (a.second < b.second) { - return true; - } else if (a.second == b.second) { - return a.millisecond < b.millisecond; - } - } - } - return false; + int64_t a_ms = (a.hour * 60 * 60 + a.minute * 60 + a.second) * 1000 + a.millisecond; + int64_t b_ms = (b.hour * 60 * 60 + b.minute * 60 + b.second) * 1000 + b.millisecond; + return a_ms < b_ms; } -bool operator>(const tk_time_of_day_t& a, const tk_time_of_day_t& b) { - return !(a < b || a == b); +bool operator<=(const tk_time_of_day_t& a, const tk_time_of_day_t& b) { + return (a < b) || (a == b); } -bool time_is_within_hour_from(const tk_time_of_day_t& start_of_hour, const tk_time_of_day_t& now) { - // Calculate the time difference in minutes - int64_t start_minutes = start_of_hour.hour * 60 + start_of_hour.minute; - int64_t now_minutes = now.hour * 60 + now.minute; - int64_t diff_minutes = now_minutes - start_minutes; - - // Handle wrapping around midnight - if (diff_minutes < 0) { - diff_minutes += 24 * 60; - } +bool operator>(const tk_time_of_day_t& a, const tk_time_of_day_t& b) { + int64_t a_ms = (a.hour * 60 * 60 + a.minute * 60 + a.second) * 1000 + a.millisecond; + int64_t b_ms = (b.hour * 60 * 60 + b.minute * 60 + b.second) * 1000 + b.millisecond; + return a_ms > b_ms; +} - // Check if the difference is within 1 hour (60 minutes) - return diff_minutes <= 60; -} \ No newline at end of file +bool operator>=(const tk_time_of_day_t& a, const tk_time_of_day_t& b) { + return (a > b) || (a == b); +} From f168bcb742c56872658fc7623dde7a78dd24f732 Mon Sep 17 00:00:00 2001 From: akasaka Date: Fri, 19 Jul 2024 16:41:06 +0900 Subject: [PATCH 13/21] Some fixes to melody data; fix change_state across threads --- helper/chimes/bouken.mid | Bin 508 -> 508 bytes helper/chimes/caramelldansen.mid | Bin 603 -> 680 bytes helper/chimes/duvet.mid | Bin 499 -> 499 bytes helper/chimes/hishoku.mid | Bin 476 -> 461 bytes helper/chimes/like_the_wind.mid | Bin 473 -> 473 bytes helper/chimes/truth.mid | Bin 526 -> 519 bytes helper/chimes/when_present_is_past.mid | Bin 465 -> 464 bytes helper/midi_to_chime.py | 13 ++- src/main.cpp | 16 ++- src/sound/melodies.cpp | 131 ++++++++++++++++--------- 10 files changed, 110 insertions(+), 50 deletions(-) diff --git a/helper/chimes/bouken.mid b/helper/chimes/bouken.mid index 1441c5accff8c3d57d2e93be31a9d5de16e46221..b339fdc626fd22470cd1f403c2a66814823faef4 100644 GIT binary patch delta 18 Zcmeyv{D*miBO_C@%H)ZR*^?6({QyJ525bNT delta 18 acmeyv{D*miBO}v%qsbE)vnMAo`T+n%Sq7E> diff --git a/helper/chimes/caramelldansen.mid b/helper/chimes/caramelldansen.mid index 02b9945a4341f32373052d103a0461a4b52d93d5..73568b5d90a6668f5777e0f2d935493a85ac0d91 100644 GIT binary patch literal 680 zcmdT>I|_qP5F82Gr^pFJ1e;u7MG#ReEJ8d$0$T*q84Jrm+7zB87c@&A6O8EtY2Gli zyYu$bv?m1+z{46t({^WL8%(>Ef4^V~{C$7y`rBdJ!Ueqt##H5+0%M|*0Hv|gSYcQg z=7u?=LN!u}0LG{;Idyuvs>~cS$Vio0td3P;z`cxxD&hBkO9y7t$yNPgzTvgA+9s=Q ivdXZsov}am<2FpjZa-Dl5DOh0RMKDs=D57P%R$7X^(P`^DH=Ayd;W-i1% zHZ*W=1=!!7K=*nA-Rlmd-GTmxxdUb|gogPS5gssgFxn-h0xA63c;JD}z`8g9iGvgk J46K8m0{|#zs$T#A diff --git a/helper/chimes/duvet.mid b/helper/chimes/duvet.mid index 25d2731df0020c9e4d7bca622b7ccadb1db083ed..734611b0a14c9b2563e37cdbbf37dda07ec7561b 100644 GIT binary patch delta 153 wcmey&{F!-!GGjf%1hbT~2D6kV>;L);z9B`~3=9mc{S5Tt@S<2Wc|D^G006cq%K!iX delta 153 zcmey&{F!-!GGjgO1hbT~2D6lwnE(0=z9B`~3=9mclQZeVnGnE>WYL6x$?F+i04j1V A1poj5 diff --git a/helper/chimes/hishoku.mid b/helper/chimes/hishoku.mid index ce5ff8083bf56c8ca72d1e777d4a3bf63c551a04..70394fd5ac36d3dc72d281146afcb1505f25ae4a 100644 GIT binary patch delta 21 dcmcb^e3p5FCgX#NT6Y+2CKobhPj+C81^`@L2fY9Q delta 40 qcmX@he2000Cgb~wT6YARbT&9JFgP%_M1ZM^$%>3?CLdtr1z`XhQw-Mt diff --git a/helper/chimes/like_the_wind.mid b/helper/chimes/like_the_wind.mid index 3c04fc94a7b39319bd9e4d88a503cf94b218cd63..369e3bfe3486ef24b6c8fc03853cd82250515f39 100644 GIT binary patch delta 31 ncmcb~e3N;DAR~X1$$xzY-;kng1_lP!SqhUA87n3mFggMNo~sER delta 31 ncmcb~e3N;DAR~Vp&wqUe-;kng1_lP!xfPQW87n3mFggMNp3MnV diff --git a/helper/chimes/truth.mid b/helper/chimes/truth.mid index fde7749a69bb78f8997f74d96446da124bc77630..8c8c70c66b9521545ccbd0e34ba691aa685b60de 100644 GIT binary patch delta 39 scmeBUX=j0_Co$*8zdON>#J*)B86j-kozzdnO+NKrNe0|V=Vl*tzu%_l!#R7b+R0HA6R AtN;K2 diff --git a/helper/chimes/when_present_is_past.mid b/helper/chimes/when_present_is_past.mid index 077b74e04eea9dd37a4568b69cc3a97b8a828b8b..5f829e579548bfdbdd54b1efb80caf94bf91fd0d 100644 GIT binary patch delta 219 zcmXAjxeCHi6hyNi#sv#OKfvD3mjn|MVnS+L?F6edN)!TBVx$Bwf z{I9PGI!9yJxx|>VsHvW&I7*7S<~T@>nO3+;1z$^?q{KiAv{JCovEzQB1{*Q#BZLwm zp<2V@LC~5YPct-H#@9=V?%IiOMS delta 220 zcmW-by9&ZU5JipALJt$T#Fujj)smLrrj! zggn80<_Cj}k6Kz_F9mw6YZj8D%Zm#S*tkM7XXk+3nRhVkF#J(6OJ>V5(933q8*6*7e!DIdG2d^LZXi)S3 diff --git a/helper/midi_to_chime.py b/helper/midi_to_chime.py index bdc7a09..f16fd5e 100644 --- a/helper/midi_to_chime.py +++ b/helper/midi_to_chime.py @@ -12,6 +12,7 @@ last_time = 0 evts = [] # of (freq in hz or 0, delay in ms) +ended = False for msg in mid: if msg.type == "note_on" or msg.type == "note_off": @@ -23,13 +24,23 @@ else: # note off evts.append([0, 0]) + elif msg.type == "end_of_track": + print(msg) + if ended: + raise Exception("WTF, already ended") + ended = True + if evts[-1][0] == 0: + # pause exists, just extend it + evts[-1][1] = int(msg.time * 1000) + else: + evts.append([0, int(msg.time*1000)]) print(evts) print("static const melody_item_t "+name+"_data[] = {") i = 0 -while i < len(evts) - 1: +while i < len(evts): if evts[i][0] != 0 or evts[i][1] != 0: print(" {"+str(evts[i][0])+", "+str(evts[i][1])+"}, ") i+=1 diff --git a/src/main.cpp b/src/main.cpp index ed307e2..8afb99b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,6 +35,8 @@ static char LOG_TAG[] = "APL_MAIN"; static device_state_t current_state = STATE_BOOT; +static device_state_t _actual_current_state = STATE_BOOT; +static transition_type_t _next_transition = TRANSITION_NONE; const device_state_t startup_state = STATE_IDLE; static std::stack state_stack = {}; @@ -55,12 +57,18 @@ static bool fps_counter = false; void change_state(device_state_t to, transition_type_t transition) { if(to == STATE_OTAFVU) { current_state = STATE_OTAFVU; + _actual_current_state = STATE_OTAFVU; return; // all other things handled in the FVU process } if(to == current_state) return; + + // calling `change_state` from outside of main thread causes a prepare() call on the upcoming view + // to be out of sequence with step()/render() depending on what the main thread was up to at the time. + // So just keep the requested change in memory and do it when main thread gets around to it. + // might we need a queue here? + _next_transition = transition; current_state = to; - appHost->switch_to(current_state, transition); } void push_state(device_state_t next, transition_type_t transition) { @@ -252,4 +260,10 @@ void loop() { graph->unlock(); } desktop->step(); + + // thread safe state change kind of thing + if(_actual_current_state != current_state) { + _actual_current_state = current_state; + appHost->switch_to(_actual_current_state, _next_transition); + } } diff --git a/src/sound/melodies.cpp b/src/sound/melodies.cpp index c08c93a..d59ab06 100644 --- a/src/sound/melodies.cpp +++ b/src/sound/melodies.cpp @@ -84,43 +84,66 @@ const melody_sequence_t arise = MELODY_OF(arise_data); static const melody_item_t caramell_data[] = { {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {880, 181}, - {0, 181}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, + {293, 181}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {1479, 181}, {1318, 181}, {1174, 181}, - {0, 363}, - {1318, 181}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, + {293, 181}, {0, 181}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {1318, 181}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, + {220, 181}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {1174, 181}, {1318, 181}, {1174, 181}, {1318, 181}, {1318, 181}, {1479, 181}, - {0, 181}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, + {220, 181}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {987, 181}, {0, 181}, {1479, 181}, {1318, 181}, {1174, 181}, - {0, 363}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, + {246, 181}, + {246, 181}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {1318, 181}, - {0, 181}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, + {195, 181}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {1174, 181}, {1318, 181}, {1479, 181}, {1318, 181}, {1318, 181}, - {1174, 181}, - {0, 181}, + {1174, 181}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, + {195, 181}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {880, 181}, - {0, 181}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, + {293, 181}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {1479, 181}, {1318, 181}, {1174, 181}, - {0, 363}, - {1318, 181}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, + {293, 181}, {0, 181}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {1318, 181}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, + {220, 181}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {1174, 181}, {1318, 181}, {1174, 181}, @@ -132,15 +155,20 @@ static const melody_item_t caramell_data[] = { {1567, 181}, {1479, 181}, {1174, 181}, - {0, 181}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, + {293, 181}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {1174, 181}, {1318, 181}, - {0, 181}, + {0x1FF, SEQ_LEN_FLAG_TIMBRE_SET}, + {293, 181}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {1174, 181}, {1479, 181}, {1479, 181}, {1318, 363}, {1174, 181}, + {0, 181}, }; const melody_sequence_t caramell = MELODY_OF(caramell_data); @@ -176,7 +204,7 @@ static const melody_item_t duvet_data[] = { {0, 69}, {523, 284}, {0, 37}, - {523, 575}, + {523, 575}, {0, 69}, {466, 284}, {0, 37}, @@ -185,8 +213,8 @@ static const melody_item_t duvet_data[] = { {523, 284}, {0, 37}, {698, 575}, - {0, 69}, {587, 634}, + {0, 1693}, }; const melody_sequence_t duvet = MELODY_OF(duvet_data); @@ -205,7 +233,7 @@ static const melody_item_t truth_data[] = { {0, 9}, {698, 371}, {0, 12}, - {1244, 374}, + {1244, 374}, {0, 9}, {1108, 182}, {0, 9}, @@ -248,7 +276,7 @@ static const melody_item_t truth_data[] = { {830, 374}, {0, 9}, {554, 371}, - {0, 12}, + {0, 12}, {932, 374}, {0, 9}, {554, 371}, @@ -262,7 +290,7 @@ static const melody_item_t truth_data[] = { {932, 57}, {0, 6}, {830, 57}, - {0, 6}, + {0, 6}, {739, 182}, {0, 9}, {698, 951}, @@ -284,8 +312,7 @@ static const melody_item_t truth_data[] = { {622, 182}, {0, 9}, {830, 182}, - {0, 9}, - {932, 288}, + {0, 201}, }; const melody_sequence_t truth = MELODY_OF(truth_data); @@ -310,51 +337,55 @@ static const melody_item_t haruhi_no_omoi_data[] = { const melody_sequence_t haruhi_no_omoi = MELODY_OF(haruhi_no_omoi_data); static const melody_item_t wpip_data[] = { - {659, 617}, - {0, 8}, + {659, 625}, + {0, 75}, {659, 291}, {0, 8}, {830, 416}, - {0, 62}, + {0, 91}, {987, 404}, - {0, 75}, + {0, 87}, {1318, 483}, + {0, 12}, {1244, 462}, - {0, 16}, + {0, 37}, {1108, 475}, - {0, 4}, + {0, 12}, {987, 370}, - {0, 108}, - {554, 170}, - {0, 58}, - {622, 470}, - {0, 16}, - {622, 1183}, - {0, 16}, + {0, 141}, + {554, 275}, + {0, 12}, + {622, 225}, + {0, 245}, + {622, 754}, + {0, 508}, {830, 241}, + {0, 8}, {932, 441}, - {0, 37}, - {783, 695}, - {0, 20}, - {622, 479}, - {659, 670}, + {0, 58}, + {783, 720}, + {0, 33}, + {622, 441}, {0, 50}, - {659, 241}, - {830, 462}, - {0, 16}, - {987, 462}, - {0, 16}, + {659, 516}, + {0, 220}, + {659, 216}, + {0, 33}, + {830, 500}, + {987, 479}, + {0, 29}, {1479, 225}, {0, 12}, - {1318, 466}, - {0, 12}, + {1318, 483}, + {0, 4}, {1244, 225}, - {0, 16}, + {0, 41}, {1318, 475}, - {0, 4}, + {0, 33}, {1108, 466}, - {0, 12}, + {0, 20}, {1244, 1025}, + {0, 3041}, }; const melody_sequence_t wpip = MELODY_OF(wpip_data); @@ -392,6 +423,7 @@ static const melody_item_t hishoku_data[] = { {0, 428}, {1396, 214}, {1661, 857}, + {0, 214}, }; const melody_sequence_t hishoku = MELODY_OF(hishoku_data); @@ -453,6 +485,7 @@ static const melody_item_t bouken_data[] = { {1174, 196}, {1108, 392}, {880, 588}, + {0, 1372}, }; const melody_sequence_t bouken = MELODY_OF(bouken_data); @@ -537,6 +570,7 @@ static const melody_item_t gentle_jena_data[] = { {1661, 1762}, {0, 16}, {1396, 1925}, + {0, 125}, }; const melody_sequence_t gentle_jena = MELODY_OF(gentle_jena_data); @@ -657,6 +691,7 @@ static const melody_item_t like_the_wind_data[] = { {0, 30}, {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, {587, 1123}, + {0, 1184}, }; const melody_sequence_t like_the_wind = MELODY_OF(like_the_wind_data); From e8cb14c4b693d758467791d50da2331b509a7227 Mon Sep 17 00:00:00 2001 From: akasaka Date: Sat, 20 Jul 2024 14:05:24 +0900 Subject: [PATCH 14/21] The more music the merrier --- helper/chimes/gtrhero.mid | Bin 0 -> 1473 bytes helper/chimes/steiner.mid | Bin 0 -> 1306 bytes helper/chimes/syabon.mid | Bin 0 -> 1232 bytes helper/midi_to_chime.py | 11 +- include/sound/melodies.h | 11 +- include/sound/sequencer.h | 2 + src/sound/melodies.cpp | 663 +++++++++++++++++++++++++++++++++++++- src/sound/sequencer.cpp | 7 +- 8 files changed, 687 insertions(+), 7 deletions(-) create mode 100644 helper/chimes/gtrhero.mid create mode 100644 helper/chimes/steiner.mid create mode 100644 helper/chimes/syabon.mid diff --git a/helper/chimes/gtrhero.mid b/helper/chimes/gtrhero.mid new file mode 100644 index 0000000000000000000000000000000000000000..a7b3d923316b8a6c1f95d5875bf08e29148b6344 GIT binary patch literal 1473 zcmchX%}T>S5XVQ`=B#%KJ$mz0N<#>#Ye)M~*s#r4C{7uj}_;+a;g6`*+#D z<+rf=3gx|r^4`zw-a~os=XTy;o;Q@|Z8@D8fAwhXP_|o6r`8VTcqrQ^hEr(u&XnaC z%64t+8MMkHf>!SrRW6Ow+5Ur+~>knpn9pIF%_Pay@wz8slrMc>! z_IpI;&9sXUI~n54cvWoo1hjv`x8WXCcJR(=$gz`(#zsS9c$r8JR^xB*`f z*T5}>=lJ-Dl&V9=PH*0G-t+%C>HN#JbFS`o+>yIa=ik1<4&7$gxy^L<;Q8h0YV+Bt zz1Dha7VXT>+c@jBv+;Q1ib1x>2hJ6K_IRB8{Agilft3(JqY^Yqzwr=VxE1<1KCldgyxCEq;V<1W&U2JOMrONxdF+(fObZ(!|~@ z{Y)kTFWUz{Aox^Ga&DvV{p09$1OE5x@huy%?+8 zA-;p%^4R`t9|8F!&R$j@uJ_Qo_;K6xe{My$YFB>01)C~d>jx99t1YdoErI2hDju(@ zE3Nc3oc{9jQ|azOuay4u%WVZq?K%Qw>%pD!^tMs@d*AhYn1M6=F?PBB>~>Ldf2ps< VTk7{Z{qcB{Pu7wmqWlymzv(?>E~EQhV?7?Jn81s~$~j zgGpsHZ51e0Ytxp&$TOM}-U?n1ZyCrVz(3Tj4^DC)axQWtVYSO$n-X5hm5QkRWRO_1 zX&7Ar?+{Phn6?OxA=v1AuIOO(Y(jqrS)f0M451g06?D$d_y8@HjZ>6S5~JgoJ!TTK zG;GCF;69qnI{W-`$8uR)Xeeb-o96^>yJ|oHq?l7Gg*zrB+!2cUV9B9AI@J`f#n2dG z#lw>*%DgnI;Nil~4H+fVP<-F>-&KEIAXf>Mm0X?M)VWNjyBw$G%TRYq7y8lU zCFdp}t}78pKYLIbDe)1beQ~3FC>0hr0zqAj65{f_K)xPcD2U2XaRkpgRJAHiX^SZ4 z<8p6C#%Iv+)L|=M3_J1SDo7n$r~eUOFzODbF&-LjM?OMh_;~RYHjUw<%}<~OXVC}w z#0QCUZ5SjyZnZn;hjEnVIHfuA7pno{&lmDE?E(Xs!8L<^1$hNIg`C1Zft)~&A;)3w Vn^S*t`Tggq|1tg_ 0 and len(evts) > 0 and evts[-1][1] == 0: evts[-1][1] = int(msg.time * 1000) if msg.type == "note_on" and msg.velocity > 0: - evts.append([int(freq_note_converter.from_note_index(msg.note).freq), 0]) + evts.append([int(freq_note_converter.from_note_index(msg.note).freq), 0, ""]) else: # note off - evts.append([0, 0]) + evts.append([0, 0, ""]) elif msg.type == "end_of_track": print(msg) if ended: @@ -33,7 +33,9 @@ # pause exists, just extend it evts[-1][1] = int(msg.time * 1000) else: - evts.append([0, int(msg.time*1000)]) + evts.append([0, int(msg.time*1000), ""]) + elif msg.type == "marker": + evts[-1][2] = msg.text print(evts) @@ -43,6 +45,9 @@ while i < len(evts): if evts[i][0] != 0 or evts[i][1] != 0: print(" {"+str(evts[i][0])+", "+str(evts[i][1])+"}, ") + if evts[i][2] != "": + print(" ") + print(" // " + evts[i][2]) i+=1 print("};") diff --git a/include/sound/melodies.h b/include/sound/melodies.h index c9e0e4c..cf0a39f 100644 --- a/include/sound/melodies.h +++ b/include/sound/melodies.h @@ -19,8 +19,11 @@ extern const melody_sequence_t gammapolisz; extern const melody_sequence_t like_the_wind; extern const melody_sequence_t waiting_freqs; extern const melody_sequence_t the_way; +extern const melody_sequence_t guitar_hero; +extern const melody_sequence_t syabon; +extern const melody_sequence_t steiner; -static constexpr const char * all_chime_names_csv = "Just Beep,PC-98 Boot,Русь 28,Штрих-М,A.M. - Arise,Caramelldansen,BoA - Duvet,T-Square - Truth,Haruhi no Omoi,When Present Is Past,Hishoku no Sora,Bouken Desho Desho,Gentle Jena,Gammapolisz,Like The Wind (TMMS),NightRadio - Waiting Freqs,NightRadio - The Way,Random"; +static constexpr const char * all_chime_names_csv = "Just Beep,PC-98 Boot,Русь 28,Штрих-М,A.M. - Arise,Caramelldansen,BoA - Duvet,T-Square - Truth,Haruhi no Omoi,WAVE & DRAX - When Present Is Past,Hishoku no Sora,Bouken Desho Desho,Gentle Jena,Omega - Gammapolisz,Like The Wind (TMMS),NightRadio - Waiting Freqs,NightRadio - The Way,Dougal & Gammer - Guitar Hero,Hachiya Nanashi - Shabon,Gate Of Steiner,Random"; static const std::vector all_chime_names = { "Beep", "PC-98 Boot", @@ -39,6 +42,9 @@ static const std::vector all_chime_names = { "Like The Wind (TMMS)", "Waiting Freqs", "The Way", + "Guitar Hero", + "Shabon", + "Gate Of Steiner", "(Randomize)" }; static const melody_sequence_t all_chime_list[] = { @@ -59,6 +65,9 @@ static const melody_sequence_t all_chime_list[] = { like_the_wind, waiting_freqs, the_way, + guitar_hero, + syabon, + steiner, }; static const int all_chime_count = sizeof(all_chime_list)/sizeof(melody_sequence_t); diff --git a/include/sound/sequencer.h b/include/sound/sequencer.h index 9b64a57..7233ae4 100644 --- a/include/sound/sequencer.h +++ b/include/sound/sequencer.h @@ -2,6 +2,7 @@ #include #define SEQ_LEN_FLAG_TIMBRE_SET (INT_MAX - 1) +#define SEQ_LEN_FLAG_THE_LOOPAH (INT_MAX - 2) typedef struct melody_item { /// @brief Frequency of the note in Hz. 0 for a pause. @@ -35,6 +36,7 @@ class BeepSequencer { melody_sequence_t current_sequence; beeper_channel_t current_channel; size_t pointer; + size_t loop_point; bool is_running; int repetitions; TaskHandle_t hTask; diff --git a/src/sound/melodies.cpp b/src/sound/melodies.cpp index d59ab06..f01449c 100644 --- a/src/sound/melodies.cpp +++ b/src/sound/melodies.cpp @@ -725,7 +725,7 @@ static const melody_item_t waiting_freqs_data[] = { const melody_sequence_t waiting_freqs = MELODY_OF(waiting_freqs_data); static const melody_item_t the_way_data[] = { - {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {0xAA, SEQ_LEN_FLAG_TIMBRE_SET}, {932, 100}, {0, 20}, @@ -819,3 +819,664 @@ static const melody_item_t the_way_data[] = { {932, 480}, }; const melody_sequence_t the_way = MELODY_OF(the_way_data); + +static const melody_item_t guitar_hero_data[] = { + {0x90, SEQ_LEN_FLAG_TIMBRE_SET}, + {1108, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {2093, 80}, + {0, 5}, + {1864, 86}, + + // LOOP + {0x90, SEQ_LEN_FLAG_TIMBRE_SET}, + {0, SEQ_LEN_FLAG_THE_LOOPAH}, + {1661, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {2093, 80}, + {0, 5}, + {1864, 167}, + {0, 5}, + {1244, 80}, + {0, 5}, + {1567, 80}, + {0, 5}, + {1244, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {1244, 80}, + {0, 5}, + {1567, 80}, + {0, 5}, + {1244, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {1244, 80}, + {0, 5}, + {1567, 80}, + {0, 5}, + {1244, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {1244, 80}, + {0, 5}, + {1567, 80}, + {0, 5}, + {1244, 80}, + {0, 5}, + {2093, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {2093, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {2093, 167}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {2093, 80}, + {0, 5}, + {2217, 80}, + {0, 5}, + {2093, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {2093, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1108, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {2093, 80}, + {0, 5}, + {1864, 167}, + {0, 5}, + {1244, 80}, + {0, 5}, + {1567, 80}, + {0, 5}, + {1244, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {1244, 80}, + {0, 5}, + {1567, 80}, + {0, 5}, + {1244, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {1244, 80}, + {0, 5}, + {1567, 80}, + {0, 5}, + {1244, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {1244, 80}, + {0, 5}, + {1567, 80}, + {0, 5}, + {1244, 80}, + {0, 5}, + {2093, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {2093, 80}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {2093, 167}, + {0, 5}, + {1396, 80}, + {0, 5}, + {1661, 80}, + {0, 5}, + {2093, 80}, + {0, 5}, + {2217, 80}, + {0, 5}, + {2093, 80}, + {0, 5}, + {1864, 80}, + {0, 5}, + {2093, 80}, + + // BRIDGE + {0xAA, SEQ_LEN_FLAG_TIMBRE_SET}, + {830, 341}, + {0, 179}, + {830, 167}, + {0, 5}, + {1396, 231}, + {0, 115}, + {554, 86}, + {0, 86}, + {1396, 80}, + {0, 92}, + {1244, 80}, + {0, 92}, + {1396, 80}, + {0, 92}, + {1244, 80}, + {0, 92}, + {1396, 341}, + {0, 179}, + {1244, 341}, + {0, 5}, + {932, 341}, + {0, 5}, + {622, 173}, + {932, 167}, + {0, 5}, + {1567, 341}, + {0, 5}, + {698, 173}, + {1567, 80}, + {0, 92}, + {1046, 80}, + {0, 92}, + {1567, 80}, + {0, 92}, + {1046, 80}, + {0, 92}, + {1661, 341}, + {0, 5}, + {1661, 167}, + {0, 5}, + {1567, 167}, + {0, 5}, + {1244, 86}, + {0, 86}, +}; +const melody_sequence_t guitar_hero = MELODY_OF(guitar_hero_data); + +static const melody_item_t syabon_data[] = { + {739, 254}, + {1108, 127}, + {0, 127}, + {987, 127}, + {1108, 127}, + {0, 127}, + {1108, 127}, + {987, 127}, + {0, 127}, + {880, 127}, + {880, 381}, + {0, 254}, + {739, 254}, + {1108, 127}, + {0, 127}, + {987, 127}, + {1108, 127}, + {0, 127}, + {1108, 127}, + {987, 127}, + {0, 127}, + {880, 127}, + {1108, 127}, + {0, 381}, + {0, 127}, + {554, 254}, + {659, 127}, + {0, 127}, + {739, 127}, + {0, 254}, + {0, 127}, + {739, 254}, + {880, 127}, + {0, 127}, + {830, 127}, + {0, 127}, + {739, 127}, + {0, 127}, + {659, 127}, + {0, 127}, + {554, 127}, + {0, 127}, + {1108, 127}, + {0, 127}, + {987, 127}, + {0, 127}, + {987, 254}, + {880, 127}, + {0, 508}, + {0, 127}, + {739, 254}, + {1108, 127}, + {0, 127}, + {987, 127}, + {987, 127}, + {1108, 127}, + {1108, 127}, + {987, 127}, + {987, 127}, + {880, 127}, + {880, 381}, + {739, 127}, + {0, 127}, + {739, 254}, + {1108, 127}, + {0, 127}, + {987, 127}, + {0, 127}, + {1108, 127}, + {0, 127}, + {987, 127}, + {0, 127}, + {880, 127}, + {739, 127}, + {0, 381}, + {0, 127}, + {880, 254}, + {739, 127}, + {0, 127}, + {1108, 127}, + {0, 254}, + {880, 127}, + {0, 254}, + {739, 127}, + {659, 127}, + {739, 127}, + {880, 127}, + {1108, 127}, + {0, 127}, + {987, 127}, + {0, 127}, + {880, 127}, + {739, 127}, + {0, 381}, + {0, 889}, + {659, 254}, + {880, 127}, + {0, 127}, + {987, 127}, + {0, 127}, + {1108, 127}, + {0, 381}, + {880, 127}, + {0, 381}, + {1108, 127}, + {0, 381}, + {880, 127}, + {0, 254}, + {987, 127}, + {1108, 127}, + {0, 381}, + {987, 127}, + {987, 127}, + {880, 127}, + {739, 127}, + {0, 127}, + {0, 127}, + {739, 254}, + {880, 127}, + {0, 127}, + {987, 127}, + {0, 127}, + {1108, 127}, + {1174, 127}, + {0, 127}, + {1108, 254}, + {1174, 127}, + {0, 127}, + {1108, 127}, + {0, 254}, + {987, 127}, + {880, 127}, + {659, 127}, + {739, 127}, + {739, 254}, + {659, 127}, + {0, 127}, + {739, 127}, + {0, 127}, + {880, 127}, + {0, 127}, + {739, 127}, + {0, 127}, + {830, 127}, + {0, 127}, + {880, 127}, + {0, 127}, + {987, 127}, + {0, 127}, + {880, 127}, + {0, 127}, + {1108, 127}, + {0, 381}, + {880, 127}, + {0, 381}, + {1108, 127}, + {0, 381}, + {880, 127}, + {0, 254}, + {987, 127}, + {1108, 127}, + {0, 381}, + {987, 127}, + {987, 127}, + {880, 127}, + {739, 127}, + {0, 127}, + {0, 127}, + {739, 254}, + {880, 127}, + {0, 127}, + {987, 127}, + {0, 127}, + {1108, 127}, + {1108, 127}, + {880, 127}, + {880, 127}, + {739, 127}, + {739, 127}, + {830, 127}, + {739, 127}, + {659, 127}, + {0, 42}, + {739, 127}, + {0, 42}, + {880, 127}, + {0, 42}, + {830, 127}, + {0, 42}, + {739, 127}, + {0, 42}, + {659, 127}, + {0, 42}, + {739, 127}, + {2793, 127}, + {2959, 42}, + {0, 84}, + {2959, 127}, + {0, 127}, + {2217, 127}, + {1975, 127}, + {2093, 127}, + {0, 127}, + {1760, 127}, + {0, 127}, + {1479, 127}, + {0, 2033}, +}; +const melody_sequence_t syabon = MELODY_OF(syabon_data); + +static const melody_item_t steiner_data[] = { + {0x2AA, SEQ_LEN_FLAG_TIMBRE_SET}, + {1108, 576}, + {830, 961}, + {277, 576}, + {415, 384}, + {1108, 192}, + {830, 192}, + {1108, 192}, + {1244, 576}, + {830, 961}, + {415, 576}, + {622, 384}, + {1244, 192}, + {830, 192}, + {1244, 192}, + {1318, 576}, + {659, 384}, + {1318, 192}, + {1479, 192}, + {1318, 192}, + {1244, 576}, + {739, 384}, + {1244, 192}, + {1975, 192}, + {1661, 769}, + {622, 576}, + {739, 192}, + {830, 192}, + {207, 576}, + {622, 961}, + {622, 64}, + {0, 12}, + {830, 51}, + {1108, 544}, + {830, 865}, + {277, 576}, + {415, 384}, + {1108, 192}, + {830, 192}, + {1108, 192}, + {1244, 384}, + {1318, 192}, + {1244, 384}, + {830, 576}, + {415, 576}, + {622, 384}, + {830, 192}, + {1244, 192}, + {1318, 192}, + {1479, 576}, + {987, 384}, + {1661, 192}, + {1318, 192}, + {1244, 192}, + {246, 576}, + {987, 384}, + {1108, 192}, + {1244, 192}, + {1108, 769}, + {830, 576}, + {987, 384}, + {1108, 576}, + {1396, 576}, + {1479, 384}, + {1661, 576}, + {1479, 576}, + {1975, 384}, + {1661, 576}, + {1479, 576}, + {1244, 192}, + {1318, 192}, + {1479, 576}, + {1318, 576}, + {1975, 384}, + {1244, 576}, + {1318, 384}, + {622, 192}, + {659, 192}, + {622, 192}, + {1661, 576}, + {1479, 576}, + {1975, 384}, + {2217, 576}, + {1975, 384}, + {2637, 192}, + {2489, 192}, + {1661, 384}, + {5274, 192}, + {4978, 192}, + {3322, 576}, + {987, 358}, + {0, 25}, + {880, 576}, + {739, 384}, + {622, 192}, + {493, 384}, + {1661, 384}, + {1108, 192}, + {1479, 384}, + {880, 192}, + {1975, 192}, + {987, 192}, + {1661, 384}, + {880, 192}, + {1479, 576}, + {1244, 192}, + {1318, 192}, + {1479, 384}, + {830, 192}, + {1318, 384}, + {622, 192}, + {1975, 384}, + {1244, 576}, + {1318, 384}, + {622, 192}, + {659, 192}, + {622, 192}, + {1479, 384}, + {1661, 192}, + {1760, 384}, + {1975, 384}, + {2217, 384}, + {880, 192}, + {1318, 192}, + {880, 192}, + {2217, 192}, + {739, 192}, + {2959, 192}, + {1479, 192}, + {2959, 576}, + {830, 576}, + {622, 384}, + {2793, 1538}, + {0, 3076}, +}; +const melody_sequence_t steiner = MELODY_OF(steiner_data); \ No newline at end of file diff --git a/src/sound/sequencer.cpp b/src/sound/sequencer.cpp index 2adf942..bf553cf 100644 --- a/src/sound/sequencer.cpp +++ b/src/sound/sequencer.cpp @@ -28,6 +28,7 @@ BeepSequencer::~BeepSequencer() { void BeepSequencer::play_sequence(const melody_sequence_t melody, beeper_channel_t ch, int repeat) { pointer = 0; + loop_point = 0; timbre = DUTY_SQUARE; current_sequence = melody; current_channel = ch; @@ -73,6 +74,8 @@ void BeepSequencer::task() { melody_item_t cur = current_sequence.array[pointer]; if(cur.length == SEQ_LEN_FLAG_TIMBRE_SET) { timbre = cur.frequency; + } else if(cur.length == SEQ_LEN_FLAG_THE_LOOPAH) { + loop_point = pointer + 1; } else { if(cur.frequency == 0) { beeper->stop_tone(current_channel); @@ -87,9 +90,9 @@ void BeepSequencer::task() { if(pointer >= current_sequence.count) { if(repetitions > 0) { repetitions--; - pointer = 0; + pointer = loop_point; } else if(repetitions < 0) { - pointer = 0; + pointer = loop_point; } else { beeper->stop_tone(current_channel); is_running = false; From d4b84179caf75ea9c513f5e0bf2a5df5cc75d2ed Mon Sep 17 00:00:00 2001 From: akasaka Date: Sun, 21 Jul 2024 10:23:58 +0900 Subject: [PATCH 15/21] More music, slight drawing optimizations to reduce jitter by using more RAM --- helper/chimes/mermgrl.mid | Bin 0 -> 672 bytes helper/chimes/towa.mid | Bin 0 -> 1564 bytes include/sound/melodies.h | 53 +-- include/views/common/string_scroll.h | 7 + src/console.cpp | 2 +- src/graphics/fanta_manipulator.cpp | 15 +- src/network/admin_panel.cpp | 2 +- src/sensor/sensor_pool.cpp | 2 +- src/service/alarm.cpp | 16 - src/service/foo_client.cpp | 2 +- src/service/owm/weather.cpp | 2 +- src/service/power_management.cpp | 2 +- src/service/wordnik.cpp | 2 +- src/sound/melodies.cpp | 457 ++++++++++++++++++++- src/views/common/string_scroll.cpp | 49 ++- src/views/idle_screens/current_weather.cpp | 9 +- src/views/idle_screens/word_of_the_day.cpp | 4 +- src/views/menu/list_selection_item.cpp | 1 + src/views/menu/number_selection_item.cpp | 1 + 19 files changed, 531 insertions(+), 95 deletions(-) create mode 100644 helper/chimes/mermgrl.mid create mode 100644 helper/chimes/towa.mid diff --git a/helper/chimes/mermgrl.mid b/helper/chimes/mermgrl.mid new file mode 100644 index 0000000000000000000000000000000000000000..3bcc12662ce252778492344c9d5683dc9ca530fa GIT binary patch literal 672 zcmd5(yK2KQ6g_dA7&NtW^$qT-#l{#3q2L4x34}}?3N2)ErYzzN-8%IP`V0L;SF&(1 zWG-|&_v+sB(B1CeUI47Iz&)SZR>;zfrLxF7sxHh^Xu$H-qNpe;K(h=CnvtiWY9?$LIn%CTZ&0L;p)^5d ze62XDFgls5SoVQ`(kg?xqjHmn4b>)gdWWJNcawVJtU0E*;cqCK=sdFBW=RZxks>*F vlnzPla5PN(3n%Uvq+?uVfbA}uc1_25#is)IW z$09xJdym?`c+>S#dtB08LQ1+4?t<)?!q1bZGj{vM-sb6eYr0mH3TW&0b~?Xh z_IoR_klRZPpt?bzrnfiA)ZkY)sLR#~&NkX!jXj@E+ zFK*BHFovbQhyrXcLPM7}>sGv878s40soX7LMwxBdj%#&|C|uLJ_NoeFdw15@+pgQ!f(h?zb?Gp=+G?PgZW;e?sKKsi?mWXeommx3$p=$R>D z!j*0(^*@$dzt`lPnSLvdR1K|Nf2ov_bgi~H&xIB*7!}YgF?ldCtbg~<=Rc=G8u(ux F{sPd&M!5h0 literal 0 HcmV?d00001 diff --git a/include/sound/melodies.h b/include/sound/melodies.h index cf0a39f..ee7f1c6 100644 --- a/include/sound/melodies.h +++ b/include/sound/melodies.h @@ -22,53 +22,10 @@ extern const melody_sequence_t the_way; extern const melody_sequence_t guitar_hero; extern const melody_sequence_t syabon; extern const melody_sequence_t steiner; +extern const melody_sequence_t towa; -static constexpr const char * all_chime_names_csv = "Just Beep,PC-98 Boot,Русь 28,Штрих-М,A.M. - Arise,Caramelldansen,BoA - Duvet,T-Square - Truth,Haruhi no Omoi,WAVE & DRAX - When Present Is Past,Hishoku no Sora,Bouken Desho Desho,Gentle Jena,Omega - Gammapolisz,Like The Wind (TMMS),NightRadio - Waiting Freqs,NightRadio - The Way,Dougal & Gammer - Guitar Hero,Hachiya Nanashi - Shabon,Gate Of Steiner,Random"; -static const std::vector all_chime_names = { - "Beep", - "PC-98 Boot", - "\x90\xE3\xE1\xEC 28", - "\x98\xE2\xE0\xA8\xE5-M", - "A.M. - Arise", - "Caramelldansen", - "BoA - Duvet", - "T-Square - Truth", - "Haruhi no Omoi", - "When Present Is Past", - "Hishoku no Sora", - "Bouken Desho Desho", - "Gentle Jena", - "Gammapolisz", - "Like The Wind (TMMS)", - "Waiting Freqs", - "The Way", - "Guitar Hero", - "Shabon", - "Gate Of Steiner", - "(Randomize)" -}; -static const melody_sequence_t all_chime_list[] = { - just_beep, - pc98_pipo, - tulula_fvu, - oelutz_fvu, - arise, - caramell, - duvet, - truth, - haruhi_no_omoi, - wpip, - hishoku, - bouken, - gentle_jena, - gammapolisz, - like_the_wind, - waiting_freqs, - the_way, - guitar_hero, - syabon, - steiner, -}; -static const int all_chime_count = sizeof(all_chime_list)/sizeof(melody_sequence_t); - +extern const char * all_chime_names_csv; +extern const std::vector all_chime_names; +extern const melody_sequence_t all_chime_list[]; +extern const int all_chime_count; melody_sequence_t melody_from_no(int melody_no); \ No newline at end of file diff --git a/include/views/common/string_scroll.h b/include/views/common/string_scroll.h index 54687f0..2bf7edc 100644 --- a/include/views/common/string_scroll.h +++ b/include/views/common/string_scroll.h @@ -13,9 +13,11 @@ class StringScroll: public Renderable { int left_margin; void set_string(const char*); void set_y_position(int); + void rewind(); int estimated_frame_count(); void prepare(); void render(FantaManipulator *); + void step(); private: const font_definition_t * font; @@ -26,4 +28,9 @@ class StringScroll: public Renderable { int increment; int frame_counter; int wait_frames; + + fanta_buffer_t backing_buffer; + fanta_buffer_t temp_buffer; + int backing_buffer_width; + size_t backing_buffer_size; }; \ No newline at end of file diff --git a/src/console.cpp b/src/console.cpp index 6014896..a365a43 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -35,7 +35,7 @@ Console::Console(const font_definition_t * f, DisplayFramebuffer * fb) { "CONS", 4096, this, - 10, + 2, &hTask ) != pdPASS) { ESP_LOGE(LOG_TAG, "Task creation failed!"); diff --git a/src/graphics/fanta_manipulator.cpp b/src/graphics/fanta_manipulator.cpp index 239e519..42f738c 100644 --- a/src/graphics/fanta_manipulator.cpp +++ b/src/graphics/fanta_manipulator.cpp @@ -21,17 +21,19 @@ FantaManipulator::~FantaManipulator() { } bool FantaManipulator::lock(TickType_t timeout) { + if(buffer_semaphore == NULL) return true; return xSemaphoreTake(buffer_semaphore, timeout); } void FantaManipulator::unlock() { + if(buffer_semaphore == NULL) return; xSemaphoreGive(buffer_semaphore); } FantaManipulator* FantaManipulator::slice(int x, int w) { if(x > buffer_size/2) { ESP_LOGE(LOG_TAG, "Position (X=%i) is out of bounds of the screen", x); - return NULL; + return nullptr; } if(w > width - x) { @@ -71,9 +73,7 @@ void FantaManipulator::put_fanta(fanta_buffer_t fanta, int x, int y, int w, int if(w <= 0 || h <= 0) return; uint16_t fanta_column_mask = 0x0; - for(int i = y; i < y + h; i++) { - if(i < 0) continue; - if(i > 16) break; + for(int i = std::max(y, 0); i < std::min(y + h, 16); i++) { fanta_column_mask |= (1 << i); } @@ -84,10 +84,9 @@ void FantaManipulator::put_fanta(fanta_buffer_t fanta, int x, int y, int w, int uint8_t * mask_data = (uint8_t*) &fanta_column_mask; - size_t target_idx = x * 2; - for(int i = 0; i < w*2; i++) { - if(target_idx + i > buffer_size - 1) continue; - if(target_idx + i < 0) continue; + int target_idx = x * 2; + for(int i = std::max(0, -target_idx); i < w*2; i++) { + if(target_idx + i > buffer_size - 1) break; uint8_t current_column_mask = mask_data[i % 2]; if(mask) { current_column_mask &= mask[i]; diff --git a/src/network/admin_panel.cpp b/src/network/admin_panel.cpp index 09dedf5..34a3578 100644 --- a/src/network/admin_panel.cpp +++ b/src/network/admin_panel.cpp @@ -592,7 +592,7 @@ void admin_panel_prepare(SensorPool* s, Beeper* b) { "ADM", 4096, nullptr, - 10, + 2, &hTask ) != pdPASS) { ESP_LOGE(LOG_TAG, "Task creation failed!"); diff --git a/src/sensor/sensor_pool.cpp b/src/sensor/sensor_pool.cpp index abbd2ac..71ba1d1 100644 --- a/src/sensor/sensor_pool.cpp +++ b/src/sensor/sensor_pool.cpp @@ -25,7 +25,7 @@ SensorPool::SensorPool() { "SENS", 4096, this, - 10, + 3, &hTask ) != pdPASS) { ESP_LOGE(LOG_TAG, "Task creation failed!"); diff --git a/src/service/alarm.cpp b/src/service/alarm.cpp index 4453ffa..5e2baa2 100644 --- a/src/service/alarm.cpp +++ b/src/service/alarm.cpp @@ -147,22 +147,6 @@ const alarm_setting_t* get_upcoming_alarm() { } } - // If none today, look for alarms in tomorrow - if(next_alarm == nullptr) { - for (int i = 0; i < ALARM_LIST_SIZE; i++) { - const alarm_setting_t * alarm = &alarms[i]; - if (alarm->enabled) { - if (alarm->days == 0 || ALARM_ON_DAY(*alarm, (today.dayOfWeek + 1) % 7)) { - tk_time_of_day_t alarm_time = { .hour = alarm->hour, .minute = alarm->minute, .second = 0, .millisecond = 0 }; - if (alarm_time < next_alarm_time) { - next_alarm_time = alarm_time; - next_alarm = alarm; - } - } - } - } - } - return next_alarm; } diff --git a/src/service/foo_client.cpp b/src/service/foo_client.cpp index 0dcc5ea..bd85e70 100644 --- a/src/service/foo_client.cpp +++ b/src/service/foo_client.cpp @@ -225,7 +225,7 @@ void foo_client_begin() { "FOOCLI", 4096, nullptr, - 10, + 2, &hTask ) != pdPASS) { ESP_LOGE(LOG_TAG, "Task creation failed!"); diff --git a/src/service/owm/weather.cpp b/src/service/owm/weather.cpp index 62fae8f..32d5675 100644 --- a/src/service/owm/weather.cpp +++ b/src/service/owm/weather.cpp @@ -120,7 +120,7 @@ void weather_start() { "OWM", 4096, nullptr, - 8, + 1, &hTask ) != pdPASS) { ESP_LOGE(LOG_TAG, "Task creation failed!"); diff --git a/src/service/power_management.cpp b/src/service/power_management.cpp index b400961..2cb69fe 100644 --- a/src/service/power_management.cpp +++ b/src/service/power_management.cpp @@ -164,7 +164,7 @@ void power_mgmt_start(SensorPool * s, DisplayDriver * d, Beeper * b) { "PM", 4096, nullptr, - 10, + 4, &hTask ) != pdPASS) { ESP_LOGE(LOG_TAG, "Task creation failed!"); diff --git a/src/service/wordnik.cpp b/src/service/wordnik.cpp index 02e7af0..62a7e72 100644 --- a/src/service/wordnik.cpp +++ b/src/service/wordnik.cpp @@ -113,7 +113,7 @@ void wotd_start() { "WOTD", 8000, nullptr, - 8, + 1, &hTask ) != pdPASS) { ESP_LOGE(LOG_TAG, "Task creation failed!"); diff --git a/src/sound/melodies.cpp b/src/sound/melodies.cpp index f01449c..05435bd 100644 --- a/src/sound/melodies.cpp +++ b/src/sound/melodies.cpp @@ -1479,4 +1479,459 @@ static const melody_item_t steiner_data[] = { {2793, 1538}, {0, 3076}, }; -const melody_sequence_t steiner = MELODY_OF(steiner_data); \ No newline at end of file +const melody_sequence_t steiner = MELODY_OF(steiner_data); + +static const melody_item_t towa_data[] = { + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {349, 94}, + {391, 94}, + {415, 569}, + {391, 94}, + {415, 94}, + {466, 379}, + {311, 379}, + {523, 379}, + {622, 379}, + {587, 189}, + {622, 189}, + {587, 189}, + {466, 189}, + {698, 379}, + {830, 379}, + {783, 284}, + {698, 284}, + {622, 189}, + {0xAA, SEQ_LEN_FLAG_TIMBRE_SET}, + {523, 759}, + {0, 569}, + + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {349, 94}, + {391, 94}, + {415, 569}, + {391, 94}, + {415, 94}, + {466, 379}, + {311, 379}, + {523, 379}, + {622, 379}, + {587, 189}, + {622, 189}, + {587, 189}, + {466, 189}, + {698, 1518}, + {1396, 94}, + {0, 189}, + {0xAA, SEQ_LEN_FLAG_TIMBRE_SET}, + {932, 94}, + {0, 94}, + {0x55, SEQ_LEN_FLAG_TIMBRE_SET}, + {1244, 94}, + {0, 189}, + {0xAA, SEQ_LEN_FLAG_TIMBRE_SET}, + {830, 94}, + {0, 189}, + {0x55, SEQ_LEN_FLAG_TIMBRE_SET}, + {1108, 94}, + {0, 94}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {1479, 94}, + {0, 189}, + {0, 1518}, + // LOOP + {0, SEQ_LEN_FLAG_THE_LOOPAH}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {698, 189}, + {783, 189}, + {830, 189}, + {783, 189}, + {698, 189}, + {523, 189}, + {466, 189}, + {523, 379}, + {622, 189}, + {523, 569}, + {0, 569}, + {698, 189}, + {783, 189}, + {830, 189}, + {783, 189}, + {698, 189}, + {830, 189}, + {932, 189}, + {1046, 759}, + {0, 949}, + {698, 189}, + {783, 189}, + {830, 189}, + {783, 189}, + {698, 189}, + {523, 189}, + {466, 189}, + {523, 189}, + {523, 189}, + {622, 189}, + {523, 569}, + {0, 569}, + {1046, 189}, + {932, 189}, + {1046, 189}, + {932, 189}, + {830, 189}, + {783, 189}, + {622, 189}, + {698, 759}, + {0, 113}, + {0xAA, SEQ_LEN_FLAG_TIMBRE_SET}, + {830, 94}, + {0, 75}, + {830, 94}, + {0, 3}, + {783, 205}, + {0, 550}, + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {830, 189}, + {783, 189}, + {698, 379}, + {783, 189}, + {830, 189}, + {783, 189}, + {783, 189}, + {830, 189}, + {783, 189}, + {0, 189}, + {830, 189}, + {783, 189}, + {783, 189}, + {830, 189}, + {0, 189}, + {783, 189}, + {830, 189}, + {0, 189}, + {783, 189}, + {0, 189}, + {830, 189}, + {0, 189}, + {783, 189}, + {830, 189}, + {783, 189}, + {830, 189}, + {830, 189}, + {932, 379}, + {0, 379}, + {830, 189}, + {783, 189}, + {698, 379}, + {783, 189}, + {830, 189}, + {783, 379}, + {830, 189}, + {783, 379}, + {698, 379}, + {622, 189}, + {698, 189}, + {1046, 189}, + {1046, 759}, + {0, 189}, + {622, 189}, + {698, 379}, + {1046, 189}, + {1046, 379}, + {932, 379}, + {830, 189}, + {783, 1518}, + {0, 142}, + {391, 47}, + {622, 189}, + {554, 189}, + {523, 189}, + {0, 569}, + {698, 94}, + {783, 94}, + {830, 569}, + {932, 189}, + {830, 189}, + {783, 189}, + {698, 189}, + {622, 189}, + {523, 379}, + {1046, 379}, + {932, 379}, + {0, 189}, + {622, 94}, + {698, 94}, + {783, 569}, + {830, 189}, + {783, 189}, + {698, 189}, + {622, 189}, + {554, 189}, + {523, 379}, + {932, 379}, + {830, 379}, + {783, 379}, + {698, 379}, + {0, 189}, + {523, 189}, + {830, 379}, + {0, 189}, + {523, 189}, + {783, 569}, + {783, 189}, + {783, 379}, + {0, 189}, + {698, 189}, + {698, 379}, + {659, 379}, + {698, 569}, + {783, 189}, + {783, 759}, + {0, 569}, + {698, 94}, + {783, 94}, + {830, 569}, + {932, 189}, + {830, 189}, + {783, 189}, + {698, 189}, + {622, 189}, + {523, 379}, + {1046, 379}, + {932, 379}, + {0, 189}, + {622, 94}, + {698, 94}, + {783, 379}, + {783, 189}, + {830, 189}, + {783, 189}, + {698, 189}, + {622, 189}, + {554, 189}, + {523, 379}, + {932, 379}, + {830, 379}, + {783, 379}, + {698, 189}, + {0, 189}, + {349, 47}, + {554, 332}, + {391, 47}, + {622, 332}, + {415, 47}, + {698, 332}, + {466, 47}, + {783, 332}, + {523, 47}, + {830, 332}, + {466, 47}, + {783, 332}, + {391, 47}, + {622, 332}, + {415, 47}, + {698, 522}, + {698, 47}, + {1046, 142}, + {698, 47}, + {1046, 712}, + {0, 569}, + {830, 189}, + {783, 189}, + {622, 94}, + {523, 284}, + {783, 94}, + {698, 94}, + {0, 1518}, +}; +const melody_sequence_t towa = MELODY_OF(towa_data); + +static const melody_item_t mermgirl_data[] = { + {1174, 199}, + {0, 14}, + {1318, 199}, + {0, 14}, + {1396, 199}, + {0, 14}, + {932, 99}, + {0, 7}, + {1396, 199}, + {0, 14}, + {880, 99}, + {0, 7}, + {1174, 199}, + {0, 14}, + {1567, 199}, + {0, 14}, + {2093, 199}, + {0, 14}, + {1318, 35}, + {1567, 367}, + {0, 24}, + {880, 35}, + {1318, 164}, + {0, 14}, + {1318, 199}, + {0, 14}, + {1046, 199}, + {0, 14}, + {1318, 199}, + {0, 14}, + {1396, 199}, + {0, 14}, + {1567, 199}, + {0, 14}, + {1396, 403}, + {0, 24}, + {1396, 199}, + {0, 14}, + {1396, 99}, + {0, 7}, + {1396, 199}, + {0, 14}, + {880, 99}, + {0, 7}, + {1174, 199}, + {0, 14}, + {1567, 199}, + {0, 14}, + {2093, 199}, + {0, 14}, + {1567, 403}, + {0, 24}, + {1318, 35}, + {1567, 164}, + {0, 14}, + {1760, 199}, + {0, 14}, + {2093, 199}, + {0, 14}, + {1864, 199}, + {0, 14}, + {1760, 403}, + {0, 24}, + {1174, 199}, + {0, 14}, + {1318, 199}, + {0, 14}, + {1396, 199}, + {0, 14}, + {932, 99}, + {0, 7}, + {1396, 199}, + {0, 14}, + {880, 99}, + {0, 7}, + {1174, 199}, + {0, 14}, + {1046, 35}, + {1567, 164}, + {0, 14}, + {2093, 199}, + {0, 14}, + {1318, 35}, + {1567, 367}, + {0, 24}, + {1318, 199}, + {0, 14}, + {1318, 199}, + {0, 14}, + {1046, 199}, + {0, 14}, + {1318, 199}, + {0, 14}, + {1396, 199}, + {0, 14}, + {1567, 199}, + {0, 14}, + {1396, 403}, + {0, 24}, + {1396, 199}, + {0, 14}, + {1396, 99}, + {0, 7}, + {1396, 199}, + {0, 14}, + {880, 99}, + {0, 7}, + {1174, 199}, + {0, 14}, + {1567, 199}, + {0, 14}, + {2093, 199}, + {0, 14}, + {1567, 403}, + {0, 24}, + {1760, 199}, + {0, 14}, + {1760, 99}, + {0, 7}, + {1760, 199}, + {0, 14}, + {1567, 99}, + {0, 7}, + {1760, 199}, + {0, 14}, + {3520, 35}, + {1760, 164}, + {0, 14}, + {4186, 35}, + {2093, 164}, + {0, 14}, + {4698, 35}, + {2349, 164}, + {0, 228}, +}; +const melody_sequence_t mermgirl = MELODY_OF(mermgirl_data); + +const melody_sequence_t all_chime_list[] = { + just_beep, + pc98_pipo, + tulula_fvu, + oelutz_fvu, + arise, + caramell, + duvet, + truth, + haruhi_no_omoi, + wpip, + hishoku, + bouken, + gentle_jena, + gammapolisz, + like_the_wind, + waiting_freqs, + the_way, + guitar_hero, + syabon, + steiner, + towa, + mermgirl, +}; +const int all_chime_count = sizeof(all_chime_list)/sizeof(melody_sequence_t); + +const char * all_chime_names_csv = "Just Beep,PC-98 Boot,Русь 28,Штрих-М,A.M. - Arise,Caramelldansen,BoA - Duvet,T-Square - Truth,Haruhi no Omoi,WAVE & DRAX - When Present Is Past,Hishoku no Sora,Bouken Desho Desho,Gentle Jena,Omega - Gammapolisz,Like The Wind (TMMS),NightRadio - Waiting Freqs,NightRadio - The Way,Dougal & Gammer - Guitar Hero,Hachiya Nanashi - Shabon,Gate Of Steiner,deadballP - Towa ni tsuzuku gosenfu,Cream puff - Mermaid girl,Random"; +const std::vector all_chime_names = { + "Beep", + "PC-98 Boot", + "\x90\xE3\xE1\xEC 28", + "\x98\xE2\xE0\xA8\xE5-M", + "A.M. - Arise", + "Caramelldansen", + "BoA - Duvet", + "T-Square - Truth", + "Haruhi no Omoi", + "When Present Is Past", + "Hishoku no Sora", + "Bouken Desho Desho", + "Gentle Jena", + "Gammapolisz", + "Like The Wind (TMMS)", + "Waiting Freqs", + "The Way", + "Guitar Hero", + "Shabon", + "Gate Of Steiner", + "Towa ni tsuzuku gosenfu", + "Mermaid girl", + "(Randomize)" +}; \ No newline at end of file diff --git a/src/views/common/string_scroll.cpp b/src/views/common/string_scroll.cpp index 83c7cab..5a5e64f 100644 --- a/src/views/common/string_scroll.cpp +++ b/src/views/common/string_scroll.cpp @@ -3,7 +3,6 @@ StringScroll::StringScroll(const font_definition_t * f, const char * s) { font = f; - string = s; position = INT_MAX; string_width = 0; scroll_only_if_not_fit = true; @@ -13,6 +12,9 @@ StringScroll::StringScroll(const font_definition_t * f, const char * s) { wait_frames = 0; left_margin = 0; holdoff = 0; + backing_buffer = nullptr; + temp_buffer = nullptr; + set_string(s); switch(prefs_get_int(PREFS_KEY_DISP_SCROLL_SPEED)) { case SCROLL_SPEED_SLOW: @@ -45,14 +47,39 @@ StringScroll::StringScroll(const font_definition_t * f, const char * s) { void StringScroll::set_string(const char * s) { string = s; + if(backing_buffer != nullptr) { + free(backing_buffer); + } + if(temp_buffer != nullptr) { + free(temp_buffer); + } + + if(string != nullptr) { + int width = measure_string_width(font, string); + backing_buffer_width = width; + string_width = width; + + backing_buffer_size = 2 * width; + backing_buffer = (fanta_buffer_t) malloc(backing_buffer_size); + temp_buffer = (fanta_buffer_t) malloc(backing_buffer_size); + if(backing_buffer == nullptr || temp_buffer == nullptr) { + ESP_LOGE("SCRL", "Out Of Memory allocating backing buffer of size %i", backing_buffer_size); + } + else { + bool dummy; + FantaManipulator * bb = new FantaManipulator(backing_buffer, backing_buffer_size, width, 16, NULL, &dummy); + bb->put_string(font, string, 0, 0); + delete bb; + memcpy(temp_buffer, backing_buffer, backing_buffer_size); + } + } + prepare(); } void StringScroll::prepare() { position = INT_MAX; frame_counter = 0; - if(string == nullptr) return; - string_width = measure_string_width(font, string); } void StringScroll::set_y_position(int y) { @@ -64,11 +91,11 @@ int StringScroll::estimated_frame_count() { } void StringScroll::render(FantaManipulator * fb) { - if(string == nullptr) return; + if(temp_buffer == nullptr) return; if(scroll_only_if_not_fit) { if(string_width <= fb->get_width()) { - fb->put_string(font, string, align_to_right ? fb->get_width() - string_width : 0, y_position); + fb->put_fanta(temp_buffer, align_to_right ? fb->get_width() - string_width : 0, y_position, backing_buffer_width, font->height); return; } } @@ -96,5 +123,15 @@ void StringScroll::render(FantaManipulator * fb) { } } - fb->put_string(font, string, fb->get_width() - position, y_position); + fb->put_fanta(temp_buffer, fb->get_width() - position, y_position, backing_buffer_width, font->height); +} + +void StringScroll::step() { + // because put_fanta corrupts the provided buffer, we need to copy it before rendering the next frame + if(temp_buffer == nullptr || backing_buffer == nullptr) return; + memcpy(temp_buffer, backing_buffer, backing_buffer_size); +} + +void StringScroll::rewind() { + position = 0; } \ No newline at end of file diff --git a/src/views/idle_screens/current_weather.cpp b/src/views/idle_screens/current_weather.cpp index b962525..db24686 100644 --- a/src/views/idle_screens/current_weather.cpp +++ b/src/views/idle_screens/current_weather.cpp @@ -32,14 +32,7 @@ void CurrentWeatherView::prepare() { } void CurrentWeatherView::step() { - current_weather_t w; - if(weather_get_current(&w) && w.last_updated != weather.last_updated) { - weather = w; - prepare_for_new_weather(); - } else { - prerender_icon_frame(); - } - + prerender_icon_frame(); bottom_line->step(); } diff --git a/src/views/idle_screens/word_of_the_day.cpp b/src/views/idle_screens/word_of_the_day.cpp index c4b9ae2..737f582 100644 --- a/src/views/idle_screens/word_of_the_day.cpp +++ b/src/views/idle_screens/word_of_the_day.cpp @@ -68,14 +68,16 @@ void WordOfTheDayView::prepare() { last_update = ts; wotd_get_current(word_buffer, 32, definition_buffer, 256); ESP_LOGI(LOG_TAG, "New word of the day"); + bottom_line->set_string(definition_buffer); } + bottom_line->rewind(); icon_state = ani_sprite_prepare(&book_icon); - bottom_line->set_string(definition_buffer); } void WordOfTheDayView::step() { current_icon_frame = ani_sprite_frame(&icon_state); + bottom_line->step(); } void WordOfTheDayView::render(FantaManipulator *fb) { diff --git a/src/views/menu/list_selection_item.cpp b/src/views/menu/list_selection_item.cpp index 581264b..4b2f4b8 100644 --- a/src/views/menu/list_selection_item.cpp +++ b/src/views/menu/list_selection_item.cpp @@ -23,6 +23,7 @@ MenuListSelectorView::MenuListSelectorView(const char * title, std::vector Date: Sun, 21 Jul 2024 10:48:24 +0900 Subject: [PATCH 16/21] put_fanta doesn't corrupt the buffer anymore, so we can save memory on stringscroll backing buffers --- include/graphics/sprite.h | 2 -- include/views/common/string_scroll.h | 2 -- src/graphics/fanta_manipulator.cpp | 29 +++++++++++++++++++--------- src/graphics/sprite.cpp | 11 ----------- src/views/common/string_scroll.cpp | 22 +++++---------------- 5 files changed, 25 insertions(+), 41 deletions(-) diff --git a/include/graphics/sprite.h b/include/graphics/sprite.h index e6df599..691ba4d 100644 --- a/include/graphics/sprite.h +++ b/include/graphics/sprite.h @@ -42,8 +42,6 @@ typedef uint8_t* fanta_buffer_t; /// @note Unused pixels are filled with 0's. Transparency et al. should be handled by the drawing code. extern fanta_buffer_t sprite_to_fanta(const sprite_t*); extern fanta_buffer_t mask_to_fanta(const sprite_t*); -/// @brief Offset a raw Fanta buffer vertically. Negative is towards the top. -extern void fanta_offset_y(fanta_buffer_t,int,size_t); /// @brief Initialize a playback context for an animated sprite extern ani_sprite_state_t ani_sprite_prepare(const ani_sprite*); diff --git a/include/views/common/string_scroll.h b/include/views/common/string_scroll.h index 2bf7edc..41876e5 100644 --- a/include/views/common/string_scroll.h +++ b/include/views/common/string_scroll.h @@ -17,7 +17,6 @@ class StringScroll: public Renderable { int estimated_frame_count(); void prepare(); void render(FantaManipulator *); - void step(); private: const font_definition_t * font; @@ -30,7 +29,6 @@ class StringScroll: public Renderable { int wait_frames; fanta_buffer_t backing_buffer; - fanta_buffer_t temp_buffer; int backing_buffer_width; size_t backing_buffer_size; }; \ No newline at end of file diff --git a/src/graphics/fanta_manipulator.cpp b/src/graphics/fanta_manipulator.cpp index 42f738c..d18a701 100644 --- a/src/graphics/fanta_manipulator.cpp +++ b/src/graphics/fanta_manipulator.cpp @@ -69,6 +69,7 @@ void FantaManipulator::plot_pixel(int x, int y, bool state) { *dirty = true; } +// NB: This still relies too much on the fact our displays are 16 bits tall. Someday when porting to another display this will bite us in the ass. void FantaManipulator::put_fanta(fanta_buffer_t fanta, int x, int y, int w, int h, fanta_buffer_t mask, bool invert) { if(w <= 0 || h <= 0) return; @@ -77,22 +78,25 @@ void FantaManipulator::put_fanta(fanta_buffer_t fanta, int x, int y, int w, int fanta_column_mask |= (1 << i); } - fanta_offset_y(fanta, y, w); - if(mask) { - fanta_offset_y(mask, y, w); - } - uint8_t * mask_data = (uint8_t*) &fanta_column_mask; int target_idx = x * 2; for(int i = std::max(0, -target_idx); i < w*2; i++) { if(target_idx + i > buffer_size - 1) break; - uint8_t current_column_mask = mask_data[i % 2]; + uint8_t current_halfcolumn_mask = mask_data[i % 2]; if(mask) { - current_column_mask &= mask[i]; + uint16_t buf_mask_column = ((uint16_t*) mask)[i / 2]; + if(y > 0) buf_mask_column <<= y; + else if(y < 0) buf_mask_column >>= abs(y); + + current_halfcolumn_mask &= ((uint8_t*)&buf_mask_column)[i % 2]; } - buffer[target_idx + i] = ((uint8_t) (invert ? ~fanta[i] : fanta[i]) & current_column_mask) | (buffer[target_idx + i] & ~current_column_mask); + uint16_t buf_column = ((uint16_t*)fanta)[i / 2]; + if(y > 0) buf_column <<= y; + else if(y < 0) buf_column >>= abs(y); + uint8_t buf_halfcolumn = ((uint8_t*)&buf_column)[i % 2]; + buffer[target_idx + i] = ((uint8_t) (invert ? ~buf_halfcolumn : buf_halfcolumn) & current_halfcolumn_mask) | (buffer[target_idx + i] & ~current_halfcolumn_mask); } *dirty = true; @@ -128,7 +132,14 @@ void FantaManipulator::put_string(const font_definition_t * font, const char * s void FantaManipulator::scroll(int dx, int dy) { if(dy != 0) { - fanta_offset_y(buffer, dy, width); + uint16_t * columns = (uint16_t*) buffer; + for(int i = 0; i < width; i++) { + if(dy > 0) { + columns[i] <<= dy; + } else if(dy < 0) { + columns[i] >>= abs(dy); + } + } } if(dx > 0) { diff --git a/src/graphics/sprite.cpp b/src/graphics/sprite.cpp index 37be349..7ac338c 100644 --- a/src/graphics/sprite.cpp +++ b/src/graphics/sprite.cpp @@ -40,17 +40,6 @@ fanta_buffer_t sprite_to_fanta(const sprite_t* sprite) { return _convert_to_fanta(sprite->width, sprite->height, sprite->data); } -void fanta_offset_y(fanta_buffer_t fanta, int vert_offset, size_t width) { - uint16_t * columns = (uint16_t*) fanta; - for(int i = 0; i < width; i++) { - if(vert_offset > 0) { - columns[i] <<= vert_offset; - } else if(vert_offset < 0) { - columns[i] >>= abs(vert_offset); - } - } -} - ani_sprite_state_t ani_sprite_prepare(const ani_sprite* as) { return ani_sprite_state_t { .ani_sprite = as, diff --git a/src/views/common/string_scroll.cpp b/src/views/common/string_scroll.cpp index 5a5e64f..9db5609 100644 --- a/src/views/common/string_scroll.cpp +++ b/src/views/common/string_scroll.cpp @@ -13,7 +13,6 @@ StringScroll::StringScroll(const font_definition_t * f, const char * s) { left_margin = 0; holdoff = 0; backing_buffer = nullptr; - temp_buffer = nullptr; set_string(s); switch(prefs_get_int(PREFS_KEY_DISP_SCROLL_SPEED)) { @@ -50,9 +49,6 @@ void StringScroll::set_string(const char * s) { if(backing_buffer != nullptr) { free(backing_buffer); } - if(temp_buffer != nullptr) { - free(temp_buffer); - } if(string != nullptr) { int width = measure_string_width(font, string); @@ -61,8 +57,7 @@ void StringScroll::set_string(const char * s) { backing_buffer_size = 2 * width; backing_buffer = (fanta_buffer_t) malloc(backing_buffer_size); - temp_buffer = (fanta_buffer_t) malloc(backing_buffer_size); - if(backing_buffer == nullptr || temp_buffer == nullptr) { + if(backing_buffer == nullptr) { ESP_LOGE("SCRL", "Out Of Memory allocating backing buffer of size %i", backing_buffer_size); } else { @@ -70,7 +65,6 @@ void StringScroll::set_string(const char * s) { FantaManipulator * bb = new FantaManipulator(backing_buffer, backing_buffer_size, width, 16, NULL, &dummy); bb->put_string(font, string, 0, 0); delete bb; - memcpy(temp_buffer, backing_buffer, backing_buffer_size); } } @@ -91,11 +85,11 @@ int StringScroll::estimated_frame_count() { } void StringScroll::render(FantaManipulator * fb) { - if(temp_buffer == nullptr) return; + if(backing_buffer == nullptr) return; if(scroll_only_if_not_fit) { if(string_width <= fb->get_width()) { - fb->put_fanta(temp_buffer, align_to_right ? fb->get_width() - string_width : 0, y_position, backing_buffer_width, font->height); + fb->put_fanta(backing_buffer, align_to_right ? fb->get_width() - string_width : 0, y_position, backing_buffer_width, font->height); return; } } @@ -123,14 +117,8 @@ void StringScroll::render(FantaManipulator * fb) { } } - fb->put_fanta(temp_buffer, fb->get_width() - position, y_position, backing_buffer_width, font->height); -} - -void StringScroll::step() { - // because put_fanta corrupts the provided buffer, we need to copy it before rendering the next frame - if(temp_buffer == nullptr || backing_buffer == nullptr) return; - memcpy(temp_buffer, backing_buffer, backing_buffer_size); -} + fb->put_fanta(backing_buffer, fb->get_width() - position, y_position, backing_buffer_width, font->height); +} void StringScroll::rewind() { position = 0; From 0a31a268bf27ab1c3a2505f4dcb66237606780d3 Mon Sep 17 00:00:00 2001 From: akasaka Date: Sun, 21 Jul 2024 11:00:07 +0900 Subject: [PATCH 17/21] Fix action item with subtitle not updating after refactoring --- include/views/menu/action_item.h | 1 + src/views/menu/action_item.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/views/menu/action_item.h b/include/views/menu/action_item.h index 703fb04..f74784b 100644 --- a/include/views/menu/action_item.h +++ b/include/views/menu/action_item.h @@ -16,5 +16,6 @@ class MenuActionItemView: public Composite { const sprite_t * _icon; StringScroll * label; StringScroll * sublabel; + const char * subtitle; std::function _action; }; diff --git a/src/views/menu/action_item.cpp b/src/views/menu/action_item.cpp index 101579f..c9e9b17 100644 --- a/src/views/menu/action_item.cpp +++ b/src/views/menu/action_item.cpp @@ -1,6 +1,7 @@ #include -MenuActionItemView::MenuActionItemView(const char * title, std::function action, const sprite_t * icon, const char * subtitle) { +MenuActionItemView::MenuActionItemView(const char * title, std::function action, const sprite_t * icon, const char * subtitle_) { + subtitle = subtitle_; label = new StringScroll((subtitle == nullptr) ? &keyrus0816_font : &keyrus0808_font, title); label->start_at_visible = true; label->holdoff = 240; @@ -27,6 +28,11 @@ MenuActionItemView::~MenuActionItemView() { } void MenuActionItemView::step() { + if(sublabel != nullptr && subtitle != nullptr) { + // dynamically update subtitle because the underlying value might have changed + sublabel->set_string(subtitle); + } + Composite::step(); if(hid_test_key_state(KEY_RIGHT) == KEYSTATE_HIT) { From 3547584119c0c74a9469bd3eabd88c82d51a22a4 Mon Sep 17 00:00:00 2001 From: akasaka Date: Mon, 22 Jul 2024 00:02:39 +0900 Subject: [PATCH 18/21] open your eyes --- helper/chimes/eye_opener.mid | Bin 0 -> 1365 bytes src/sound/melodies.cpp | 179 ++++++++++++++++++++++++++++++++++- 2 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 helper/chimes/eye_opener.mid diff --git a/helper/chimes/eye_opener.mid b/helper/chimes/eye_opener.mid new file mode 100644 index 0000000000000000000000000000000000000000..2cf0a41743ffb8aabde4e3baec554452fdb7e6a0 GIT binary patch literal 1365 zcmeH{%WB(D5QZm?Bf3g2^d!<1XLj<_QEu2z38rWs^bfGByp@*F=?#` z6S~!l^d0&rc@yP7v6Wtopj~v=)oA9Q`RAPPfZCmbOGFxNP-)|~-H{_?uPEE(cFS&k zu;g$u;yv!j;pOFwU$8S1{3Z~dkd&QnZk>Ij?0vahdisd6m$V$;B8(G#KcUo&bQ77j ztwwm}Uq}`+F`={;CCwU<73mgHeV#PyM0Tv(L`Ix64Jh%w5-$uW6*uv&E0oA|^;OEW z$M1yT?s&}MK=9a~3LbiVE+}f zgwpr=9Qu8M_W`5*C<*p~BU<7>0FDNNB8H%dp+ymkZcacf)5>pKM@N*}>hbMQrS$Og z^!gCdzRC+%zZHHcdFz+Fb-rqyuL_R(aNuS_F%gRk#md_3aSYdEjR24Hfx1HZvATEKX?a0leo|T literal 0 HcmV?d00001 diff --git a/src/sound/melodies.cpp b/src/sound/melodies.cpp index 05435bd..4839ffb 100644 --- a/src/sound/melodies.cpp +++ b/src/sound/melodies.cpp @@ -1883,6 +1883,181 @@ static const melody_item_t mermgirl_data[] = { }; const melody_sequence_t mermgirl = MELODY_OF(mermgirl_data); +static const melody_item_t eye_opener_data[] = { + // Open your eyes + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {1046, 29}, + {523, 147}, + {1174, 29}, + {587, 147}, + {1318, 29}, + {659, 147}, + {1174, 29}, + {587, 323}, + {0x2AA, SEQ_LEN_FLAG_TIMBRE_SET}, + {220, 174}, + {0, 178}, + {220, 174}, + + // See all the love in me + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {1396, 29}, + {698, 147}, + {1318, 29}, + {659, 147}, + {1174, 29}, + {587, 147}, + {1318, 29}, + {659, 499}, + {1046, 29}, + {523, 147}, + {1046, 29}, + {523, 323}, + {0x2AA, SEQ_LEN_FLAG_TIMBRE_SET}, + {174, 174}, + {0, 178}, + + // I've got enough forever + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {783, 176}, + {698, 352}, + {659, 352}, + {587, 352}, + {523, 352}, + {587, 176}, + {659, 352}, + {0x2AA, SEQ_LEN_FLAG_TIMBRE_SET}, + {261, 176}, + + // Don't be afraid + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {1046, 29}, + {523, 147}, + {1174, 29}, + {587, 147}, + {1318, 29}, + {659, 147}, + {1174, 29}, + {587, 323}, + {0x2AA, SEQ_LEN_FLAG_TIMBRE_SET}, + {220, 174}, + {0, 178}, + {220, 174}, + + // Take all you need from me + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {1396, 29}, + {698, 147}, + {1318, 29}, + {659, 147}, + {1174, 29}, + {587, 147}, + {1318, 29}, + {659, 499}, + {1046, 29}, + {523, 147}, + {1046, 29}, + {523, 323}, + {0x2AA, SEQ_LEN_FLAG_TIMBRE_SET}, + {174, 174}, + {0, 178}, + + // And we'll be strong together + {0xFF, SEQ_LEN_FLAG_TIMBRE_SET}, + {440, 176}, + {523, 352}, + {587, 352}, + {698, 352}, + {659, 352}, + {587, 176}, + {523, 529}, + + // RIFF + {0xAA, SEQ_LEN_FLAG_TIMBRE_SET}, + {1318, 175}, + {880, 87}, + {1318, 175}, + {880, 87}, + {1318, 175}, + {1318, 175}, + {880, 87}, + {1318, 175}, + {880, 87}, + {1318, 175}, + {1396, 175}, + {1046, 87}, + {1396, 175}, + {1046, 87}, + {1396, 175}, + {1396, 175}, + {1046, 87}, + {1396, 175}, + {1046, 87}, + {1396, 175}, + {1567, 175}, + {1046, 87}, + {1567, 175}, + {1046, 87}, + {1567, 175}, + {1567, 175}, + {1046, 87}, + {1567, 175}, + {1046, 87}, + {1567, 175}, + {1174, 175}, + {1046, 87}, + {1174, 175}, + {1046, 87}, + {1174, 175}, + {1174, 175}, + {1046, 87}, + {1174, 175}, + {1046, 87}, + {1174, 175}, + {1318, 175}, + {880, 87}, + {1318, 175}, + {880, 87}, + {1318, 175}, + {1318, 175}, + {880, 87}, + {1318, 175}, + {880, 87}, + {1318, 175}, + {1396, 175}, + {1046, 87}, + {1396, 175}, + {1046, 87}, + {1396, 175}, + {1396, 175}, + {1046, 87}, + {1396, 175}, + {1046, 87}, + {1396, 175}, + {1567, 175}, + {1046, 87}, + {1567, 175}, + {1046, 87}, + {1567, 175}, + {1567, 175}, + {1046, 87}, + {1567, 175}, + {1046, 87}, + {1567, 175}, + {1174, 175}, + {1046, 87}, + {1174, 175}, + {1046, 87}, + {1174, 175}, + {1174, 175}, + {1046, 87}, + {1174, 175}, + {1046, 87}, + {1174, 174}, + {0, 707}, +}; +const melody_sequence_t eye_opener = MELODY_OF(eye_opener_data); + const melody_sequence_t all_chime_list[] = { just_beep, pc98_pipo, @@ -1906,10 +2081,11 @@ const melody_sequence_t all_chime_list[] = { steiner, towa, mermgirl, + eye_opener, }; const int all_chime_count = sizeof(all_chime_list)/sizeof(melody_sequence_t); -const char * all_chime_names_csv = "Just Beep,PC-98 Boot,Русь 28,Штрих-М,A.M. - Arise,Caramelldansen,BoA - Duvet,T-Square - Truth,Haruhi no Omoi,WAVE & DRAX - When Present Is Past,Hishoku no Sora,Bouken Desho Desho,Gentle Jena,Omega - Gammapolisz,Like The Wind (TMMS),NightRadio - Waiting Freqs,NightRadio - The Way,Dougal & Gammer - Guitar Hero,Hachiya Nanashi - Shabon,Gate Of Steiner,deadballP - Towa ni tsuzuku gosenfu,Cream puff - Mermaid girl,Random"; +const char * all_chime_names_csv = "Just Beep,PC-98 Boot,Русь 28,Штрих-М,A.M. - Arise,Caramelldansen,BoA - Duvet,T-Square - Truth,Haruhi no Omoi,WAVE & DRAX - When Present Is Past,Hishoku no Sora,Bouken Desho Desho,Gentle Jena,Omega - Gammapolisz,Like The Wind (TMMS),NightRadio - Waiting Freqs,NightRadio - The Way,Dougal & Gammer - Guitar Hero,Hachiya Nanashi - Shabon,Gate Of Steiner,deadballP - Towa ni tsuzuku gosenfu,Cream puff - Mermaid girl,Brisk & Trixxy - Eye Opener,Random"; const std::vector all_chime_names = { "Beep", "PC-98 Boot", @@ -1933,5 +2109,6 @@ const std::vector all_chime_names = { "Gate Of Steiner", "Towa ni tsuzuku gosenfu", "Mermaid girl", + "Eye Opener", "(Randomize)" }; \ No newline at end of file From 6dbb288332bbf8794bcba8f587f1229de5915b7d Mon Sep 17 00:00:00 2001 From: akasaka Date: Mon, 22 Jul 2024 22:40:43 +0900 Subject: [PATCH 19/21] Some crazy memory shite to make more use of the PSRAM --- include/graphics/sprite.h | 5 ++++- src/graphics/sprite.cpp | 10 ++++++++- src/main.cpp | 21 +++++++++++++++++-- src/service/owm/weather.cpp | 6 +++--- src/service/wordnik.cpp | 14 ++++++------- src/views/common/string_scroll.cpp | 2 +- src/views/transitions/transition_animator.cpp | 4 ++-- 7 files changed, 45 insertions(+), 17 deletions(-) diff --git a/include/graphics/sprite.h b/include/graphics/sprite.h index 691ba4d..65470dc 100644 --- a/include/graphics/sprite.h +++ b/include/graphics/sprite.h @@ -46,4 +46,7 @@ extern fanta_buffer_t mask_to_fanta(const sprite_t*); /// @brief Initialize a playback context for an animated sprite extern ani_sprite_state_t ani_sprite_prepare(const ani_sprite*); /// @brief Get the current animation frame to be drawn for an animated sprite -extern sprite_t ani_sprite_frame(ani_sprite_state_t*); \ No newline at end of file +extern sprite_t ani_sprite_frame(ani_sprite_state_t*); + +/// Allocate memory in the fast space, for graphics use specifically +void* gralloc(const size_t); \ No newline at end of file diff --git a/src/graphics/sprite.cpp b/src/graphics/sprite.cpp index 7ac338c..4b9e9e2 100644 --- a/src/graphics/sprite.cpp +++ b/src/graphics/sprite.cpp @@ -5,10 +5,18 @@ static char LOG_TAG[] = "FB_SPR"; +void* gralloc(const size_t size) { +#ifndef BOARD_HAS_PSRAM + return malloc(size); +#else + return heap_caps_malloc(size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); +#endif +} + fanta_buffer_t _convert_to_fanta(uint8_t width, uint8_t height, const uint8_t* data) { size_t required_size = width; - uint16_t * columns = (uint16_t*) malloc(required_size * sizeof(uint16_t)); + uint16_t * columns = (uint16_t*) gralloc(required_size * sizeof(uint16_t)); if(columns == nullptr) { ESP_LOGE(LOG_TAG, "Allocation failed"); return nullptr; diff --git a/src/main.cpp b/src/main.cpp index 8afb99b..6fec94c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,7 +30,6 @@ #include #include #include -#include static char LOG_TAG[] = "APL_MAIN"; @@ -44,7 +43,7 @@ static ViewCompositor * desktop; static ViewMultiplexor * appHost; static FpsCounter * fpsCounter; -static DisplayFramebuffer * fb; +IRAM_ATTR static DisplayFramebuffer * fb; static FantaManipulator * graph; static Console * con; static SensorPool * sensors; @@ -170,6 +169,11 @@ void bringup_hid() { void setup() { // Set up serial for logs Serial.begin(115200); + +#ifdef BOARD_HAS_PSRAM + heap_caps_malloc_extmem_enable(16); +#endif + display_driver.reset(); display_driver.set_power(true); @@ -253,6 +257,18 @@ void setup() { alarm_init(sensors); } +static TickType_t memory_last_print = 0; +static void print_memory() { + TickType_t now = xTaskGetTickCount(); + if(now - memory_last_print > pdMS_TO_TICKS(30000)) { + ESP_LOGI(LOG_TAG, "HEAP: %d free of %d (%d minimum)", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getMinFreeHeap()); +#ifdef BOARD_HAS_PSRAM + ESP_LOGI(LOG_TAG, "PSRAM: %d free of %d (%d minimum)", ESP.getFreePsram(), ESP.getPsramSize(), ESP.getMinFreePsram()); +#endif + memory_last_print = now; + } +} + void loop() { fb->wait_next_frame(); if(graph->lock()) { @@ -266,4 +282,5 @@ void loop() { _actual_current_state = current_state; appHost->switch_to(_actual_current_state, _next_transition); } + print_memory(); } diff --git a/src/service/owm/weather.cpp b/src/service/owm/weather.cpp index 32d5675..b3aa821 100644 --- a/src/service/owm/weather.cpp +++ b/src/service/owm/weather.cpp @@ -42,10 +42,10 @@ void WeatherTaskFunction( void * pvParameter ) { ESP_LOGI(LOG_TAG, "Task started"); - static WiFiClient client; - static HTTPClient http; + EXT_RAM_ATTR static WiFiClient client; + EXT_RAM_ATTR static HTTPClient http; - static char url[256]; + EXT_RAM_ATTR static char url[256]; snprintf(url, 150, currentApi, latitude.c_str(), longitude.c_str(), apiKey.c_str()); bool isFailure = false; diff --git a/src/service/wordnik.cpp b/src/service/wordnik.cpp index 62a7e72..9d0989c 100644 --- a/src/service/wordnik.cpp +++ b/src/service/wordnik.cpp @@ -11,14 +11,14 @@ static char LOG_TAG[] = "WORDNIK"; static const char * currentApi = "https://api.wordnik.com/v4/words.json/wordOfTheDay?api_key=%s"; -static String apiKey; +EXT_RAM_ATTR static String apiKey; static TickType_t interval; static TaskHandle_t hTask = NULL; static SemaphoreHandle_t cacheSemaphore; -static char wordCache[128]; -static char definitionCache[256]; +EXT_RAM_ATTR static char wordCache[128]; +EXT_RAM_ATTR static char definitionCache[256]; static TickType_t lastUpdate = 0; TickType_t wotd_get_last_update() { @@ -44,10 +44,10 @@ void WordnikTaskFunction( void * pvParameter ) { ESP_LOGI(LOG_TAG, "Task started"); - static WiFiClientSecure client; - static HTTPClient http; + EXT_RAM_ATTR static WiFiClientSecure client; + EXT_RAM_ATTR static HTTPClient http; - static char url[128]; + EXT_RAM_ATTR static char url[128]; snprintf(url, 150, currentApi, apiKey.c_str()); bool isFailure = false; @@ -58,7 +58,7 @@ void WordnikTaskFunction( void * pvParameter ) ESP_LOGV(LOG_TAG, "Query: %s", url); int response = http.GET(); if(response == 200) { - JsonDocument response; + EXT_RAM_ATTR JsonDocument response; DeserializationError error = deserializeJson(response, http.getStream()); diff --git a/src/views/common/string_scroll.cpp b/src/views/common/string_scroll.cpp index 9db5609..2a972b8 100644 --- a/src/views/common/string_scroll.cpp +++ b/src/views/common/string_scroll.cpp @@ -56,7 +56,7 @@ void StringScroll::set_string(const char * s) { string_width = width; backing_buffer_size = 2 * width; - backing_buffer = (fanta_buffer_t) malloc(backing_buffer_size); + backing_buffer = (fanta_buffer_t) gralloc(backing_buffer_size); if(backing_buffer == nullptr) { ESP_LOGE("SCRL", "Out Of Memory allocating backing buffer of size %i", backing_buffer_size); } diff --git a/src/views/transitions/transition_animator.cpp b/src/views/transitions/transition_animator.cpp index c22804a..98935ae 100644 --- a/src/views/transitions/transition_animator.cpp +++ b/src/views/transitions/transition_animator.cpp @@ -1,8 +1,8 @@ #include TransitionAnimationCoordinator::TransitionAnimationCoordinator() { - backingBufferA = (fanta_buffer_t) malloc(DisplayFramebuffer::BUFFER_SIZE); - backingBufferB = (fanta_buffer_t) malloc(DisplayFramebuffer::BUFFER_SIZE); + backingBufferA = (fanta_buffer_t) gralloc(DisplayFramebuffer::BUFFER_SIZE); + backingBufferB = (fanta_buffer_t) gralloc(DisplayFramebuffer::BUFFER_SIZE); backingA = new FantaManipulator(backingBufferA, DisplayFramebuffer::BUFFER_SIZE, DisplayFramebuffer::width, DisplayFramebuffer::height, semaA, &dirtyA); backingB = new FantaManipulator(backingBufferB, DisplayFramebuffer::BUFFER_SIZE, DisplayFramebuffer::width, DisplayFramebuffer::height, semaB, &dirtyB); backingA->clear(); From 008199824f30821600b487234b14968d700dfbdf Mon Sep 17 00:00:00 2001 From: akasaka Date: Mon, 22 Jul 2024 22:42:13 +0900 Subject: [PATCH 20/21] Why is this even a feature? Don't ask --- README.md | 11 +- include/app/weighing.h | 29 + include/device_config.h | 3 +- include/sensor/sensor.h | 5 + include/service/balance_board.h | 17 + include/service/prefs.h | 2 + include/state.h | 4 + lib/Wiimote/LICENSE | 21 + lib/Wiimote/Wiimote.cpp | 945 ++++++++++++++++++++++++++++++++ lib/Wiimote/Wiimote.h | 38 ++ lib/Wiimote/wiimote_bt.h | 148 +++++ src/app/idle.cpp | 8 + src/app/menu.cpp | 13 + src/app/weighing.cpp | 123 +++++ src/main.cpp | 4 + src/service/alarm.cpp | 2 +- src/service/balance_board.cpp | 150 +++++ 17 files changed, 1520 insertions(+), 3 deletions(-) create mode 100644 include/app/weighing.h create mode 100644 include/service/balance_board.h create mode 100644 lib/Wiimote/LICENSE create mode 100644 lib/Wiimote/Wiimote.cpp create mode 100644 lib/Wiimote/Wiimote.h create mode 100644 lib/Wiimote/wiimote_bt.h create mode 100644 src/app/weighing.cpp create mode 100644 src/service/balance_board.cpp diff --git a/README.md b/README.md index 20600cf..3964e19 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ A somewhat portable relatively-stylish pixel-art clock/weather station. ### Software flags * `HAS_WORDNIK_API`: compile with the Word Of The Day service using [Wordnik API](https://developer.wordnik.com/). This requires SSL, so bloats the firmware size significantly. -* `HAS_BLUETOOTH_LE`: automatically set on ESP32. Required for Switchbot-over-BLE. Uses [NimBLE](https://github.com/apache/mynewt-nimble), but still increases firmware size significantly. +* `HAS_BLUETOOTH_LE`: automatically set on ESP32. Required for Switchbot-over-BLE. Uses Arduino-BLE and increases firmware size significantly. * `HAS_OTAFVU`: OTA updates via ArduinoOTA. Currently disabled due to partition size constraints from the above. ### Thermal sensors @@ -44,6 +44,15 @@ A somewhat portable relatively-stylish pixel-art clock/weather station. * Opto-resistor in voltage divider mode ([driver](src/sensor/light.cpp), feature flag `HAS_LIGHT_SENSOR`) +### HID + +* Keypad/D-Pad. Set feature flag `HAS_KEYPAD` and define `const keypad_definition_t HWCONF_KEYPAD` in the device definition. [Driver](src/input/keypad.cpp) +* Touch plane. E.g. a faceplate with touch sensitive arrow keys to work in place of a D-pad. Set feature flag `HAS_TOUCH_PLANE` and define `const touch_plane_definition_t HWCONF_TOUCH_PLANE` in the device definition. [Driver](src/input/touch_plane.cpp) + +### Others + +* Wii Balance Board. Set feature flag `HAS_BALANCE_BOARD_INTEGRATION`. [Driver](src/service/balance_board.cpp), based upon [code by Sasaki Takeru](https://github.com/takeru/Wiimote/tree/d81319c62ac5931da868cc289386a6d4880a4b15) + ## Morio Denki Plasma Display Info **This display uses high voltage, which could be lethal!!** diff --git a/include/app/weighing.h b/include/app/weighing.h new file mode 100644 index 0000000..543b4f2 --- /dev/null +++ b/include/app/weighing.h @@ -0,0 +1,29 @@ +#pragma once +#include +#include +#include +#include + +#if HAS(BALANCE_BOARD_INTEGRATION) +class AppShimWeighing: public Composite { +public: + AppShimWeighing(SensorPool *); + ~AppShimWeighing(); + + void prepare(); + void render(FantaManipulator*); + void step(); + +private: + ViewMultiplexor * carousel; + SensorPool * sensors; + TickType_t lastActivity; + class WeighingView; + enum WeighingAppState { + NEED_CONNECT, + WAIT_CONNECT, + WEIGHING + }; + void update_state(transition_type_t); +}; +#endif \ No newline at end of file diff --git a/include/device_config.h b/include/device_config.h index 950854e..c87f160 100644 --- a/include/device_config.h +++ b/include/device_config.h @@ -6,7 +6,8 @@ // ---- SOFTWARE FEATURE FLAGS #define HAS_WORDNIK_API // #define HAS_OTAFVU -//#define HAS_SWITCHBOT_METER_INTEGRATION // <- low on RAM with the board used in big-clock: wait until they disable HTTPS enforcement, or buy bigger ESP board +// #define HAS_SWITCHBOT_METER_INTEGRATION +#define HAS_BALANCE_BOARD_INTEGRATION // ---- HARDWARE diff --git a/include/sensor/sensor.h b/include/sensor/sensor.h index b3f5cf7..722b938 100644 --- a/include/sensor/sensor.h +++ b/include/sensor/sensor.h @@ -19,6 +19,11 @@ typedef enum sensor_id { /// @brief A sensor using Switchbot Meter broadcasts for humidity measurement SENSOR_ID_SWITCHBOT_INDOOR_HUMIDITY, + /// @brief A sensor using a Balance Board for weight measurements, 100 units = 1kg + SENSOR_ID_BALANCE_BOARD_MILLIKILOS, + /// @brief A sensor triggering when a significant change of weight was detected on the Balance Board + SENSOR_ID_BALANCE_BOARD_STARTLED, + /// @brief A virtual sensor indicating whether the PMU has been startled (brought out of the low power mode) recently VIRTSENSOR_ID_PMU_STARTLED, /// @brief A virtual sensor indicating whether the HID has been startled recently diff --git a/include/service/balance_board.h b/include/service/balance_board.h new file mode 100644 index 0000000..07558e2 --- /dev/null +++ b/include/service/balance_board.h @@ -0,0 +1,17 @@ +#pragma once +#include +#include + +#if HAS(BALANCE_BOARD_INTEGRATION) + +typedef enum BalanceBoardState { + BB_IDLE, + BB_SCANNING, + BB_CONNECTED +} balance_board_state_t; + +void balance_board_start(SensorPool*); +void balance_board_scan(bool); +balance_board_state_t balance_board_state(); +void balance_board_zero(); +#endif \ No newline at end of file diff --git a/include/service/prefs.h b/include/service/prefs.h index 6ccc20c..d22d9ed 100644 --- a/include/service/prefs.h +++ b/include/service/prefs.h @@ -80,6 +80,8 @@ static constexpr prefs_key_t PREFS_KEY_ALARM_MAX_DURATION_MINUTES = "alm_dur"; static constexpr prefs_key_t PREFS_KEY_TIMER_TIME_SECONDS = "timer_time"; static constexpr prefs_key_t PREFS_KEY_TIMER_MELODY = "timer_melo"; +static constexpr prefs_key_t PREFS_KEY_BALANCE_BOARD_OFFSET = "bbrd_offs"; + void prefs_force_save(); String prefs_get_string(prefs_key_t, String def = String()); diff --git a/include/state.h b/include/state.h index dc0b8f8..82aad1c 100644 --- a/include/state.h +++ b/include/state.h @@ -1,4 +1,5 @@ #include +#include typedef enum device_state { STATE_BOOT, @@ -7,6 +8,9 @@ typedef enum device_state { STATE_TIMER_EDITOR, STATE_IDLE, STATE_ALARMING, +#if HAS(BALANCE_BOARD_INTEGRATION) + STATE_WEIGHING, +#endif STATE_OTAFVU, } device_state_t; diff --git a/lib/Wiimote/LICENSE b/lib/Wiimote/LICENSE new file mode 100644 index 0000000..552ea08 --- /dev/null +++ b/lib/Wiimote/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 sasaki takeru + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/Wiimote/Wiimote.cpp b/lib/Wiimote/Wiimote.cpp new file mode 100644 index 0000000..456d498 --- /dev/null +++ b/lib/Wiimote/Wiimote.cpp @@ -0,0 +1,945 @@ +#include +#include +#include +#include +#include + +#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED) +#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it +#endif + +#ifndef CONFIG_CLASSIC_BT_ENABLED +#error Need CLASSIC BT. +#endif + +#include "wiimote_bt.h" +#include "Wiimote.h" + +#define PSM_HID_Control_11 0x0011 +#define PSM_HID_Interrupt_13 0x0013 + +static Wiimote *_singleton = NULL; + +static uint8_t tmp_data[256]; +static bool wiimoteConnected = false; +static uint8_t _g_identifier = 1; +static uint16_t _g_local_cid = 0x0040; + +static uint16_t balance_calibration[12]; + +/** + * Queue + */ +typedef struct { + size_t len; + uint8_t data[]; +} lendata_t; +#define RX_QUEUE_SIZE 32 +#define TX_QUEUE_SIZE 32 +static xQueueHandle _rx_queue = NULL; +static xQueueHandle _tx_queue = NULL; +static esp_err_t _queue_data(xQueueHandle queue, uint8_t *data, size_t len){ + if(!data || !len){ + log_w("No data provided"); + return ESP_OK; + } + lendata_t * lendata = (lendata_t*)malloc(sizeof(lendata_t) + len); + if(!lendata){ + log_e("lendata Malloc Failed!"); + return ESP_FAIL; + } + lendata->len = len; + memcpy(lendata->data, data, len); + if (xQueueSend(queue, &lendata, portMAX_DELAY) != pdPASS) { + log_e("xQueueSend failed"); + free(lendata); + return ESP_FAIL; + } + return ESP_OK; +} + +/** + * Utils + */ +#define FORMAT_HEX_MAX_BYTES 30 +static char formatHexBuffer[FORMAT_HEX_MAX_BYTES*3+3+1]; +static char* formatHex(uint8_t* data, uint16_t len){ + for(uint16_t i=0; iaddr, c->bd_addr.addr, BD_ADDR_LEN) == 0){ + return i; + } + } + return -1; +} +static int scanned_device_add(struct scanned_device_t scanned_device){ + if(SCANNED_DEVICE_LIST_SIZE == scanned_device_list_size){ + return -1; + } + scanned_device_list[scanned_device_list_size++] = scanned_device; + return scanned_device_list_size; +} +static void scanned_device_clear(void){ + scanned_device_list_size = 0; +} + +/** + * L2CAP Connection + */ +struct l2cap_connection_t { + uint16_t connection_handle; + uint16_t psm; + uint16_t local_cid; + uint16_t remote_cid; +}; +static int l2cap_connection_size = 0; +#define L2CAP_CONNECTION_LIST_SIZE 8 +static l2cap_connection_t l2cap_connection_list[L2CAP_CONNECTION_LIST_SIZE]; +static int l2cap_connection_find_by_psm(uint16_t connection_handle, uint16_t psm){ + for(int i=0; iconnection_handle && psm == c->psm){ + return i; + } + } + return -1; +} +static int l2cap_connection_find_by_local_cid(uint16_t connection_handle, uint16_t local_cid){ + for(int i=0; iconnection_handle && local_cid == c->local_cid){ + return i; + } + } + return -1; +} + +static int l2cap_connection_add(struct l2cap_connection_t l2cap_connection){ + if(L2CAP_CONNECTION_LIST_SIZE == l2cap_connection_size){ + return -1; + } + l2cap_connection_list[l2cap_connection_size++] = l2cap_connection; + return l2cap_connection_size; +} +static void l2cap_connection_clear(void){ + l2cap_connection_size = 0; +} + +/** + * callback + */ +static void _notify_host_send_available(void){ +} + +static int _notify_host_recv(uint8_t *data, uint16_t len){ + if(ESP_OK == _queue_data(_rx_queue, data, len)){ + return ESP_OK; + }else{ + return ESP_FAIL; + } +} + +static const esp_vhci_host_callback_t callback = { + _notify_host_send_available, + _notify_host_recv +}; + +static void _reset(void){ + uint16_t len = make_cmd_reset(tmp_data); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued reset."); +} + +static void _scan_start(){ + scanned_device_clear(); + uint8_t timeout = 10; //0x30; + uint16_t len = make_cmd_inquiry(tmp_data, 0x9E8B33, timeout, 0x00); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued inquiry."); +} + +static void _scan_stop(){ + uint16_t len = make_cmd_inquiry_cancel(tmp_data); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued inquiry_cancel."); +} + +static void process_command_complete_event(uint8_t len, uint8_t* data){ + if(data[1]==0x03 && data[2]==0x0C){ // reset + // data[0] Num_HCI_Command_Packets + if(data[3]==0x00){ // OK + log_d("reset OK."); + uint16_t len = make_cmd_read_bd_addr(tmp_data); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued read_bd_addr."); + }else{ + log_d("reset failed."); + } + }else + if(data[1]==0x09 && data[2]==0x10){ // read_bd_addr + // data[0] Num_HCI_Command_Packets + if(data[3]==0x00){ // OK + log_d("read_bd_addr OK. BD_ADDR=%s", formatHex(data+4, 6)); + + char name[] = "ESP32-BT-L2CAP"; + log_d("sizeof(name)=%d", sizeof(name)); + uint16_t len = make_cmd_write_local_name(tmp_data, (uint8_t*)name, sizeof(name)); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued write_local_name."); + }else{ + log_d("read_bd_addr failed."); + } + }else + if(data[1]==0x13 && data[2]==0x0C){ // write_local_name + // data[0] Num_HCI_Command_Packets + if(data[3]==0x00){ // OK + log_d("write_local_name OK."); + uint8_t cod[3] = {0x04, 0x05, 0x00}; + uint16_t len = make_cmd_write_class_of_device(tmp_data, cod); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued write_class_of_device."); + }else{ + log_d("write_local_name failed."); + } + }else + if(data[1]==0x24 && data[2]==0x0C){ // write_class_of_device + // data[0] Num_HCI_Command_Packets + if(data[3]==0x00){ // OK + log_d("write_class_of_device OK."); + uint16_t len = make_cmd_write_scan_enable(tmp_data, 3); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued write_scan_enable."); + }else{ + log_d("write_class_of_device failed."); + } + }else + if(data[1]==0x1A && data[2]==0x0C){ // write_scan_enable + // data[0] Num_HCI_Command_Packets + if(data[3]==0x00){ // OK + log_d("write_scan_enable OK."); + _singleton->_callback(WIIMOTE_EVENT_INITIALIZE, 0, NULL, 0); + }else{ + log_d("write_scan_enable failed."); + } + }else + if(data[1]==0x02 && data[2]==0x04){ // inquiry_cancel + // data[0] Num_HCI_Command_Packets + if(data[3]==0x00){ // OK + log_d("inquiry_cancel OK."); + }else{ + log_d("inquiry_cancel failed."); + } + }else{ + log_d("### process_command_complete_event no impl ###"); + } +} + +static void process_command_status_event(uint8_t len, uint8_t* data){ + if(data[2]==0x01 && data[3]==0x04){ // inquiry + // data[1] Num_HCI_Command_Packets + if(data[0]==0x00){ // 0x00=pending + log_d("inquiry pending!"); + _singleton->_callback(WIIMOTE_EVENT_SCAN_START, 0, NULL, 0); + }else{ + log_d("inquiry failed. error=%02X", data[0]); + } + }else + if(data[2]==0x19 && data[3]==0x04){ + // data[1] Num_HCI_Command_Packets + if(data[0]==0x00){ // 0x00=pending + log_d("remote_name_request pending!"); + }else{ + log_d("remote_name_request failed. error=%02X", data[0]); + } + }else + if(data[2]==0x05 && data[3]==0x04){ + // data[1] Num_HCI_Command_Packets + if(data[0]==0x00){ // 0x00=pending + log_d("create_connection pending!"); + }else{ + log_d("create_connection failed. error=%02X", data[0]); + } + }else{ + log_d("### process_command_status_event no impl ###"); + } +} + +static void process_inquiry_result_event(uint8_t len, uint8_t* data){ + uint8_t num = data[0]; + //log_d("inquiry_result num=%d", num); + + for(int i=0; i_callback(WIIMOTE_EVENT_SCAN_STOP, 0, NULL, 0); +} + +static void process_remote_name_request_complete_event(uint8_t len, uint8_t* data){ + uint8_t status = data[0]; + log_d("remote_name_request_complete status=%02X", status); + struct bd_addr_t bd_addr; + STREAM_TO_BDADDR(bd_addr.addr, data+1); + log_d(" BD_ADDR = %s", formatHex((uint8_t*)&bd_addr.addr, BD_ADDR_LEN)); + + char* name = (char*)(data+7); + log_d(" REMOTE_NAME = %s", name); + + int idx = scanned_device_find(&bd_addr); + if(0<=idx && (strcmp("Nintendo RVL-CNT-01", name)==0 || strcmp("Nintendo RVL-WBC-01", name)==0)){ + struct scanned_device_t scanned_device = scanned_device_list[idx]; + uint16_t pt = 0x0008; + uint8_t ars = 0x00; + uint16_t len = make_cmd_create_connection(tmp_data, scanned_device.bd_addr, pt, scanned_device.psrm, scanned_device.clkofs, ars); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued create_connection."); + } +} + +static void _l2cap_connect(uint16_t connection_handle, uint16_t psm, uint16_t source_cid){ + uint8_t packet_boundary_flag = 0b10; // Packet_Boundary_Flag + uint8_t broadcast_flag = 0b00; // Broadcast_Flag + uint16_t channel_id = 0x0001; + uint8_t data[] = { + 0x02, // CONNECTION REQUEST + _g_identifier++, // Identifier + 0x04, 0x00, // Length: 0x0004 + psm & 0xFF, psm >> 8, // PSM: HID_Control=0x0011, HID_Interrupt=0x0013 + source_cid & 0xFF, source_cid >> 8 // Source CID: 0x0040+ + }; + uint16_t data_len = 8; + uint16_t len = make_acl_l2cap_single_packet(tmp_data, connection_handle, packet_boundary_flag, broadcast_flag, channel_id, data, data_len); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued acl_l2cap_single_packet(CONNECTION REQUEST)"); + + struct l2cap_connection_t l2cap_connection; + l2cap_connection.connection_handle = connection_handle; + l2cap_connection.psm = psm; + l2cap_connection.local_cid = source_cid; + l2cap_connection.remote_cid = 0; + int idx = l2cap_connection_add(l2cap_connection); + if(idx == -1){ + log_d("!!! l2cap_connection_add failed."); + } +} + +static void _set_rumble(uint16_t connection_handle, bool rumble){ + int idx = l2cap_connection_find_by_psm(connection_handle, PSM_HID_Interrupt_13); + struct l2cap_connection_t l2cap_connection = l2cap_connection_list[idx]; + + uint8_t packet_boundary_flag = 0b10; // Packet_Boundary_Flag + uint8_t broadcast_flag = 0b00; // Broadcast_Flag + uint16_t channel_id = l2cap_connection.remote_cid; + uint8_t data[] = { + 0xA2, + 0x10, + rumble ? 0x01 : 0x00 // 0x0? - 0xF? + }; + uint16_t data_len = 3; + uint16_t len = make_acl_l2cap_single_packet(tmp_data, connection_handle, packet_boundary_flag, broadcast_flag, channel_id, data, data_len); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued acl_l2cap_single_packet(Set Rumble)"); +} + +static void _set_led(uint16_t connection_handle, uint8_t leds){ + int idx = l2cap_connection_find_by_psm(connection_handle, PSM_HID_Interrupt_13); + struct l2cap_connection_t l2cap_connection = l2cap_connection_list[idx]; + + uint8_t packet_boundary_flag = 0b10; // Packet_Boundary_Flag + uint8_t broadcast_flag = 0b00; // Broadcast_Flag + uint16_t channel_id = l2cap_connection.remote_cid; + uint8_t data[] = { + 0xA2, + 0x11, + leds << 4 // 0x0? - 0xF? + }; + uint16_t data_len = 3; + uint16_t len = make_acl_l2cap_single_packet(tmp_data, connection_handle, packet_boundary_flag, broadcast_flag, channel_id, data, data_len); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued acl_l2cap_single_packet(Set LEDs)"); +} + +static void _set_reporting_mode(uint16_t connection_handle, uint8_t reporting_mode, bool continuous){ + int idx = l2cap_connection_find_by_psm(connection_handle, PSM_HID_Interrupt_13); + struct l2cap_connection_t l2cap_connection = l2cap_connection_list[idx]; + + uint8_t packet_boundary_flag = 0b10; // Packet_Boundary_Flag + uint8_t broadcast_flag = 0b00; // Broadcast_Flag + uint16_t channel_id = l2cap_connection.remote_cid; + uint8_t data[] = { + 0xA2, + 0x12, + continuous ? 0x04 : 0x00, // 0x00, 0x04 + reporting_mode + }; + uint16_t data_len = 4; + uint16_t len = make_acl_l2cap_single_packet(tmp_data, connection_handle, packet_boundary_flag, broadcast_flag, channel_id, data, data_len); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued acl_l2cap_single_packet(Set reporting mode)"); +} + +enum address_space_t { + EEPROM_MEMORY, + CONTROL_REGISTER +}; + +static uint8_t _address_space(address_space_t as){ + switch(as){ + case EEPROM_MEMORY : return 0x00; + case CONTROL_REGISTER: return 0x04; + } + return 0xFF; +} + +static void _write_memory(uint16_t connection_handle, address_space_t as, uint32_t offset, uint8_t size, const uint8_t* d){ + int idx = l2cap_connection_find_by_psm(connection_handle, PSM_HID_Interrupt_13); + struct l2cap_connection_t l2cap_connection = l2cap_connection_list[idx]; + + uint8_t packet_boundary_flag = 0b10; // Packet_Boundary_Flag + uint8_t broadcast_flag = 0b00; // Broadcast_Flag + uint16_t channel_id = l2cap_connection.remote_cid; + // (a2) 16 MM FF FF FF SS DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD + uint8_t data[] = { + 0xA2, + 0x16, // Write + _address_space(as), // MM 0x00=EEPROM, 0x04=ControlRegister + (offset >> 16) & 0xFF, // FF + (offset >> 8) & 0xFF, // FF + (offset ) & 0xFF, // FF + size, // SS size 1..16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + memcpy(data+7, d, size); + + uint16_t data_len = 7 + 16; + uint16_t len = make_acl_l2cap_single_packet(tmp_data, connection_handle, packet_boundary_flag, broadcast_flag, channel_id, data, data_len); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued acl_l2cap_single_packet(write memory)"); +} + +static void _read_memory(uint16_t connection_handle, address_space_t as, uint32_t offset, uint16_t size){ + int idx = l2cap_connection_find_by_psm(connection_handle, PSM_HID_Interrupt_13); + struct l2cap_connection_t l2cap_connection = l2cap_connection_list[idx]; + + uint8_t packet_boundary_flag = 0b10; // Packet_Boundary_Flag + uint8_t broadcast_flag = 0b00; // Broadcast_Flag + uint16_t channel_id = l2cap_connection.remote_cid; + // (a2) 17 MM FF FF FF SS SS + uint8_t data[] = { + 0xA2, + 0x17, // Read + _address_space(as), // MM 0x00=EEPROM, 0x04=ControlRegister + (offset >> 16) & 0xFF, // FF + (offset >> 8) & 0xFF, // FF + (offset ) & 0xFF, // FF + (size >> 8 ) & 0xFF, // SS + (size ) & 0xFF // SS + }; + uint16_t data_len = 8; + uint16_t len = make_acl_l2cap_single_packet(tmp_data, connection_handle, packet_boundary_flag, broadcast_flag, channel_id, data, data_len); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued acl_l2cap_single_packet(read memory)"); +} + +static void process_connection_complete_event(uint8_t len, uint8_t* data){ + uint8_t status = data[0]; + log_d("connection_complete status=%02X", status); + + uint16_t connection_handle = data[2] << 8 | data[1]; + struct bd_addr_t bd_addr; + STREAM_TO_BDADDR(bd_addr.addr, data+3); + uint8_t lt = data[9]; // Link_Type + uint8_t ee = data[10]; // Encryption_Enabled + + log_d(" Connection_Handle = 0x%04X", connection_handle); + log_d(" BD_ADDR = %s", formatHex((uint8_t*)&bd_addr.addr, BD_ADDR_LEN)); + log_d(" Link_Type = %02X", lt); + log_d(" Encryption_Enabled = %02X", ee); + + _l2cap_connect(connection_handle, PSM_HID_Control_11, _g_local_cid++); +} + +static void process_disconnection_complete_event(uint8_t len, uint8_t* data){ + uint8_t status = data[0]; + log_d("disconnection_complete status=%02X", status); + + uint16_t ch = data[2] << 8 | data[1]; //Connection_Handle + uint8_t reason = data[3]; // Reason + + log_d(" Connection_Handle = 0x%04X", ch); + log_d(" Reason = %02X", reason); + + _singleton->_callback(WIIMOTE_EVENT_DISCONNECT, ch, NULL, 0); +} + +static void process_l2cap_connection_response(uint16_t connection_handle, uint8_t* data){ + uint8_t identifier = data[ 1]; + uint16_t len = (data[ 3] << 8) | data[ 2]; + uint16_t destination_cid = (data[ 5] << 8) | data[ 4]; + uint16_t source_cid = (data[ 7] << 8) | data[ 6]; + uint16_t result = (data[ 9] << 8) | data[ 8]; + uint16_t status = (data[11] << 8) | data[10]; + + log_d("L2CAP CONNECTION RESPONSE"); + log_d(" identifier = %02X", identifier); + log_d(" destination_cid = %04X", destination_cid); + log_d(" source_cid = %04X", source_cid); + log_d(" result = %04X", result); + log_d(" status = %04X", status); + + if(result == 0x0000){ + int idx = l2cap_connection_find_by_local_cid(connection_handle, source_cid); + struct l2cap_connection_t *l2cap_connection = &l2cap_connection_list[idx]; + l2cap_connection->remote_cid = destination_cid; + + uint8_t packet_boundary_flag = 0b10; // Packet_Boundary_Flag + uint8_t broadcast_flag = 0b00; // Broadcast_Flag + uint16_t channel_id = 0x0001; + uint8_t data[] = { + 0x04, // CONFIGURATION REQUEST + _g_identifier++, // Identifier + 0x08, 0x00, // Length: 0x0008 + destination_cid & 0xFF, destination_cid >> 8, // Destination CID + 0x00, 0x00, // Flags + 0x01, 0x02, 0x40, 0x00 // type=01 len=02 value=00 40 + }; + uint16_t data_len = 12; + uint16_t len = make_acl_l2cap_single_packet(tmp_data, connection_handle, packet_boundary_flag, broadcast_flag, channel_id, data, data_len); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued acl_l2cap_single_packet(CONFIGURATION REQUEST)"); + } +} + +static void process_l2cap_configuration_response(uint16_t connection_handle, uint8_t* data){ + uint8_t identifier = data[ 1]; + uint16_t len = (data[ 3] << 8) | data[ 2]; + uint16_t source_cid = (data[ 5] << 8) | data[ 4]; + uint16_t flags = (data[ 7] << 8) | data[ 6]; + uint16_t result = (data[ 9] << 8) | data[ 8]; + // config = data[10..] + + log_d("L2CAP CONFIGURATION RESPONSE"); + log_d(" identifier = %02X", identifier); + log_d(" len = %04X", len); + log_d(" source_cid = %04X", source_cid); + log_d(" flags = %04X", flags); + log_d(" result = %04X", result); + log_d(" config = %s", formatHex(data+10, len-6)); +} + +static void process_l2cap_configuration_request(uint16_t connection_handle, uint8_t* data){ + uint8_t identifier = data[ 1]; + uint16_t len = (data[ 3] << 8) | data[ 2]; + uint16_t destination_cid = (data[ 5] << 8) | data[ 4]; + uint16_t flags = (data[ 7] << 8) | data[ 6]; + // config = data[8..] + + log_d("L2CAP CONFIGURATION REQUEST"); + log_d(" identifier = %02X", identifier); + log_d(" len = %02X", len); + log_d(" destination_cid = %04X", destination_cid); + log_d(" flags = %04X", flags); + log_d(" config = %s", formatHex(data+8, len-4)); + + if(flags != 0x0000){ + log_d("!!! flags!=0x0000"); + return; + } + if(len != 0x08){ + log_d("!!! len!=0x08"); + return; + } + if(data[8]==0x01 && data[9]==0x02){ // MTU + uint16_t mtu = (data[11] << 8) | data[10]; + log_d(" MTU=%d", mtu); + + int idx = l2cap_connection_find_by_local_cid(connection_handle, destination_cid); + struct l2cap_connection_t l2cap_connection = l2cap_connection_list[idx]; + + uint8_t packet_boundary_flag = 0b10; // Packet_Boundary_Flag + uint8_t broadcast_flag = 0b00; // Broadcast_Flag + uint16_t channel_id = 0x0001; + uint16_t source_cid = l2cap_connection.remote_cid; + uint8_t data[] = { + 0x05, // CONFIGURATION RESPONSE + identifier, // Identifier + 0x0A, 0x00, // Length: 0x000A + source_cid & 0xFF, source_cid >> 8, // Source CID + 0x00, 0x00, // Flags + 0x00, 0x00, // Res + 0x01, 0x02, mtu & 0xFF, mtu >> 8 // type=01 len=02 value=xx xx + }; + uint16_t data_len = 14; + uint16_t len = make_acl_l2cap_single_packet(tmp_data, connection_handle, packet_boundary_flag, broadcast_flag, channel_id, data, data_len); + _queue_data(_tx_queue, tmp_data, len); // TODO: check return + log_d("queued acl_l2cap_single_packet(CONFIGURATION RESPONSE)"); + + if(l2cap_connection.psm == PSM_HID_Control_11){ + _l2cap_connect(connection_handle, PSM_HID_Interrupt_13, _g_local_cid++); + } + if(l2cap_connection.psm == PSM_HID_Interrupt_13){ + _singleton->_callback(WIIMOTE_EVENT_CONNECT, connection_handle, NULL, 0); + } + } +} + +static void process_report(uint16_t connection_handle, uint8_t* data, uint16_t len){ + log_d("REPORT len=%d data=%s", len, formatHex(data, len)); + _singleton->_callback(WIIMOTE_EVENT_DATA, connection_handle, data, len); +} + +static void process_extension_controller_reports(uint16_t connection_handle, uint16_t channel_id, uint8_t* data, uint16_t len){ + static int controller_query_state = 0; + + switch(controller_query_state){ + case 0: + // 0x20 Status + // (a1) 20 BB BB LF 00 00 VV + if(data[1] == 0x20){ + if(data[4] & 0x02){ // extension controller is connected + _write_memory(connection_handle, CONTROL_REGISTER, 0xA400F0, 1, (const uint8_t[]){0x55}); + controller_query_state = 1; + }else{ // extension controller is NOT connected + _set_reporting_mode(connection_handle, 0x30, false); // 0x30: Core Buttons : 30 BB BB + //_set_reporting_mode(connection_handle, 0x31, false); // 0x31: Core Buttons and Accelerometer : 31 BB BB AA AA AA + } + } + break; + case 1: + // A1 22 00 00 16 00 => OK + // A1 22 00 00 16 04 => NG + if(data[1]==0x22 && data[4]==0x16){ + if(data[5]==0x00){ + _write_memory(connection_handle, CONTROL_REGISTER, 0xA400FB, 1, (const uint8_t[]){0x00}); + controller_query_state = 2; + }else{ + controller_query_state = 0; + } + } + break; + case 2: + if(data[1]==0x22 && data[4]==0x16){ + if(data[5]==0x00){ + _read_memory(connection_handle, CONTROL_REGISTER, 0xA400FA, 6); // read controller type + controller_query_state = 3; + }else{ + controller_query_state = 0; + } + } + break; + case 3: + // 0x21 Read response + // (a1) 21 BB BB SE FF FF DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD + if(data[1] == 0x21){ + if(memcmp(data+5, (const uint8_t[]){0x00, 0xFA}, 2)==0){ + if(memcmp(data+7, (const uint8_t[]){0x00, 0x00, 0xA4, 0x20, 0x00, 0x00}, 6)==0){ // Nunchuck + _set_reporting_mode(connection_handle, 0x32, false); // 0x32: Core Buttons with 8 Extension bytes : 32 BB BB EE EE EE EE EE EE EE EE + } + if(memcmp(data+7, (const uint8_t[]){0x00, 0x00, 0xA4, 0x20, 0x04, 0x02}, 6)==0){ // Wii Balance Board + _read_memory(connection_handle, CONTROL_REGISTER, 0xA40024, 16); // read calibration 0 kg and 17kg + + controller_query_state = 4; + } + else { + controller_query_state = 0; + } + } + } + break; + case 4: + { + log_d("BALANCE CALIBRATION DATA 1 len=%d data=%s", len, formatHex(data, len)); + uint8_t* cal = data+7; + balance_calibration[0] = cal[0] * 256 + cal[1];//Top Right 0kg + balance_calibration[1] = cal[2] * 256 + cal[3]; //Bottom Right 0kg + balance_calibration[2] = cal[4] * 256 + cal[5]; //Top Left 0kg + balance_calibration[3] = cal[6] * 256 + cal[7]; //Bottom Left 0kg + + balance_calibration[4] = cal[8] * 256 + cal[9];//Top Right 17kg + balance_calibration[5] = cal[10] * 256 + cal[11];//Bottom Right 17kg + balance_calibration[6] = cal[12] * 256 + cal[13];//Top Left 17kg + balance_calibration[7] = cal[14] * 256 + cal[15];//Bottom Left 17kg + + _read_memory(connection_handle, CONTROL_REGISTER, 0xA40034, 8); // read calibration 34kg + + controller_query_state = 5; + } + break; + case 5: + { + log_d("BALANCE CALIBRATION DATA 2 len=%d data=%s", len, formatHex(data, len)); + uint8_t* cal = data+7; + balance_calibration[8] = cal[0] * 256 + cal[1];//Top Right 34kg + balance_calibration[9] = cal[2] * 256 + cal[3]; //Bottom Right 34kg + balance_calibration[10] = cal[4] * 256 + cal[5]; //Top Left 34kg + balance_calibration[11] = cal[6] * 256 + cal[7]; //Bottom Left 34kg + + _set_reporting_mode(connection_handle, 0x34, false); // 0x34: Core Buttons with 19 Extension bytes : 34 BB BB EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE + + controller_query_state = 0; + } + break; + } +} + +static void process_l2cap_data(uint16_t connection_handle, uint16_t channel_id, uint8_t* data, uint16_t len){ + if(data[0]==0x03){ // CONNECTION RESPONSE + process_l2cap_connection_response(connection_handle, data); + }else + if(data[0]==0x05){ // CONFIGURATION RESPONSE + process_l2cap_configuration_response(connection_handle, data); + }else + if(data[0]==0x04){ // CONFIGURATION REQUEST + process_l2cap_configuration_request(connection_handle, data); + }else + if(data[0]==0xA1){ // HID 0xA1 + process_extension_controller_reports(connection_handle, channel_id, data, len); + process_report(connection_handle, data, len); + }else{ + log_d(" ### process_l2cap_data no impl ###"); + log_d(" L2CAP len=%d data=%s", len, formatHex(data, len)); + } +} + +static void process_acl_data(uint8_t* data, size_t len){ + if(data[0]!=0xA1){ + log_d("**** ACL_DATA len=%d data=%s", len, formatHex(data, len)); + } + + uint16_t connection_handle = ((data[1] & 0x0F) << 8) | data[0]; + uint8_t packet_boundary_flag = (data[1] & 0x30) >> 4; // Packet_Boundary_Flag + uint8_t broadcast_flag = (data[1] & 0xC0) >> 6; // Broadcast_Flag + uint16_t acl_len = (data[3] << 8) | data[2]; + if(packet_boundary_flag != 0b10){ + log_d("!!! packet_boundary_flag = 0b%02B", packet_boundary_flag); + return; + } + if(broadcast_flag != 0b00){ + log_d("!!! broadcast_flag = 0b%02B", broadcast_flag); + return; + } + uint16_t l2cap_len = (data[5] << 8) | data[4]; + uint16_t channel_id = (data[7] << 8) | data[6]; + + process_l2cap_data(connection_handle, channel_id, data + 8, l2cap_len); +} + +static void process_hci_event(uint8_t event_code, uint8_t len, uint8_t* data){ + if(event_code != 0x02){ // suppress inquiry_result_event + log_d("**** HCI_EVENT code=%02X len=%d data=%s", event_code, len, formatHex(data, len)); + } + + if(event_code == 0x0E){ + process_command_complete_event(len, data); + }else if(event_code == 0x0F){ + process_command_status_event(len, data); + }else if(event_code == 0x02){ + process_inquiry_result_event(len, data); + }else if(event_code == 0x01){ + process_inquiry_complete_event(len, data); + }else if(event_code == 0x07){ + process_remote_name_request_complete_event(len, data); + }else if(event_code == 0x03){ + process_connection_complete_event(len, data); + }else if(event_code == 0x05){ + process_disconnection_complete_event(len, data); + }else if(event_code == 0x13){ + log_d(" (Number Of Completed Packets Event)"); + }else if(event_code == 0x0D){ + log_d(" (QoS Setup Complete Event)"); + }else{ + log_d(" ### process_hci_event no impl ###"); + } +} + +float balance_interpolate(uint8_t pos, uint16_t *values, uint16_t *cal) { + float weight = 0; + + if(values[pos] < cal[pos]) {//0kg + weight = 0; + } + else if(values[pos] < cal[pos+4]) {//17kg + weight = 17 * (float)(values[pos]-cal[pos])/(float)(cal[pos+4]-cal[pos]); + } + else /* if (values[pos] > cal[pos+5])*/ {//34kg + weight = 17 + 17 * (float)(values[pos]-cal[pos+4])/(float)(cal[pos+8]-cal[pos+4]); + } + + return weight; +} + +void Wiimote::init(wiimote_callback_t cb){ + if(_singleton){ + return; + } + _singleton = this; + + this->_wiimote_callback = cb; + l2cap_connection_clear(); + + _tx_queue = xQueueCreate(TX_QUEUE_SIZE, sizeof(lendata_t*)); + if (_tx_queue == NULL){ + log_e("xQueueCreate(_tx_queue) failed"); + return; + } + _rx_queue = xQueueCreate(RX_QUEUE_SIZE, sizeof(lendata_t*)); + if (_rx_queue == NULL){ + log_e("xQueueCreate(_rx_queue) failed"); + return; + } + + if(!btStart()){ + log_e("btStart failed"); + return; + } + + esp_err_t ret; + ret = esp_vhci_host_register_callback(&callback); + if (ret != ESP_OK) { + log_e("esp_vhci_host_register_callback failed: %d %s", ret, esp_err_to_name(ret)); + return; + } + + _reset(); +} + +void Wiimote::handle(){ + if(this != _singleton){ return; } + if(!btStarted()){ + return; + } + + if(uxQueueMessagesWaiting(_tx_queue)){ + bool ok = esp_vhci_host_check_send_available(); + if(ok){ + lendata_t *lendata = NULL; + if(xQueueReceive(_tx_queue, &lendata, 0) == pdTRUE){ + esp_vhci_host_send_packet(lendata->data, lendata->len); + log_d("SEND => %s", formatHex(lendata->data, lendata->len)); + free(lendata); + } + } + } + + if(uxQueueMessagesWaiting(_rx_queue)){ + lendata_t *lendata = NULL; + if(xQueueReceive(_rx_queue, &lendata, 0) == pdTRUE){ + switch(lendata->data[0]){ + case 0x04: + process_hci_event(lendata->data[1], lendata->data[2], lendata->data+3); + break; + case 0x02: + process_acl_data(lendata->data+1, lendata->len-1); + break; + default: + log_d("**** !!! Not HCI Event !!! ****"); + log_d("len=%d data=%s", lendata->len, formatHex(lendata->data, lendata->len)); + } + free(lendata); + } + } +} + +void Wiimote::scan(bool enable){ + if(this != _singleton){ return; } + + if(enable){ + _scan_start(); + }else{ + _scan_stop(); + } +} + +void Wiimote::_callback(wiimote_event_type_t event_type, uint16_t handle, uint8_t *data, size_t len){ + if(this != _singleton){ return; } + + if(this->_wiimote_callback){ + this->_wiimote_callback(event_type, handle, data, len); + } +} + +void Wiimote::set_led(uint16_t handle, uint8_t leds){ + _set_led(handle, leds); +} + +void Wiimote::set_rumble(uint16_t handle, bool rumble){ + _set_rumble(handle, rumble); +} + +void Wiimote::get_balance_weight(uint8_t *data, float *weight) { + uint8_t* ext = data+4; + + uint16_t values[4]; + + values[BALANCE_POSITION_TOP_RIGHT] = ext[0] * 256 + ext[1]; //TopRight + values[BALANCE_POSITION_BOTTOM_RIGHT] = ext[2] * 256 + ext[3]; //BottomRight + values[BALANCE_POSITION_TOP_LEFT] = ext[4] * 256 + ext[5]; //TopLeft + values[BALANCE_POSITION_BOTTOM_LEFT] = ext[6] * 256 + ext[7]; //BottomLeft + + weight[BALANCE_POSITION_TOP_RIGHT] = balance_interpolate(BALANCE_POSITION_TOP_RIGHT, values, balance_calibration); + weight[BALANCE_POSITION_BOTTOM_RIGHT] = balance_interpolate(BALANCE_POSITION_BOTTOM_RIGHT, values, balance_calibration); + weight[BALANCE_POSITION_TOP_LEFT] = balance_interpolate(BALANCE_POSITION_TOP_LEFT, values, balance_calibration); + weight[BALANCE_POSITION_BOTTOM_LEFT] = balance_interpolate(BALANCE_POSITION_BOTTOM_LEFT, values, balance_calibration); +} diff --git a/lib/Wiimote/Wiimote.h b/lib/Wiimote/Wiimote.h new file mode 100644 index 0000000..c5878b7 --- /dev/null +++ b/lib/Wiimote/Wiimote.h @@ -0,0 +1,38 @@ +#ifndef _WIIMOTE_H_ +#define _WIIMOTE_H_ + +#include + +enum wiimote_event_type_t { + WIIMOTE_EVENT_INITIALIZE, + WIIMOTE_EVENT_SCAN_START, + WIIMOTE_EVENT_SCAN_STOP, + WIIMOTE_EVENT_CONNECT, + WIIMOTE_EVENT_DISCONNECT, + WIIMOTE_EVENT_DATA +}; + +enum balance_position_type_t { + BALANCE_POSITION_TOP_RIGHT, + BALANCE_POSITION_BOTTOM_RIGHT, + BALANCE_POSITION_TOP_LEFT, + BALANCE_POSITION_BOTTOM_LEFT, +}; + +typedef void (* wiimote_callback_t)(wiimote_event_type_t event_type, uint16_t handle, uint8_t *data, size_t len); + + +class Wiimote { + public: + void init(wiimote_callback_t cb); + void handle(); + void scan(bool enable); + void _callback(wiimote_event_type_t event_type, uint16_t handle, uint8_t *data, size_t len); + void set_led(uint16_t handle, uint8_t leds); + void set_rumble(uint16_t handle, bool rumble); + void get_balance_weight(uint8_t *data, float *weight); + private: + wiimote_callback_t _wiimote_callback; +}; + +#endif diff --git a/lib/Wiimote/wiimote_bt.h b/lib/Wiimote/wiimote_bt.h new file mode 100644 index 0000000..6a5d64b --- /dev/null +++ b/lib/Wiimote/wiimote_bt.h @@ -0,0 +1,148 @@ +#define HCI_H4_CMD_PREAMBLE_SIZE (4) +#define HCI_H4_ACL_PREAMBLE_SIZE (5) + +/* HCI Command opcode group field(OGF) */ +#define HCI_GRP_LINK_CONT_CMDS (0x01 << 10) /* 0x0400 */ +#define HCI_GRP_HOST_CONT_BASEBAND_CMDS (0x03 << 10) /* 0x0C00 */ +#define HCI_GRP_INFO_PARAMS_CMDS (0x04 << 10) + +// OGF + OCF +#define HCI_RESET (0x0003 | HCI_GRP_HOST_CONT_BASEBAND_CMDS) +#define HCI_READ_BD_ADDR (0x0009 | HCI_GRP_INFO_PARAMS_CMDS) +#define HCI_WRITE_LOCAL_NAME (0x0013 | HCI_GRP_HOST_CONT_BASEBAND_CMDS) +#define HCI_WRITE_CLASS_OF_DEVICE (0x0024 | HCI_GRP_HOST_CONT_BASEBAND_CMDS) +#define HCI_WRITE_SCAN_ENABLE (0x001A | HCI_GRP_HOST_CONT_BASEBAND_CMDS) +#define HCI_INQUIRY (0x0001 | HCI_GRP_LINK_CONT_CMDS) +#define HCI_INQUIRY_CANCEL (0x0002 | HCI_GRP_LINK_CONT_CMDS) +#define HCI_REMOTE_NAME_REQUEST (0x0019 | HCI_GRP_LINK_CONT_CMDS) +#define HCI_CREATE_CONNECTION (0x0005 | HCI_GRP_LINK_CONT_CMDS) + +#define BD_ADDR_LEN (6) +struct bd_addr_t { + uint8_t addr[BD_ADDR_LEN]; +}; + +#define UINT16_TO_STREAM(p, u16) {*(p)++ = (uint8_t)(u16); *(p)++ = (uint8_t)((u16) >> 8);} +#define UINT8_TO_STREAM(p, u8) {*(p)++ = (uint8_t)(u8);} +#define BDADDR_TO_STREAM(p, a) {int ijk; for (ijk = 0; ijk < BD_ADDR_LEN; ijk++) *(p)++ = (uint8_t) a[BD_ADDR_LEN - 1 - ijk];} +#define STREAM_TO_BDADDR(a, p) {int ijk; for (ijk = 0; ijk < BD_ADDR_LEN; ijk++) a[BD_ADDR_LEN - 1 - ijk] = (p)[ijk];} +#define ARRAY_TO_STREAM(p, a, len) {int ijk; for (ijk = 0; ijk < len; ijk++) *(p)++ = (uint8_t) a[ijk];} + +enum { + H4_TYPE_COMMAND = 1, + H4_TYPE_ACL = 2, + H4_TYPE_SCO = 3, + H4_TYPE_EVENT = 4 +}; + +static uint16_t make_cmd_reset(uint8_t *buf){ + UINT8_TO_STREAM (buf, H4_TYPE_COMMAND); + UINT16_TO_STREAM (buf, HCI_RESET); + UINT8_TO_STREAM (buf, 0); + return HCI_H4_CMD_PREAMBLE_SIZE; +} + +static uint16_t make_cmd_read_bd_addr(uint8_t *buf){ + UINT8_TO_STREAM (buf, H4_TYPE_COMMAND); + UINT16_TO_STREAM (buf, HCI_READ_BD_ADDR); + UINT8_TO_STREAM (buf, 0); + return HCI_H4_CMD_PREAMBLE_SIZE; +} + +static uint16_t make_cmd_write_local_name(uint8_t *buf, uint8_t* name, uint8_t len){ + // name ends with null. TODO check len<=248 + UINT8_TO_STREAM (buf, H4_TYPE_COMMAND); + UINT16_TO_STREAM (buf, HCI_WRITE_LOCAL_NAME); + UINT8_TO_STREAM (buf, 248); + ARRAY_TO_STREAM(buf, name, len); + for(uint8_t i=len; i<248; i++){ + UINT8_TO_STREAM (buf, 0); + } + return HCI_H4_CMD_PREAMBLE_SIZE + 248; +} + +static uint16_t make_cmd_write_class_of_device(uint8_t *buf, uint8_t* cod){ + UINT8_TO_STREAM (buf, H4_TYPE_COMMAND); + UINT16_TO_STREAM (buf, HCI_WRITE_CLASS_OF_DEVICE); + UINT8_TO_STREAM (buf, 3); + for(uint8_t i=0; i<3; i++){ + UINT8_TO_STREAM (buf, cod[i]); + } + return HCI_H4_CMD_PREAMBLE_SIZE + 3; +} + +static uint16_t make_cmd_write_scan_enable(uint8_t *buf, uint8_t mode){ + UINT8_TO_STREAM (buf, H4_TYPE_COMMAND); + UINT16_TO_STREAM (buf, HCI_WRITE_SCAN_ENABLE); + UINT8_TO_STREAM (buf, 1); + + UINT8_TO_STREAM (buf, mode); + return HCI_H4_CMD_PREAMBLE_SIZE + 1; +} + +static uint16_t make_cmd_inquiry(uint8_t *buf, uint32_t lap, uint8_t len, uint8_t num){ + UINT8_TO_STREAM (buf, H4_TYPE_COMMAND); + UINT16_TO_STREAM (buf, HCI_INQUIRY); + UINT8_TO_STREAM (buf, 5); + + UINT8_TO_STREAM (buf, ( lap & 0xFF)); // lap 0x33 <- 0x9E8B33 + UINT8_TO_STREAM (buf, ((lap>> 8) & 0xFF)); // lap 0x8B + UINT8_TO_STREAM (buf, ((lap>>16) & 0xFF)); // lap 0x9E + UINT8_TO_STREAM (buf, len); + UINT8_TO_STREAM (buf, num); + return HCI_H4_CMD_PREAMBLE_SIZE + 5; +} + +static uint16_t make_cmd_inquiry_cancel(uint8_t *buf){ + UINT8_TO_STREAM (buf, H4_TYPE_COMMAND); + UINT16_TO_STREAM (buf, HCI_INQUIRY_CANCEL); + UINT8_TO_STREAM (buf, 0); + + return HCI_H4_CMD_PREAMBLE_SIZE + 0; +} + +static uint16_t make_cmd_remote_name_request(uint8_t *buf, struct bd_addr_t bd_addr, uint8_t psrm, uint16_t clkofs){ + UINT8_TO_STREAM (buf, H4_TYPE_COMMAND); + UINT16_TO_STREAM (buf, HCI_REMOTE_NAME_REQUEST); + UINT8_TO_STREAM (buf, 6+1+1+2); + + BDADDR_TO_STREAM (buf, bd_addr.addr); + UINT8_TO_STREAM (buf, psrm); // Page_Scan_Repetition_Mode + UINT8_TO_STREAM (buf, 0); // Reserved + UINT16_TO_STREAM (buf, clkofs); // Clock_Offset + return HCI_H4_CMD_PREAMBLE_SIZE + 10; +} + +static uint16_t make_cmd_create_connection(uint8_t *buf, struct bd_addr_t bd_addr, uint16_t pt, uint8_t psrm, uint16_t clkofs, uint8_t ars){ + UINT8_TO_STREAM (buf, H4_TYPE_COMMAND); + UINT16_TO_STREAM (buf, HCI_CREATE_CONNECTION); + UINT8_TO_STREAM (buf, 6+2+1+1+2+1); + + BDADDR_TO_STREAM (buf, bd_addr.addr); + UINT16_TO_STREAM (buf, pt); // Packet_Type + UINT8_TO_STREAM (buf, psrm); // Page_Scan_Repetition_Mode + UINT8_TO_STREAM (buf, 0); // Reserved + UINT16_TO_STREAM (buf, clkofs); // Clock_Offset + UINT8_TO_STREAM (buf, ars); // Allow_Role_Switch + return HCI_H4_CMD_PREAMBLE_SIZE + 13; +} + +// TODO long data is split to multi packets +static uint16_t make_l2cap_single_packet(uint8_t *buf, uint16_t channel_id, uint8_t *data, uint16_t len){ + UINT16_TO_STREAM (buf, len); + UINT16_TO_STREAM (buf, channel_id); // 0x0001=Signaling channel + ARRAY_TO_STREAM (buf, data, len); + return 2 + 2 + len; +} + +static uint16_t make_acl_l2cap_single_packet(uint8_t *buf, uint16_t connection_handle, uint8_t packet_boundary_flag, uint8_t broadcast_flag, uint16_t channel_id, uint8_t *data, uint8_t len){ + uint8_t* l2cap_buf = buf + HCI_H4_ACL_PREAMBLE_SIZE; + uint16_t l2cap_len = make_l2cap_single_packet(l2cap_buf, channel_id, data, len); + + UINT8_TO_STREAM (buf, H4_TYPE_ACL); + UINT8_TO_STREAM (buf, connection_handle & 0xFF); + UINT8_TO_STREAM (buf, ((connection_handle >> 8) & 0x0F) | packet_boundary_flag << 4 | broadcast_flag << 6); + UINT16_TO_STREAM (buf, l2cap_len); + + return HCI_H4_ACL_PREAMBLE_SIZE + l2cap_len; +} diff --git a/src/app/idle.cpp b/src/app/idle.cpp index 946bc8e..229ff2d 100644 --- a/src/app/idle.cpp +++ b/src/app/idle.cpp @@ -330,4 +330,12 @@ void app_idle_process() { tick_tock_enable = prefs_get_bool(PREFS_KEY_TICKING_SOUND); hourly_chime_on = prefs_get_bool(PREFS_KEY_HOURLY_CHIME_ON); + +#if HAS(BALANCE_BOARD_INTEGRATION) + if(sensor_info_t * info = sensors->get_info(SENSOR_ID_BALANCE_BOARD_STARTLED)) { + if(info->last_result) { + push_state(STATE_WEIGHING, TRANSITION_SLIDE_HORIZONTAL_LEFT); + } + } +#endif } diff --git a/src/app/menu.cpp b/src/app/menu.cpp index e3209f1..fc5808c 100644 --- a/src/app/menu.cpp +++ b/src/app/menu.cpp @@ -136,6 +136,16 @@ AppShimMenu::AppShimMenu(Beeper *b): ProtoShimNavMenu::ProtoShimNavMenu() { static const sprite_t wrench_icns = { .width = 16, .height = 16, .data = wrench_icns_data, .mask = nullptr }; +#if HAS(BALANCE_BOARD_INTEGRATION) + static const uint8_t weight_icns_data[] = { + // By PiiXL + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x08, 0x10, 0x28, 0x14, 0x28, 0x14, 0xab, 0xd5, + 0xab, 0xd5, 0xab, 0xd5, 0x28, 0x14, 0x28, 0x14, 0x08, 0x10, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00 + }; + + static const sprite_t weight_icns = { .width = 16, .height = 16, .data = weight_icns_data, .mask = nullptr }; +#endif + static ListView * settings_menu = new ListView(); settings_menu->add_view(new MenuActionItemView("Clock", [this](){ push_submenu(clock_menu); }, &clock_icns)); settings_menu->add_view(new MenuActionItemView("Display", [this](){ push_submenu(display_menu); }, &display_icns)); @@ -153,6 +163,9 @@ AppShimMenu::AppShimMenu(Beeper *b): ProtoShimNavMenu::ProtoShimNavMenu() { main_menu->add_view(new MenuActionItemView("Clock", [this](){ pop_state(STATE_MENU, TRANSITION_SLIDE_HORIZONTAL_LEFT); }, &clock_icns)); main_menu->add_view(new MenuActionItemView("Timer", [this](){ push_state(STATE_TIMER_EDITOR, TRANSITION_SLIDE_HORIZONTAL_LEFT); }, &hourglass_icns)); main_menu->add_view(new MenuActionItemView("Alarm", [this](){ push_state(STATE_ALARM_EDITOR, TRANSITION_SLIDE_HORIZONTAL_LEFT); }, &alarm_icns)); +#if HAS(BALANCE_BOARD_INTEGRATION) + main_menu->add_view(new MenuActionItemView("Weighing", [this](){ push_state(STATE_WEIGHING, TRANSITION_SLIDE_HORIZONTAL_LEFT); }, &weight_icns)); +#endif main_menu->add_view(new MenuActionItemView("Settings", [this](){ push_submenu(settings_menu); }, &wrench_icns)); } diff --git a/src/app/weighing.cpp b/src/app/weighing.cpp new file mode 100644 index 0000000..961f365 --- /dev/null +++ b/src/app/weighing.cpp @@ -0,0 +1,123 @@ +#include +#include +#include +#include + +#if HAS(BALANCE_BOARD_INTEGRATION) + +class AppShimWeighing::WeighingView: public Renderable { +public: + WeighingView(SensorPool*s): + sensors(s), + font(&keyrus0816_font), + value_str { 0 }, + value_len { 0 }, + disp_value { 0 } + {} + + void render(FantaManipulator *fb) { + int text_width = value_len * font->width; + int left_offset = fb->get_width() / 2 - text_width / 2; + fb->put_string(font, value_str, left_offset, 0); + } + + void step() { + if(sensors->exists(SENSOR_ID_BALANCE_BOARD_MILLIKILOS)) { + if(sensor_info_t * info = sensors->get_info(SENSOR_ID_BALANCE_BOARD_MILLIKILOS)) { + int value = info->last_result / 10; + snprintf(value_str, 12, "%c %d.%d kg %c", value == 0 ? 0x10 : ' ', value / 10, abs(value % 10), value == 0 ? 0x11 : ' '); + value_len = strlen(value_str); + } + } + } + +private: + SensorPool *sensors; + const font_definition_t * font; + int disp_value; + char value_str[12]; + int value_len; +}; + + +AppShimWeighing::AppShimWeighing(SensorPool* s) { + carousel = new ViewMultiplexor(); + sensors = s; + lastActivity = xTaskGetTickCount(); + + carousel->add_view(new MenuInfoItemView("Disconnected", "\x1A to connect"), NEED_CONNECT); + carousel->add_view(new MenuInfoItemView("Scanning...", "Press the SYNC button on the Balance Board now"), WAIT_CONNECT); + carousel->add_view(new WeighingView(s), WEIGHING); + + add_subrenderable(carousel); + + balance_board_start(s); +} + +AppShimWeighing::~AppShimWeighing() { + delete carousel; +} + +void AppShimWeighing::prepare() { + Composite::prepare(); + lastActivity = xTaskGetTickCount(); + update_state(TRANSITION_NONE); +} + +void AppShimWeighing::render(FantaManipulator* fb) { + fb->clear(); + Composite::render(fb); +} + +void AppShimWeighing::step() { + Composite::step(); + update_state(TRANSITION_WIPE); + balance_board_state_t sts = balance_board_state(); + if(sts == BB_IDLE) { + if(hid_test_key_state(KEY_RIGHT) == KEYSTATE_HIT) { + balance_board_scan(true); + } + } else if(sts == BB_SCANNING) { + if(hid_test_key_state(KEY_RIGHT) == KEYSTATE_HIT) { + balance_board_scan(false); + } + } else if(sts == BB_CONNECTED) { + if(hid_test_key_state(KEY_RIGHT) == KEYSTATE_HIT) { + balance_board_zero(); + } else if(hid_test_key_state(KEY_RIGHT) == KEYSTATE_HOLDING) { + balance_board_scan(true); + } + } + + if(hid_test_key_state(KEY_LEFT) == KEYSTATE_HIT) { + if(sts == BB_SCANNING) balance_board_scan(false); + pop_state(STATE_WEIGHING, TRANSITION_SLIDE_HORIZONTAL_RIGHT); + } + + TickType_t now = xTaskGetTickCount(); + if(sensor_info_t * activity = sensors->get_info(SENSOR_ID_BALANCE_BOARD_STARTLED)) { + if(activity->last_result) { + lastActivity = now; + } + } + + if(now - lastActivity > pdMS_TO_TICKS(25000) && sts == BB_CONNECTED) { + pop_state(STATE_WEIGHING, TRANSITION_SLIDE_HORIZONTAL_LEFT); + } +} + +void AppShimWeighing::update_state(transition_type_t t) { + switch(balance_board_state()) { + case BB_IDLE: + carousel->switch_to(NEED_CONNECT, t); + break; + case BB_SCANNING: + carousel->switch_to(WAIT_CONNECT, t); + break; + case BB_CONNECTED: + carousel->switch_to(WEIGHING, t); + break; + } +} + +#endif diff --git a/src/main.cpp b/src/main.cpp index 6fec94c..d6bbe3c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -252,6 +253,9 @@ void setup() { appHost->add_view(new AppShimMenu(beepola), STATE_MENU); appHost->add_view(new AppShimAlarmEditor(beepola), STATE_ALARM_EDITOR); appHost->add_view(new AppShimTimerEditor(beepola), STATE_TIMER_EDITOR); +#if HAS(BALANCE_BOARD_INTEGRATION) + appHost->add_view(new AppShimWeighing(sensors), STATE_WEIGHING); +#endif change_state(startup_state); alarm_init(sensors); diff --git a/src/service/alarm.cpp b/src/service/alarm.cpp index 5e2baa2..dbf9198 100644 --- a/src/service/alarm.cpp +++ b/src/service/alarm.cpp @@ -118,7 +118,7 @@ static void alarm_task(void*) { } } - vTaskDelay(pdMS_TO_TICKS(1000)); + vTaskDelay(pdMS_TO_TICKS(500)); } } diff --git a/src/service/balance_board.cpp b/src/service/balance_board.cpp new file mode 100644 index 0000000..fc24e74 --- /dev/null +++ b/src/service/balance_board.cpp @@ -0,0 +1,150 @@ +#include +#include +#if HAS(BALANCE_BOARD_INTEGRATION) +#include + +static char LOG_TAG[] = "BBRD"; + +EXT_RAM_ATTR Wiimote wiimote; +static balance_board_state_t sts; +static int kilos = 0; +static int offset = 0; +static bool initialized = false; +static FauxSensor *kiloSensor; +static TimerSensor *startledSensor; +static TaskHandle_t hTask; +static TickType_t lastUpdate = 0; +static int blinks = 0; +static bool button = false; +// Low-pass filter parameters +static const float alpha = 0.1f; // Smoothing factor (0.0 - 1.0) +static int filteredKilos = 0; // Filtered weight value +static int avgKilos = 0; + +void balance_board_zero() +{ + offset = kilos; + prefs_set_int(PREFS_KEY_BALANCE_BOARD_OFFSET, offset); +} + +void wiimote_callback(wiimote_event_type_t event_type, uint16_t handle, uint8_t *data, size_t len) +{ + if (event_type == WIIMOTE_EVENT_DATA) + { + if (data[1] == 0x34) + { + // https://wiibrew.org/wiki/Wii_Balance_Board#Data_Format + TickType_t now = xTaskGetTickCount(); + + float weight[4]; + wiimote.get_balance_weight(data, weight); + float measurement = weight[BALANCE_POSITION_TOP_LEFT] + weight[BALANCE_POSITION_TOP_RIGHT] + weight[BALANCE_POSITION_BOTTOM_LEFT] + weight[BALANCE_POSITION_BOTTOM_RIGHT]; + int current = (int)(trunc(measurement * 100.0)); + + // Apply low-pass filter + filteredKilos = (int)(alpha * current + (1.0f - alpha) * filteredKilos); + + avgKilos += filteredKilos; + avgKilos /= 2; + + if (now - lastUpdate >= pdMS_TO_TICKS(100)) + { + if (abs(avgKilos - kilos) >= 2000) + { + startledSensor->trigger(); + } + kilos = avgKilos; + kiloSensor->value = (kilos - offset); + + ESP_LOGV(LOG_TAG, "Balance board value: %f kg", kilos); + lastUpdate = now; + + if (blinks > 0) + { + blinks--; + wiimote.set_led(handle, blinks % 2); + } + } + + button = (data[3] & 0x08) != 0; + if (button) + { + startledSensor->trigger(); + } + } + } + else if (event_type == WIIMOTE_EVENT_INITIALIZE) + { + ESP_LOGI(LOG_TAG, "WIIMOTE_EVENT_INITIALIZE\n"); + if (sts != BB_CONNECTED) + balance_board_scan(true); + } + else if (event_type == WIIMOTE_EVENT_SCAN_START) + { + ESP_LOGI(LOG_TAG, "WIIMOTE_EVENT_SCAN_START"); + sts = BB_SCANNING; + } + else if (event_type == WIIMOTE_EVENT_SCAN_STOP) + { + ESP_LOGI(LOG_TAG, "WIIMOTE_EVENT_SCAN_STOP"); + if (sts != BB_CONNECTED) + sts = BB_IDLE; + } + else if (event_type == WIIMOTE_EVENT_CONNECT) + { + ESP_LOGI(LOG_TAG, "WIIMOTE_EVENT_CONNECT"); + wiimote.set_led(handle, 1); + sts = BB_CONNECTED; + blinks = 20; + } + else if (event_type == WIIMOTE_EVENT_DISCONNECT) + { + ESP_LOGI(LOG_TAG, " event_type=WIIMOTE_EVENT_DISCONNECT\n"); + sts = BB_IDLE; + } +} + +void bb_task(void *) +{ + while (1) + { + wiimote.handle(); + vTaskDelay(pdMS_TO_TICKS(10)); + } +} + +void balance_board_start(SensorPool *sensors) +{ + if (initialized) + return; + wiimote.init(wiimote_callback); + sts = BB_IDLE; + offset = prefs_get_int(PREFS_KEY_BALANCE_BOARD_OFFSET); + kiloSensor = new FauxSensor(0); + startledSensor = new TimerSensor(pdMS_TO_TICKS(500)); + sensors->add(SENSOR_ID_BALANCE_BOARD_MILLIKILOS, kiloSensor, pdMS_TO_TICKS(250)); + sensors->add(SENSOR_ID_BALANCE_BOARD_STARTLED, startledSensor, pdMS_TO_TICKS(250)); + if (!xTaskCreate( + bb_task, + LOG_TAG, + 8192, + nullptr, + 2, + &hTask)) + { + ESP_LOGE(LOG_TAG, "Task creation failed"); + } + initialized = true; +} + +void balance_board_scan(bool state) +{ + wiimote.scan(state); +} + +balance_board_state_t balance_board_state() +{ + return sts; +} + +#endif From df57165e340d1294010e6048e99bb255be3d16ff Mon Sep 17 00:00:00 2001 From: akasaka Date: Tue, 23 Jul 2024 17:23:17 +0900 Subject: [PATCH 21/21] Make WROVER the base requirement. --- README.md | 8 ++++++-- include/devices/big_clock.h | 8 +++----- platformio.ini | 8 ++++---- src/main.cpp | 3 --- src/service/balance_board.cpp | 1 - 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 3964e19..f529834 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ Personal Information System OS (formerly Plasma Information Screen OS). A somewhat portable relatively-stylish pixel-art clock/weather station. +## System Requirements + +The basic configuration without any bluetooth functionality (no Switchbot or Balance Board integration) seems to work just fine on an ESP32 WROOM. However to be less limited by RAM size in further features I've decided to make WROVER the requirement, so further versions are not guaranteed to run on WROOM. + ## Predefined target devices * `DEVICE_PLASMA_CLOCK`: a [clock](https://youtu.be/D4MiHmhhjeQ) that I built around a plasma screen from an old Japanese bus/train. @@ -34,7 +38,7 @@ A somewhat portable relatively-stylish pixel-art clock/weather station. ### Thermal sensors * AM2322 over IIC ([driver](src/sensor/am2322.cpp), feature flag `HAS_TEMP_SENSOR`) -* Switchbot Meter over BLE (unstable, [driver](src/sensor/switchbot/api.cpp), feature flag `SWITCHBOT_METER_INTEGRATION`) +* Switchbot Meter over BLE (unstable, [driver](src/sensor/switchbot/api.cpp), feature flag `SWITCHBOT_METER_INTEGRATION`, needs extra ram of a WROVER module) ### Motion sensors @@ -51,7 +55,7 @@ A somewhat portable relatively-stylish pixel-art clock/weather station. ### Others -* Wii Balance Board. Set feature flag `HAS_BALANCE_BOARD_INTEGRATION`. [Driver](src/service/balance_board.cpp), based upon [code by Sasaki Takeru](https://github.com/takeru/Wiimote/tree/d81319c62ac5931da868cc289386a6d4880a4b15) +* Wii Balance Board. Set feature flag `HAS_BALANCE_BOARD_INTEGRATION`. [Driver](src/service/balance_board.cpp), based upon [code by Sasaki Takeru](https://github.com/takeru/Wiimote/tree/d81319c62ac5931da868cc289386a6d4880a4b15), requires WROVER module ## Morio Denki Plasma Display Info diff --git a/include/devices/big_clock.h b/include/devices/big_clock.h index a79dcf8..351047f 100644 --- a/include/devices/big_clock.h +++ b/include/devices/big_clock.h @@ -20,8 +20,8 @@ const gpio_num_t HWCONF_PLASMA_DATABUS_GPIOS[] = { GPIO_NUM_2, GPIO_NUM_0, GPIO_NUM_4, - GPIO_NUM_16, - GPIO_NUM_17, + GPIO_NUM_32, + GPIO_NUM_33, GPIO_NUM_5, GPIO_NUM_18 }; @@ -48,9 +48,7 @@ const gpio_num_t HWCONF_LIGHTSENSE_GPIO = GPIO_NUM_35; const gpio_num_t HWCONF_I2C_SDA_GPIO = GPIO_NUM_26; const gpio_num_t HWCONF_I2C_SCL_GPIO = GPIO_NUM_25; -// ---- TBD: Connection of buttons ---- -// Free GPIOS: 36, 39, 34, 27 - +// ---- Connection of buttons ---- const keypad_definition_t HWCONF_KEYPAD = { {GPIO_NUM_27, KEY_LEFT}, {GPIO_NUM_39, KEY_RIGHT}, diff --git a/platformio.ini b/platformio.ini index e02730f..b8af2c1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -28,8 +28,8 @@ extra_scripts = pre:helper/env-extra.py [env:bigclock-nodemcu-32s] -board = nodemcu-32s -build_flags = -DDEVICE_PLASMA_CLOCK ${common.build_flags} +board = esp32dev +build_flags = -DDEVICE_PLASMA_CLOCK ${common.build_flags} -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue upload_speed = 921600 monitor_speed = 115200 monitor_filters = esp32_exception_decoder @@ -38,8 +38,8 @@ monitor_filters = esp32_exception_decoder ; Feature-flag: HAS_OTAFVU [env:bigclock-nodemcu-32s-ota] -board = nodemcu-32s -build_flags = -DDEVICE_PLASMA_CLOCK ${common.build_flags} +board = esp32dev +build_flags = -DDEVICE_PLASMA_CLOCK ${common.build_flags} -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue upload_protocol = espota upload_port = 192.168.1.144 upload_flags = --auth=plasma-otafvu diff --git a/src/main.cpp b/src/main.cpp index d6bbe3c..ddf8d8d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -52,8 +52,6 @@ static OTAFVUManager * ota; static Beeper * beepola; static BeepSequencer * bs; -static bool fps_counter = false; - void change_state(device_state_t to, transition_type_t transition) { if(to == STATE_OTAFVU) { current_state = STATE_OTAFVU; @@ -236,7 +234,6 @@ void setup() { foo_client_begin(); power_mgmt_start(sensors, &display_driver, beepola); admin_panel_prepare(sensors, beepola); - fps_counter = prefs_get_bool(PREFS_KEY_FPS_COUNTER); vTaskPrioritySet(NULL, configMAX_PRIORITIES - 1); diff --git a/src/service/balance_board.cpp b/src/service/balance_board.cpp index fc24e74..18f93ad 100644 --- a/src/service/balance_board.cpp +++ b/src/service/balance_board.cpp @@ -33,7 +33,6 @@ void wiimote_callback(wiimote_event_type_t event_type, uint16_t handle, uint8_t { if (data[1] == 0x34) { - // https://wiibrew.org/wiki/Wii_Balance_Board#Data_Format TickType_t now = xTaskGetTickCount(); float weight[4];