Verified Commit fd96a857 authored by Rahix's avatar Rahix

Merge 'Personal State API'

Add support for personal state as described on

    https://card10.badge.events.ccc.de/ps/
parents 3e886c50 dd2dc383
...@@ -29,6 +29,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the ...@@ -29,6 +29,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the
pycardium/leds pycardium/leds
pycardium/light-sensor pycardium/light-sensor
pycardium/os pycardium/os
pycardium/personal_state
pycardium/utime pycardium/utime
pycardium/vibra pycardium/vibra
......
.. py:module:: personal_state
``personal_state`` - Personal State
===================================
The :py:mod:`personal_state` module allows you to set and get the card10 users personal state from your script. The personal state is displayed on the top-left LED on the bottom of the harmonics board. While the personal state is set the LED can't be controlled by the :py:mod:`leds` module.
**Example**:
.. code-block:: python
import personal_state
# Enable the "camp" state only while the app is running.
personal_state.set(personal_state.CAMP, False)
# Enable the "chaos" state and keep it after the app exits.
personal_state.set(personal_state.CHAOS, True)
# Query the currently configured state and if it's persistent.
state, persistent = personal_state.get()
# Clear the currently configured state
personal_state.clear()
.. py:function:: personal_state.set(state, persistent)
Set the users personal state.
:param int state: ID of the personal state to set. Must be one of :py:data:`personal_state.NO_CONTACT`, :py:data:`personal_state.CHAOS`, :py:data:`personal_state.COMMUNICATION`, :py:data:`personal_state.CAMP`.
:param int persistent: Controls whether the personal state is persistent. A persistent state is not reset when the pycardium application is changed or restarted. In persistent mode the personal state LED is not controllable by the pycardium application.
.. py:function:: personal_state.clear()
Clears a previously set personal state.
If no personal state was set this function does nothing. It does not matter
if a set state is marked as persistent or not.
.. py:function:: personal_state.get()
Get the users personal state.
:returns: A tuple containing the currently set state and a boolean indicating if it's persistent or not.
.. py:data:: personal_state.NO_STATE
State ID reported when no personal state is set.
.. py:data:: personal_state.NO_CONTACT
State ID for the "No Contact" personal state.
.. py:data:: personal_state.CHAOS
State ID for the "Chaos" personal state.
.. py:data:: personal_state.COMMUNICATION
State ID for the "Communicatoin" personal state.
.. py:data:: personal_state.CAMP
State ID for the "Camp" personal state.
...@@ -84,6 +84,7 @@ typedef _Bool bool; ...@@ -84,6 +84,7 @@ typedef _Bool bool;
#define API_LEDS_SET_ALL 0x6a #define API_LEDS_SET_ALL 0x6a
#define API_LEDS_SET_ALL_HSV 0x6b #define API_LEDS_SET_ALL_HSV 0x6b
#define API_LEDS_SET_GAMMA_TABLE 0x6c #define API_LEDS_SET_GAMMA_TABLE 0x6c
#define API_LEDS_CLEAR_ALL 0x6d
#define API_VIBRA_SET 0x70 #define API_VIBRA_SET 0x70
#define API_VIBRA_VIBRATE 0x71 #define API_VIBRA_VIBRATE 0x71
...@@ -101,6 +102,10 @@ typedef _Bool bool; ...@@ -101,6 +102,10 @@ typedef _Bool bool;
#define API_TRNG_READ 0xB0 #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 */ /* clang-format on */
typedef uint32_t api_int_id_t; typedef uint32_t api_int_id_t;
...@@ -648,6 +653,75 @@ API(API_LEDS_SET_GAMMA_TABLE, void epic_leds_set_gamma_table( ...@@ -648,6 +653,75 @@ API(API_LEDS_SET_GAMMA_TABLE, void epic_leds_set_gamma_table(
uint8_t *gamma_table uint8_t *gamma_table
)); ));
/**
* Set all LEDs to a certain RGB color.
*
* :param uint8_t r: Value for the red color channel.
* :param uint8_t g: Value for the green color channel.
* :param uint8_t b: Value for the blue color channel.
*/
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 * Sensor Data Streams
* =================== * ===================
......
...@@ -73,6 +73,18 @@ int main(void) ...@@ -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 */ /* Lifecycle */
if (xTaskCreate( if (xTaskCreate(
vLifecycleTask, vLifecycleTask,
......
...@@ -190,10 +190,22 @@ int hardware_reset(void) ...@@ -190,10 +190,22 @@ int hardware_reset(void)
api_interrupt_init(); api_interrupt_init();
api_dispatcher_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
*/ */
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(0, 0);
epic_leds_set_rocket(1, 0); epic_leds_set_rocket(1, 0);
epic_leds_set_rocket(2, 0); epic_leds_set_rocket(2, 0);
......
#include "leds.h" #include "leds.h"
#include "pmic.h" #include "pmic.h"
//#include "FreeRTOS.h" #include "FreeRTOS.h"
//#include "task.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 //TODO: create smth like vTaskDelay(pdMS_TO_TICKS(//put ms here)) for us, remove blocking delay from /lib/leds.c to avoid process blocking
void epic_leds_set(int led, uint8_t r, uint8_t g, uint8_t b) #define NUM_LEDS 15 /* Take from lib/card10/leds.c */
static void do_update()
{ {
leds_prep(led, r, g, b); while (hwlock_acquire(HWLOCK_LED, pdMS_TO_TICKS(1)) < 0) {
vTaskDelay(pdMS_TO_TICKS(1));
}
leds_update_power(); leds_update_power();
leds_update(); leds_update();
hwlock_release(HWLOCK_LED);
}
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);
do_update();
} }
void epic_leds_set_hsv(int led, float h, float s, float v) 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_prep_hsv(led, h, s, v);
leds_update_power(); do_update();
leds_update();
} }
void epic_leds_prep(int led, uint8_t r, uint8_t g, uint8_t b) 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); leds_prep(led, r, g, b);
} }
void epic_leds_prep_hsv(int led, float h, float s, float v) 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); leds_prep_hsv(led, h, s, v);
} }
...@@ -33,32 +61,38 @@ void epic_leds_set_all(uint8_t *pattern_ptr, uint8_t len) ...@@ -33,32 +61,38 @@ void epic_leds_set_all(uint8_t *pattern_ptr, uint8_t len)
{ {
uint8_t(*pattern)[3] = (uint8_t(*)[3])pattern_ptr; uint8_t(*pattern)[3] = (uint8_t(*)[3])pattern_ptr;
for (int i = 0; i < len; i++) { 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_prep(i, pattern[i][0], pattern[i][1], pattern[i][2]);
} }
leds_update_power(); do_update();
leds_update();
} }
void epic_leds_set_all_hsv(float *pattern_ptr, uint8_t len) void epic_leds_set_all_hsv(float *pattern_ptr, uint8_t len)
{ {
float(*pattern)[3] = (float(*)[3])pattern_ptr; float(*pattern)[3] = (float(*)[3])pattern_ptr;
for (int i = 0; i < len; i++) { 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_prep_hsv(i, pattern[i][0], pattern[i][1], pattern[i][2]);
} }
leds_update_power(); do_update();
leds_update();
} }
void epic_leds_dim_top(uint8_t value) void epic_leds_dim_top(uint8_t value)
{ {
leds_set_dim_top(value); leds_set_dim_top(value);
leds_update(); if (personal_state_enabled() == 0)
leds_update();
} }
void epic_leds_dim_bottom(uint8_t value) void epic_leds_dim_bottom(uint8_t value)
{ {
leds_set_dim_bottom(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) void epic_leds_set_rocket(int led, uint8_t value)
...@@ -74,8 +108,7 @@ void epic_set_flashlight(bool power) ...@@ -74,8 +108,7 @@ void epic_set_flashlight(bool power)
void epic_leds_update(void) void epic_leds_update(void)
{ {
leds_update_power(); do_update();
leds_update();
} }
void epic_leds_set_powersave(bool eco) void epic_leds_set_powersave(bool eco)
...@@ -87,3 +120,15 @@ void epic_leds_set_gamma_table(uint8_t rgb_channel, uint8_t gamma_table[256]) ...@@ -87,3 +120,15 @@ void epic_leds_set_gamma_table(uint8_t rgb_channel, uint8_t gamma_table[256])
{ {
leds_set_gamma_table(rgb_channel, gamma_table); leds_set_gamma_table(rgb_channel, gamma_table);
} }
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())
continue;
leds_prep(i, r, g, b);
}
do_update();
}
...@@ -10,6 +10,7 @@ module_sources = files( ...@@ -10,6 +10,7 @@ module_sources = files(
'lifecycle.c', 'lifecycle.c',
'light_sensor.c', 'light_sensor.c',
'log.c', 'log.c',
'personal_state.c',
'pmic.c', 'pmic.c',
'rtc.c', 'rtc.c',
'serial.c', 'serial.c',
......
...@@ -26,6 +26,11 @@ void return_to_menu(void); ...@@ -26,6 +26,11 @@ void return_to_menu(void);
void vSerialTask(void *pvParameters); void vSerialTask(void *pvParameters);
void serial_enqueue_char(char chr); void serial_enqueue_char(char chr);
/* ---------- LED Animation / Personal States ------------------------------ */
#define PERSONAL_STATE_LED 14
void vLedTask(void *pvParameters);
int personal_state_enabled();
/* ---------- PMIC --------------------------------------------------------- */ /* ---------- PMIC --------------------------------------------------------- */
/* In 1/10s */ /* In 1/10s */
#define PMIC_PRESS_SLEEP 20 #define PMIC_PRESS_SLEEP 20
...@@ -43,6 +48,7 @@ void hwlock_init(void); ...@@ -43,6 +48,7 @@ void hwlock_init(void);
enum hwlock_periph { enum hwlock_periph {
HWLOCK_I2C = 0, HWLOCK_I2C = 0,
HWLOCK_ADC, HWLOCK_ADC,
HWLOCK_LED,
_HWLOCK_MAX, _HWLOCK_MAX,
}; };
......
#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) {
while (hwlock_acquire(HWLOCK_LED, pdMS_TO_TICKS(1)) < 0) {
vTaskDelay(pdMS_TO_TICKS(1));
}
leds_prep(PERSONAL_STATE_LED, 0, 0, 0);
leds_update_power();
leds_update();
hwlock_release(HWLOCK_LED);
}
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) {
while (hwlock_acquire(HWLOCK_LED, pdMS_TO_TICKS(1)) <
0) {
vTaskDelay(pdMS_TO_TICKS(1));
}
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();
hwlock_release(HWLOCK_LED);
}
vTaskDelay(led_animation_rate / portTICK_PERIOD_MS);
}
}
"""
Personal State Script
===========
With this script you can
"""
import buttons
import color
import display
import os
import personal_state
states = [
("No State", personal_state.NO_STATE),
("No Contact", personal_state.NO_CONTACT),
("Chaos", personal_state.CHAOS),
("Communication", personal_state.COMMUNICATION),
("Camp", personal_state.CAMP),
]