From dd841b5af698a7489c1c964959add864daa106c6 Mon Sep 17 00:00:00 2001 From: trilader <trilader@schroedingers-bit.net> Date: Tue, 20 Aug 2019 18:41:45 +0200 Subject: [PATCH] feat(epicardium): Add personal state support This includes a new RTOS task to animate the personal state LED independently of pycardium. While the animation is running pycardium can't control the personal state LED. --- epicardium/epicardium.h | 64 ++++++++++++++ epicardium/main.c | 12 +++ epicardium/modules/hardware.c | 14 ++- epicardium/modules/leds.c | 56 +++++++++--- epicardium/modules/meson.build | 1 + epicardium/modules/modules.h | 5 ++ epicardium/modules/personal_state.c | 128 ++++++++++++++++++++++++++++ 7 files changed, 266 insertions(+), 14 deletions(-) create mode 100644 epicardium/modules/personal_state.c diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 325b80d5..87b24e89 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -102,6 +102,10 @@ typedef _Bool bool; #define API_TRNG_READ 0xB0 +#define API_PERSONAL_STATE_SET 0xc0 +#define API_PERSONAL_STATE_GET 0xc1 +#define API_PERSONAL_STATE_IS_PERSISTENT 0xc2 + /* clang-format on */ typedef uint32_t api_int_id_t; @@ -658,6 +662,66 @@ API(API_LEDS_SET_GAMMA_TABLE, void epic_leds_set_gamma_table( */ API(API_LEDS_CLEAR_ALL, void epic_leds_clear_all(uint8_t r, uint8_t g, uint8_t b)); +/** + * Personal State + * ============== + * Card10 can display your personal state. + * + * If a personal state is set the top-left LED on the bottom side of the + * harmonics board is directly controlled by epicardium and it can't be + * controlled by pycardium. + * + * To re-enable pycardium control the personal state has to be cleared. To do + * that simply set it to ``STATE_NONE``. + * + * The personal state can be set to be persistent which means it won't get reset + * on pycardium application change/restart. + */ + +/** Possible personal states. */ +enum personal_state { + /** ``0``, No personal state - LED is under regular application control. */ + STATE_NONE = 0, + /** ``1``, "no contact, please!" - I am overloaded. Please leave me be - red led, continuously on. */ + STATE_NO_CONTACT = 1, + /** ``2``, "chaos" - Adventure time - blue led, short blink, long blink. */ + STATE_CHAOS = 2, + /** ``3``, "communication" - want to learn something or have a nice conversation - green led, long blinks. */ + STATE_COMMUNICATION = 3, + /** ``4``, "camp" - I am focussed on self-, camp-, or community maintenance - yellow led, fade on and off. */ + STATE_CAMP = 4, +}; + +/** + * Set the users personal state. + * + * Using :c:func:`epic_personal_state_set` an application can set the users personal state. + * + * :param uint8_t state: The users personal state. Must be one of :c:type:`personal_state`. + * :param bool persistent: Indicates whether the configured personal state will remain set and active on pycardium application restart/change. + * :returns: ``0`` on success, ``-EINVAL`` if an invalid state was requested. + */ +API(API_PERSONAL_STATE_SET, int epic_personal_state_set(uint8_t state, + bool persistent)); + +/** + * Get the users personal state. + * + * Using :c:func:`epic_personal_state_get` an application can get the currently set personal state of the user. + * + * :returns: A value with exactly one value of :c:type:`personal_state` set. + */ +API(API_PERSONAL_STATE_GET, int epic_personal_state_get()); + +/** + * Get whether the users personal state is persistent. + * + * Using :c:func:`epic_personal_state_is_persistent` an app can find out whether the users personal state is persistent or transient. + * + * :returns: ``1`` if the state is persistent, ``0`` otherwise. + */ +API(API_PERSONAL_STATE_IS_PERSISTENT, int epic_personal_state_is_persistent()); + /** * Sensor Data Streams * =================== diff --git a/epicardium/main.c b/epicardium/main.c index 57b07ca4..7074d4df 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -73,6 +73,18 @@ int main(void) } } + /* LEDs */ + if (xTaskCreate( + vLedTask, + (const char *)"LED", + configMINIMAL_STACK_SIZE, + NULL, + tskIDLE_PRIORITY + 1, + NULL) != pdPASS) { + LOG_CRIT("startup", "Failed to create %s task!", "LED"); + abort(); + } + /* Lifecycle */ if (xTaskCreate( vLifecycleTask, diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c index 75a995e9..cb6b70b5 100644 --- a/epicardium/modules/hardware.c +++ b/epicardium/modules/hardware.c @@ -190,10 +190,22 @@ int hardware_reset(void) api_interrupt_init(); api_dispatcher_init(); + /* Personal State */ + const int personal_state_is_persistent = + epic_personal_state_is_persistent(); + + if (personal_state_is_persistent == 0) { + epic_personal_state_set(STATE_NONE, 0); + } + /* * LEDs */ - leds_init(); + if (personal_state_is_persistent) { + epic_leds_clear_all(0, 0, 0); + } else { + leds_init(); + } epic_leds_set_rocket(0, 0); epic_leds_set_rocket(1, 0); epic_leds_set_rocket(2, 0); diff --git a/epicardium/modules/leds.c b/epicardium/modules/leds.c index 290fb16d..a4e22e5c 100644 --- a/epicardium/modules/leds.c +++ b/epicardium/modules/leds.c @@ -1,12 +1,29 @@ #include "leds.h" #include "pmic.h" -//#include "FreeRTOS.h" -//#include "task.h" +#include "FreeRTOS.h" +#include "task.h" +#include "epicardium.h" +#include "modules.h" + +#include <stdbool.h> //TODO: create smth like vTaskDelay(pdMS_TO_TICKS(//put ms here)) for us, remove blocking delay from /lib/leds.c to avoid process blocking +#define NUM_LEDS 15 /* Take from lib/card10/leds.c */ + +static void update_if_needed() +{ + if (personal_state_enabled() == 0) { + leds_update_power(); + leds_update(); + } +} + void epic_leds_set(int led, uint8_t r, uint8_t g, uint8_t b) { + if (led == PERSONAL_STATE_LED && personal_state_enabled()) + return; + leds_prep(led, r, g, b); leds_update_power(); leds_update(); @@ -14,18 +31,26 @@ void epic_leds_set(int led, uint8_t r, uint8_t g, uint8_t b) void epic_leds_set_hsv(int led, float h, float s, float v) { + if (led == PERSONAL_STATE_LED && personal_state_enabled()) + return; + leds_prep_hsv(led, h, s, v); - leds_update_power(); - leds_update(); + update_if_needed(); } void epic_leds_prep(int led, uint8_t r, uint8_t g, uint8_t b) { + if (led == PERSONAL_STATE_LED && personal_state_enabled()) + return; + leds_prep(led, r, g, b); } void epic_leds_prep_hsv(int led, float h, float s, float v) { + if (led == PERSONAL_STATE_LED && personal_state_enabled()) + return; + leds_prep_hsv(led, h, s, v); } @@ -33,32 +58,38 @@ void epic_leds_set_all(uint8_t *pattern_ptr, uint8_t len) { uint8_t(*pattern)[3] = (uint8_t(*)[3])pattern_ptr; for (int i = 0; i < len; i++) { + if (i == PERSONAL_STATE_LED && personal_state_enabled()) + continue; + leds_prep(i, pattern[i][0], pattern[i][1], pattern[i][2]); } - leds_update_power(); - leds_update(); + update_if_needed(); } void epic_leds_set_all_hsv(float *pattern_ptr, uint8_t len) { float(*pattern)[3] = (float(*)[3])pattern_ptr; for (int i = 0; i < len; i++) { + if (i == PERSONAL_STATE_LED && personal_state_enabled()) + continue; + leds_prep_hsv(i, pattern[i][0], pattern[i][1], pattern[i][2]); } - leds_update_power(); - leds_update(); + update_if_needed(); } void epic_leds_dim_top(uint8_t value) { leds_set_dim_top(value); - leds_update(); + if (personal_state_enabled() == 0) + leds_update(); } void epic_leds_dim_bottom(uint8_t value) { leds_set_dim_bottom(value); - leds_update(); + if (personal_state_enabled() == 0) + leds_update(); } void epic_leds_set_rocket(int led, uint8_t value) @@ -74,8 +105,7 @@ void epic_set_flashlight(bool power) void epic_leds_update(void) { - leds_update_power(); - leds_update(); + update_if_needed(); } void epic_leds_set_powersave(bool eco) @@ -91,7 +121,7 @@ void epic_leds_set_gamma_table(uint8_t rgb_channel, uint8_t gamma_table[256]) void epic_leds_clear_all(uint8_t r, uint8_t g, uint8_t b) { for (int i = 0; i < NUM_LEDS; i++) { - if (i == PERSONAL_STATE_LED && personal_state_enabled) + if (i == PERSONAL_STATE_LED && personal_state_enabled()) continue; leds_prep(i, r, g, b); diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build index 4966be6c..5b4199b1 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -10,6 +10,7 @@ module_sources = files( 'lifecycle.c', 'light_sensor.c', 'log.c', + 'personal_state.c', 'pmic.c', 'rtc.c', 'serial.c', diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h index ccd8a900..9b78e48b 100644 --- a/epicardium/modules/modules.h +++ b/epicardium/modules/modules.h @@ -26,6 +26,11 @@ void return_to_menu(void); void vSerialTask(void *pvParameters); void serial_enqueue_char(char chr); +/* ---------- LED Animation / Personal States ------------------------------ */ +#define PERSONAL_STATE_LED 14 +void vLedTask(void *pvParameters); +int personal_state_enabled(); + /* ---------- PMIC --------------------------------------------------------- */ /* In 1/10s */ #define PMIC_PRESS_SLEEP 20 diff --git a/epicardium/modules/personal_state.c b/epicardium/modules/personal_state.c new file mode 100644 index 00000000..bd02c921 --- /dev/null +++ b/epicardium/modules/personal_state.c @@ -0,0 +1,128 @@ +#include "epicardium.h" +#include "leds.h" +#include "modules.h" + +#include <math.h> + +uint8_t _personal_state_enabled = 0; +uint8_t personal_state = STATE_NONE; +uint8_t personal_state_persistent = 0; + +int led_animation_ticks = 0; +int led_animation_state = 0; + +int personal_state_enabled() +{ + return _personal_state_enabled; +} + +int epic_personal_state_set(uint8_t state, bool persistent) +{ + if (state < STATE_NONE || state > STATE_CAMP) + return -EINVAL; + + led_animation_state = 0; + led_animation_ticks = 0; + personal_state = state; + + uint8_t was_enabled = _personal_state_enabled; + + _personal_state_enabled = (state != STATE_NONE); + personal_state_persistent = persistent; + + if (was_enabled && !_personal_state_enabled) { + leds_prep(PERSONAL_STATE_LED, 0, 0, 0); + leds_update_power(); + leds_update(); + } + + return 0; +} + +int epic_personal_state_get() +{ + return personal_state; +} + +int epic_personal_state_is_persistent() +{ + return personal_state_persistent; +} + +void vLedTask(void *pvParameters) +{ + const int led_animation_rate = 1000 / 25; /* 25Hz -> 40ms*/ + while (1) { + if (_personal_state_enabled) { + led_animation_ticks++; + if (personal_state == STATE_NO_CONTACT) { + leds_prep(PERSONAL_STATE_LED, 255, 0, 0); + } else if (personal_state == STATE_CHAOS) { + if (led_animation_state == 0) { + leds_prep( + PERSONAL_STATE_LED, 0, 0, 255 + ); + if (led_animation_ticks > + (200 / led_animation_rate)) { + led_animation_ticks = 0; + led_animation_state = 1; + } + } else if (led_animation_state == 1) { + leds_prep(PERSONAL_STATE_LED, 0, 0, 0); + if (led_animation_ticks > + (300 / led_animation_rate)) { + led_animation_ticks = 0; + led_animation_state = 2; + } + } else if (led_animation_state == 2) { + leds_prep( + PERSONAL_STATE_LED, 0, 0, 255 + ); + if (led_animation_ticks > + (1000 / led_animation_rate)) { + led_animation_ticks = 0; + led_animation_state = 3; + } + } else if (led_animation_state == 3) { + leds_prep(PERSONAL_STATE_LED, 0, 0, 0); + if (led_animation_ticks > + (300 / led_animation_rate)) { + led_animation_ticks = 0; + led_animation_state = 0; + } + } + } else if (personal_state == STATE_COMMUNICATION) { + if (led_animation_state == 0) { + leds_prep( + PERSONAL_STATE_LED, 255, 255, 0 + ); + if (led_animation_ticks > + (1000 / led_animation_rate)) { + led_animation_ticks = 0; + led_animation_state = 1; + } + } else if (led_animation_state == 1) { + leds_prep(PERSONAL_STATE_LED, 0, 0, 0); + if (led_animation_ticks > + (300 / led_animation_rate)) { + led_animation_ticks = 0; + led_animation_state = 0; + } + } + } else if (personal_state == STATE_CAMP) { + leds_prep_hsv( + PERSONAL_STATE_LED, + 120.0f, + 1.0f, + fabs(sin( + led_animation_ticks / + (float)(1000 / + led_animation_rate)))); + } + leds_update_power(); + leds_update(); + } + + vTaskDelay(led_animation_rate / portTICK_PERIOD_MS); + } +} -- GitLab