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