Commits (87)
......@@ -4,7 +4,7 @@ image: "derq3k/card10-build-env:20190806-195837Z-f95b541-dirty"
stage: build
- ./
- ./ --werror
- ninja -C build/
- arm-none-eabi-size build/bootloader/bootloader.elf build/epicardium/epicardium.elf build/pycardium/pycardium.elf
......@@ -14,7 +14,7 @@ build:
stage: build
- ./
- ./ --werror
- ninja -C build/
- arm-none-eabi-size build/bootloader/bootloader.elf build/epicardium/epicardium.elf build/pycardium/pycardium.elf
......@@ -6,6 +6,42 @@ The format is based on [Keep a Changelog](
## [Unreleased]
## [v1.13] - 2019-12-09 - [Mushroom]
### Added
- ECG plotter tool (for desktop machines) which can plot ECG logs taken with card10.
- The `input()` Python function.
- Enabled the MicroPython `framebuf` module for a Pycardium-only framebuffer
- Added the `utime.ticks_us()` and `utime.ticks_ms()` functions for very
accurate timing of MicroPython code.
- Added an option to use the right buttons for scrolling and the left one for
selecting. This will be made configurable in a future release.
- Made timezone configurable with a new `timezone` option in `card10.cfg`.
- Added a setting-menu to the ECG App.
### Changed
- Changed default timezone to CET.
- Made a few library functions callable without any parameters so they are
easier to use.
- Refactored the `card10.cfg` config parser.
### Fixed
- Fixed the Pycardium delay implementation in preparation for features like
button-interrupts. Should also be more accurate now.
- Fixed the filter which is used by the ECG app.
- Fixed the display staying off while printing the sleep-messages.
- Improved the USB-Storage mode in the menu app.
- Fixed GPIO module not properly configuring a pin if both IN and ADC are given.
- Added missing documentation for `os.mkdir()` and `os.rename()`.
- Fixed all `-Wextra` warnings, including a few bugs. Warnings exist for a reason!
### Removed
- Removed unnecessary out-of-bounds checks in display module. Drawing outside
the display is now perfectly fine and the pixels will silently be ignored.
## [v1.12] - 2019-10-19 - [Leek]
......@@ -313,7 +349,8 @@ fbf7c8c0 fix( Refactored based on !138
## [v1.0] - 2019-08-21 00:50
Initial release.
On the card10 the ARM Cordio-B50 stack is used, which is in a very early experimental state and has some incompatibilities with some smartphones.
Therefore some alternative stacks are evaluated, which meight be used as a replacement in the long term.
Here a stack called NimBLE is presented, which claims to be feature complete. Originally it has been developed for Mynewt, an open source embedded operating system by Apache (
There is a working port for the ESP32 espressif ESP-IDF framework.
Like Epicardium, ESP-IDF is based on FreeRTOS. Therefore it can be used for evaluation purposes.
Getting NimBLE run on the ESP32
Install required packages:
.. code-block:: shell-session
sudo apt install git virtualenv python2.7 cmake
.. code-block:: shell-session
sudo pacman -S git python2 python2-virtualenv cmake
Download and extract xtensa ESP32 compiler:
.. code-block:: shell-session
tar -xf xtensa-esp32-elf-gcc8_2_0-esp32-2018r1-linux-amd64.tar.xz
Clone esp-idf:
.. code-block:: shell-session
git clone
Add xtensa and ESP-IDF path to $PATH:
bash shell:
.. code-block:: shell-session
export IDF_PATH=$PWD/esp-idf
export PATH=${PATH}:$PWD/xtensa-esp32-elf/bin:$PWD/esp-idf/tools
fish shell:
.. code-block:: shell-session
set -gx IDF_PATH $PWD/esp-idf
set -gx PATH $PWD/xtensa-esp32-elf/bin/ $PWD/esp-idf/tools $PATH
Create a python2.7 virtualenv:
.. code-block:: shell-session
cd esp-idf
virtualenv -p /usr/bin/python2.7 venv
Enter the virtualenv:
bash shell:
.. code-block:: shell-session
. venv/bin/activate
fish shell:
.. code-block:: shell-session
. venv/bin/
Init git submodules and install all required Python packages:
.. code-block:: shell-session
git submodule update --init --recursive
pip install -r requirements.txt
Now you are ready to build!
The following steps assume that your ESP32 is connected via USB and
is accessible via /dev/ttyUSB0. This meight be different on your system.
There are a few NimbLE examples which can be used for playing around:
Build a BLE server example (host mode):
.. code-block:: shell-session
cd examples/bluetooth/nimble/bleprph -p /dev/ttyUSB0 flash monitor
This will build and flash the example to the ESP32 and instantly listens on /dev/ttyUSB0 serial port.
After the flashing process the ESP32 will anounce itself as **nimble-bleprph** device via BLE.
Build a BLE client example (central mode):
.. code-block:: shell-session
cd examples/bluetooth/nimble/blecent -p /dev/ttyUSB0 flash monitor
This will build and flash the example to the ESP32 and instantly listens on /dev/ttyUSB0 serial port.
After the flashing process the ESP32 creates a GATT client and performs passive scan, it then connects to peripheral device if the device advertises connectability and the device advertises support for the Alert Notification service (0x1811) as primary service UUID.
......@@ -38,4 +38,6 @@ Supported options
Option name Type Description
=============== ========== ===========
``execute_elf`` Boolean Allow running of binary :ref:`l0dables`. These files can be nefarious, so this option is off by default.
--------------- ---------- -----------
``timezone`` String Timezone for card10; must be of format ``[+-]HHMM``. Examples: ``+0800``, ``-0200``
=============== ========== ===========
......@@ -3,6 +3,8 @@ import subprocess
import sys
import time
import sphinx.util.logging
from docutils import nodes
from docutils.parsers import rst
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
......@@ -47,6 +49,21 @@ todo_include_todos = True
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["output", "Thumbs.db", ".DS_Store", "hawkmoth"]
# -- Extensions -------------------------------------------------------------- {{{
class ColorExample(rst.Directive):
has_content = False
required_arguments = 1
optional_arguments = 0
option_spec = {}
def run(self):
color = self.arguments[0]
html_text = '<div style="width: 30px;height: 30px;background: {};border: black 1px solid;border-radius: 15px;"></div>'
return [nodes.raw("", html_text.format(color), format="html")]
# }}}
# -- Options for HTML output ------------------------------------------------- {{{
# The Read the Docs theme is available from
......@@ -125,3 +142,4 @@ except ImportError as e:
# -- Sphinx Setup ------------------------------------------------------------
def setup(app):
app.add_config_value("has_hawkmoth", has_hawkmoth, "")
app.add_directive("color-example", ColorExample)
Ontop of FreeRTOS, we have our own mutex implementation. **Never use the
FreeRTOS mutexes directly! Always use this abstraction layer instead**. This
mutex implementation tries to make reasoning about program flow and locking
behavior easier. And most importantly tries to help with debugging possible
There are a few guiding design principles:
- Mutexes can only be used from tasks, **never** from interrupts!
- Timers can use mutexes, but only with :c:func:`mutex_trylock`, **never** with
:c:func:`mutex_lock` (Because they are not allowed to block).
- Locking can *never* fail (if it does, we consider this a fatal error ⇒ panic).
- No recursive locking.
- An unlock can only occur from the task which previously acquired the mutex.
- An unlock is only allowed if the mutex was previously acquired.
For a more elaborate explanation of the rationale behind these rules take a
look at the :ref:`mutex-design-reasons`.
.. c:autodoc:: epicardium/modules/mutex.h
.. _mutex-design-reasons:
Reasons for this Design
Locking can *never* fail
This might seem like a bold claim at first but in the end, it is just a matter
of definition and shifting responsibilities. Instead of requiring all code to
be robust against a locking attempt failing, we require all code to properly
lock and unlock their mutexes and thus never producing a situation where
locking would fail.
Because all code using any of the mutexes is contained in the Epicardium
code-base, we can - *hopefully* - audit it properly behaving ahead of time and
thus don't need to add code to ensure correctness at runtime. This makes
downstream code easier to read and easier to reason about.
History of this project has shown that most code does not properly deal with
locking failures anyway: There was code simply skipping the mutexed action on
failure, code blocking a module entirely until reboot, and worst of all: Code
exposing the locking failure to 'user-space' (Pycardium) instead of retrying.
This has lead to spurious errors where technically there would not need to be
Only from tasks
Locking a mutex from an ISR, a FreeRTOS software timer or any other context
which does not allow blocking is complicated to do right. The biggest
difficulty is that a task might be holding the mutex during execution of such a
context and there is no way to wait for it to release the mutex. This requires
careful design of the program flow to choose an alternative option in such a
case. A common approach is to 'outsource' the relevant parts of the code into
an 'IRQ worker' which is essentially just a task waiting for the IRQ to wake it
up and then attempts to lock the mutex.
If you absolutely do need it (and for legacy reasons), software timers *can*
lock a mutex using :c:func:`mutex_trylock` (which never blocks). I strongly
recommend **not** doing that, though. As shown above, you will have to deal
with the case of the mutex being held by another task and it is very well
possible that your timer will get starved of the mutex because the scheduler
has no knowledge of its intentions. In most cases, it is a better idea to use
a task and attempt locking using :c:func:`mutex_lock`.
.. todo::
We might introduce a generic IRQ worker queue system at some point.
No recursive locking
Recursive locking refers to the ability to 'reacquire' a mutex already held by
the current task, deeper down in the call-chain. Only the outermost unlock
will actually release the mutex. This feature is sometimes implemented to
allow more elegant abstractions where downstream code does not need to know
about the mutexes upstream code uses and can still also create a larger region
where the same mutex is held.
But exactly by hiding the locking done by a function, these abstractions make
it hard to trace locking chains and in some cases even make it impossible to
create provably correct behavior. As an alternative, I would suggest using
different mutexes for the different levels of abstraction. This also helps
keeping each mutex separated and 'local' to its purpose.
Only unlock from the acquiring task
Because of the above mentioned mutex locking semantics, there should never be a
need to force-unlock a forgein mutex. Even in cases of failures, all code
should still properly release all mutexes it holds. One notable exceptions is
``panic()``\s which will abort all ongoing operations anyway.
Only unlock once after acquisition
Justified with an argument of robustness, sometimes the :c:func:`mutex_unlock`
call is written in a way that allows unlocking an already unlocked mutex. But
robustness of downstream code will not really be improved by the upstream API
dealing with arguably invalid usage. For example, this could encourage
practices like unlocking everything again at the end of a function "just to be
Instead, code should be written in a way where the lock/unlock pair is
immediately recognizable as belonging together and is thus easily auditable to
have correct locking behavior. A common pattern to help with readability in
this regard is the *Single Function Exit* which looks like this:
.. code-block:: cpp
int function()
int ret;
ret = foo();
if (ret) {
/* Return with an error code */
ret = -ENODEV;
goto out_unlock;
ret = bar();
if (ret) {
/* Return the return value from foo */
goto out_unlock;
ret = 0;
return ret;
......@@ -52,6 +52,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the
.. toctree::
......@@ -68,6 +69,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the
Indices and tables
......@@ -14,23 +14,65 @@ The color module also contains a few constanst for commonly used colors:
Camp Colors
.. py:data:: CHAOSBLUE
.. color-example:: #0076BA
.. py:data:: CHAOSBLUE_DARK
.. color-example:: #005383
.. py:data:: COMMYELLOW
.. color-example:: #FFC600
.. py:data:: COMMYELLOW_DARK
.. color-example:: #D39A00
.. py:data:: CAMPGREEN
.. color-example:: #99BA00
.. py:data:: CAMPGREEN_DARK
.. color-example:: #6F8700
.. py:data:: BLACK
.. color-example:: #000
.. py:data:: WHITE
.. color-example:: #fff
.. py:data:: RED
.. color-example:: #f00
.. py:data:: GREEN
.. color-example:: #0f0
.. py:data:: YELLOW
.. color-example:: #ff0
.. py:data:: BLUE
.. color-example:: #00f
.. py:data:: MAGENTA
.. color-example:: #f0f
.. py:data:: CYAN
.. color-example:: #0ff
.. py:module:: htmlcolor
``htmlcolor`` - Color Constants
......@@ -39,142 +81,561 @@ The ``htmlcolor`` module contains even more color constants. Note
that loading the ``htmlcolor`` module will require ~12K of RAM.
.. py:data:: ALICEBLUE
.. color-example:: aliceblue
.. py:data:: ANTIQUEWHITE
.. color-example:: antiquewhite
.. py:data:: AQUA
.. color-example:: aqua
.. py:data:: AQUAMARINE
.. color-example:: aquamarine
.. py:data:: AZURE
.. color-example:: azure
.. py:data:: BEIGE
.. color-example:: beige
.. py:data:: BISQUE
.. color-example:: bisque
.. py:data:: BLACK
.. color-example:: black
.. color-example:: blanchedalmond
.. py:data:: BLUE
.. color-example:: blue
.. py:data:: BLUEVIOLET
.. color-example:: blueviolet
.. py:data:: BROWN
.. color-example:: brown
.. py:data:: BURLYWOOD
.. color-example:: burlywood
.. py:data:: CADETBLUE
.. color-example:: cadetblue
.. py:data:: CHARTREUSE
.. color-example:: chartreuse
.. py:data:: CHOCOLATE
.. color-example:: chocolate
.. py:data:: CORAL
.. color-example:: coral
.. color-example:: cornflowerblue
.. py:data:: CORNSILK
.. color-example:: cornsilk
.. py:data:: CRIMSON
.. color-example:: crimson
.. py:data:: CYAN
.. color-example:: cyan
.. py:data:: DARKBLUE
.. color-example:: darkblue
.. py:data:: DARKCYAN
.. color-example:: darkcyan
.. py:data:: DARKGOLDENROD
.. color-example:: darkgoldenrod
.. py:data:: DARKGRAY
.. color-example:: darkgray
.. py:data:: DARKGREEN
.. color-example:: darkgreen
.. py:data:: DARKKHAKI
.. color-example:: darkkhaki
.. py:data:: DARKMAGENTA
.. color-example:: darkmagenta
.. color-example:: darkolivegreen
.. py:data:: DARKORANGE
.. color-example:: darkorange
.. py:data:: DARKORCHID
.. color-example:: darkorchid
.. py:data:: DARKRED
.. color-example:: darkred
.. py:data:: DARKSALMON
.. color-example:: darksalmon
.. py:data:: DARKSEAGREEN
.. color-example:: darkseagreen
.. py:data:: DARKSLATEBLUE
.. color-example:: darkslateblue
.. py:data:: DARKSLATEGRAY
.. color-example:: darkslategray
.. py:data:: DARKTURQUOISE
.. color-example:: darkturquoise
.. py:data:: DARKVIOLET
.. color-example:: darkviolet
.. py:data:: DEEPPINK
.. color-example:: deeppink
.. py:data:: DEEPSKYBLUE
.. color-example:: deepskyblue
.. py:data:: DIMGRAY
.. color-example:: dimgray
.. py:data:: DODGERBLUE
.. color-example:: dodgerblue
.. py:data:: FIREBRICK
.. color-example:: firebrick
.. py:data:: FLORALWHITE
.. color-example:: floralwhite
.. py:data:: FORESTGREEN
.. color-example:: forestgreen
.. py:data:: FUCHSIA
.. color-example:: fuchsia
.. py:data:: GAINSBORO
.. color-example:: gainsboro
.. py:data:: GHOSTWHITE
.. color-example:: ghostwhite
.. py:data:: GOLD
.. color-example:: gold
.. py:data:: GOLDENROD
.. color-example:: goldenrod
.. py:data:: GRAY
.. color-example:: gray
.. py:data:: GREEN
.. color-example:: green
.. py:data:: GREENYELLOW
.. color-example:: greenyellow
.. py:data:: HONEYDEW
.. color-example:: honeydew
.. py:data:: HOTPINK
.. color-example:: hotpink
.. py:data:: INDIANRED
.. color-example:: indianred
.. py:data:: INDIGO
.. color-example:: indigo
.. py:data:: IVORY
.. color-example:: ivory
.. py:data:: KHAKI
.. color-example:: khaki
.. py:data:: LAVENDER
.. color-example:: lavender
.. py:data:: LAVENDERBLUSH
.. color-example:: lavenderblush
.. py:data:: LAWNGREEN
.. color-example:: lawngreen
.. py:data:: LEMONCHIFFON
.. color-example:: lemonchiffon
.. py:data:: LIGHTBLUE
.. color-example:: lightblue
.. py:data:: LIGHTCORAL
.. color-example:: lightcoral
.. py:data:: LIGHTCYAN
.. color-example:: lightcyan
.. color-example:: lightgoldenrodyellow
.. py:data:: LIGHTGRAY
.. color-example:: lightgray
.. py:data:: LIGHTGREEN
.. color-example:: lightgreen
.. py:data:: LIGHTPINK
.. color-example:: lightpink
.. py:data:: LIGHTSALMON
.. color-example:: lightsalmon
.. py:data:: LIGHTSEAGREEN
.. color-example:: lightseagreen
.. py:data:: LIGHTSKYBLUE
.. color-example:: lightskyblue
.. color-example:: lightslategray
.. color-example:: lightsteelblue
.. py:data:: LIGHTYELLOW
.. color-example:: lightyellow
.. py:data:: LIME
.. color-example:: lime
.. py:data:: LIMEGREEN
.. color-example:: limegreen
.. py:data:: LINEN
.. color-example:: linen
.. py:data:: MAGENTA
.. color-example:: magenta
.. py:data:: MAROON
.. color-example:: maroon
.. color-example:: mediumaquamarine
.. py:data:: MEDIUMBLUE
.. color-example:: mediumblue
.. py:data:: MEDIUMORCHID
.. color-example:: mediumorchid
.. py:data:: MEDIUMPURPLE
.. color-example:: mediumpurple
.. color-example:: mediumseagreen
.. color-example:: mediumslateblue
.. color-example:: mediumspringgreen
.. color-example:: mediumturquoise
.. color-example:: mediumvioletred
.. py:data:: MIDNIGHTBLUE
.. color-example:: midnightblue
.. py:data:: MINTCREAM
.. color-example:: mintcream
.. py:data:: MISTYROSE
.. color-example:: mistyrose
.. py:data:: MOCCASIN
.. color-example:: moccasin
.. py:data:: NAVAJOWHITE
.. color-example:: navajowhite
.. py:data:: NAVY
.. color-example:: navy
.. py:data:: OLDLACE
.. color-example:: oldlace
.. py:data:: OLIVE
.. color-example:: olive
.. py:data:: OLIVEDRAB
.. color-example:: olivedrab
.. py:data:: ORANGE
.. color-example:: orange
.. py:data:: ORANGERED
.. color-example:: orangered
.. py:data:: ORCHID
.. color-example:: orchid
.. py:data:: PALEGOLDENROD
.. color-example:: palegoldenrod
.. py:data:: PALEGREEN
.. color-example:: palegreen
.. py:data:: PALETURQUOISE
.. color-example:: paleturquoise
.. py:data:: PALEVIOLETRED
.. color-example:: palevioletred
.. py:data:: PAPAYAWHIP
.. color-example:: papayawhip
.. py:data:: PEACHPUFF
.. color-example:: peachpuff
.. py:data:: PERU
.. color-example:: peru
.. py:data:: PINK
.. color-example:: pink
.. py:data:: PLUM
.. color-example:: plum
.. py:data:: POWDERBLUE
.. color-example:: powderblue
.. py:data:: PURPLE
.. color-example:: purple
.. py:data:: RED
.. color-example:: red
.. py:data:: ROSYBROWN
.. color-example:: rosybrown
.. py:data:: ROYALBLUE
.. color-example:: royalblue
.. py:data:: SADDLEBROWN
.. color-example:: saddlebrown
.. py:data:: SALMON
.. color-example:: salmon
.. py:data:: SANDYBROWN
.. color-example:: sandybrown
.. py:data:: SEAGREEN
.. color-example:: seagreen
.. py:data:: SEASHELL
.. color-example:: seashell
.. py:data:: SIENNA
.. color-example:: sienna
.. py:data:: SILVER
.. color-example:: silver
.. py:data:: SKYBLUE
.. color-example:: skyblue
.. py:data:: SLATEBLUE
.. color-example:: slateblue
.. py:data:: SLATEGRAY
.. color-example:: slategray
.. py:data:: SNOW
.. color-example:: snow
.. py:data:: SPRINGGREEN
.. color-example:: springgreen
.. py:data:: STEELBLUE
.. color-example:: steelblue
.. py:data:: TAN
.. color-example:: tan
.. py:data:: TEAL
.. color-example:: teal
.. py:data:: THISTLE
.. color-example:: thistle
.. py:data:: TOMATO
.. color-example:: tomato
.. py:data:: TURQUOISE
.. color-example:: turquoise
.. py:data:: VIOLET
.. color-example:: violet
.. py:data:: WHEAT
.. color-example:: wheat
.. py:data:: WHITE
.. color-example:: white
.. py:data:: WHITESMOKE
.. color-example:: whitesmoke
.. py:data:: YELLOW
.. color-example:: yellow
.. py:data:: YELLOWGREEN
.. color-example:: yellowgreen
......@@ -8,12 +8,6 @@ functions found in CPythons ``os`` module.
.. py:function:: unlink(path)
Unlink (remove) a file.
:param str path: The file to remove.
.. py:function:: listdir(dir)
List contents of a directory.
......@@ -22,6 +16,28 @@ CPython-Like
:returns: A list of entities (files or subdirectories) in the directory
.. py:function:: mkdir(path)
Create a directory named *path*.
:param str path: Path to the directory to create. Only the last component
of this path will be created.
.. py:function:: rename(src, dst)
Rename the file or directory *src* to *dst*. If *dst* exists, the operation
will fail.
:param str src: Path to source file to rename.
:param str dst: Destination path to rename to. Must not exist before
calling :py:func:`os.rename`.
.. py:function:: unlink(path)
Unlink (remove) a file.
:param str path: The file to remove.
.. py:function:: urandom(n)
Return ``n`` random bytes.
......@@ -3,6 +3,14 @@ MicroPython Standard Library
Pycardium contains some modules from the MicroPython standard library. These
.. py:module:: framebuf
Refer to the official `MicroPython docs for framebuf`_.
.. _MicroPython docs for framebuf:
.. py:module:: ubinascii
......@@ -219,7 +227,7 @@ Struct module.
UUID version accordiung to RFC 4122
.. py:function:: uuid4():
.. py:function:: uuid4()
Generate a new UUID version 4 (random UUID).
......@@ -45,6 +45,24 @@ alarm.
.. versionadded:: 1.11
.. py:function:: ticks_ms()
Return processor ticks (converted to milliseconds) since Pycardium startup.
This function should be the preferred method for timing and profiling
because it does not need an API call and thus is very fast.
.. versionadded:: 1.13
.. py:function:: ticks_us()
Return processor ticks (converted to microseconds) since Pycardium startup.
This function should be the preferred method for timing and profiling
because it does not need an API call and thus is very fast.
.. versionadded:: 1.13
.. py:function:: unix_time()
Return the current unix time as seconds since the epoch.
#!/usr/bin/env python3
import sys
import warnings
import crc16
......@@ -52,6 +52,8 @@
#define INCLUDE_vTaskDelay 1
#define INCLUDE_uxTaskGetStackHighWaterMark 1
#define INCLUDE_xTimerPendFunctionCall 1
#define INCLUDE_xSemaphoreGetMutexHolder 1
/* Allow static allocation of data structures */
......@@ -109,7 +109,7 @@ int api_fetch_args(char *buf, size_t cnt)
return 0;
int i;
size_t i;
for (i = 0; i < cnt && API_CALL_MEM->buffer[i + 0x20] != '\0'; i++) {
buf[i] = API_CALL_MEM->buffer[i + 0x20];
......@@ -86,7 +86,7 @@ void api_prepare_args(char *args)
* collide with any integer return value of API calls like epic_exec().
API_CALL_MEM->id = 0;
for (int i = 0; i <= strlen(args); i++) {
for (size_t i = 0; i <= strlen(args); i++) {
API_CALL_MEM->buffer[i + 0x20] = args[i];
......@@ -11,7 +11,7 @@ int api_interrupt_trigger(api_int_id_t id)
if (int_enabled[id]) {
while (API_CALL_MEM->int_id != (-1))
while (API_CALL_MEM->int_id != (api_int_id_t)(-1))
API_CALL_MEM->int_id = id;
......@@ -122,6 +122,11 @@ static const smpCfg_t bleSmpCfg =
16, /*! Maximum encryption key length */
3, /*! Attempts to trigger 'repeated attempts' timeout */
DM_AUTH_MITM_FLAG, /*! Device authentication requirements */
/* TODO: The following three parameters should probably get proper values */
0, /*! Maximum 'Repeated attempts' timeout in msec */
0, /*! Time msec before attemptExp decreases */
0, /*! Exponent to raise attemptTimeout on maxAttempts */
/* Configuration structure */
......@@ -143,6 +143,9 @@ typedef _Bool bool;
#define API_WS2812_WRITE 0x0120
/* clang-format on */
typedef uint32_t api_int_id_t;
......@@ -317,7 +320,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, size_t length
......@@ -1924,5 +1927,59 @@ API(API_USB_CDCACM, int epic_usb_cdcacm(void));
API(API_WS2812_WRITE, void epic_ws2812_write(uint8_t pin, uint8_t *pixels, uint32_t n_bytes));
* Configuration
* =============
* Read an integer from the configuration file
* :param char* key: Name of the option to read
* :param int* value: Place to read the value into
* :return: `0` on success or a negative value if an error occured. Possible
* errors:
* - ``-ENOENT``: Value can not be read
* .. versionadded:: 1.13
API(API_CONFIG_GET_INTEGER, int epic_config_get_integer(const char *key, int *value));
* Read a boolean from the configuration file
* :param char* key: Name of the option to read
* :param bool* value: Place to read the value into
* :return: `0` on success or a negative value if an error occured. Possible
* errors:
* - ``-ENOENT``: Value can not be read
* .. versionadded:: 1.13
API(API_CONFIG_GET_BOOLEAN, int epic_config_get_boolean(const char *key, bool *value));
* Read a string from the configuration file.
* If the buffer supplied is too small for the config option,
* no error is reported and the first `buf_len - 1` characters
* are returned (0 terminated).
* :param char* key: Name of the option to read
* :param char* buf: Place to read the string into
* :param size_t buf_len: Size of the provided buffer
* :return: `0` on success or a negative value if an error occured. Possible
* errors:
* - ``-ENOENT``: Value can not be read
* .. versionadded:: 1.13
API(API_CONFIG_GET_STRING, int epic_config_get_string(const char *key, char *buf, size_t buf_len));
#endif /* _EPICARDIUM_H */
......@@ -25,15 +25,12 @@
#include "modules/log.h"
#include "modules/modules.h"
#include "api/common.h"
#include "modules/mutex.h"
#define SSLOG_DEBUG(...) LOG_DEBUG("fatfs", __VA_ARGS__)
#define SSLOG_INFO(...) LOG_INFO("fatfs", __VA_ARGS__)
#define SSLOG_ERR(...) LOG_ERR("fatfs", __VA_ARGS__)
/* clang-format off */