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
pycardium/leds
pycardium/light-sensor
pycardium/os
pycardium/personal_state
pycardium/utime
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;
#define API_LEDS_SET_ALL 0x6a
#define API_LEDS_SET_ALL_HSV 0x6b
#define API_LEDS_SET_GAMMA_TABLE 0x6c
#define API_LEDS_CLEAR_ALL 0x6d
#define API_VIBRA_SET 0x70
#define API_VIBRA_VIBRATE 0x71
......@@ -101,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;
......@@ -648,6 +653,75 @@ API(API_LEDS_SET_GAMMA_TABLE, void epic_leds_set_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
* ===================
......
......@@ -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,
......
......@@ -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
*/
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);
......
#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
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();
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)
{
if (led == PERSONAL_STATE_LED && personal_state_enabled())
return;
leds_prep_hsv(led, h, s, v);
leds_update_power();
leds_update();
do_update();
}
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,31 +61,37 @@ 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();
do_update();
}
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();
do_update();
}
void epic_leds_dim_top(uint8_t value)
{
leds_set_dim_top(value);
if (personal_state_enabled() == 0)
leds_update();
}
void epic_leds_dim_bottom(uint8_t value)
{
leds_set_dim_bottom(value);
if (personal_state_enabled() == 0)
leds_update();
}
......@@ -74,8 +108,7 @@ void epic_set_flashlight(bool power)
void epic_leds_update(void)
{
leds_update_power();
leds_update();
do_update();
}
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])
{
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(
'lifecycle.c',
'light_sensor.c',
'log.c',
'personal_state.c',
'pmic.c',
'rtc.c',
'serial.c',
......
......@@ -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
......@@ -43,6 +48,7 @@ void hwlock_init(void);
enum hwlock_periph {
HWLOCK_I2C = 0,
HWLOCK_ADC,
HWLOCK_LED,
_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),
]
def button_events():
"""Iterate over button presses (event-loop)."""
yield 0
button_pressed = False
while True:
v = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT)
if v == 0:
button_pressed = False
if not button_pressed and v & buttons.BOTTOM_LEFT != 0:
button_pressed = True
yield buttons.BOTTOM_LEFT
if not button_pressed and v & buttons.BOTTOM_RIGHT != 0:
button_pressed = True
yield buttons.BOTTOM_RIGHT
if not button_pressed and v & buttons.TOP_RIGHT != 0:
button_pressed = True
yield buttons.TOP_RIGHT
COLOR1, COLOR2 = (color.CHAOSBLUE_DARK, color.CHAOSBLUE)
def draw_menu(disp, idx, offset):
disp.clear()
for y, i in enumerate(range(len(states) + idx - 3, len(states) + idx + 4)):
selected = states[i % len(states)]
disp.print(
" " + selected[0] + " " * (11 - len(selected[0])),
posy=offset + y * 20 - 40,
bg=COLOR1 if i % 2 == 0 else COLOR2,
)
disp.print(">", posy=20, fg=color.COMMYELLOW, bg=COLOR2 if idx % 2 == 0 else COLOR1)
disp.update()
def main():
disp = display.open()
numstates = len(states)
current, _ = personal_state.get()
for ev in button_events():
if ev == buttons.BOTTOM_RIGHT:
# Scroll down
draw_menu(disp, current, -8)
current = (current + 1) % numstates
state = states[current]
personal_state.set(state[1], False)
elif ev == buttons.BOTTOM_LEFT:
# Scroll up
draw_menu(disp, current, 8)
current = (current + numstates - 1) % numstates
state = states[current]
personal_state.set(state[1], False)
elif ev == buttons.TOP_RIGHT:
state = states[current]
personal_state.set(state[1], True)
# Select & start
disp.clear().update()
disp.close()
os.exit(0)
draw_menu(disp, current, 0)
if __name__ == "__main__":
main()
......@@ -9,6 +9,7 @@ modsrc = files(
'modules/sys_leds.c',
'modules/light_sensor.c',
'modules/os.c',
'modules/personal_state.c',
'modules/sys_display.c',
'modules/utime.c',
'modules/vibra.c',
......
#include "epicardium.h"
#include "py/builtin.h"
#include "py/obj.h"
#include "py/runtime.h"
static mp_obj_t