diff --git a/CHANGELOG.md b/CHANGELOG.md index e580443a064b511fba178ad0f53a2330ced62723..78dfc0332b2e17a5281d7f17759dd1754d468fdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Documentation/bluetooth/card10.rst b/Documentation/bluetooth/card10.rst index a88115119cbb7fb255434c504f1e403914efdca5..16bf38e18c5beabcd78523b2b7626b6d232739ca 100644 --- a/Documentation/bluetooth/card10.rst +++ b/Documentation/bluetooth/card10.rst @@ -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`` diff --git a/Documentation/pycardium/gpio.rst b/Documentation/pycardium/gpio.rst index 51c7d5f90f270aedf583d0bd347c85323683626d..28854d141843ba5d84016b2361aa12052fae18d8 100644 --- a/Documentation/pycardium/gpio.rst +++ b/Documentation/pycardium/gpio.rst @@ -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. diff --git a/Documentation/pycardium/simple_menu.rst b/Documentation/pycardium/simple_menu.rst index 36758c643e7356e44c4343260c2aa529f651e9f1..58236e2c4691d91d5a0fe36e9a4862f50aeff748 100644 --- a/Documentation/pycardium/simple_menu.rst +++ b/Documentation/pycardium/simple_menu.rst @@ -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 diff --git a/epicardium/ble/card10.c b/epicardium/ble/card10.c index 2105ac02ca03e168fe8711f8c2797799e6801070..ebea5b3a09a339548ab59f3f080d3605b6ce9223 100644 --- a/epicardium/ble/card10.c +++ b/epicardium/ble/card10.c @@ -125,7 +125,7 @@ static const uint8_t UUID_attChar_rockets[] = { /* BLE UUID for card10 led background bottom left */ static const uint8_t UUID_char_led_bg_bottom_left[] = { - ATT_PROP_WRITE_NO_RSP, + ATT_PROP_READ | ATT_PROP_WRITE_NO_RSP, UINT16_TO_BYTES(CARD10_LED_BG_BOTTOM_LEFT_VAL_HDL), CARD10_UUID_SUFFIX, 0x11, CARD10_UUID_PREFIX }; @@ -134,9 +134,13 @@ static const uint8_t UUID_attChar_led_bg_bottom_left[] = { CARD10_UUID_SUFFIX, 0x11, CARD10_UUID_PREFIX }; +static uint8_t ledBGBottomLeftValue[] = { 0,0,0 }; +// works vor everyone? +static uint16_t rgbLen = sizeof(ledBGBottomLeftValue); + /* BLE UUID for card10 led background bottom right */ static const uint8_t UUID_char_led_bg_bottom_right[] = { - ATT_PROP_WRITE_NO_RSP, + ATT_PROP_READ | ATT_PROP_WRITE_NO_RSP, UINT16_TO_BYTES(CARD10_LED_BG_BOTTOM_RIGHT_VAL_HDL), CARD10_UUID_SUFFIX, 0x12, CARD10_UUID_PREFIX }; @@ -145,9 +149,11 @@ static const uint8_t UUID_attChar_led_bg_bottom_right[] = { CARD10_UUID_SUFFIX, 0x12, CARD10_UUID_PREFIX }; +static uint8_t ledBGBottomRightValue[] = { 0,0,0 }; + /* BLE UUID for card10 led background top right */ static const uint8_t UUID_char_led_bg_top_right[] = { - ATT_PROP_WRITE_NO_RSP, + ATT_PROP_READ | ATT_PROP_WRITE_NO_RSP, UINT16_TO_BYTES(CARD10_LED_BG_TOP_RIGHT_VAL_HDL), CARD10_UUID_SUFFIX, 0x13, CARD10_UUID_PREFIX }; @@ -156,9 +162,11 @@ static const uint8_t UUID_attChar_led_bg_top_right[] = { CARD10_UUID_SUFFIX, 0x13, CARD10_UUID_PREFIX }; +static uint8_t ledBGTopRightValue[] = { 0,0,0 }; + /* BLE UUID for card10 led background top left */ static const uint8_t UUID_char_led_bg_top_left[] = { - ATT_PROP_WRITE_NO_RSP, + ATT_PROP_READ | ATT_PROP_WRITE_NO_RSP, UINT16_TO_BYTES(CARD10_LED_BG_TOP_LEFT_VAL_HDL), CARD10_UUID_SUFFIX, 0x14, CARD10_UUID_PREFIX }; @@ -167,6 +175,8 @@ static const uint8_t UUID_attChar_led_bg_top_left[] = { CARD10_UUID_SUFFIX, 0x14, CARD10_UUID_PREFIX }; +static uint8_t ledBGTopLeftValue[] = { 0,0,0 }; + /* BLE UUID for card10 dim leds on bottom */ static const uint8_t UUID_char_leds_bottom_dim[] = { ATT_PROP_WRITE, @@ -227,7 +237,7 @@ static uint16_t personalStateLen = sizeof(personalStateValue); /* BLE UUID for card10 above leds */ static const uint8_t UUID_char_leds_above[] = { - ATT_PROP_WRITE_NO_RSP, + ATT_PROP_READ | ATT_PROP_WRITE_NO_RSP, UINT16_TO_BYTES(CARD10_LEDS_ABOVE_VAL_HDL), CARD10_UUID_SUFFIX, 0x20, CARD10_UUID_PREFIX }; @@ -235,6 +245,21 @@ static const uint8_t UUID_char_leds_above[] = { static const uint8_t UUID_attChar_leds_above[] = { CARD10_UUID_SUFFIX, 0x20, CARD10_UUID_PREFIX }; +static uint8_t aboveLEDsValue[] = { + 0,0,0, // 0 + 0,0,0, // 1 + 0,0,0, // 2 + 0,0,0, // 3 + 0,0,0, // 4 + 0,0,0, // 5 + 0,0,0, // 6 + 0,0,0, // 7 + 0,0,0, // 8 + 0,0,0, // 9 + 0,0,0, // 10 +}; +static uint16_t aboveLEDsLen = sizeof(aboveLEDsValue); + // starting at 0xf0 with read only characteristics /* BLE UUID for card10 char light sensor */ @@ -256,228 +281,281 @@ static uint16_t initLightSensorLen = sizeof(initLightSensorValue); */ static const attsAttr_t card10SvcAttrList[] = { - { .pUuid = attPrimSvcUuid, - .pValue = (uint8_t *)UUID_svc, - .pLen = (uint16_t *)&UUID_len, - .maxLen = sizeof(UUID_svc), - .permissions = ATTS_PERMIT_READ }, + { + .pUuid = attPrimSvcUuid, + .pValue = (uint8_t *)UUID_svc, + .pLen = (uint16_t *)&UUID_len, + .maxLen = sizeof(UUID_svc), + .permissions = ATTS_PERMIT_READ, + }, // TIME - { .pUuid = attChUuid, - .pValue = (uint8_t *)UUID_char_time, - .pLen = (uint16_t *)&UUID_char_len, - .maxLen = sizeof(UUID_char_time), - .permissions = ATTS_PERMIT_READ }, - { .pUuid = UUID_attChar_time, - .pValue = timeValue, - .pLen = &timeLen, - .maxLen = sizeof(uint64_t), - .settings = (ATTS_SET_WRITE_CBACK | ATTS_SET_READ_CBACK), - .permissions = - (ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | - ATTS_PERMIT_WRITE_AUTH | ATTS_PERMIT_READ | - ATTS_PERMIT_READ_ENC | ATTS_PERMIT_READ_AUTH) }, + { + .pUuid = attChUuid, + .pValue = (uint8_t *)UUID_char_time, + .pLen = (uint16_t *)&UUID_char_len, + .maxLen = sizeof(UUID_char_time), + .permissions = ATTS_PERMIT_READ, + }, + { + .pUuid = UUID_attChar_time, + .pValue = timeValue, + .pLen = &timeLen, + .maxLen = sizeof(uint64_t), + .settings = ATTS_SET_WRITE_CBACK | ATTS_SET_READ_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_AUTH | ATTS_PERMIT_READ | + ATTS_PERMIT_READ_ENC | ATTS_PERMIT_READ_AUTH, + }, // VIBRA - { .pUuid = attChUuid, - .pValue = (uint8_t *)UUID_char_vibra, - .pLen = (uint16_t *)&UUID_char_len, - .maxLen = sizeof(UUID_char_vibra), - .permissions = ATTS_PERMIT_READ }, - { .pUuid = UUID_attChar_vibra, - .pValue = NULL, - .maxLen = sizeof(uint16_t), - .settings = ATTS_SET_WRITE_CBACK, - .permissions = - (ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | - ATTS_PERMIT_WRITE_AUTH) }, + { + .pUuid = attChUuid, + .pValue = (uint8_t *)UUID_char_vibra, + .pLen = (uint16_t *)&UUID_char_len, + .maxLen = sizeof(UUID_char_vibra), + .permissions = ATTS_PERMIT_READ, + }, + { + .pUuid = UUID_attChar_vibra, + .pValue = NULL, + .maxLen = sizeof(uint16_t), + .settings = ATTS_SET_WRITE_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_AUTH, + }, // ROCKETS - { .pUuid = attChUuid, - .pValue = (uint8_t *)UUID_char_rockets, - .pLen = (uint16_t *)&UUID_char_len, - .maxLen = sizeof(UUID_char_rockets), - .permissions = ATTS_PERMIT_READ }, - { .pUuid = UUID_attChar_rockets, - .pValue = NULL, - .maxLen = 3 * sizeof(uint8_t), - .settings = ATTS_SET_WRITE_CBACK, - .permissions = - (ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | - ATTS_PERMIT_WRITE_AUTH) }, + { + .pUuid = attChUuid, + .pValue = (uint8_t *)UUID_char_rockets, + .pLen = (uint16_t *)&UUID_char_len, + .maxLen = sizeof(UUID_char_rockets), + .permissions = ATTS_PERMIT_READ, + }, + { + .pUuid = UUID_attChar_rockets, + .pValue = NULL, + .maxLen = 3 * sizeof(uint8_t), + .settings = ATTS_SET_WRITE_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_AUTH, + }, // BG LED Bottom left - { .pUuid = attChUuid, - .pValue = (uint8_t *)UUID_char_led_bg_bottom_left, - .pLen = (uint16_t *)&UUID_char_len, - .maxLen = sizeof(UUID_char_led_bg_bottom_left), - .permissions = ATTS_PERMIT_READ }, - { .pUuid = UUID_attChar_led_bg_bottom_left, - .pValue = NULL, - .maxLen = 3 * sizeof(uint8_t), - .settings = ATTS_SET_WRITE_CBACK, - .permissions = - (ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | - ATTS_PERMIT_WRITE_AUTH) }, + { + .pUuid = attChUuid, + .pValue = (uint8_t *)UUID_char_led_bg_bottom_left, + .pLen = (uint16_t *)&UUID_char_len, + .maxLen = sizeof(UUID_char_led_bg_bottom_left), + .permissions = ATTS_PERMIT_READ, + }, + { + .pUuid = UUID_attChar_led_bg_bottom_left, + .pValue = ledBGBottomLeftValue, + .pLen = &rgbLen, + .maxLen = 3 * sizeof(uint8_t), + .settings = ATTS_SET_WRITE_CBACK | ATTS_SET_READ_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_AUTH | ATTS_PERMIT_READ | + ATTS_PERMIT_READ_ENC | ATTS_PERMIT_READ_AUTH, + }, // BG LED Bottom right - { .pUuid = attChUuid, - .pValue = (uint8_t *)UUID_char_led_bg_bottom_right, - .pLen = (uint16_t *)&UUID_char_len, - .maxLen = sizeof(UUID_char_led_bg_bottom_right), - .permissions = ATTS_PERMIT_READ }, - { .pUuid = UUID_attChar_led_bg_bottom_right, - .pValue = NULL, - .maxLen = 3 * sizeof(uint8_t), - .settings = ATTS_SET_WRITE_CBACK, - .permissions = - (ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | - ATTS_PERMIT_WRITE_AUTH) }, + { + .pUuid = attChUuid, + .pValue = (uint8_t *)UUID_char_led_bg_bottom_right, + .pLen = (uint16_t *)&UUID_char_len, + .maxLen = sizeof(UUID_char_led_bg_bottom_right), + .permissions = ATTS_PERMIT_READ, + }, + { + .pUuid = UUID_attChar_led_bg_bottom_right, + .pValue = ledBGBottomRightValue, + .pLen = &rgbLen, + .maxLen = 3 * sizeof(uint8_t), + .settings = ATTS_SET_WRITE_CBACK | ATTS_SET_READ_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_AUTH | ATTS_PERMIT_READ | + ATTS_PERMIT_READ_ENC | ATTS_PERMIT_READ_AUTH, + }, // BG LED top right - - { .pUuid = attChUuid, - .pValue = (uint8_t *)UUID_char_led_bg_top_right, - .pLen = (uint16_t *)&UUID_char_len, - .maxLen = sizeof(UUID_char_led_bg_top_right), - .settings = 0, - .permissions = ATTS_PERMIT_READ }, - { .pUuid = UUID_attChar_led_bg_top_right, - .pValue = NULL, - .maxLen = 3 * sizeof(uint8_t), - .settings = ATTS_SET_WRITE_CBACK, - .permissions = - (ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | - ATTS_PERMIT_WRITE_AUTH) }, + { + .pUuid = attChUuid, + .pValue = (uint8_t *)UUID_char_led_bg_top_right, + .pLen = (uint16_t *)&UUID_char_len, + .maxLen = sizeof(UUID_char_led_bg_top_right), + .settings = 0, + .permissions = ATTS_PERMIT_READ, + }, + { + .pUuid = UUID_attChar_led_bg_top_right, + .pValue = ledBGTopRightValue, + .pLen = &rgbLen, + .maxLen = 3 * sizeof(uint8_t), + .settings = ATTS_SET_WRITE_CBACK | ATTS_SET_READ_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_AUTH | ATTS_PERMIT_READ | + ATTS_PERMIT_READ_ENC | ATTS_PERMIT_READ_AUTH, + }, // BG LED top left - { .pUuid = attChUuid, - .pValue = (uint8_t *)UUID_char_led_bg_top_left, - .pLen = (uint16_t *)&UUID_char_len, - .maxLen = sizeof(UUID_char_led_bg_top_left), - .permissions = ATTS_PERMIT_READ }, - { .pUuid = UUID_attChar_led_bg_top_left, - .pValue = NULL, - .maxLen = 3 * sizeof(uint8_t), - .settings = ATTS_SET_WRITE_CBACK, - .permissions = - (ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | - ATTS_PERMIT_WRITE_AUTH) }, + { + .pUuid = attChUuid, + .pValue = (uint8_t *)UUID_char_led_bg_top_left, + .pLen = (uint16_t *)&UUID_char_len, + .maxLen = sizeof(UUID_char_led_bg_top_left), + .permissions = ATTS_PERMIT_READ, + }, + { + .pUuid = UUID_attChar_led_bg_top_left, + .pValue = ledBGTopLeftValue, + .pLen = &rgbLen, + .maxLen = 3 * sizeof(uint8_t), + .settings = ATTS_SET_WRITE_CBACK | ATTS_SET_READ_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_AUTH | ATTS_PERMIT_READ | + ATTS_PERMIT_READ_ENC | ATTS_PERMIT_READ_AUTH, + }, // Dim bottom module - { .pUuid = attChUuid, - .pValue = (uint8_t *)UUID_char_leds_bottom_dim, - .pLen = (uint16_t *)&UUID_char_len, - .maxLen = sizeof(UUID_char_leds_bottom_dim), - .permissions = ATTS_PERMIT_READ }, - { .pUuid = UUID_attChar_leds_bottom_dim, - .pValue = NULL, - .pLen = 0, - .maxLen = sizeof(uint8_t), - .settings = ATTS_SET_WRITE_CBACK, - .permissions = - (ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | - ATTS_PERMIT_WRITE_AUTH) }, + { + .pUuid = attChUuid, + .pValue = (uint8_t *)UUID_char_leds_bottom_dim, + .pLen = (uint16_t *)&UUID_char_len, + .maxLen = sizeof(UUID_char_leds_bottom_dim), + .permissions = ATTS_PERMIT_READ, + }, + { + .pUuid = UUID_attChar_leds_bottom_dim, + .pValue = NULL, + .pLen = 0, + .maxLen = sizeof(uint8_t), + .settings = ATTS_SET_WRITE_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_AUTH, + }, // Dim top module - { .pUuid = attChUuid, - .pValue = (uint8_t *)UUID_char_leds_top_dim, - .pLen = (uint16_t *)&UUID_char_len, - .maxLen = sizeof(UUID_char_leds_top_dim), - .permissions = ATTS_PERMIT_READ }, - { .pUuid = UUID_attChar_leds_top_dim, - .pValue = NULL, - .maxLen = sizeof(uint8_t), - .settings = ATTS_SET_WRITE_CBACK, - .permissions = - (ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | - ATTS_PERMIT_WRITE_AUTH) }, + { + .pUuid = attChUuid, + .pValue = (uint8_t *)UUID_char_leds_top_dim, + .pLen = (uint16_t *)&UUID_char_len, + .maxLen = sizeof(UUID_char_leds_top_dim), + .permissions = ATTS_PERMIT_READ, + }, + { + .pUuid = UUID_attChar_leds_top_dim, + .pValue = NULL, + .maxLen = sizeof(uint8_t), + .settings = ATTS_SET_WRITE_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_AUTH, + }, // led powersafe - { .pUuid = attChUuid, - .pValue = (uint8_t *)UUID_char_led_powersafe, - .pLen = (uint16_t *)&UUID_char_len, - .maxLen = sizeof(UUID_char_led_powersafe), - .permissions = ATTS_PERMIT_READ }, - { .pUuid = UUID_attChar_led_powersafe, - .pValue = NULL, - .maxLen = sizeof(uint8_t), - .settings = ATTS_SET_WRITE_CBACK, - .permissions = - (ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | - ATTS_PERMIT_WRITE_AUTH) }, + { + .pUuid = attChUuid, + .pValue = (uint8_t *)UUID_char_led_powersafe, + .pLen = (uint16_t *)&UUID_char_len, + .maxLen = sizeof(UUID_char_led_powersafe), + .permissions = ATTS_PERMIT_READ, + }, + { + .pUuid = UUID_attChar_led_powersafe, + .pValue = NULL, + .maxLen = sizeof(uint8_t), + .settings = ATTS_SET_WRITE_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_AUTH, + }, // flashlight - { .pUuid = attChUuid, - .pValue = (uint8_t *)UUID_char_flashlight, - .pLen = (uint16_t *)&UUID_char_len, - .maxLen = sizeof(UUID_char_flashlight), - .permissions = ATTS_PERMIT_READ }, - { .pUuid = UUID_attChar_flashlight, - .pValue = NULL, - .maxLen = sizeof(uint8_t), - .settings = ATTS_SET_WRITE_CBACK, - .permissions = - (ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | - ATTS_PERMIT_WRITE_AUTH) }, + { + .pUuid = attChUuid, + .pValue = (uint8_t *)UUID_char_flashlight, + .pLen = (uint16_t *)&UUID_char_len, + .maxLen = sizeof(UUID_char_flashlight), + .permissions = ATTS_PERMIT_READ, + }, + { + .pUuid = UUID_attChar_flashlight, + .pValue = NULL, + .maxLen = sizeof(uint8_t), + .settings = ATTS_SET_WRITE_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_AUTH, + }, // personal state - { .pUuid = attChUuid, - .pValue = (uint8_t *)UUID_char_personal_state, - .pLen = (uint16_t *)&UUID_char_len, - .maxLen = sizeof(UUID_char_personal_state), - .permissions = ATTS_PERMIT_READ }, - { .pUuid = UUID_attChar_personal_state, - .pValue = &personalStateValue, - .pLen = &personalStateLen, - .maxLen = sizeof(uint16_t), - .settings = (ATTS_SET_WRITE_CBACK | ATTS_SET_READ_CBACK), - .permissions = - (ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | - ATTS_PERMIT_WRITE_AUTH | ATTS_PERMIT_READ | - ATTS_PERMIT_READ_ENC | ATTS_PERMIT_READ_AUTH) }, + { + .pUuid = attChUuid, + .pValue = (uint8_t *)UUID_char_personal_state, + .pLen = (uint16_t *)&UUID_char_len, + .maxLen = sizeof(UUID_char_personal_state), + .permissions = ATTS_PERMIT_READ, + }, + { + .pUuid = UUID_attChar_personal_state, + .pValue = &personalStateValue, + .pLen = &personalStateLen, + .maxLen = sizeof(uint16_t), + .settings = ATTS_SET_WRITE_CBACK | ATTS_SET_READ_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_AUTH | ATTS_PERMIT_READ | + ATTS_PERMIT_READ_ENC | ATTS_PERMIT_READ_AUTH, + }, // ABOVE LEDS - { .pUuid = attChUuid, - .pValue = (uint8_t *)UUID_char_leds_above, - .pLen = (uint16_t *)&UUID_char_len, - .maxLen = sizeof(UUID_char_leds_above), - .permissions = ATTS_PERMIT_READ }, - { .pUuid = UUID_attChar_leds_above, - .pValue = NULL, - .maxLen = 11 * 3 * sizeof(uint8_t), - .settings = ATTS_SET_WRITE_CBACK, - .permissions = - (ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | - ATTS_PERMIT_WRITE_AUTH) }, + { + .pUuid = attChUuid, + .pValue = (uint8_t *)UUID_char_leds_above, + .pLen = (uint16_t *)&UUID_char_len, + .maxLen = sizeof(UUID_char_leds_above), + .permissions = ATTS_PERMIT_READ, + }, + { + .pUuid = UUID_attChar_leds_above, + .pValue = aboveLEDsValue, + .pLen = &aboveLEDsLen, + .maxLen = 11 * 3 * sizeof(uint8_t), + .settings = ATTS_SET_WRITE_CBACK | ATTS_SET_READ_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_AUTH | ATTS_PERMIT_READ | + ATTS_PERMIT_READ_ENC | ATTS_PERMIT_READ_AUTH, + }, // Light sensor - { .pUuid = attChUuid, - .pValue = (uint8_t *)UUID_char_light_sensor, - .pLen = (uint16_t *)&UUID_char_len, - .maxLen = sizeof(UUID_char_light_sensor), - .permissions = ATTS_PERMIT_READ }, - { .pUuid = UUID_attChar_light_sensor, - .pValue = initLightSensorValue, - .pLen = &initLightSensorLen, - .maxLen = sizeof(uint8_t), - .settings = ATTS_SET_READ_CBACK, - .permissions = - (ATTS_PERMIT_READ | ATTS_PERMIT_READ_ENC | - ATTS_PERMIT_READ_AUTH) }, + { + .pUuid = attChUuid, + .pValue = (uint8_t *)UUID_char_light_sensor, + .pLen = (uint16_t *)&UUID_char_len, + .maxLen = sizeof(UUID_char_light_sensor), + .permissions = ATTS_PERMIT_READ, + }, + { + .pUuid = UUID_attChar_light_sensor, + .pValue = initLightSensorValue, + .pLen = &initLightSensorLen, + .maxLen = sizeof(uint8_t), + .settings = ATTS_SET_READ_CBACK, + .permissions = ATTS_PERMIT_READ | ATTS_PERMIT_READ_ENC | + ATTS_PERMIT_READ_AUTH, + }, }; // validating, that the service really get all charateristics @@ -500,6 +578,38 @@ static uint8_t setTime(uint8_t *pValue) return ATT_SUCCESS; } +/* + * Set a rgb led + */ +static uint8_t setRGBLed(uint8_t led, uint8_t *pValue) +{ + epic_leds_set(led, pValue[0], pValue[1], pValue[2]); + APP_TRACE_INFO4( + "ble-card10: set rgb led %d: #%02x%02x%02x\n", + led, + pValue[0], + pValue[1], + pValue[2] + ); + return ATT_SUCCESS; +} + +/* + * Get value of a rgb led + */ +static uint8_t getRGBLed(uint8_t led, attsAttr_t *pAttr) +{ + epic_leds_get_rgb(led, pAttr->pValue); + APP_TRACE_INFO4( + "ble-card10: set rgb led %d: #%02x%02x%02x\n", + led, + pAttr->pValue[0], + pAttr->pValue[1], + pAttr->pValue[2] + ); + return ATT_SUCCESS; +} + /* * BLE card10 write callback. */ @@ -540,41 +650,13 @@ static uint8_t writeCard10CB( return ATT_SUCCESS; // bg leds case CARD10_LED_BG_BOTTOM_LEFT_VAL_HDL: - epic_leds_set(11, pValue[0], pValue[1], pValue[2]); - APP_TRACE_INFO3( - "ble-card10: set bg bottom left: #%02x%02x%02x\n", - pValue[0], - pValue[1], - pValue[2] - ); - return ATT_SUCCESS; + return setRGBLed(11, pValue); case CARD10_LED_BG_BOTTOM_RIGHT_VAL_HDL: - epic_leds_set(12, pValue[0], pValue[1], pValue[2]); - APP_TRACE_INFO3( - "ble-card10: set bg bottom right: #%02x%02x%02x\n", - pValue[0], - pValue[1], - pValue[2] - ); - return ATT_SUCCESS; + return setRGBLed(12, pValue); case CARD10_LED_BG_TOP_RIGHT_VAL_HDL: - epic_leds_set(13, pValue[0], pValue[1], pValue[2]); - APP_TRACE_INFO3( - "ble-card10: set bg top right: #%02x%02x%02x\n", - pValue[0], - pValue[1], - pValue[2] - ); - return ATT_SUCCESS; + return setRGBLed(13, pValue); case CARD10_LED_BG_TOP_LEFT_VAL_HDL: - epic_leds_set(14, pValue[0], pValue[1], pValue[2]); - APP_TRACE_INFO3( - "ble-card10: set bg top left: #%02x%02x%02x\n", - pValue[0], - pValue[1], - pValue[2] - ); - return ATT_SUCCESS; + return setRGBLed(14, pValue); // dim case CARD10_LEDS_BOTTOM_DIM_VAL_HDL: ui8 = pValue[0]; @@ -725,8 +807,10 @@ static uint8_t readCard10CB( ) { uint16_t ui16 = 0; uint64_t ui64 = 0; + uint8_t rgb[] = { 0, 0, 0 }; switch (handle) { + // time case CARD10_TIME_VAL_HDL: ui64 = epic_rtc_get_milliseconds(); uint64_t time; @@ -736,11 +820,38 @@ static uint8_t readCard10CB( APP_TRACE_INFO0("ble-card10: read time\n"); return ATT_SUCCESS; + // background leds + case CARD10_LED_BG_BOTTOM_LEFT_VAL_HDL: + return getRGBLed(11, pAttr); + case CARD10_LED_BG_BOTTOM_RIGHT_VAL_HDL: + return getRGBLed(12, pAttr); + case CARD10_LED_BG_TOP_RIGHT_VAL_HDL: + return getRGBLed(13, pAttr); + case CARD10_LED_BG_TOP_LEFT_VAL_HDL: + return getRGBLed(14, pAttr); + // personal state case CARD10_PERSONAL_STATE_VAL_HDL: ui16 = epic_personal_state_get(); *pAttr->pValue = ui16; APP_TRACE_INFO1("ble-card10: read personal state: %d\n", ui16); return ATT_SUCCESS; + // leds above + case CARD10_LEDS_ABOVE_VAL_HDL: + for (ui16 = 0; ui16 < 11; ui16++) { + epic_leds_get_rgb(ui16, rgb); + pAttr->pValue[ui16 * 3] = rgb[0]; + pAttr->pValue[ui16 * 3 + 1] = rgb[1]; + pAttr->pValue[ui16 * 3 + 2] = rgb[2]; + APP_TRACE_INFO4( + "ble-card10: get led %ld above to #%02x%02x%02x\n", + ui16, + pAttr->pValue[ui16 * 3], + pAttr->pValue[ui16 * 3 + 1], + pAttr->pValue[ui16 * 3 + 2] + ); + } + return ATT_SUCCESS; + // light sensor case CARD10_LIGHT_SENSOR_VAL_HDL: epic_light_sensor_get(&ui16); *pAttr->pValue = ui16; diff --git a/epicardium/ble/filetransfer.c b/epicardium/ble/filetransfer.c index fa2cff4d3e8f884a6f382f469fab13c7126045cb..88b31308bf19cf9c02f45615a34b58fa19d5300f 100644 --- a/epicardium/ble/filetransfer.c +++ b/epicardium/ble/filetransfer.c @@ -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, }, }; diff --git a/epicardium/ble/uart.c b/epicardium/ble/uart.c index 584ae0ea211cc6728963bb6d3669344307ad527f..5fdfb74ac9885975e125f51216bd2953986eca9c 100644 --- a/epicardium/ble/uart.c +++ b/epicardium/ble/uart.c @@ -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); } diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 615440aff0caa2e42ce377c20ddcb55c0f812f08..0b440189db0de69265428055ae0fc5bee070ca1a 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -96,6 +96,7 @@ typedef _Bool bool; #define API_LEDS_SET_ALL_HSV 0x6b #define API_LEDS_SET_GAMMA_TABLE 0x6c #define API_LEDS_CLEAR_ALL 0x6d +#define API_LEDS_GET 0x6f #define API_VIBRA_SET 0x70 #define API_VIBRA_VIBRATE 0x71 @@ -150,6 +151,13 @@ typedef uint32_t api_int_id_t; * the other direction. These interrupts can be enabled/disabled * (masked/unmasked) using :c:func:`epic_interrupt_enable` and * :c:func:`epic_interrupt_disable`. + * + * .. warning:: + * + * Never attempt to call the API from inside an ISR. This might trigger an + * assertion if a call is already being made from thread context. We plan to + * lift this restriction at some point, but for the time being, this is how + * it is. */ /** @@ -177,17 +185,16 @@ API(API_INTERRUPT_DISABLE, int epic_interrupt_disable(api_int_id_t int_id)); #define EPIC_INT_CTRL_C 1 /** UART Receive interrupt. See :c:func:`epic_isr_uart_rx`. */ #define EPIC_INT_UART_RX 2 -/** RTC Alarm interrupt. See :c:func:`epic_isr_rtc_alarm` */ +/** RTC Alarm interrupt. See :c:func:`epic_isr_rtc_alarm`. */ #define EPIC_INT_RTC_ALARM 3 -/** BHI */ +/** BHI180 Accelerometer. See :c:func:`epic_isr_bhi160_accelerometer`. */ #define EPIC_INT_BHI160_ACCELEROMETER 4 -API_ISR(EPIC_INT_BHI160_ACCELEROMETER, epic_isr_bhi160_accelerometer); +/** BHI180 Orientation Sensor. See :c:func:`epic_isr_bhi160_orientation`. */ #define EPIC_INT_BHI160_ORIENTATION 5 -API_ISR(EPIC_INT_BHI160_ORIENTATION, epic_isr_bhi160_orientation); +/** BHI180 Gyroscope. See :c:func:`epic_isr_bhi160_gyroscope`. */ #define EPIC_INT_BHI160_GYROSCOPE 6 -API_ISR(EPIC_INT_BHI160_GYROSCOPE, epic_isr_bhi160_gyroscope); +/** MAX30001 ECG. See :c:func:`epic_isr_max30001_ecg`. */ #define EPIC_INT_MAX30001_ECG 7 -API_ISR(EPIC_INT_MAX30001_ECG, epic_isr_max30001_ecg); /* Number of defined interrupts. */ #define EPIC_INT_NUM 8 @@ -305,8 +312,7 @@ API(API_THERMISTOR_VOLTAGE, int epic_read_thermistor_voltage(float *result)); * :param length: Amount of bytes to print. */ API(API_UART_WRITE_STR, void epic_uart_write_str( - const char *str, - intptr_t length + const char *str, intptr_t length )); /** @@ -332,7 +338,7 @@ API(API_UART_READ_CHAR, int epic_uart_read_char(void)); API(API_UART_READ_STR, int epic_uart_read_str(char *buf, size_t cnt)); /** - * **Interrupt Service Routine** + * **Interrupt Service Routine** for :c:data:`EPIC_INT_UART_RX` * * UART receive interrupt. This interrupt is triggered whenever a new character * becomes available on any connected UART device. This function is weakly @@ -362,7 +368,7 @@ API(API_UART_READ_STR, int epic_uart_read_str(char *buf, size_t cnt)); API_ISR(EPIC_INT_UART_RX, epic_isr_uart_rx); /** - * **Interrupt Service Routine** + * **Interrupt Service Routine** for :c:data:`EPIC_INT_CTRL_C` * * A user-defineable ISR which is triggered when a ``^C`` (``0x04``) is received * on any serial input device. This function is weakly aliased to @@ -461,6 +467,7 @@ enum gpio_mode { EPIC_GPIO_MODE_IN = (1<<0), /** Configure the pin as output */ EPIC_GPIO_MODE_OUT = (1<<1), + EPIC_GPIO_MODE_ADC = (1<<2), /** Enable the internal pull-up resistor */ EPIC_GPIO_PULL_UP = (1<<6), @@ -489,7 +496,9 @@ enum gpio_mode { * :param uint8_t mode: Mode to be configured. Use a combination of the :c:type:`gpio_mode` flags. * :returns: ``0`` if the mode was set, ``-EINVAL`` if ``pin`` is not valid or the mode could not be set. */ -API(API_GPIO_SET_PIN_MODE, int epic_gpio_set_pin_mode(uint8_t pin, uint8_t mode)); +API(API_GPIO_SET_PIN_MODE, int epic_gpio_set_pin_mode( + uint8_t pin, uint8_t mode +)); /** * Get the mode of a card10 GPIO pin. @@ -584,6 +593,19 @@ API(API_GPIO_READ_PIN, int epic_gpio_read_pin(uint8_t pin)); */ API(API_LEDS_SET, void epic_leds_set(int led, uint8_t r, uint8_t g, uint8_t b)); + +/** + * Get one of card10's RGB LEDs in format of RGB. + * + * :c:func:`epic_leds_get_rgb` will get the value of a RGB LED described by ``led``. + * + * :param int led: Which LED to get. 0-10 are the LEDs on the top and 11-14 + * are the 4 "ambient" LEDs. + * :param uint8_t * rgb: need tree byte array to get the value of red, green and blue. + * :returns: ``0`` on success or ``-EPERM`` if the LED is blocked by personal-state. + */ +API(API_LEDS_GET, int epic_leds_get_rgb(int led, uint8_t * rgb)); + /** * Set one of card10's RGB LEDs to a certain color in HSV format. * @@ -596,7 +618,9 @@ API(API_LEDS_SET, void epic_leds_set(int led, uint8_t r, uint8_t g, uint8_t b)); * :param float s: Saturation component of the color. (0 <= s <= 1) * :param float v: Value/Brightness component of the color. (0 <= v <= 0) */ -API(API_LEDS_SET_HSV, void epic_leds_set_hsv(int led, float h, float s, float v)); +API(API_LEDS_SET_HSV, void epic_leds_set_hsv( + int led, float h, float s, float v +)); /** * Set multiple of card10's RGB LEDs to a certain color in RGB format. @@ -619,7 +643,9 @@ API(API_LEDS_SET_ALL, void epic_leds_set_all(uint8_t *pattern, uint8_t len)); * LEDs. (0 <= h < 360, 0 <= s <= 1, 0 <= v <= 1) * :param uint8_t len: Length of 1st dimension of ``pattern``, see above. */ -API(API_LEDS_SET_ALL_HSV, void epic_leds_set_all_hsv(float *pattern, uint8_t len)); +API(API_LEDS_SET_ALL_HSV, void epic_leds_set_all_hsv( + float *pattern, uint8_t len +)); /** * Prepare one of card10's RGB LEDs to be set to a certain color in RGB format. @@ -632,7 +658,9 @@ API(API_LEDS_SET_ALL_HSV, void epic_leds_set_all_hsv(float *pattern, uint8_t len * :param uint8_t g: Green component of the color. * :param uint8_t b: Blue component of the color. */ -API(API_LEDS_PREP, void epic_leds_prep(int led, uint8_t r, uint8_t g, uint8_t b)); +API(API_LEDS_PREP, void epic_leds_prep( + int led, uint8_t r, uint8_t g, uint8_t b +)); /** * Prepare one of card10's RGB LEDs to be set to a certain color in HSV format. @@ -645,7 +673,9 @@ API(API_LEDS_PREP, void epic_leds_prep(int led, uint8_t r, uint8_t g, uint8_t b) * :param uint8_t s: Saturation component of the color. (float, 0 <= s <= 1) * :param uint8_t v: Value/Brightness component of the color. (float, 0 <= v <= 0) */ -API(API_LEDS_PREP_HSV, void epic_leds_prep_hsv(int led, float h, float s, float v)); +API(API_LEDS_PREP_HSV, void epic_leds_prep_hsv( + int led, float h, float s, float v +)); /** * Set global brightness for top RGB LEDs. @@ -728,8 +758,7 @@ API(API_LEDS_SET_FLASHLIGHT, void epic_set_flashlight(bool power)); * :param uint8_t[256] gamma_table: Gamma lookup table. (default = 4th order power function rounded up) */ API(API_LEDS_SET_GAMMA_TABLE, void epic_leds_set_gamma_table( - uint8_t rgb_channel, - uint8_t *gamma_table + uint8_t rgb_channel, uint8_t *gamma_table )); /** @@ -739,7 +768,9 @@ API(API_LEDS_SET_GAMMA_TABLE, void epic_leds_set_gamma_table( * :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)); +API(API_LEDS_CLEAR_ALL, void epic_leds_clear_all( + uint8_t r, uint8_t g, uint8_t b +)); /** * BME680 @@ -806,8 +837,9 @@ API(API_BME680_DEINIT, int epic_bme680_deinit()); * - ``-EIO``: Communication with the device failed. * - ``-ENODEV``: Device was not found. */ -API(API_BME680_GET_DATA, - int epic_bme680_read_sensors(struct bme680_sensor_data *data)); +API(API_BME680_GET_DATA, int epic_bme680_read_sensors( + struct bme680_sensor_data *data +)); /** * Personal State @@ -850,8 +882,9 @@ enum 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)); +API(API_PERSONAL_STATE_SET, int epic_personal_state_set( + uint8_t state, bool persistent +)); /** * Get the users personal state. @@ -1108,6 +1141,36 @@ API(API_BHI160_DISABLE, int epic_bhi160_disable_sensor( */ API(API_BHI160_DISABLE_ALL, void epic_bhi160_disable_all_sensors()); +/** + * BHI160 Interrupt Handlers + * ------------------------- + */ + +/** + * **Interrupt Service Routine** for :c:data:`EPIC_INT_BHI160_ACCELEROMETER` + * + * :c:func:`epic_isr_bhi160_accelerometer` is called whenever the BHI160 + * accelerometer has new data available. + */ +API_ISR(EPIC_INT_BHI160_ACCELEROMETER, epic_isr_bhi160_accelerometer); + +/** + * **Interrupt Service Routine** for :c:data:`EPIC_INT_BHI160_ORIENTATION` + * + * :c:func:`epic_isr_bhi160_orientation` is called whenever the BHI160 + * orientation sensor has new data available. + */ +API_ISR(EPIC_INT_BHI160_ORIENTATION, epic_isr_bhi160_orientation); + +/** + * **Interrupt Service Routine** for :c:data:`EPIC_INT_BHI160_GYROSCOPE` + * + * :c:func:`epic_isr_bhi160_orientation` is called whenever the BHI160 + * gyroscrope has new data available. + */ +API_ISR(EPIC_INT_BHI160_GYROSCOPE, epic_isr_bhi160_gyroscope); + + /** * Vibration Motor * =============== @@ -1246,7 +1309,7 @@ API(API_DISP_PRINT, * Font Selection */ enum disp_font_name { - DISP_FONT8 = 0, + DISP_FONT8 = 0, DISP_FONT12 = 1, DISP_FONT16 = 2, DISP_FONT20 = 3, @@ -1266,15 +1329,14 @@ enum disp_font_name { * * - ``-EBUSY``: Display was already locked from another task. */ -API(API_DISP_PRINT_ADV, - int epic_disp_print_adv( - uint8_t font, - uint16_t posx, - uint16_t posy, - const char *pString, - uint16_t fg, - uint16_t bg) -); +API(API_DISP_PRINT_ADV, int epic_disp_print_adv( + uint8_t font, + uint16_t posx, + uint16_t posy, + const char *pString, + uint16_t fg, + uint16_t bg +)); /** * Fills the whole screen with one color @@ -1296,12 +1358,9 @@ API(API_DISP_CLEAR, int epic_disp_clear(uint16_t color)); * * - ``-EBUSY``: Display was already locked from another task. */ -API(API_DISP_PIXEL, - int epic_disp_pixel( - uint16_t x, - uint16_t y, - uint16_t color) - ); +API(API_DISP_PIXEL, int epic_disp_pixel( + uint16_t x, uint16_t y, uint16_t color +)); /** * Draws a line on the display @@ -1317,16 +1376,15 @@ API(API_DISP_PIXEL, * * - ``-EBUSY``: Display was already locked from another task. */ -API(API_DISP_LINE, - int epic_disp_line( - uint16_t xstart, - uint16_t ystart, - uint16_t xend, - uint16_t yend, - uint16_t color, - enum disp_linestyle linestyle, - uint16_t pixelsize) - ); +API(API_DISP_LINE, int epic_disp_line( + uint16_t xstart, + uint16_t ystart, + uint16_t xend, + uint16_t yend, + uint16_t color, + enum disp_linestyle linestyle, + uint16_t pixelsize +)); /** * Draws a rectangle on the display @@ -1342,16 +1400,15 @@ API(API_DISP_LINE, * * - ``-EBUSY``: Display was already locked from another task. */ -API(API_DISP_RECT, - int epic_disp_rect( - uint16_t xstart, - uint16_t ystart, - uint16_t xend, - uint16_t yend, - uint16_t color, - enum disp_fillstyle fillstyle, - uint16_t pixelsize) - ); +API(API_DISP_RECT, int epic_disp_rect( + uint16_t xstart, + uint16_t ystart, + uint16_t xend, + uint16_t yend, + uint16_t color, + enum disp_fillstyle fillstyle, + uint16_t pixelsize +)); /** * Draws a circle on the display @@ -1366,15 +1423,14 @@ API(API_DISP_RECT, * * - ``-EBUSY``: Display was already locked from another task. */ -API(API_DISP_CIRC, - int epic_disp_circ( - uint16_t x, - uint16_t y, - uint16_t rad, - uint16_t color, - enum disp_fillstyle fillstyle, - uint16_t pixelsize) - ); +API(API_DISP_CIRC, int epic_disp_circ( + uint16_t x, + uint16_t y, + uint16_t rad, + uint16_t color, + enum disp_fillstyle fillstyle, + uint16_t pixelsize +)); /** * Immediately send the contents of a framebuffer to the display. This overrides @@ -1385,7 +1441,9 @@ API(API_DISP_CIRC, * * - ``-EBUSY``: Display was already locked from another task. */ -API(API_DISP_FRAMEBUFFER, int epic_disp_framebuffer(union disp_framebuffer *fb)); +API(API_DISP_FRAMEBUFFER, int epic_disp_framebuffer( + union disp_framebuffer *fb +)); /** @@ -1486,10 +1544,9 @@ API(API_FILE_READ, int epic_file_read(int fd, void* buf, size_t nbytes)); * :return: ``< 0`` on error, ``nbytes`` on success. (Partial writes don't occur on success!) * */ -API( - API_FILE_WRITE, - int epic_file_write(int fd, const void* buf, size_t nbytes) -); +API(API_FILE_WRITE, int epic_file_write( + int fd, const void* buf, size_t nbytes +)); /** */ API(API_FILE_FLUSH, int epic_file_flush(int fd)); @@ -1655,7 +1712,9 @@ API(API_RTC_GET_MILLISECONDS, uint64_t epic_rtc_get_milliseconds(void)); /** * Sets the current RTC time in milliseconds */ -API(API_RTC_SET_MILLISECONDS, void epic_rtc_set_milliseconds(uint64_t milliseconds)); +API(API_RTC_SET_MILLISECONDS, void epic_rtc_set_milliseconds( + uint64_t milliseconds +)); /** * Schedule the RTC alarm for the given timestamp. @@ -1669,7 +1728,7 @@ API(API_RTC_SET_MILLISECONDS, void epic_rtc_set_milliseconds(uint64_t millisecon API(API_RTC_SCHEDULE_ALARM, int epic_rtc_schedule_alarm(uint32_t timestamp)); /** - * **Interrupt Service Routine** + * **Interrupt Service Routine** for :c:data:`EPIC_INT_RTC_ALARM` * * ``epic_isr_rtc_alarm()`` is called when the RTC alarm triggers. The RTC alarm * can be scheduled using :c:func:`epic_rtc_schedule_alarm`. @@ -1721,8 +1780,8 @@ struct max30001_sensor_config { bool usb; /** - * Set to true if the interal lead bias of the MAX30001 is to be used. - */ + * Set to true if the interal lead bias of the MAX30001 is to be used. + */ bool bias; /** Always zero. Reserved for future parameters. */ @@ -1730,9 +1789,10 @@ struct max30001_sensor_config { }; /** - * Enable a MAX30001 ecg sensor. Calling this funciton will instruct the - * MAX30001 to collect data for this sensor. You can then - * retrieve the samples using :c:func:`epic_stream_read`. + * Enable a MAX30001 ecg sensor. + * + * Calling this funciton will instruct the MAX30001 to collect data for this + * sensor. You can then retrieve the samples using :c:func:`epic_stream_read`. * * :param max30001_sensor_config* config: Configuration for this sensor. * :returns: A sensor descriptor which can be used with @@ -1752,23 +1812,36 @@ API(API_MAX30001_ENABLE, int epic_max30001_enable_sensor( * * .. versionadded:: 1.6 */ -API(API_MAX30001_DISABLE, int epic_max30001_disable_sensor( -void -)); +API(API_MAX30001_DISABLE, int epic_max30001_disable_sensor()); + +/** + * **Interrupt Service Routine** for :c:data:`EPIC_INT_MAX30001_ECG` + * + * This interrupt handler is called whenever the MAX30001 ECG has new data + * available. + */ +API_ISR(EPIC_INT_MAX30001_ECG, epic_isr_max30001_ecg); + +/** + * USB + * === + */ /** * De-initialize the currently configured USB device (if any) * */ API(API_USB_SHUTDOWN, int epic_usb_shutdown(void)); + /** * Configure the USB peripheral to export the internal FLASH - * as a Mass Storage device + * as a Mass Storage device. */ API(API_USB_STORAGE, int epic_usb_storage(void)); + /** * Configure the USB peripheral to provide card10's stdin/stdout - * on a USB CDC-ACM device + * on a USB CDC-ACM device. */ API(API_USB_CDCACM, int epic_usb_cdcacm(void)); diff --git a/epicardium/main.c b/epicardium/main.c index 56639ea56ead01fc06138e0f2f712518193c475f..51edbe93d6f1abe06bf5a15fbb01bf48f99ff383 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -1,6 +1,7 @@ #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); diff --git a/epicardium/meson.build b/epicardium/meson.build index cf67023b9f7143336875d973108d8ee0a080ffd1..a6a6c05261c16ced283c4d8591b335237f6acd37 100644 --- a/epicardium/meson.build +++ b/epicardium/meson.build @@ -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', diff --git a/epicardium/modules/config.c b/epicardium/modules/config.c new file mode 100644 index 0000000000000000000000000000000000000000..47396e2195b8b262f41279dbdad4e588430efdef --- /dev/null +++ b/epicardium/modules/config.c @@ -0,0 +1,347 @@ +#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)); +} diff --git a/epicardium/modules/config.def b/epicardium/modules/config.def new file mode 100644 index 0000000000000000000000000000000000000000..455aaedd8d61f821c1d08ee99bb45c3a61d983ba --- /dev/null +++ b/epicardium/modules/config.def @@ -0,0 +1,11 @@ +#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 diff --git a/epicardium/modules/config.h b/epicardium/modules/config.h new file mode 100644 index 0000000000000000000000000000000000000000..b72b08a5205bf9344bc3e3d805423dc826f30ccc --- /dev/null +++ b/epicardium/modules/config.h @@ -0,0 +1,21 @@ +#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 diff --git a/epicardium/modules/gpio.c b/epicardium/modules/gpio.c index 4011e9ec34812b6454c7bc97052854020720598b..58bf4e29e735a669308f5362903852a9b3bdb9ca 100644 --- a/epicardium/modules/gpio.c +++ b/epicardium/modules/gpio.c @@ -1,14 +1,17 @@ #include "epicardium.h" #include "gpio.h" #include "max32665.h" +#include "mxc_sys.h" +#include "adc.h" #include "mxc_errors.h" -#include "modules/gpio_common.h" +#include "modules/log.h" +#include "modules/modules.h" /* * Despite what the schematic (currently, 2019-08-18) says these are the correct * pins for wristband GPIO 1-4 (not 0-3 as the schematic states) */ -gpio_cfg_t gpio_configs[] = { +extern gpio_cfg_t gpio_configs[] = { [EPIC_GPIO_WRISTBAND_1] = { PORT_0, PIN_21, GPIO_FUNC_OUT, @@ -27,6 +30,17 @@ 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) @@ -44,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; } @@ -72,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) @@ -107,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; } diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c index 17c707217283c41f61bfad79b9b6fb69d7b20d11..370f9afbe33f00046b9d55084bb934d068cef573 100644 --- a/epicardium/modules/hardware.c +++ b/epicardium/modules/hardware.c @@ -254,6 +254,12 @@ int hardware_reset(void) * Display */ display_init_slim(); + epic_disp_backlight(20); + + /* + * Vibration Motor + */ + epic_vibra_set(false); /* * BHI160 diff --git a/epicardium/modules/leds.c b/epicardium/modules/leds.c index 93b80a993717adaa0d058da554e3d7f6433e2dc5..aab84c86e2d80dddf7617cb1e667068421bc5753 100644 --- a/epicardium/modules/leds.c +++ b/epicardium/modules/leds.c @@ -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()) diff --git a/epicardium/modules/lifecycle.c b/epicardium/modules/lifecycle.c index 375cfef61dd9972fbb418f72659a1e30e69ef615..650664d2e97ee15ecbd35199e176ded925a78bfb 100644 --- a/epicardium/modules/lifecycle.c +++ b/epicardium/modules/lifecycle.c @@ -1,6 +1,7 @@ #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); diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build index f2ffa57893dc9e7f3bb8404d3afc16f7e154942d..e943af4fedd6f9996ccb66d098388c17b9549297 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -22,5 +22,6 @@ module_sources = files( 'vibra.c', 'watchdog.c', 'usb.c', + 'config.c', 'ws2812.c' ) diff --git a/epicardium/modules/usb.c b/epicardium/modules/usb.c index e14032e4e3dccb91342630317786d8c35d363b3a..572b2b5fcffa8fd2684c28b60c4d9f2937fdd3f9 100644 --- a/epicardium/modules/usb.c +++ b/epicardium/modules/usb.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 */ diff --git a/lib/card10/leds.c b/lib/card10/leds.c index 3f284db4ab1d1dae0f561510e1671b93b83bcafe..d21049cfd6cf1337ee9eccee9627da6487fb91e2 100644 --- a/lib/card10/leds.c +++ b/lib/card10/leds.c @@ -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) diff --git a/lib/card10/leds.h b/lib/card10/leds.h index 8977daa7cc75233ac6d4b5bf9ace7a1f03781811..cdb4dfdb0b10e401f8872615ab7c3cca4331e9c0 100644 --- a/lib/card10/leds.h +++ b/lib/card10/leds.h @@ -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); diff --git a/lib/gfx/gfx.c b/lib/gfx/gfx.c index 5af5bbb360248650fc40cb1ec445d9091c90575d..49b8d3a8d618bf49c1ed849f49c97316b9210496 100644 --- a/lib/gfx/gfx.c +++ b/lib/gfx/gfx.c @@ -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); } } diff --git a/preload/apps/card10_nickname/__init__.py b/preload/apps/card10_nickname/__init__.py index 16fc8ad00869017bca56a5a329e3eeb13b074225..c6cd1b0b8037252cbc5f33f56ce19122a8c6c2cc 100644 --- a/preload/apps/card10_nickname/__init__.py +++ b/preload/apps/card10_nickname/__init__.py @@ -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]), ) diff --git a/preload/apps/ecg/__init__.py b/preload/apps/ecg/__init__.py index d8ff79e736d3a267a19d7c5e99697231ed9d6543..6a0dd08886f37173306c49bbf40eee792d105f89 100644 --- a/preload/apps/ecg/__init__.py +++ b/preload/apps/ecg/__init__.py @@ -39,7 +39,75 @@ disp = display.open() leds.dim_top(1) COLORS = [((23 + (15 * i)) % 360, 1.0, 1.0) for i in range(11)] -colors = COLORS + + +# 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): @@ -47,22 +115,16 @@ def callback_ecg(datasets): update_screen += len(datasets) # update histogram datalist - if pause_histogram == False: - if len(datasets) > 0: - for value in datasets: - history.append(value) - if len(history) > HISTORY_MAX: - r = len(history) - HISTORY_MAX - for i in range(r): - history.pop(0) + if not pause_histogram: + update_history(datasets) + detect_pulse(len(datasets)) # buffer for writes if write > 0: - if len(datasets) > 0: - for value in datasets: - filebuffer.extend(struct.pack("h", value)) - if len(filebuffer) >= FILEBUFFERBLOCK: - write_filebuffer() + for value in datasets: + filebuffer.extend(struct.pack("h", value)) + if len(filebuffer) >= FILEBUFFERBLOCK: + write_filebuffer() # don't update on every callback if update_screen >= DRAW_AFTER_SAMPLES: @@ -74,14 +136,7 @@ def write_filebuffer(): # write to file chars = "" lt = utime.localtime(write) - filename = "/ecg-%04d-%02d-%02d_%02d%02d%02d.log" % ( - lt[0], - lt[1], - lt[2], - lt[3], - lt[4], - lt[5], - ) + filename = "/ecg-{:04d}-{:02d}-{:02d}_{:02d}{:02d}{:02d}.log".format(*lt) # write stuff to disk try: @@ -104,13 +159,12 @@ def write_filebuffer(): write = 0 filebuffer = bytearray() - return def open_sensor(): global sensor sensor = max30001.MAX30001( - usb=(False if current_mode == MODE_FINGER else True), + usb=(current_mode == MODE_USB), bias=bias, sample_rate=ECG_RATE, callback=callback_ecg, @@ -132,9 +186,8 @@ def toggle_mode(): def toggle_bias(): global bias close_sensor() - bias = False if bias == True else True + bias = not bias open_sensor() - return def toggle_write(): @@ -153,12 +206,11 @@ def toggle_write(): disp.print("logging", posx=30, posy=40, fg=COLOR_TEXT) disp.update() - return def toggle_pause(): global pause_histogram, histogram_offset, history - if pause_histogram == True: + if pause_histogram: pause_histogram = False history = [] else: @@ -167,7 +219,6 @@ def toggle_pause(): def draw_leds(val): - global colors # val should be in [0, 11] for i in range(11): leds.prep_hsv(10 - i, COLORS[10 - i] if i < val else (0, 0, 0)) @@ -190,47 +241,30 @@ def draw_histogram(): disp.clear(COLOR_BACKGROUND) # offset in pause_histogram mode - samples = len(history) - s_start = samples - (histogram_offset + (ECG_RATE * 2)) - s_end = samples - (histogram_offset + 1) - s_draw = s_end - (WIDTH - 1) + window_end = int(len(history) - histogram_offset) + s_start = max(0, window_end - (ECG_RATE * 2)) + s_end = max(0, window_end) + s_draw = max(0, s_end - WIDTH) # get max value and calc scale - value_max = 0 - for i, value in enumerate(history): - if i >= s_start and i <= s_end and abs(value) > value_max: - value_max = abs(value) + value_max = max(abs(x) for x in history[s_start:s_end]) scale = SCALE_FACTOR / (value_max if value_max > 0 else 1) # draw histogram - old = False - x = 0 - for i, value in enumerate(history): - if old == False: - old = value - x += 1 - continue - elif i > s_start and i > s_draw and i < s_end: - - oldy = int(old * scale) - if oldy < -SCALE_FACTOR: - oldy = -SCALE_FACTOR - elif oldy > SCALE_FACTOR: - oldy = SCALE_FACTOR - - disp.line( - x - 1, oldy + OFFSET_Y, x, int(value * scale) + OFFSET_Y, col=COLOR_LINE - ) - old = value - x += 1 + # values need to be inverted so high values are drawn with low pixel coordinates (at the top of the screen) + draw_points = (int(-x * scale + OFFSET_Y) for x in history[s_draw:s_end]) + + prev = next(draw_points) + for x, value in enumerate(draw_points): + disp.line(x, prev, x + 1, value, col=COLOR_LINE) + prev = value - draw_leds((60 - int((max(history[-3:]) * scale + OFFSET_Y) - 20)) * 11 / 60) # draw text: mode/bias/write - if pause_histogram == True: + if pause_histogram: disp.print( "Pause" + ( - " -%0.1fs" % (histogram_offset / ECG_RATE) + " -{:0.1f}s".format(histogram_offset / ECG_RATE) if histogram_offset > 0 else "" ), @@ -239,12 +273,25 @@ def draw_histogram(): fg=COLOR_TEXT, ) else: - 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), - ) + draw_leds((max(history[-5:]) * scale + SCALE_FACTOR) * 11 / (SCALE_FACTOR * 2)) + 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: @@ -277,11 +324,11 @@ def main(): buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT ) - # BUTTOM LEFT + # BOTTOM LEFT if button_pressed["BOTTOM_LEFT"] == 0 and v & buttons.BOTTOM_LEFT != 0: button_pressed["BOTTOM_LEFT"] = utime.time_ms() - if pause_histogram == False: + if not pause_histogram: toggle_write() else: l = len(history) @@ -293,11 +340,11 @@ def main(): duration = utime.time_ms() - button_pressed["BOTTOM_LEFT"] button_pressed["BOTTOM_LEFT"] = 0 - # BUTTOM RIGHT + # BOTTOM RIGHT if button_pressed["BOTTOM_RIGHT"] == 0 and v & buttons.BOTTOM_RIGHT != 0: button_pressed["BOTTOM_RIGHT"] = utime.time_ms() - if pause_histogram == False: + if not pause_histogram: toggle_bias() else: histogram_offset -= ECG_RATE / 2 @@ -330,13 +377,11 @@ def main(): elif button_pressed["TOP_RIGHT"] > 0 and v & buttons.TOP_RIGHT == 0: duration = utime.time_ms() - button_pressed["TOP_RIGHT"] button_pressed["TOP_RIGHT"] = 0 - if pause_histogram == True: + if pause_histogram: toggle_pause() else: toggle_mode() - pass - if __name__ == "__main__": main() diff --git a/preload/apps/personal_state/__init__.py b/preload/apps/personal_state/__init__.py index 7605652c95070dba46cb8e572e48e2ebd2f6c641..f636e0c43cccfe9bd1781833a04b8776021e97ed 100644 --- a/preload/apps/personal_state/__init__.py +++ b/preload/apps/personal_state/__init__.py @@ -1,13 +1,11 @@ """ 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() diff --git a/preload/menu.py b/preload/menu.py index b8479c1dbdf9600d2af700249a35a33a770e9ad1..e9077e3611920f1fdb74b33347fe8db4f88eca27 100644 --- a/preload/menu.py +++ b/preload/menu.py @@ -5,286 +5,91 @@ You can customize this script however you want :) If you want to go back to the default version, just delete this file; the firmware will recreate it on next run. """ -import buttons +import collections import color import display import os -import utime -import ujson +import simple_menu import sys +import ujson +import utime -BUTTON_TIMER_POPPED = -1 -COLOR_BG = color.CHAOSBLUE_DARK -COLOR_BG_SEL = color.CHAOSBLUE -COLOR_ARROW = color.COMMYELLOW -COLOR_TEXT = color.COMMYELLOW -MAXCHARS = 11 -HOMEAPP = "main.py" - - -def create_folders(): - try: - os.mkdir("/apps") - except: - pass - - -def read_metadata(app_folder): - try: - info_file = "/apps/%s/metadata.json" % (app_folder) - with open(info_file) as f: - information = f.read() - return ujson.loads(information) - except Exception as e: - print("Failed to read metadata for %s" % (app_folder)) - sys.print_exception(e) - return { - "author": "", - "name": app_folder, - "description": "", - "category": "", - "revision": 0, - } +App = collections.namedtuple("App", ["name", "path"]) -def list_apps(): - """Create a list of available apps.""" - apps = [] +def enumerate_apps(): + """List all installed apps.""" + for f in os.listdir("/"): + if f == "main.py": + yield App("Home", f) - # add main application - for mainFile in os.listdir("/"): - if mainFile == HOMEAPP: - apps.append( - [ - "/%s" % HOMEAPP, - { - "author": "card10badge Team", - "name": "Home", - "description": "", - "category": "", - "revision": 0, - }, - ] - ) - - dirlist = [ - entry for entry in sorted(os.listdir("/apps")) if not entry.startswith(".") - ] + for app in sorted(os.listdir("/apps")): + if app.startswith("."): + continue - # list all hatchary style apps (not .elf and not .py) - # with or without metadata.json - for appFolder in dirlist: - if not (appFolder.endswith(".py") or appFolder.endswith(".elf")): - metadata = read_metadata(appFolder) - if not metadata.get("bin", None): - fileName = "/apps/%s/__init__.py" % appFolder - else: - fileName = "/apps/%s/%s" % (appFolder, metadata["bin"]) - apps.append([fileName, metadata]) + if app.endswith(".py") or app.endswith(".elf"): + yield App(app, "/apps/" + app) + continue - # list simple python scripts - for pyFile in dirlist: - if pyFile.endswith(".py"): - apps.append( - [ - "/apps/%s" % pyFile, - { - "author": "", - "name": pyFile, - "description": "", - "category": "", - "revision": 0, - }, - ] - ) + try: + with open("/apps/" + app + "/metadata.json") as f: + info = ujson.load(f) - # list simple elf binaries - for elfFile in dirlist: - if elfFile.endswith(".elf"): - apps.append( - [ - "/apps/%s" % elfFile, - { - "author": "", - "name": elfFile, - "description": "", - "category": "", - "revision": 0, - }, - ] + yield App( + info["name"], "/apps/{}/{}".format(app, info.get("bin", "__init__.py")) ) + except Exception as e: + print("Could not load /apps/{}/metadata.json!".format(app)) + sys.print_exception(e) + + +class MainMenu(simple_menu.Menu): + timeout = 30.0 + + def entry2name(self, app): + return app.name + + def on_select(self, app, index): + self.disp.clear().update() + try: + print("Trying to load " + app.path) + os.exec(app.path) + except OSError as e: + print("Loading failed: ") + sys.print_exception(e) + self.error("Loading", "failed") + utime.sleep(1.0) + os.exit(1) + + def on_timeout(self): + try: + f = open("main.py") + f.close() + os.exec("main.py") + except OSError: + pass + + +def no_apps_message(): + """Display a warning if no apps are installed.""" + with display.open() as disp: + disp.clear(color.COMMYELLOW) + disp.print( + " No apps ", posx=17, posy=20, fg=color.COMMYELLOW_DARK, bg=color.COMMYELLOW + ) + disp.print( + "available", posx=17, posy=40, fg=color.COMMYELLOW_DARK, bg=color.COMMYELLOW + ) + disp.update() - return apps - - -def button_events(timeout=0): - """Iterate over button presses (event-loop).""" - yield 0 - button_pressed = False - count = 0 while True: - v = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT) - if timeout > 0 and count > 0 and count % timeout == 0: - yield BUTTON_TIMER_POPPED - - if timeout > 0: - count += 1 - - 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 - - utime.sleep_ms(10) - + utime.sleep(0.5) -def triangle(disp, x, y, left, scale=6, color=[255, 0, 0]): - """Draw a triangle to show there's more text in this line""" - yf = 1 if left else -1 - disp.line(x - scale * yf, int(y + scale / 2), x, y, col=color) - disp.line(x, y, x, y + scale, col=color) - disp.line(x, y + scale, x - scale * yf, y + int(scale / 2), col=color) +if __name__ == "__main__": + apps = list(enumerate_apps()) -def draw_menu(disp, applist, pos, appcount, lineoffset): - disp.clear() - - start = 0 - if pos > 0: - start = pos - 1 - if start + 4 > appcount: - start = appcount - 4 - if start < 0: - start = 0 - - for i, app in enumerate(applist): - if i >= start + 4 or i >= appcount: - break - if i >= start: - disp.rect( - 0, - (i - start) * 20, - 159, - (i - start) * 20 + 20, - col=COLOR_BG_SEL if i == pos else COLOR_BG, - ) - - line = app[1]["name"] - linelength = len(line) - off = 0 - - # calc line offset for scrolling - if i == pos and linelength > (MAXCHARS - 1) and lineoffset > 0: - off = ( - lineoffset - if lineoffset + (MAXCHARS - 1) < linelength - else linelength - (MAXCHARS - 1) - ) - if lineoffset > linelength: - off = 0 - - disp.print( - " " + line[off : (off + (MAXCHARS - 1))], - posy=(i - start) * 20, - fg=COLOR_TEXT, - bg=COLOR_BG_SEL if i == pos else COLOR_BG, - ) - if i == pos: - disp.print(">", posy=(i - start) * 20, fg=COLOR_ARROW, bg=COLOR_BG_SEL) - - if linelength > (MAXCHARS - 1) and off < linelength - (MAXCHARS - 1): - triangle(disp, 153, (i - start) * 20 + 6, False, 6) - triangle(disp, 154, (i - start) * 20 + 7, False, 4) - triangle(disp, 155, (i - start) * 20 + 8, False, 2) - if off > 0: - triangle(disp, 24, (i - start) * 20 + 6, True, 6) - triangle(disp, 23, (i - start) * 20 + 7, True, 4) - triangle(disp, 22, (i - start) * 20 + 8, True, 2) - - disp.update() - - -def main(): - create_folders() - disp = display.open() - applist = list_apps() - numapps = len(applist) - current = 0 - lineoffset = 0 - timerscrollspeed = 1 - timerstartscroll = 5 - timercountpopped = 0 - timerinactivity = 100 - for ev in button_events(10): - if numapps == 0: - disp.clear(COLOR_BG) - disp.print(" No apps ", posx=17, posy=20, fg=COLOR_TEXT, bg=COLOR_BG) - disp.print("available", posx=17, posy=40, fg=COLOR_TEXT, bg=COLOR_BG) - disp.update() - continue - - if ev == buttons.BOTTOM_RIGHT: - # Scroll down - current = (current + 1) % numapps - lineoffset = 0 - timercountpopped = 0 - - elif ev == buttons.BOTTOM_LEFT: - # Scroll up - current = (current + numapps - 1) % numapps - lineoffset = 0 - timercountpopped = 0 - - elif ev == BUTTON_TIMER_POPPED: - timercountpopped += 1 - if ( - timercountpopped >= timerstartscroll - and (timercountpopped - timerstartscroll) % timerscrollspeed == 0 - ): - lineoffset += 1 - - if applist[0][0] == "/%s" % HOMEAPP and timercountpopped >= timerinactivity: - print("Inactivity timer popped") - disp.clear().update() - disp.close() - try: - os.exec("/%s" % HOMEAPP) - except OSError as e: - print("Loading failed: ", e) - os.exit(1) - - elif ev == buttons.TOP_RIGHT: - # Select & start - disp.clear().update() - disp.close() - try: - os.exec(applist[current][0]) - except OSError as e: - print("Loading failed: ", e) - os.exit(1) - - draw_menu(disp, applist, current, numapps, lineoffset) - + if apps == []: + no_apps_message() -if __name__ == "__main__": - try: - main() - except Exception as e: - sys.print_exception(e) - with display.open() as d: - d.clear(color.COMMYELLOW) - d.print("Menu", posx=52, posy=20, fg=color.COMMYELLOW_DARK, bg=color.COMMYELLOW) - d.print("crashed", posx=31, posy=40, fg=color.COMMYELLOW_DARK, bg=color.COMMYELLOW) - d.update() - utime.sleep(2) - os.exit(1) + MainMenu(apps).run() diff --git a/pycardium/modules/gpio.c b/pycardium/modules/gpio.c index 49c3c37c4ea52ee1f207ddf5d78ecd65a957fdc9..08db00b536b02ade2544b74a3ef24a94ef84e386 100644 --- a/pycardium/modules/gpio.c +++ b/pycardium/modules/gpio.c @@ -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), diff --git a/pycardium/modules/py/display.py b/pycardium/modules/py/display.py index 7b51cef374f8006b550ca955230a5ef9f3652d6c..ef9cc87e05a72c06349b4acb57f58de65f596ba5 100644 --- a/pycardium/modules/py/display.py +++ b/pycardium/modules/py/display.py @@ -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 :param rad: radius :param col: color of the outline and fill (expects RGB triple) :param filled: whether the rectangle should be filled or not diff --git a/pycardium/modules/py/leds.py b/pycardium/modules/py/leds.py index ad5563cfb2a3c8fca7bf4a4909ac460ba0e86808..9b7d2511544be91ab632d04cb3b598503b1c166f 100644 --- a/pycardium/modules/py/leds.py +++ b/pycardium/modules/py/leds.py @@ -131,6 +131,17 @@ def set(led, color): sys_leds.set(led, color) +def get_rgb(led): + """ + Get the current RGB value for an RGB LED. + + :param int led: Which LED to set. 0-10 are the LEDs on the top and 11-14 + are the 4 "ambient" LEDs + :return [r,g,b] color: RGB triplet + """ + return sys_leds.get_rgb(led) + + def set_hsv(led, color): """ Prepare an RGB LED to be set to an HSV value. diff --git a/pycardium/modules/py/simple_menu.py b/pycardium/modules/py/simple_menu.py index 42b117cc7998ac94397bee646f1c3fa82538af11..b9c535eeac14fcec5048b7a09377ee615161ec34 100644 --- a/pycardium/modules/py/simple_menu.py +++ b/pycardium/modules/py/simple_menu.py @@ -1,9 +1,14 @@ import buttons import color import display +import sys +import utime +TIMEOUT = 0x100 +""":py:func:`~simple_menu.button_events` timeout marker.""" -def button_events(): + +def button_events(timeout=None): """ Iterate over button presses (event-loop). @@ -26,11 +31,30 @@ def button_events(): pass .. versionadded:: 1.4 + + :param float,optional timeout: + Timeout after which the generator should yield in any case. If a + timeout is defined, the generator will periodically yield + :py:data:`simple_menu.TIMEOUT`. + + .. versionadded:: 1.9 """ yield 0 + v = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT) button_pressed = True if v != 0 else False + + if timeout is not None: + timeout = int(timeout * 1000) + next_tick = utime.time_ms() + timeout + while True: + if timeout is not None: + current_time = utime.time_ms() + if current_time >= next_tick: + next_tick += timeout + yield TIMEOUT + v = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT) if v == 0: @@ -49,6 +73,10 @@ def button_events(): yield buttons.TOP_RIGHT +class _ExitMenuException(Exception): + pass + + class Menu: """ A simple menu for card10. @@ -77,6 +105,21 @@ class Menu: color_sel = color.COMMYELLOW """Color of the selector.""" + scroll_speed = 0.5 + """ + Time to wait before scrolling to the right. + + .. versionadded:: 1.9 + """ + + timeout = None + """ + Optional timeout for inactivity. Once this timeout is reached, + :py:meth:`~simple_menu.Menu.on_timeout` will be called. + + .. versionadded:: 1.9 + """ + def on_scroll(self, item, index): """ Hook when the selector scrolls to a new item. @@ -102,12 +145,30 @@ class Menu: """ pass + def on_timeout(self): + """ + The inactivity timeout has been triggered. See + :py:attr:`simple_menu.Menu.timeout`. + + .. versionadded:: 1.9 + """ + self.exit() + + def exit(self): + """ + Exit the event-loop. This should be called from inside an ``on_*`` hook. + + .. versionadded:: 1.9 + """ + raise _ExitMenuException() + def __init__(self, entries): if len(entries) == 0: raise ValueError("at least one entry is required") self.entries = entries self.idx = 0 + self.select_time = utime.time_ms() self.disp = display.open() def entry2name(self, value): @@ -142,8 +203,21 @@ class Menu: but **not** an index into ``entries``. :param int offset: Y-offset for this entry. """ + string = self.entry2name(value) + + if offset != 20 or len(string) < 10: + string = " " + string + " " * 9 + else: + # Slowly scroll entry to the side + time_offset = (utime.time_ms() - self.select_time) // int( + self.scroll_speed * 1000 + ) + time_offset = time_offset % (len(string) - 7) - 1 + time_offset = min(len(string) - 10, max(0, time_offset)) + string = " " + string[time_offset:] + self.disp.print( - " " + self.entry2name(value) + " " * 9, + string, posy=offset, fg=self.color_text, bg=self.color_1 if index % 2 == 0 else self.color_2, @@ -171,18 +245,70 @@ class Menu: self.disp.update() + def error(self, line1, line2=""): + """ + Display an error message. + + :param str line1: First line of the error message. + :param str line2: Second line of the error message. + + .. versionadded:: 1.9 + """ + self.disp.clear(color.COMMYELLOW) + + offset = max(0, (160 - len(line1) * 14) // 2) + self.disp.print( + line1, posx=offset, posy=20, fg=color.COMMYELLOW_DARK, bg=color.COMMYELLOW + ) + + offset = max(0, (160 - len(line2) * 14) // 2) + self.disp.print( + line2, posx=offset, posy=40, fg=color.COMMYELLOW_DARK, bg=color.COMMYELLOW + ) + + self.disp.update() + def run(self): """Start the event-loop.""" - for ev in button_events(): - if ev == buttons.BOTTOM_RIGHT: - self.draw_menu(-8) - self.idx = (self.idx + 1) % len(self.entries) - self.on_scroll(self.entries[self.idx], self.idx) - elif ev == buttons.BOTTOM_LEFT: - self.draw_menu(8) - self.idx = (self.idx + len(self.entries) - 1) % len(self.entries) - self.on_scroll(self.entries[self.idx], self.idx) - elif ev == buttons.TOP_RIGHT: - self.on_select(self.entries[self.idx], self.idx) - - self.draw_menu() + try: + timeout = self.scroll_speed + if self.timeout is not None and self.timeout < self.scroll_speed: + timeout = self.timeout + + for ev in button_events(timeout): + if ev == buttons.BOTTOM_RIGHT: + self.select_time = utime.time_ms() + self.draw_menu(-8) + self.idx = (self.idx + 1) % len(self.entries) + try: + self.on_scroll(self.entries[self.idx], self.idx) + except Exception as e: + print("Exception during menu.on_scroll():") + sys.print_exception(e) + elif ev == buttons.BOTTOM_LEFT: + self.select_time = utime.time_ms() + self.draw_menu(8) + self.idx = (self.idx + len(self.entries) - 1) % len(self.entries) + try: + self.on_scroll(self.entries[self.idx], self.idx) + except Exception as e: + print("Exception during menu.on_scroll():") + sys.print_exception(e) + elif ev == buttons.TOP_RIGHT: + try: + self.on_select(self.entries[self.idx], self.idx) + self.select_time = utime.time_ms() + except Exception as e: + print("Menu crashed!") + sys.print_exception(e) + self.error("Menu", "crashed") + utime.sleep(1.0) + + self.draw_menu() + + if self.timeout is not None and ( + utime.time_ms() - self.select_time + ) > int(self.timeout * 1000): + self.on_timeout() + except _ExitMenuException: + pass diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h index a3a873cbdc01b9f7fd295ed72aa4159f338ccada..648a1726df71f33a834812e3301fa59d9d9b8a9e 100644 --- a/pycardium/modules/qstrdefs.h +++ b/pycardium/modules/qstrdefs.h @@ -8,6 +8,7 @@ Q(sys_leds) Q(update) Q(set) +Q(get_rgb) Q(set_hsv) Q(prep) Q(prep_hsv) @@ -151,6 +152,7 @@ Q(WRISTBAND_3) Q(WRISTBAND_4) Q(INPUT) Q(OUTPUT) +Q(ADC) Q(PULL_UP) Q(PULL_DOWN) diff --git a/pycardium/modules/sys_leds.c b/pycardium/modules/sys_leds.c index 7c1c2d5a35156ae06c31c424546bf1623d69d311..990a4a36a3a97612ebe5d9dd745bd6317b61f438 100644 --- a/pycardium/modules/sys_leds.c +++ b/pycardium/modules/sys_leds.c @@ -6,7 +6,6 @@ #include <stdio.h> - static mp_obj_t mp_leds_set(mp_obj_t led_in, mp_obj_t color_in) { int led = mp_obj_get_int(led_in); @@ -79,6 +78,33 @@ static mp_obj_t mp_leds_prep(mp_obj_t led_in, mp_obj_t color_in) } static MP_DEFINE_CONST_FUN_OBJ_2(leds_prep_obj, mp_leds_prep); +static mp_obj_t mp_leds_get_rgb(mp_obj_t led_in) +{ + int led = mp_obj_get_int(led_in); + uint8_t rgb[] = { 0, 0, 0 }; + int retAPI = epic_leds_get_rgb(led, rgb); + if (retAPI == -EPERM) { + mp_raise_ValueError( + "no permission: maybe blocked by personal state" + ); + } + if (retAPI == -EINVAL) { + mp_raise_ValueError( + "invalid value: maybe the led does not exists" + ); + } + + mp_obj_t ret = mp_obj_new_tuple(3, NULL); + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(ret); + + tuple->items[0] = MP_OBJ_NEW_SMALL_INT(rgb[0]); + tuple->items[1] = MP_OBJ_NEW_SMALL_INT(rgb[1]); + tuple->items[2] = MP_OBJ_NEW_SMALL_INT(rgb[2]); + + return ret; +} +static MP_DEFINE_CONST_FUN_OBJ_1(leds_get_rgb_obj, mp_leds_get_rgb); + static mp_obj_t mp_leds_prep_hsv(mp_obj_t led_in, mp_obj_t color_in) { int led = mp_obj_get_int(led_in); @@ -238,6 +264,7 @@ static const mp_rom_map_elem_t leds_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&leds_set_obj) }, { MP_ROM_QSTR(MP_QSTR_set_hsv), MP_ROM_PTR(&leds_set_hsv_obj) }, { MP_ROM_QSTR(MP_QSTR_prep), MP_ROM_PTR(&leds_prep_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_rgb), MP_ROM_PTR(&leds_get_rgb_obj) }, { MP_ROM_QSTR(MP_QSTR_prep_hsv), MP_ROM_PTR(&leds_prep_hsv_obj) }, { MP_ROM_QSTR(MP_QSTR_set_all), MP_ROM_PTR(&leds_set_all_obj) }, { MP_ROM_QSTR(MP_QSTR_set_all_hsv), MP_ROM_PTR(&leds_set_all_hsv_obj) }, diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h index 63de3f5d53fcb8377bfbfcbf68ae7dd276b34419..6ddd29c9d109afc6122f2e8dafa634296aa4269f 100644 --- a/pycardium/mpconfigport.h +++ b/pycardium/mpconfigport.h @@ -3,6 +3,7 @@ #define MICROPY_HW_MCU_NAME "max32666" /* MicroPython Config Options */ +#define MICROPY_PY_MICROPYTHON_MEM_INFO (1) /* We raise asynchronously from an interrupt handler */ #define MICROPY_ASYNC_KBD_INTR (1)