diff --git a/CHANGELOG.md b/CHANGELOG.md index e580443a064b511fba178ad0f53a2330ced62723..65648e861ca72314e2e10e797fdddf7cf7cf5088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ 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] + + +## [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 +21,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 +207,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/pycardium/gpio.rst b/Documentation/pycardium/gpio.rst index 7d17b3fdf3597aba664ebc46feae7f7a41dc62ba..28854d141843ba5d84016b2361aa12052fae18d8 100644 --- a/Documentation/pycardium/gpio.rst +++ b/Documentation/pycardium/gpio.rst @@ -52,7 +52,7 @@ output in your scripts. :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 + 0V to 3.3V (:py:data:`gpio.ADC` is only available in 1.9+). .. py:data:: WRISTBAND_1 @@ -81,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..78e128bb4e82ac74133614cad0ef56cd0f198d89 100644 --- a/epicardium/ble/card10.c +++ b/epicardium/ble/card10.c @@ -256,228 +256,272 @@ 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 = 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 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 = 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 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 = 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 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 = NULL, + .maxLen = 3 * sizeof(uint8_t), + .settings = ATTS_SET_WRITE_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_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 = NULL, + .maxLen = 11 * 3 * sizeof(uint8_t), + .settings = ATTS_SET_WRITE_CBACK, + .permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC | + ATTS_PERMIT_WRITE_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 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/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/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 693ca9a8f94a432ff64d770f91d9e9005c88d42a..628249a305098911b6dfe3c861b8759d6d5a5a62 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -21,5 +21,6 @@ module_sources = files( 'trng.c', 'vibra.c', 'watchdog.c', - 'usb.c' + 'usb.c', + 'config.c', ) 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/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