diff --git a/Documentation/card10-cfg.rst b/Documentation/card10-cfg.rst index 3b3fdf7ab2f25229a314f96734b4cfe613c3abbf..8ad094c0e8c2a8de425990bb72c5eb8e05472698 100644 --- a/Documentation/card10-cfg.rst +++ b/Documentation/card10-cfg.rst @@ -40,4 +40,6 @@ 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`` +--------------- ---------- ----------- +``default_app`` String Full path to the exectutable file of the default application. If this option is not set,``apps/analog_clock/__init__.py`` is used. =============== ========== =========== diff --git a/Documentation/conf.py b/Documentation/conf.py index d5c08c5f4b769dea51941bc31451da0b19eee0df..b80d8dadd0485fdaee5a14577412eb6972d6b213 100644 --- a/Documentation/conf.py +++ b/Documentation/conf.py @@ -62,6 +62,8 @@ class ColorExample(rst.Directive): 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 ------------------------------------------------- {{{ @@ -112,6 +114,7 @@ autodoc_mock_imports = [ "sys_display", "sys_leds", "sys_max30001", + "sys_config", "ucollections", "urandom", "utime", diff --git a/Documentation/index.rst b/Documentation/index.rst index 6693f3c2637eb5f53ed908623723231bfd8a6559..f6ce6fcd564b08a7f01229a1a235334bb8d1c7c2 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -27,6 +27,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the pycardium/max30001 pycardium/buttons pycardium/color + pycardium/config pycardium/display pycardium/gpio pycardium/leds diff --git a/Documentation/pycardium/config.rst b/Documentation/pycardium/config.rst new file mode 100644 index 0000000000000000000000000000000000000000..926098eb1daa66b553732648eb344634626cb621 --- /dev/null +++ b/Documentation/pycardium/config.rst @@ -0,0 +1,9 @@ +``config`` - Configuration +========================== +The ``config`` module provides functions to interact with card10's +configuration file (``card10.cfg``). + +.. automodule:: config + :members: + + diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 84071d042c85643dfc181549f038e46c9c4b354a..f2398732994160a13b949f79825060221d53587f 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -145,6 +145,7 @@ typedef _Bool bool; #define API_CONFIG_GET_STRING 0x130 #define API_CONFIG_GET_INTEGER 0x131 #define API_CONFIG_GET_BOOLEAN 0x132 +#define API_CONFIG_SET_STRING 0x133 /* clang-format on */ typedef uint32_t api_int_id_t; @@ -2060,5 +2061,21 @@ API(API_CONFIG_GET_BOOLEAN, int epic_config_get_boolean(const char *key, bool *v API(API_CONFIG_GET_STRING, int epic_config_get_string(const char *key, char *buf, size_t buf_len)); +/** + * Write a string to the configuration file. + * + * :param char* key: Name of the option to write + * :param char* value: The value to write + * :return: `0` on success or a negative value if an error occured. Possivle + * errors: + * + * - ``-EINVAL``: Parameters out of range + * - ``-ENOENT``: Key already exists but can not be read + * - ``-EIO`` : Unspecified I/O error + * - Any fopen/fread/fwrite/fclose related error code + * + * .. versionadded:: 1.16 + */ +API(API_CONFIG_SET_STRING, int epic_config_set_string(const char *key, const char *value)); #endif /* _EPICARDIUM_H */ diff --git a/epicardium/modules/config.c b/epicardium/modules/config.c index 7371f02a94d984b67b60b3652651015484eb582a..d4c2b74e0b2fed4915ae2670a8da39b078f6042c 100644 --- a/epicardium/modules/config.c +++ b/epicardium/modules/config.c @@ -7,9 +7,11 @@ #include <stdbool.h> #include <ctype.h> #include <string.h> +#include <strings.h> #include <stdlib.h> #include <unistd.h> #include <stddef.h> +#include <stdio.h> #define MAX_LINE_LENGTH 80 #define KEYS_PER_BLOCK 16 @@ -125,25 +127,39 @@ static void add_config_pair( slot->value_offset = value_offset; } +static void trim(char *str) +{ + char *start = str; + while (*start && !isgraph(*start)) + start++; + + if (strlen(start) > 0) { + char *end = start + strlen(start) - 1; + while (*end && !isgraph(*end)) + end--; + end[1] = 0; + } + + memmove(str, start, strlen(start) + 1); +} + // parses one line of the config file -static void -parse_line(char *line, char *eol, int line_number, size_t line_offset) +static void parse_line(char *line, int line_number, size_t line_offset) { - char *line_start = line; + char *line_start = line; + char *value_start = strchr(line, '=') + 1; - //skip leading whitespace - while (*line && isspace((int)*line)) - ++line; + trim(line); - char *key = line; - if (*key == '#') { + //printf(line); + if (*line == '#') { //skip comments return; } char *eq = strchr(line, '='); if (!eq) { - if (*key) { + if (*line) { LOG_WARN( "card10.cfg", "line %d: syntax error", @@ -152,26 +168,19 @@ parse_line(char *line, char *eol, int line_number, size_t line_offset) } return; } + *eq = 0; + + char *key = line; + trim(key); - char *e_key = eq - 1; - //skip trailing whitespace in key - while (e_key > key && isspace((int)*e_key)) - --e_key; - e_key[1] = '\0'; if (*key == '\0') { LOG_WARN("card10.cfg", "line %d: empty key", line_number); return; } char *value = eq + 1; - //skip leading whitespace - while (*value && isspace((int)*value)) - ++value; - - char *e_val = eol - 1; - //skip trailing whitespace - while (e_val > value && isspace((int)*e_val)) - --e_val; + trim(value); + if (*value == '\0') { LOG_WARN( "card10.cfg", @@ -182,21 +191,44 @@ parse_line(char *line, char *eol, int line_number, size_t line_offset) return; } - size_t value_offset = value - line_start + line_offset; + size_t value_offset = value_start - line_start + line_offset; add_config_pair(key, value, line_number, value_offset); } -// convert windows line endings to unix line endings. -// we don't care about the extra empty lines -static void convert_crlf_to_lflf(char *buf, int n) +typedef struct { + int line_number; + int file_offset; + int line_start; + char line[MAX_LINE_LENGTH + 1]; + int line_length; +} parser_state; + +int parse_character(char c, parser_state *s) { - while (n--) { - if (*buf == '\r') { - *buf = '\n'; + if (c != '\r' && c != '\n') { + if (s->line_length == MAX_LINE_LENGTH) { + LOG_WARN( + "card10.cfg", + "line:%d: too long - aborting", + s->line_number + ); + return -1; + } + s->line[s->line_length++] = c; + } else { + s->line[s->line_length] = 0; + //printf("New line: %s (%d %d)\n", s->line, s->line_number, s->line_start); + parse_line(s->line, s->line_number, s->line_start); + s->line_length = 0; + s->line_start = s->file_offset + 1; + if (c == '\n') { + s->line_number++; } - buf++; } + + s->file_offset++; + return 0; } // parses the entire config file @@ -213,77 +245,21 @@ void load_config(void) ); return; } - char buf[MAX_LINE_LENGTH + 1]; - int line_number = 0; - size_t file_offset = 0; + + char buf[128]; int nread; + parser_state s; + memset(&s, 0, sizeof(s)); + s.line_number = 1; do { - nread = epic_file_read(fd, buf, MAX_LINE_LENGTH); - convert_crlf_to_lflf(buf, nread); - if (nread < MAX_LINE_LENGTH) { - //add fake EOL to ensure termination - buf[nread++] = '\n'; + nread = epic_file_read(fd, buf, sizeof(buf)); + int i; + for (i = 0; i < nread; i++) { + parse_character(buf[i], &s); } - //zero-terminate buffer - buf[nread] = '\0'; - char *line = buf; - char *eol = NULL; - int last_eol = 0; - while (line) { - //line points one character past the last (if any) '\n' hence '- 1' - last_eol = line - buf - 1; - eol = strchr(line, '\n'); - ++line_number; - if (eol) { - *eol = '\0'; - parse_line(line, eol, line_number, file_offset); - file_offset += eol - line + 1; - line = eol + 1; - continue; - } - if (line == buf) { - //line did not fit into buf - LOG_WARN( - "card10.cfg", - "line:%d: too long - aborting", - line_number - ); - return; - } - int seek_back = last_eol - nread; + } while (nread == sizeof(buf)); + parse_character('\n', &s); - LOG_DEBUG( - "card10.cfg", - "nread, last_eol, seek_back: %d,%d,%d", - nread, - last_eol, - seek_back - ); - assert(seek_back <= 0); - if (!seek_back) { - break; - } - - 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' && newline != '\r')) { - LOG_ERR("card10.cfg", "read failed, aborting"); - LOG_DEBUG( - "card10.cfg", - "read failed at read-back of newline: rc: %d read: %d", - rc, - (int)newline - ); - return; - } - break; - } - } while (nread == MAX_LINE_LENGTH); epic_file_close(fd); } @@ -355,10 +331,11 @@ int epic_config_get_string(const char *key, char *buf, size_t buf_len) return -ENOENT; } - char *eol = strchr(buf, '\n'); - if (eol) { - *eol = '\0'; - } + char *end = buf; + while (*end && !iscntrl(*end)) + end++; + *end = 0; + trim(buf); return 0; } @@ -389,10 +366,10 @@ int epic_config_get_boolean(const char *key, bool *value) char buf[MAX_LINE_LENGTH]; epic_config_get_string(key, buf, MAX_LINE_LENGTH); - if (!strcmp(buf, "true")) { + if (!strcasecmp(buf, "true")) { *value = true; return 0; - } else if (!strcmp(buf, "false")) { + } else if (!strcasecmp(buf, "false")) { *value = false; return 0; } @@ -411,3 +388,256 @@ bool config_get_boolean_with_default(const char *key, bool default_value) return value; } } + +int epic_config_set_string(const char *key, const char *value_in) +{ + char value[MAX_LINE_LENGTH + 1]; + + if (strlen(key) > MAX_LINE_LENGTH) { + return -EINVAL; + } + + /* TODO: Change interface of trim to take the buffer and size directly */ + if (strlen(value_in) > MAX_LINE_LENGTH) { + return -EINVAL; + } + + strcpy(value, value_in); + trim(value); + + if (snprintf(NULL, 0, "\n%s = %s\n", key, value) > MAX_LINE_LENGTH) { + return -EINVAL; + } + + /* Check if key is sane. No control characters, spaces, equal signs or pounds allowed */ + for (size_t i = 0; i < strlen(key); i++) { + char c = key[i]; + if (!isgraph(c) || c == '=' || c == '#') { + return -EINVAL; + } + } + /* Check if value is sane. No control characters allowed */ + for (size_t i = 0; i < strlen(value); i++) { + char c = value[i]; + if (!isprint(c)) { + return -EINVAL; + } + } + + config_slot *slot = find_config_slot(key); + bool present = slot && slot->value_offset; + int ret = 0; + + if (!present) { + /* Easy case: We simply add the new option at the + * end of the file. */ + + char buf[MAX_LINE_LENGTH]; + /* Leading new line because I'm lazy */ + ret = snprintf(buf, sizeof(buf), "\n%s = %s\n", key, value); + + if (ret < 0 || ret >= (int)sizeof(buf)) { + return -EINVAL; + } + + int fd = epic_file_open("card10.cfg", "a"); + if (fd < 0) { + LOG_DEBUG( + "card10.cfg", + "open for appending failed: %s (%d)", + strerror(-fd), + fd + ); + return fd; + } + + int write_ret = epic_file_write(fd, buf, strlen(buf)); + if (write_ret < 0) { + LOG_DEBUG( + "card10.cfg", + "writing failed: %s (%d)", + strerror(-write_ret), + write_ret + ); + } + + if (write_ret < (int)strlen(buf)) { + LOG_DEBUG( + "card10.cfg", + "writing failed to write all bytes (%d of %d)", + write_ret, + strlen(buf) + ); + } + + ret = epic_file_close(fd); + + if (ret < 0) { + LOG_DEBUG( + "card10.cfg", + "close failed: %s (%d)", + strerror(-ret), + ret + ); + } + if (write_ret < 0) { + ret = write_ret; + } + if (ret < 0) { + goto out; + } + if (write_ret < (int)strlen(buf)) { + LOG_DEBUG( + "card10.cfg", + "writing failed to write all bytes (%d of %d)", + write_ret, + strlen(buf) + ); + ret = -EIO; + goto out; + } + } else { + /* Complex case: The value is already somewhere in the file. + * We do not want to lose existing formatting or comments. + * Solution: Copy parts of the file, insert new value, copy + * rest, rename. + */ + char buf[MAX_LINE_LENGTH + 1]; + int fd1 = -1; + int fd2 = -1; + ret = epic_config_get_string(key, buf, sizeof(buf)); + + size_t nread = read_config_offset( + slot->value_offset, buf, sizeof(buf) + ); + if (nread == 0) { + LOG_DEBUG("card10.cfg", "could not read old value", ); + goto complex_out; + } + + char *end = buf; + while (*end && (!iscntrl(*end) || isblank(*end))) + end++; + *end = 0; + + int old_len = strlen(buf); + + fd1 = epic_file_open("card10.cfg", "r"); + if (fd1 < 0) { + LOG_DEBUG( + "card10.cfg", + "open for read failed: %s (%d)", + strerror(-fd1), + fd1 + ); + ret = fd1; + goto complex_out; + } + + fd2 = epic_file_open("card10.nfg", "w"); + if (fd2 < 0) { + LOG_DEBUG( + "card10.nfg", + "open for writing failed: %s (%d)", + strerror(-fd2), + fd2 + ); + ret = fd2; + goto complex_out; + } + + /* Copy over slot->value_offset bytes */ + int i = slot->value_offset; + while (i > 0) { + int n = i > (int)sizeof(buf) ? (int)sizeof(buf) : i; + ret = epic_file_read(fd1, buf, n); + if (ret < 0) { + LOG_DEBUG( + "card10.cfg", + "read failed: rc: %d", + ret + ); + goto complex_out; + } + + int ret2 = epic_file_write(fd2, buf, ret); + + if (ret2 < 0) { + ret = ret2; + LOG_DEBUG( + "card10.nfg", + "write failed: rc: %d", + ret + ); + goto complex_out; + } + i -= ret; + } + + /* Insert new value into the new file */ + ret = epic_file_write(fd2, value, strlen(value)); + if (ret < 0) { + LOG_DEBUG("card10.nfg", "write failed: rc: %d", ret); + goto complex_out; + } + + /* Skip the old value inside the old file */ + epic_file_seek(fd1, old_len, SEEK_CUR); + + /* Copy the rest of the old file to the new file */ + while (true) { + int ret = epic_file_read(fd1, buf, sizeof(buf)); + + if (ret == 0) { + break; + } + + if (ret < 0) { + LOG_DEBUG( + "card10.cfg", + "read failed: rc: %d", + ret + ); + goto complex_out; + } + + int ret2 = epic_file_write(fd2, buf, ret); + + if (ret2 < 0) { + ret = ret2; + LOG_DEBUG( + "card10.nfg", + "write failed: rc: %d", + ret + ); + goto complex_out; + } + + if (ret < (int)sizeof(buf)) { + break; + } + } + + complex_out: + if (fd1 >= 0) { + epic_file_close(fd1); + } + if (fd2 >= 0) { + int ret2 = epic_file_close(fd2); + if (ret >= 0) { + ret = ret2; + } + } + + if (ret >= 0) { + epic_file_unlink("card10.cfg"); + epic_file_rename("card10.nfg", "card10.cfg"); + } + } + +out: + /* Reload config so the new key or the changed value is available */ + load_config(); + + return ret < 0 ? ret : 0; +} diff --git a/epicardium/modules/usb.c b/epicardium/modules/usb.c index 572b2b5fcffa8fd2684c28b60c4d9f2937fdd3f9..5129042559d6af9079a0464dc419b6021a3678e3 100644 --- a/epicardium/modules/usb.c +++ b/epicardium/modules/usb.c @@ -12,6 +12,7 @@ #include "epicardium.h" #include "modules/filesystem.h" +#include "modules/config.h" #include "usb/cdcacm.h" #include "usb/mass_storage.h" @@ -139,6 +140,7 @@ int epic_usb_shutdown(void) esb_deinit(); if (s_fsDetached) { fatfs_attach(); + load_config(); } return 0; } @@ -155,6 +157,7 @@ int epic_usb_cdcacm(void) esb_deinit(); if (s_fsDetached) { fatfs_attach(); + load_config(); } return esb_init(&s_cfg_cdcacm); } diff --git a/preload/main.py b/preload/main.py index 7c8417e9ae1375779ef5a7c2fd4fdcca35cd36b7..eca0ccb401ba2474e61608a992c6fc377d54ed30 100644 --- a/preload/main.py +++ b/preload/main.py @@ -3,7 +3,13 @@ import os def main(): # Try loading analog clock - default_app = "apps/analog_clock/__init__.py" + try: + import config + + default_app = config.get_string("default_app") + except OSError: + default_app = "apps/analog_clock/__init__.py" + try: with open(default_app, "r"): pass diff --git a/preload/menu.py b/preload/menu.py index 7a9ad11b9105a9f79564895d478b60c5fa067903..1ace36aa43f5d5ccfe0106eba245320517263ca6 100644 --- a/preload/menu.py +++ b/preload/menu.py @@ -90,6 +90,24 @@ def usb_mode(disp): os.exit(0) +class SubMenu(simple_menu.Menu): + timeout = 30.0 + + def on_select(self, name, index): + print(name) + if name == "Make Default": + self.disp.clear(color.CHAOSBLUE) + self.disp.print("Making", posx=0, posy=20) + self.disp.print(self.app.name, posx=0, posy=40) + self.disp.print("default", posx=0, posy=60) + self.disp.update() + import config + + config.set_string("default_app", self.app.path) + utime.sleep(1) + self.exit() + + class MainMenu(simple_menu.Menu): timeout = 30.0 @@ -112,6 +130,14 @@ class MainMenu(simple_menu.Menu): utime.sleep(1.0) os.exit(1) + def on_long_select(self, app, index): + print("Long press\n") + if app.path not in ["main.py", "USB_STORAGE_FLAG"]: + # sm = SubMenu(("Make Default", "Fav", "Unfav", "Delete", "Exit")) + sm = SubMenu(("Make Default", "Exit", "", "")) + sm.app = app + sm.run() + def on_timeout(self): try: f = open("main.py") diff --git a/pycardium/meson.build b/pycardium/meson.build index 931662c251b4c52bc3cb6305351bb9f90fb67aad..d12e62f5ed6e58cd8699890a404ffc23a4b33f15 100644 --- a/pycardium/meson.build +++ b/pycardium/meson.build @@ -3,6 +3,7 @@ name = 'pycardium' modsrc = files( 'modules/bhi160-sys.c', 'modules/buttons.c', + 'modules/sys_config.c', 'modules/fat_file.c', 'modules/fat_reader_import.c', 'modules/gpio.c', diff --git a/pycardium/modules/py/config.py b/pycardium/modules/py/config.py new file mode 100644 index 0000000000000000000000000000000000000000..8d70936ff2ba9c161712ad07a244f6ebe583a646 --- /dev/null +++ b/pycardium/modules/py/config.py @@ -0,0 +1,48 @@ +import sys_config + + +def set_string(key, value): + """ + Write a string to the configuration file ``card10.cfg``. + + Both ``key`` and ``value`` must be strings or must be + convertible to a string using the ``str()`` function. + + ``key`` must not contain spaces, control characters (including tabs), + number signs ans equal signs. + ``value` must not contain control characters (including tabs). + Neither is allowed to contain the sub-string ``execute_elf``. + + The key/value pair is immediately written to the configuration + file (``card10.cfg``). After the file is written, configuration + is read again and the new value is available via ``config.get_string``. + + :param str key: Name of the configuration option. + :param str value: Value to write. + :raises OSError: If writing to the configuration file failed. + :raises OSError: If key or value contain illegal characters. + :raises ValueError: If key or value contain the sub-string ``execute_elf``. + + .. versionadded:: 1.16 + """ + + sys_config.set_string(str(key), str(value)) + + +def get_string(key): + """ + Read a string from the configuration file ``card10.cfg``. + + ``key`` must be a string or must be convertible to a string using + the ``str()`` function. + + + :param str key: Name of the configuration option. + :rtype: str + :returns: Value of the configuration option. + :raises OSError: if the key is not present in the configuration. + + .. versionadded:: 1.16 + """ + + return sys_config.get_string(str(key)) diff --git a/pycardium/modules/py/meson.build b/pycardium/modules/py/meson.build index fe26c64e2c87640a580e70f535d46d5a5b1b961f..b8d3310f053c8a46febe1935b15686cbcff86c6a 100644 --- a/pycardium/modules/py/meson.build +++ b/pycardium/modules/py/meson.build @@ -1,4 +1,5 @@ python_modules = files( + 'config.py', 'bhi160.py', 'bme680.py', 'color.py', diff --git a/pycardium/modules/py/simple_menu.py b/pycardium/modules/py/simple_menu.py index b4b27ef5741ce442c4e7187fec9efea75f1a682b..8ff0f62da0b67bd95a6acf8e3a0319bf1b339475 100644 --- a/pycardium/modules/py/simple_menu.py +++ b/pycardium/modules/py/simple_menu.py @@ -141,6 +141,18 @@ class Menu: """ pass + def on_long_select(self, item, index): + """ + Hook when an item as selected using a long press. + + The given ``index`` was selected with a long SELECT button press. Overwrite + this function in your menu to perform an action on select. + + :param item: The item which was selected. + :param int index: Index into the ``entries`` list of the ``item``. + """ + pass + def on_select(self, item, index): """ Hook when an item as selected. @@ -238,9 +250,7 @@ class Menu: offset + 20, col=self.color_1 if index % 2 == 0 else self.color_2, ) - self.disp.print( - string, posx=14, posy=offset, fg=self.color_text, bg=None, - ) + self.disp.print(string, posx=14, posy=offset, fg=self.color_text, bg=None) def draw_menu(self, offset=0): """ @@ -318,8 +328,18 @@ class Menu: print("Exception during menu.on_scroll():") sys.print_exception(e) elif ev == self.button_select: + t0 = utime.time() + long_press = False + while buttons.read(buttons.TOP_RIGHT) > 0: + if utime.time() - t0 > 1: + long_press = True + break + try: - self.on_select(self.entries[self.idx], self.idx) + if long_press: + self.on_long_select(self.entries[self.idx], self.idx) + else: + self.on_select(self.entries[self.idx], self.idx) self.select_time = utime.time_ms() except _ExitMenuException: raise diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h index c2c1fdace490c618807adca14bca281276714741..3a6e9dfcf47dfd16160b9b0cf76809436176bc55 100644 --- a/pycardium/modules/qstrdefs.h +++ b/pycardium/modules/qstrdefs.h @@ -188,3 +188,7 @@ Q(MAX86150) /* ws2812 */ Q(ws2812) Q(set_all) + +Q(config) +Q(set_string) +Q(get_string) diff --git a/pycardium/modules/sys_config.c b/pycardium/modules/sys_config.c new file mode 100644 index 0000000000000000000000000000000000000000..a811a19a82ec75fba161cb40280475039348bf83 --- /dev/null +++ b/pycardium/modules/sys_config.c @@ -0,0 +1,85 @@ +#include "epicardium.h" + +#include "py/obj.h" +#include "py/runtime.h" + +#include <string.h> +#include <strings.h> + +#include <stdbool.h> + +static void extract_string( + mp_obj_t obj, char *buf, size_t buf_len, const char *error_message +) { + size_t len; + const char *str_ptr = mp_obj_str_get_data(obj, &len); + /* + * The string retrieved from MicroPython is not NULL-terminated so we + * first need to copy it and add a NULL-byte. + */ + if (len > (buf_len - 1)) { + mp_raise_ValueError(error_message); + } + memcpy(buf, str_ptr, len); + buf[len] = '\0'; +} + +static mp_obj_t mp_config_set_string(mp_obj_t key_in, mp_obj_t value_in) +{ + const char *const forbidden_key = "execute_elf"; + + char key_str[128], value_str[128]; + + extract_string(key_in, key_str, sizeof(key_str), "key too long"); + extract_string( + value_in, value_str, sizeof(value_str), "value too long" + ); + + if (strstr(key_str, forbidden_key) != NULL || + strstr(value_str, forbidden_key) != NULL) { + /* A Permission Error might be nice but is not available in MP */ + mp_raise_ValueError("Not allowed"); + } + + int status = epic_config_set_string(key_str, value_str); + if (status < 0) { + mp_raise_OSError(-status); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(set_string_obj, mp_config_set_string); + +static mp_obj_t mp_config_get_string(mp_obj_t key_in) +{ + char key_str[128], value_str[128]; + extract_string(key_in, key_str, sizeof(key_str), "key too long"); + + int status = + epic_config_get_string(key_str, value_str, sizeof(value_str)); + if (status < 0) { + mp_raise_OSError(-status); + } + + mp_obj_t ret = mp_obj_new_str(value_str, strlen(value_str)); + return ret; +} +static MP_DEFINE_CONST_FUN_OBJ_1(get_string_obj, mp_config_get_string); + +static const mp_rom_map_elem_t config_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_sys_config) }, + { MP_ROM_QSTR(MP_QSTR_set_string), MP_ROM_PTR(&set_string_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_string), MP_ROM_PTR(&get_string_obj) }, +}; + +static MP_DEFINE_CONST_DICT(config_module_globals, config_module_globals_table); + +// Define module object. +const mp_obj_module_t config_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&config_module_globals, +}; + +/* This is a special macro that will make MicroPython aware of this module */ +/* clang-format off */ +MP_REGISTER_MODULE(MP_QSTR_sys_config, config_module, MODULE_CONFIG_ENABLED); diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h index 3d6ae86af0bb255b111f1c04cdb0c6a2c837414a..2b7059c895ae9d98c66b26c1c0319b09e6a0c71f 100644 --- a/pycardium/mpconfigport.h +++ b/pycardium/mpconfigport.h @@ -64,6 +64,7 @@ int mp_hal_trng_read_int(void); #define MODULE_UTIME_ENABLED (1) #define MODULE_VIBRA_ENABLED (1) #define MODULE_WS2812_ENABLED (1) +#define MODULE_CONFIG_ENABLED (1) /* * This port is intended to be 32-bit, but unfortunately, int32_t for