...
 
Commits (42)
......@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Added
- `micropython.mem_use()` function.
- High-pass filter and pulse detection in default ECG app.
### Fixed
- Backlight and Vibration motor were not reset when switching apps.
- Mismatch in default settings of the *Card10 Nickname* app.
## [v1.9] - 2019-08-28 23:23 - [IcebergLettuce]
[IcebergLettuce]: https://card10.badge.events.ccc.de/release/card10-v1.9-IcebergLettuce.zip
### Added
- `tools/pycard10.py`: Tool to interact with card10's serial connection and
upload files directly:
......@@ -16,18 +28,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- **pycardium**: Support for RAW REPL mode.
- **bhi160**: Function to disable all sensors (`bhi160.disable_all_sensors()`).
- `ls_cmsis_dap`: A tool to enumerate CMSIS-DAP debuggers.
- Tons of new features to `simple_menu`: Timeout, scrolling of long texts,
robustness against crashes, and proper exiting.
- `card10.cfg` config file which allows enabling *ELF* files.
- Analog read for wristband GPIOs.
### Changed
- Refactored *menu* and *personal-state* apps.
- `main.py` was moved into an app to allow easier reconfiguration of the
default app. The new `main.py` points to the "old" one so behavior is not
changed.
- After a timeout, the menu will close and `main.py` will run again.
- BLE security updates.
- More detailed battery state display in nickname app.
- Improved ECG app.
### Removed
- Some unused font files.
### Fixed
- Fixed a regression which made the ECG app no longer work.
- Fixed card10 advertising support for AT-commands.
- Rectangles being one pixel too small.
......@@ -192,7 +214,8 @@ fbf7c8c0 fix(menu.py) Refactored menu.py based on !138
## [v1.0] - 2019-08-21 00:50
Initial release.
[Unreleased]: https://git.card10.badge.events.ccc.de/card10/firmware/compare/v1.8...master
[Unreleased]: https://git.card10.badge.events.ccc.de/card10/firmware/compare/v1.9...master
[v1.9]: https://git.card10.badge.events.ccc.de/card10/firmware/compare/v1.8...v1.9
[v1.8]: https://git.card10.badge.events.ccc.de/card10/firmware/compare/v1.7...v1.8
[v1.7]: https://git.card10.badge.events.ccc.de/card10/firmware/compare/v1.6...v1.7
[v1.6]: https://git.card10.badge.events.ccc.de/card10/firmware/compare/v1.5...v1.6
......
......@@ -33,22 +33,22 @@ The current draft uses following service specification:
- Background LED Bottom Left characteristic:
UUID: ``42230211-2342-2342-2342-234223422342``
write no response
read and write no response
- Background LED Bottom Right characteristic:
UUID: ``42230212-2342-2342-2342-234223422342``
write no response
read and write no response
- Background LED Top Right characteristic:
UUID: ``42230213-2342-2342-2342-234223422342``
write no response
read and write no response
- Background LED Top Left characteristic:
UUID: ``42230214-2342-2342-2342-234223422342``
write no reponse
read and write no reponse
- LEDS dim bottom characteristic:
......@@ -78,7 +78,7 @@ The current draft uses following service specification:
- LEDs above characteristic:
UUID: ``42230220-2342-2342-2342-234223422342``
write no reponse
read and write no reponse
- Light sensor characteristic:
......@@ -120,7 +120,7 @@ Background LED <Position> characteristic
----------------------------------------
The Background LEDs <Position> characteristic makes it possible to address the bottom LEDs by position.
Just write there three ``uint8`` for the rgb color.
Just write there three ``uint8`` for the rgb color or read the current value.
Dataformat:
......@@ -170,7 +170,7 @@ It writes always as persistant and it gives feedback if the value is in range an
LEDs above characteristic
---------------------------------
This characteristic set every 11 leds on the top module at once.
This characteristic set or read the current value of every 11 leds on the top module at once.
By defining 11x rgb from left to right. You need also to set exchange a bigger MTU to use this feature.
- set a rainbow beginnig with red on the right edge: ``0xff0000ff8b00e8ff005dff0000ff2e00ffb900b9ff002eff5d00ffe800ffff008b``
......
......@@ -25,7 +25,10 @@ output in your scripts.
:param int pin: ID of the pin to be configured.
:param int mode: An integer with the bits for the wanted mode set. Create your
integer by ORing :py:data:`gpio.mode.OUTPUT`, :py:data:`gpio.mode.INPUT`,
:py:data:`gpio.mode.PULL_UP`, :py:data:`gpio.mode.PULL_DOWN`.
:py:data:`gpio.mode.ADC`, :py:data:`gpio.mode.PULL_UP`,
:py:data:`gpio.mode.PULL_DOWN`.
.. note:: On WRISTBAND_3, there is no ADC functionality available
.. py:function:: get_mode(pin)
......@@ -47,6 +50,9 @@ output in your scripts.
:param int pin: ID of the pin of to get the mode of.
:returns: Current value of the GPIO pin.
If the pin is configured as ADC, the value returned
will be between 0 and 1000, representing voltages from
0V to 3.3V (:py:data:`gpio.ADC` is only available in 1.9+).
.. py:data:: WRISTBAND_1
......@@ -75,6 +81,12 @@ output in your scripts.
Configures a pin as input.
.. py:data:: ADC
Configure pin as ADC input.
.. versionadded: 1.9
.. py:data:: PULL_UP
Enables the internal pull-up resistor of a pin.
......
......@@ -25,4 +25,6 @@ displaying menus. You can use it like this:
.. autoclass:: simple_menu.Menu
:members:
.. autodata:: simple_menu.TIMEOUT
.. autofunction:: simple_menu.button_events
This diff is collapsed.
......@@ -143,7 +143,8 @@ static const attsAttr_t fileTransCfgList[] = {
.pLen = NULL,
.maxLen = 128,
.settings = ATTS_SET_WRITE_CBACK | ATTS_SET_VARIABLE_LEN,
.permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_AUTH,
.permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC |
ATTS_PERMIT_WRITE_AUTH,
},
/* File transfer Central RX characteristic */
{
......@@ -161,7 +162,8 @@ static const attsAttr_t fileTransCfgList[] = {
.pLen = &attRxChConfigValue_len,
.maxLen = sizeof(attRxChConfigValue),
.settings = ATTS_SET_VARIABLE_LEN,
.permissions = ATTS_PERMIT_READ | ATTS_PERMIT_READ_AUTH,
.permissions = ATTS_PERMIT_READ | ATTS_PERMIT_READ_ENC |
ATTS_PERMIT_READ_AUTH,
},
/* File transfer Central RX notification channel */
{
......@@ -170,8 +172,9 @@ static const attsAttr_t fileTransCfgList[] = {
.pLen = &attRxChConfigValue_len,
.maxLen = sizeof(attRxChConfigValue),
.settings = ATTS_SET_CCC,
.permissions = ATTS_PERMIT_READ | ATTS_PERMIT_READ_AUTH |
ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_AUTH,
.permissions = ATTS_PERMIT_READ | ATTS_PERMIT_READ_ENC |
ATTS_PERMIT_READ_AUTH | ATTS_PERMIT_WRITE |
ATTS_PERMIT_WRITE_ENC | ATTS_PERMIT_WRITE_AUTH,
},
};
......
......@@ -31,66 +31,84 @@ enum { UART_SVC_HDL = UART_START_HDL, /*!< \brief UART service declaration */
/* clang-format off */
static const uint8_t UARTSvc[] = {0x9E,0xCA,0xDC,0x24,0x0E,0xE5,0xA9,0xE0,0x93,0xF3,0xA3,0xB5,0x01,0x00,0x40,0x6E};
static const uint16_t UARTSvc_len = sizeof(UARTSvc);
static const uint8_t uartRxCh[] = {ATT_PROP_WRITE, UINT16_TO_BYTES(UART_RX_HDL), 0x9E,0xCA,0xDC,0x24,0x0E,0xE5,0xA9,0xE0,0x93,0xF3,0xA3,0xB5,0x02,0x00,0x40,0x6E};
const uint8_t attUartRxChUuid[] = {0x9E,0xCA,0xDC,0x24,0x0E,0xE5, 0xA9,0xE0,0x93,0xF3,0xA3,0xB5,0x02,0x00,0x40,0x6E};
static const uint16_t uartRxCh_len = sizeof(uartRxCh);
static const uint8_t attUartRxChUuid[] = {0x9E,0xCA,0xDC,0x24,0x0E,0xE5, 0xA9,0xE0,0x93,0xF3,0xA3,0xB5,0x02,0x00,0x40,0x6E};
static const uint8_t uartTxCh[] = {ATT_PROP_READ | ATT_PROP_NOTIFY, UINT16_TO_BYTES(UART_TX_HDL), 0x9E,0xCA,0xDC,0x24,0x0E,0xE5,0xA9,0xE0,0x93,0xF3,0xA3,0xB5,0x03,0x00,0x40,0x6E};
const uint8_t attUartTxChUuid[] = {0x9E,0xCA,0xDC,0x24,0x0E,0xE5, 0xA9,0xE0,0x93,0xF3,0xA3,0xB5,0x03,0x00,0x40,0x6E};
/* clang-format on */
static const uint16_t uartTxCh_len = sizeof(uartTxCh);
static const uint8_t attUartTxChUuid[] = {0x9E,0xCA,0xDC,0x24,0x0E,0xE5, 0xA9,0xE0,0x93,0xF3,0xA3,0xB5,0x03,0x00,0x40,0x6E};
static void *SvcUARTAddGroupDyn(void)
{
void *pSHdl;
uint8_t initCcc[] = { UINT16_TO_BYTES(0x0000) };
/* Create the service */
pSHdl = AttsDynCreateGroup(UART_START_HDL, UART_END_HDL);
if (pSHdl != NULL) {
/* clang-format off */
/* Primary service */
AttsDynAddAttrConst( pSHdl, attPrimSvcUuid, UARTSvc, sizeof(UARTSvc),
0, ATTS_PERMIT_READ);
/* UART rx characteristic */
AttsDynAddAttrConst( pSHdl, attChUuid, uartRxCh, sizeof(uartRxCh),
0, ATTS_PERMIT_READ);
// XXX: attUartRxChUuid is 16 bytes but nothing says so....
/* UART rx value */
// XXX: not sure if max value of 128 is fine...
AttsDynAddAttr( pSHdl, attUartRxChUuid, NULL, 0, 128,
ATTS_SET_WRITE_CBACK | ATTS_SET_VARIABLE_LEN, ATTS_PERMIT_WRITE);
/* UART tx characteristic */
AttsDynAddAttrConst( pSHdl, attChUuid, uartTxCh, sizeof(uartTxCh),
0, ATTS_PERMIT_READ);
/* UART tx value */
/* TODO: do we need ATTS_SET_READ_CBACK ? */
AttsDynAddAttr( pSHdl, attUartTxChUuid, NULL, 0, sizeof(uint8_t),
ATTS_SET_READ_CBACK, ATTS_PERMIT_READ);
/* UART tx CCC descriptor */
AttsDynAddAttr( pSHdl, attCliChCfgUuid, initCcc, sizeof(uint16_t), sizeof(uint16_t),
ATTS_SET_CCC, ATTS_PERMIT_READ | ATTS_PERMIT_WRITE);
/* clang-format on */
}
static uint8_t ble_uart_tx_buf[128];
static uint16_t ble_uart_buf_tx_fill = 0;
/* clang-format on */
return pSHdl;
}
/* Attribute list for uriCfg group */
static const attsAttr_t uartAttrCfgList[] = {
/* Primary service */
{
.pUuid = attPrimSvcUuid,
.pValue = (uint8_t *)UARTSvc,
.pLen = (uint16_t *)&UARTSvc_len,
.maxLen = sizeof(UARTSvc),
.settings = 0,
.permissions = ATTS_PERMIT_READ,
},
/* UART rx characteristic */
{
.pUuid = attChUuid,
.pValue = (uint8_t *)uartRxCh,
.pLen = (uint16_t *)&uartRxCh_len,
.maxLen = sizeof(uartRxCh),
.settings = 0,
.permissions = ATTS_PERMIT_READ,
},
/* UART rx value */
{
.pUuid = attUartRxChUuid,
.pValue = NULL,
.pLen = NULL,
.maxLen = 128,
.settings = ATTS_SET_WRITE_CBACK | ATTS_SET_VARIABLE_LEN,
.permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC |
ATTS_PERMIT_WRITE_AUTH,
},
/* UART tx characteristic */
{
.pUuid = attChUuid,
.pValue = (uint8_t *)uartTxCh,
.pLen = (uint16_t *)&uartTxCh_len,
.maxLen = sizeof(uartTxCh),
.settings = 0,
.permissions = ATTS_PERMIT_READ,
},
/* UART tx value */
{
.pUuid = attUartTxChUuid,
.pValue = ble_uart_tx_buf,
.pLen = &ble_uart_buf_tx_fill,
.maxLen = sizeof(ble_uart_tx_buf),
.settings = 0,
.permissions = ATTS_PERMIT_READ | ATTS_PERMIT_READ_ENC |
ATTS_PERMIT_READ_AUTH,
},
/* UART tx CCC descriptor */
{
.pUuid = attCliChCfgUuid,
.pValue = NULL,
.pLen = NULL,
.maxLen = 0,
.settings = ATTS_SET_CCC,
.permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC |
ATTS_PERMIT_WRITE_AUTH | ATTS_PERMIT_READ |
ATTS_PERMIT_READ_ENC | ATTS_PERMIT_READ_AUTH,
},
};
dmConnId_t active_connection = 0;
static uint8_t UARTReadCback(
dmConnId_t connId,
uint16_t handle,
uint8_t operation,
uint16_t offset,
attsAttr_t *pAttr
) {
printf("read callback\n");
return ATT_SUCCESS;
}
static uint8_t UARTWriteCback(
dmConnId_t connId,
uint16_t handle,
......@@ -119,8 +137,6 @@ static uint8_t UARTWriteCback(
return ATT_SUCCESS;
}
static uint8_t ble_uart_tx_buf[128];
static uint8_t ble_uart_buf_tx_fill;
static int ble_uart_lasttick = 0;
void ble_uart_write(uint8_t *pValue, uint8_t len)
......@@ -134,11 +150,6 @@ void ble_uart_write(uint8_t *pValue, uint8_t len)
if (ble_uart_buf_tx_fill == 128 || pValue[i] == '\r' ||
pValue[i] == '\n') {
if (ble_uart_buf_tx_fill > 0) {
AttsSetAttr(
UART_TX_HDL,
ble_uart_buf_tx_fill,
ble_uart_tx_buf
);
if (active_connection) {
int x = xTaskGetTickCount() -
ble_uart_lasttick;
......@@ -165,11 +176,15 @@ void ble_uart_write(uint8_t *pValue, uint8_t len)
}
}
static attsGroup_t uartCfgGroup = {
.pAttr = (attsAttr_t *)uartAttrCfgList,
.writeCback = UARTWriteCback,
.startHandle = UART_START_HDL,
.endHandle = UART_END_HDL,
};
void bleuart_init(void)
{
/* Add the UART service dynamically */
void *pSHdl;
pSHdl = SvcUARTAddGroupDyn();
AttsDynRegister(pSHdl, UARTReadCback, UARTWriteCback);
//AttsDynRegister(pSHdl, NULL, UARTWriteCback);
/* Add the UART service */
AttsAddGroup(&uartCfgGroup);
}
This diff is collapsed.
#include "modules/modules.h"
#include "modules/log.h"
#include "modules/filesystem.h"
#include "modules/config.h"
#include "card10-version.h"
#include "FreeRTOS.h"
......@@ -20,14 +21,16 @@ int main(void)
LOG_DEBUG("startup", "Initializing hardware ...");
hardware_early_init();
load_config();
/*
* Version Splash
*/
const char *version_buf = CARD10_VERSION;
const int off = (160 - (int)strlen(version_buf) * 14) / 2;
epic_disp_clear(0x9dc0);
epic_disp_print(10, 20, "Epicardium", 0x6c20, 0x9dc0);
epic_disp_print(off > 0 ? off : 0, 40, version_buf, 0x6c20, 0x9dc0);
epic_disp_clear(0x0000);
epic_disp_print(10, 20, "Epicardium", 0xfe20, 0x0000);
epic_disp_print(off > 0 ? off : 0, 40, version_buf, 0xfe20, 0x0000);
epic_disp_update();
mxc_delay(2000000);
......
......@@ -70,7 +70,7 @@ subdir('ble/')
subdir('l0der/')
epicardium_cargs = []
epicardium_cargs = ['-D_POSIX_C_SOURCE=200809']
if get_option('jailbreak_card10')
epicardium_cargs += [
'-DJAILBREAK_CARD10=1',
......
#include "modules/log.h"
#include "modules/config.h"
#include "modules/filesystem.h"
#include <assert.h>
#include <stdbool.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define CONFIG_MAX_LINE_LENGTH 80
enum OptionType {
OptionType_Boolean,
OptionType_Int,
OptionType_Float,
OptionType_String,
};
struct config_option {
const char *name;
enum OptionType type;
union {
bool boolean;
long integer;
double floating_point;
char *string;
} value;
};
static struct config_option s_options[_EpicOptionCount] = {
/* clang-format off */
#define INIT_Boolean(v) { .boolean = (v) }
#define INIT_Int(v) { .integer = (v) }
#define INIT_Float(v) { .floating_point = (v) }
#define INIT_String(v) { .string = (v) }
#define INIT_(tp, v) INIT_ ## tp (v)
#define INIT(tp, v) INIT_ (tp, v)
#define CARD10_SETTING(identifier, spelling, tp, default_value) \
[Option ## identifier] = { .name = (spelling), \
.type = OptionType_ ## tp, \
.value = INIT(tp, (default_value)) },
#include "modules/config.def"
/* clang-format on */
};
static struct config_option *findOption(const char *key)
{
for (int i = 0; i < _EpicOptionCount; ++i) {
if (!strcmp(key, s_options[i].name)) {
return &s_options[i];
}
}
return NULL;
}
static bool set_bool(struct config_option *opt, const char *value)
{
bool val;
if (!strcmp(value, "1")) {
val = true;
} else if (!strcmp(value, "true")) {
val = true;
} else if (!strcmp(value, "0")) {
val = false;
} else if (!strcmp(value, "false")) {
val = false;
} else {
return false;
}
opt->value.boolean = val;
LOG_DEBUG(
"card10.cfg",
"setting '%s' to %s",
opt->name,
val ? "true" : "false"
);
return true;
}
static bool set_int(struct config_option *opt, const char *value)
{
char *endptr;
size_t len = strlen(value);
int v = strtol(value, &endptr, 0);
if (endptr != (value + len)) {
return false;
}
opt->value.integer = v;
LOG_DEBUG("card10.cfg", "setting '%s' to %d (0x%08x)", opt->name, v, v);
return true;
}
static bool set_float(struct config_option *opt, const char *value)
{
char *endptr;
size_t len = strlen(value);
double v = strtod(value, &endptr);
if (endptr != (value + len)) {
return false;
}
opt->value.floating_point = v;
LOG_DEBUG("card10.cfg", "setting '%s' to %f", opt->name, v);
return true;
}
const char *elide(const char *str)
{
static char ret[21];
size_t len = strlen(str);
if (len <= 20) {
return str;
}
strncpy(ret, str, 17);
ret[17] = '.';
ret[18] = '.';
ret[19] = '.';
ret[20] = '\0';
return ret;
}
static bool set_string(struct config_option *opt, const char *value)
{
//this leaks, but the lifetime of these ends when epicardium exits, so...
char *leaks = strdup(value);
opt->value.string = leaks;
LOG_DEBUG("card10.cfg", "setting '%s' to %s", opt->name, elide(leaks));
return true;
}
static void configure(const char *key, const char *value, int lineNumber)
{
struct config_option *opt = findOption(key);
if (!opt) {
//invalid key
LOG_WARN(
"card10.cfg",
"line %d: ignoring unknown option '%s'",
lineNumber,
key
);
return;
}
bool ok = false;
switch (opt->type) {
case OptionType_Boolean:
ok = set_bool(opt, value);
break;
case OptionType_Int:
ok = set_int(opt, value);
break;
case OptionType_Float:
ok = set_float(opt, value);
break;
case OptionType_String:
ok = set_string(opt, value);
break;
default:
assert(0 && "unreachable");
}
if (!ok) {
LOG_WARN(
"card10.cfg",
"line %d: ignoring invalid value '%s' for option '%s'",
lineNumber,
value,
key
);
}
}
static void doline(char *line, char *eol, int lineNumber)
{
//skip leading whitespace
while (*line && isspace(*line))
++line;
char *key = line;
if (*key == '#') {
//skip comments
return;
}
char *eq = strchr(line, '=');
if (!eq) {
if (*key) {
LOG_WARN(
"card10.cfg",
"line %d (%s): syntax error",
lineNumber,
elide(line)
);
}
return;
}
char *e_key = eq - 1;
//skip trailing whitespace in key
while (e_key > key && isspace(*e_key))
--e_key;
e_key[1] = '\0';
if (*key == '\0') {
LOG_WARN("card10.cfg", "line %d: empty key", lineNumber);
return;
}
char *value = eq + 1;
//skip leading whitespace
while (*value && isspace(*value))
++value;
char *e_val = eol - 1;
//skip trailing whitespace
while (e_val > value && isspace(*e_val))
--e_val;
if (*value == '\0') {
LOG_WARN(
"card10.cfg",
"line %d: empty value for option '%s'",
lineNumber,
key
);
return;
}
configure(key, value, lineNumber);
}
bool config_get_boolean(enum EpicConfigOption option)
{
struct config_option *opt = &s_options[option];
assert(opt->type == OptionType_Boolean);
return opt->value.boolean;
}
long config_get_integer(enum EpicConfigOption option)
{
struct config_option *opt = &s_options[option];
assert(opt->type == OptionType_Int);
return opt->value.integer;
}
double config_get_float(enum EpicConfigOption option)
{
struct config_option *opt = &s_options[option];
assert(opt->type == OptionType_Float);
return opt->value.floating_point;
}
const char *config_get_string(enum EpicConfigOption option)
{
struct config_option *opt = &s_options[option];
assert(opt->type == OptionType_String);
return opt->value.string;
}
void load_config(void)
{
LOG_DEBUG("card10.cfg", "loading...");
int fd = epic_file_open("card10.cfg", "r");
if (fd < 0) {
LOG_DEBUG(
"card10.cfg",
"loading failed: %s (%d)",
strerror(-fd),
fd
);
return;
}
char buf[CONFIG_MAX_LINE_LENGTH];
int lineNumber = 0;
int nread;
do {
//zero-terminate in case file is empty
buf[0] = '\0';
nread = epic_file_read(fd, buf, sizeof(buf));
if (nread < sizeof(buf)) {
//add fake EOL to ensure termination
buf[nread] = '\n';
}
char *line = buf;
char *eol = NULL;
int last_eol = 0;
while (line) {
//line points one character past the las (if any) '\n' hence '- 1'
last_eol = line - buf - 1;
eol = strchr(line, '\n');
++lineNumber;
if (eol) {
*eol = '\0';
doline(line, eol, lineNumber);
line = eol + 1;
} else {
if (line == buf) {
//line did not fit into buf
LOG_WARN(
"card10.cfg",
"line:%d: too long - aborting",
lineNumber
);
return;
} else {
int seek_back = last_eol - nread;
LOG_DEBUG(
"card10.cfg",
"nread, last_eol, seek_back: %d,%d,%d",
nread,
last_eol,
seek_back
);
assert(seek_back <= 0);
if (seek_back) {
int rc = epic_file_seek(
fd,
seek_back,
SEEK_CUR
);
if (rc < 0) {
LOG_ERR("card10.cfg",
"seek failed, aborting");
return;
}
char newline;
rc = epic_file_read(
fd, &newline, 1
);
if (rc < 0 || newline != '\n') {
LOG_ERR("card10.cfg",
"seek failed, aborting");
LOG_DEBUG(
"card10.cfg",
"seek failed at read-back of newline: rc: %d read: %d",
rc,
(int)newline
);
return;
}
}
break;
}
}
}
} while (nread == sizeof(buf));
}
#ifndef CARD10_SETTING
# define CARD10_SETTING(identifier, spelling, type, default_value)
#endif
CARD10_SETTING(ExecuteElf, "execute_elf", Boolean, false)
//CARD10_SETTING(Nick, "nick", String, "an0n")
//CARD10_SETTING(Timeout, "timeout", Integer, 123)
//CARD10_SETTING(Dampening, "dampening", Float, 420)
#undef CARD10_SETTING
#ifndef EPICARDIUM_MODULES_CONFIG_H_INCLUDED
#define EPICARDIUM_MODULES_CONFIG_H_INCLUDED
#include <stdbool.h>
enum EpicConfigOption {
#define CARD10_SETTING(identifier, spelling, type, default_value) Option ## identifier,
#include "modules/config.def"
_EpicOptionCount
};
//initialize configuration values and load card10.cfg
void load_config(void);
bool config_get_boolean(enum EpicConfigOption option);
long config_get_integer(enum EpicConfigOption option);
double config_get_float(enum EpicConfigOption option);
const char* config_get_string(enum EpicConfigOption option);
#endif//EPICARDIUM_MODULES_CONFIG_H_INCLUDED
#include "epicardium.h"
#include "gpio.h"
#include "max32665.h"
#include "mxc_sys.h"
#include "adc.h"
#include "mxc_errors.h"
#include "modules/log.h"
#include "modules/modules.h"
/*
* Despite what the schematic (currently, 2019-08-18) says these are the correct
......@@ -26,6 +30,17 @@ static gpio_cfg_t gpio_configs[] = {
GPIO_PAD_NONE },
};
static int s_adc_channels[] = {
[EPIC_GPIO_WRISTBAND_1] = ADC_CH_5,
[EPIC_GPIO_WRISTBAND_2] = ADC_CH_6,
/* on P0.29, there is no ADC available
* see GPIO matrix in MAX32665-MAX32668.pdf,
* pages 32,33
*/
[EPIC_GPIO_WRISTBAND_3] = -1,
[EPIC_GPIO_WRISTBAND_4] = ADC_CH_4,
};
int epic_gpio_set_pin_mode(uint8_t pin, uint8_t mode)
{
if (pin < EPIC_GPIO_WRISTBAND_1 || pin > EPIC_GPIO_WRISTBAND_4)
......@@ -43,14 +58,27 @@ int epic_gpio_set_pin_mode(uint8_t pin, uint8_t mode)
if (mode & EPIC_GPIO_MODE_IN) {
return -EINVAL;
}
} else if (mode & EPIC_GPIO_MODE_ADC) {
if (s_adc_channels[pin] == -1) {
LOG_WARN("gpio", "ADC not available on pin %d", pin);
return -EINVAL;
}
cfg->func = GPIO_FUNC_ALT1;
if (mode & EPIC_GPIO_MODE_OUT) {
return -EINVAL;
}
} else {
return -EINVAL;
}
if (mode & EPIC_GPIO_PULL_UP) {
cfg->pad = GPIO_PAD_PULL_UP;
} else if (mode & EPIC_GPIO_PULL_DOWN) {
cfg->pad = GPIO_PAD_PULL_DOWN;
if (!(mode & EPIC_GPIO_MODE_ADC)) {
if (mode & EPIC_GPIO_PULL_UP) {
cfg->pad = GPIO_PAD_PULL_UP;
} else if (mode & EPIC_GPIO_PULL_DOWN) {
cfg->pad = GPIO_PAD_PULL_DOWN;
} else {
cfg->pad = GPIO_PAD_NONE;
}
} else {
cfg->pad = GPIO_PAD_NONE;
}
......@@ -71,6 +99,8 @@ int epic_gpio_get_pin_mode(uint8_t pin)
res |= EPIC_GPIO_MODE_IN;
else if (cfg->func == GPIO_FUNC_OUT)
res |= EPIC_GPIO_MODE_OUT;
else if (cfg->func == GPIO_FUNC_ALT1)
res |= EPIC_GPIO_MODE_ADC;
if (cfg->pad == GPIO_PAD_PULL_UP)
res |= EPIC_GPIO_PULL_UP;
else if (cfg->pad == GPIO_PAD_PULL_DOWN)
......@@ -106,6 +136,19 @@ int epic_gpio_read_pin(uint8_t pin)
return GPIO_OutGet(cfg) != 0;
} else if (cfg->func == GPIO_FUNC_IN) {
return GPIO_InGet(cfg) != 0;
} else if (cfg->func == GPIO_FUNC_ALT1) {
int rc = hwlock_acquire(HWLOCK_ADC, pdMS_TO_TICKS(10));
if (!rc) {
ADC_StartConvert(s_adc_channels[pin], 0, 0);
uint16_t value;
int rc = ADC_GetData(&value);
hwlock_release(HWLOCK_ADC);
if (rc < 0) {
return -EIO;
}
return (int)value;
}
return rc;
} else {
return -EINVAL;
}
......
......@@ -254,6 +254,12 @@ int hardware_reset(void)
* Display
*/
display_init_slim();
epic_disp_backlight(20);
/*
* Vibration Motor
*/
epic_vibra_set(false);
/*
* BHI160
......
......@@ -49,6 +49,17 @@ void epic_leds_prep(int led, uint8_t r, uint8_t g, uint8_t b)
leds_prep(led, r, g, b);
}
int epic_leds_get_rgb(int led, uint8_t *rgb)
{
if (led == PERSONAL_STATE_LED && personal_state_enabled())
return -EPERM;
if (led < 0 || led >= NUM_LEDS)
return -EINVAL;
leds_get_rgb(led, rgb);
return 0;
}
void epic_leds_prep_hsv(int led, float h, float s, float v)
{
if (led == PERSONAL_STATE_LED && personal_state_enabled())
......
#include "epicardium.h"
#include "modules/log.h"
#include "modules/modules.h"
#include "modules/config.h"
#include "api/dispatcher.h"
#include "api/interrupt-sender.h"
#include "l0der/l0der.h"
......@@ -49,6 +50,7 @@ static volatile struct load_info async_load = {
/* Whether to write the menu script before attempting to load. */
static volatile bool write_menu = false;
static bool execute_elfs = false;
/* Helpers {{{ */
......@@ -88,9 +90,7 @@ static int load_stat(char *name)
*/
static int do_load(struct load_info *info)
{
#if defined(JAILBREAK_CARD10) && (JAILBREAK_CARD10 == 1)
struct l0dable_info l0dable;
#endif
int res;
if (*info->name == '\0') {
......@@ -129,18 +129,22 @@ static int do_load(struct load_info *info)
case PL_PYTHON_INTERP:
core1_load(PYCARDIUM_IVT, info->name);
break;
#if defined(JAILBREAK_CARD10) && (JAILBREAK_CARD10 == 1)
case PL_L0DABLE:
res = l0der_load_path(info->name, &l0dable);
if (res != 0) {
LOG_ERR("lifecycle", "l0der failed: %d\n", res);
xSemaphoreGive(api_mutex);
return -ENOEXEC;
if (execute_elfs) {
res = l0der_load_path(info->name, &l0dable);
if (res != 0) {
LOG_ERR("lifecycle", "l0der failed: %d\n", res);
xSemaphoreGive(api_mutex);
return -ENOEXEC;
}
core1_load(l0dable.isr_vector, "");
} else {
LOG_WARN(
"lifecycle",
"Execution of .elf l0dables is disabled"
);
}
core1_load(l0dable.isr_vector, "");
break;
#endif
default:
LOG_ERR("lifecyle",
"Attempted to load invalid payload (%s)",
......@@ -379,6 +383,8 @@ void vLifecycleTask(void *pvParameters)
hardware_init();
execute_elfs = config_get_boolean(OptionExecuteElf);
/* When triggered, reset core 1 to menu */
while (1) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
......
......@@ -21,5 +21,6 @@ module_sources = files(
'trng.c',
'vibra.c',
'watchdog.c',
'usb.c'
'usb.c',
'config.c',
)
......@@ -196,7 +196,7 @@ static struct config_descriptor_cdcacm config_descriptor_cdcacm = {
.bNumEndpoints = 0x01,
.bInterfaceClass = CLS_COMM,
.bInterfaceSubClass = SCLS_ACM,
.bInterfaceProtocol = PROT_AT_CMDS,
.bInterfaceProtocol = 0x00,
.iInterface = 0x00,
},
.header_functional = {
......@@ -319,4 +319,4 @@ static struct config_descriptor_msc config_descriptor_msc =
.bInterval = 0x00
}
};
/* clang-format on */
\ No newline at end of file
/* clang-format on */
......@@ -206,6 +206,13 @@ void leds_prep(uint8_t led, uint8_t r, uint8_t g, uint8_t b)
leds[led][2] = b;
}
void leds_get_rgb(uint8_t led, uint8_t *rgb)
{
rgb[0] = leds[led][0];
rgb[1] = leds[led][1];
rgb[2] = leds[led][2];
}
#if 0
//don't use, is buggy
void leds_set_autodim(uint8_t led, uint8_t r, uint8_t g, uint8_t b)
......
......@@ -6,6 +6,7 @@
void leds_set_dim_top(uint8_t value);
void leds_set_dim_bottom(uint8_t value);
void leds_prep(uint8_t led, uint8_t r, uint8_t g, uint8_t b);
void leds_get_rgb(uint8_t led, uint8_t * rgb);
void leds_prep_hsv(uint8_t led, float h, float s, float v);
void leds_update_power(void);
void leds_update(void);
......
......@@ -174,8 +174,8 @@ void gfx_rectangle(
void gfx_rectangle_fill(
struct gfx_region *reg, int x, int y, int w, int h, Color c
) {
for (int y_ = y; y_ < y + h; y_++) {
for (int x_ = x; x_ < x + w; x_++)
for (int y_ = y; y_ <= y + h; y_++) {
for (int x_ = x; x_ <= x + w; x_++)
gfx_setpixel(reg, x_, y_, c);
}
}
......
......@@ -80,17 +80,17 @@ def get_bat_color(bat):
Voltage threshold's are currently estimates as voltage isn't that great of an indicator for
battery charge.
:param bat: battery config tuple (boolean: indicator on/off, array: good rgb, array: ok rgb, array: bad rgb)
:return: false if old firmware, RGB color array otherwise
:return: battery status tuple (float: battery voltage, false if old firmware, RGB color array otherwise)
"""
try:
v = os.read_battery()
if v > 3.8:
return bat[1]
return (v, bat[1])
if v > 3.6:
return bat[2]
return bat[3]
return (v, bat[2])
return (v, bat[3])
except AttributeError:
return False
return (0, False)
def render_battery(disp, bat):
......@@ -100,10 +100,15 @@ def render_battery(disp, bat):
:param disp: open display
:param bat: battery config tuple (boolean: indicator on/off, array: good rgb, array: ok rgb, array: bad rgb)
"""
c = get_bat_color(bat)
v, c = get_bat_color(bat)
if not c:
return
disp.rect(140, 2, 155, 9, filled=True, col=c)
if v > 4.0:
disp.rect(140, 2, 155, 9, filled=True, col=c)
else:
disp.rect(140, 2, 154, 8, filled=False, col=c)
if v > 3.5:
disp.rect(141, 3, 142 + int((v - 3.5) * 24), 8, filled=True, col=c)
disp.rect(155, 4, 157, 7, filled=True, col=c)
......@@ -344,5 +349,5 @@ else:
([0, 0, 0], [0, 0, 0]),
([0, 0, 0], [0, 0, 0]),
0,
(0, [255, 0, 255], [255, 0, 255], [255, 0, 255]),
(True, [0, 230, 0], [255, 215, 0], [255, 0, 0]),
)
......@@ -41,16 +41,83 @@ leds.dim_top(1)
COLORS = [((23 + (15 * i)) % 360, 1.0, 1.0) for i in range(11)]
# variables for high-pass filter
moving_average = 0
alpha = 2
beta = 3
def update_history(datasets):
global history, moving_average, alpha, beta
for val in datasets:
history.append(val - moving_average)
moving_average = (alpha * moving_average + beta * val) / (alpha + beta)
# trim old elements
history = history[-HISTORY_MAX:]
# variables for pulse detection
pulse = -1
samples_since_last_pulse = 0
q_threshold = -1
r_threshold = 1
q_spike = -ECG_RATE
def neighbours(n, lst):
"""
neighbours(2, "ABCDE") = ("AB", "BC", "CD", "DE")
neighbours(3, "ABCDE") = ("ABC", "BCD", "CDE")
"""
for i in range(len(lst) - (n - 1)):
yield lst[i : i + n]
def detect_pulse(num_new_samples):
global history, pulse, samples_since_last_pulse, q_threshold, r_threshold, q_spike
# look at 3 consecutive samples, starting 2 samples before the samples that were just added, e.g.:
# existing samples: "ABCD"
# new samples: "EF" => "ABCDEF"
# consider ["CDE", "DEF"]
# new samples: "GHI" => "ABCDEFGHI"
# consider ["EFG", "FGH", "GHI"]
for [prev, cur, next_] in neighbours(3, history[-(num_new_samples + 2) :]):
samples_since_last_pulse += 1
if prev > cur < next_ and cur < q_threshold:
q_spike = samples_since_last_pulse
# we expect the next q-spike to be at least 60% as high as this one
q_threshold = (cur * 3) // 5
elif (
prev < cur > next_
and cur > r_threshold
and samples_since_last_pulse - q_spike < ECG_RATE // 10
):
# the full QRS complex is < 0.1s long, so the q and r spike in particular cannot be more than ECG_RATE//10 samples apart
pulse = 60 * ECG_RATE // samples_since_last_pulse
samples_since_last_pulse = 0
q_spike = -ECG_RATE
if pulse < 30 or pulse > 210:
pulse = -1
# we expect the next r-spike to be at least 60% as high as this one
r_threshold = (cur * 3) // 5
elif samples_since_last_pulse > 2 * ECG_RATE:
q_threshold = -1
r_threshold = 1
pulse = -1
def callback_ecg(datasets):
global update_screen, history, filebuffer, write
update_screen += len(datasets)
# update histogram datalist
if not pause_histogram:
history += datasets
# trim old elements
history = history[-HISTORY_MAX:]
update_history(datasets)
detect_pulse(len(datasets))
# buffer for writes
if write > 0:
......@@ -207,12 +274,24 @@ def draw_histogram():
)
else:
draw_leds((max(history[-5:]) * scale + SCALE_FACTOR) * 11 / (SCALE_FACTOR * 2))
disp.print(
current_mode + ("+Bias" if bias else ""),
posx=0,
posy=0,
fg=(COLOR_MODE_FINGER if current_mode == MODE_FINGER else COLOR_MODE_USB),
)
if pulse < 0:
disp.print(
current_mode + ("+Bias" if bias else ""),
posx=0,
posy=0,
fg=(
COLOR_MODE_FINGER if current_mode == MODE_FINGER else COLOR_MODE_USB
),
)
else:
disp.print(
"BPM: {}".format(pulse),
posx=0,
posy=0,
fg=(
COLOR_MODE_FINGER if current_mode == MODE_FINGER else COLOR_MODE_USB
),
)
# announce writing ecg log
if write > 0:
......
"""
Personal State Script
===========
With this script you can
=====================
"""
import buttons
import color
import display
import os
import personal_state
import simple_menu
states = [
("No State", personal_state.NO_STATE),
......@@ -18,75 +16,35 @@ states = [
]
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)
class StateMenu(simple_menu.Menu):
color_sel = color.WHITE
if v == 0:
button_pressed = False
def on_scroll(self, item, index):
personal_state.set(item[1], False)
if not button_pressed and v & buttons.BOTTOM_LEFT != 0:
button_pressed = True
yield buttons.BOTTOM_LEFT
def on_select(self, item, index):
personal_state.set(item[1], True)
os.exit()
if not button_pressed and v & buttons.BOTTOM_RIGHT != 0:
button_pressed = True
yield buttons.BOTTOM_RIGHT
def draw_entry(self, item, index, offset):
if item[1] == personal_state.NO_CONTACT:
bg = color.RED
fg = color.WHITE
elif item[1] == personal_state.CHAOS:
bg = color.CHAOSBLUE
fg = color.CHAOSBLUE_DARK
elif item[1] == personal_state.COMMUNICATION:
bg = color.COMMYELLOW
fg = color.COMMYELLOW_DARK
elif item[1] == personal_state.CAMP:
bg = color.CAMPGREEN
fg = color.CAMPGREEN_DARK
else:
bg = color.Color(100, 100, 100)
fg = color.Color(200, 200, 200)
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)
self.disp.print(" " + str(item[0]) + " " * 9, posy=offset, fg=fg, bg=bg)
if __name__ == "__main__":
main()
StateMenu(states).run()
This diff is collapsed.
......@@ -54,6 +54,7 @@ static const mp_rom_map_elem_t gpio_module_modes_table[] = {
{ MP_ROM_QSTR(MP_QSTR_INPUT), MP_OBJ_NEW_SMALL_INT(EPIC_GPIO_MODE_IN) },
{ MP_ROM_QSTR(MP_QSTR_OUTPUT),
MP_OBJ_NEW_SMALL_INT(EPIC_GPIO_MODE_OUT) },
{ MP_ROM_QSTR(MP_QSTR_ADC), MP_OBJ_NEW_SMALL_INT(EPIC_GPIO_MODE_ADC) },
{ MP_ROM_QSTR(MP_QSTR_PULL_UP),
MP_OBJ_NEW_SMALL_INT(EPIC_GPIO_PULL_UP) },
{ MP_ROM_QSTR(MP_QSTR_PULL_DOWN),
......
......@@ -70,13 +70,13 @@ class Display:
def print(self, text, *, fg=None, bg=None, posx=0, posy=0, font=FONT20):
"""
Prints a string on the display. Font size is locked to 20px
Prints a string on the display.
:param text: Text to print
:param fg: Foreground color (expects RGB triple)
:param bg: Background color (expects RGB triple)
:param posx: X-Position of the first character, 0 <= posx <= 160
:param posy: Y-Position of the first character, 0 <= posy <= 80
:param posx: X-Position of the first character, 0 <= posx <= 159
:param posy: Y-Position of the first character, 0 <= posy <= 79
:param font: 0 <= font <= 4 (currently) selects a font
Avaiable Fonts:
......@@ -106,8 +106,8 @@ class Display:
"""
Draws a pixel on the display
:param x: X coordinate, 0<= x <= 160
:param y: Y coordinate, 0<= y <= 80
:param x: X coordinate, 0<= x <= 159
:param y: Y coordinate, 0<= y <= 79
:param col: color of the pixel (expects RGB tripple)
"""
......@@ -130,10 +130,10 @@ class Display:
"""
Draws a line on the display.
:param xs: X start coordinate, 0 <= xs <= 160
:param ys: Y start coordinate, 0 <= ys <= 80
:param xe: X end coordinate, 0 <= xe <= 160
:param ye: Y end coordinate, 0 <= ye <= 80
:param xs: X start coordinate, 0 <= xs <= 159
:param ys: Y start coordinate, 0 <= ys <= 79
:param xe: X end coordinate, 0 <= xe <= 159
:param ye: Y end coordinate, 0 <= ye <= 79
:param col: color of the line (expects RGB triple)
:param dotted: whether the line should be dotted or not
(questionable implementation: draws every other pixel white, draws
......@@ -150,10 +150,10 @@ class Display:
"""
Draws a rectangle on the display.
:param xs: X start coordinate, 0 <= xs <= 160
:param ys: Y start coordinate, 0 <= ys <= 80
:param xe: X end coordinate, 0 <= xe <= 160
:param ye: Y end coordinate, 0 <= ye <= 80
:param xs: X start coordinate, 0 <= xs <= 159
:param ys: Y start coordinate, 0 <= ys <= 79
:param xe: X end coordinate, 0 <= xe <= 159
:param ye: Y end coordinate, 0 <= ye <= 79
:param col: color of the outline and fill (expects RGB triple)
:param filled: whether the rectangle should be filled or not
:param size: size of the individual pixels, ranges from 1 to 8
......@@ -168,8 +168,8 @@ class Display:
"""
Draws a circle on the display.
:param x: center x coordinate, 0 <= x <= 160
:param y: center y coordinate, 0 <= y <= 80
:param x: center x coordinate, 0 <= x <= 159
:param y: center y coordinate, 0 <= y <= 79