From 3d0c4a84582a2ddbea9668db21673120f1369cee Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Fri, 1 May 2026 15:10:12 -0400 Subject: [PATCH 1/4] add float support to settings.toml and supervisor.get_setting() --- shared-bindings/supervisor/__init__.c | 9 ++- supervisor/shared/settings.c | 62 +++++++++++++++++ supervisor/shared/settings.h | 15 ++++- tests/circuitpython-manual/settings/code.py | 66 +++++++++++++++++++ .../settings/settings.toml | 16 +++++ 5 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 tests/circuitpython-manual/settings/code.py create mode 100644 tests/circuitpython-manual/settings/settings.toml diff --git a/shared-bindings/supervisor/__init__.c b/shared-bindings/supervisor/__init__.c index a0a01f124f786..1200679f2e2a3 100644 --- a/shared-bindings/supervisor/__init__.c +++ b/shared-bindings/supervisor/__init__.c @@ -364,13 +364,13 @@ static mp_obj_t supervisor_set_usb_identification(size_t n_args, const mp_obj_t } MP_DEFINE_CONST_FUN_OBJ_KW(supervisor_set_usb_identification_obj, 0, supervisor_set_usb_identification); -//| def get_setting(key: str, default: object=None) -> int | str | bool: +//| def get_setting(key: str, default: object=None) -> int | float | str | bool: //| """ //| Get and parse the value for the given ``key`` from the ``/settings.toml`` file. //| If ``key`` is not found or ``settings.toml`` is not present, return the ``default`` value. //| //| :param str key: The setting key to retrieve -//| :return: The setting value as an ``int``, ``str``, or ``bool`` depending on the value in the file +//| :return: The setting value as an ``int``, ``float``, ``str``, or ``bool`` depending on the value in the file //| //| :raises ValueError: If the value cannot be parsed as a valid TOML value. //| @@ -380,8 +380,9 @@ MP_DEFINE_CONST_FUN_OBJ_KW(supervisor_set_usb_identification_obj, 0, supervisor_ //| The string may include Unicode characters, and ``\\u`` Unicode escapes. Backslash-escaped characters //| ``\\b``, ``\\r``, ``\\n``, ``\\t``, ``\\v``, ``\\v`` are also allowed. //| - ``int``: signed or unsigned integer -//| - lower-case boolean words ``true`` and ``false``. +//| - ``bool``: lower-case `true`` or ``false``. //| The values are returned as Python ``True`` or ``False`` values. +//| - ``float``: signed or unsigned decimal number with a ``.`` or exponent, or ``inf``, ``inf``, ``nan``. //| //| Example:: //| @@ -389,11 +390,13 @@ MP_DEFINE_CONST_FUN_OBJ_KW(supervisor_set_usb_identification_obj, 0, supervisor_ //| WIDTH = 42 //| color = "red" //| DEBUG = true +//| RATIO = 1.5 //| //| import supervisor //| print(supervisor.get_setting("WIDTH")) # prints 42 //| print(supervisor.get_setting("color")) # prints 'red' //| print(supervisor.get_setting("DEBUG")) # prints True +//| print(supervisor.get_setting("RATIO")) # prints 1.5 //| """ //| ... //| diff --git a/supervisor/shared/settings.c b/supervisor/shared/settings.c index d1b05a5da9db5..1414195411e66 100644 --- a/supervisor/shared/settings.c +++ b/supervisor/shared/settings.c @@ -5,6 +5,7 @@ // // SPDX-License-Identifier: MIT +#include #include #include #include @@ -419,6 +420,57 @@ settings_err_t settings_get_bool(const char *key, bool *value) { return result; } +#if MICROPY_PY_BUILTINS_FLOAT +static settings_err_t get_float(const char *key, mp_float_t *value) { + char buf[48]; + bool quoted; + settings_err_t result = settings_get_buf_terminated(key, buf, sizeof(buf), "ed); + if (result != SETTINGS_OK) { + return result; + } + if (quoted) { + return SETTINGS_ERR_BAD_VALUE; + } + + const char *str = buf; + const char *end = buf + strlen(buf); + bool neg = false; + + if (str < end && (*str == '+' || *str == '-')) { + neg = (*str == '-'); + str++; + } + + mp_float_t val; + if (end - str >= 3 && str[0] == 'i' && str[1] == 'n' && str[2] == 'f') { + str += 3; + val = (mp_float_t)INFINITY; + } else if (end - str >= 3 && str[0] == 'n' && str[1] == 'a' && str[2] == 'n') { + str += 3; + val = MICROPY_FLOAT_C_FUN(nan)(""); + } else { + const char *num_start = str; + str = mp_parse_float_internal(str, end - str, &val); + if (str == NULL || str == num_start) { + return SETTINGS_ERR_BAD_VALUE; + } + } + + if (str != end) { + return SETTINGS_ERR_BAD_VALUE; + } + + *value = neg ? -val : val; + return SETTINGS_OK; +} + +settings_err_t settings_get_float(const char *key, mp_float_t *value) { + settings_err_t result = get_float(key, value); + print_error(key, result); + return result; +} +#endif + // Get the raw value as a vstr, whether quoted or bare. Value may be an invalid TOML value. settings_err_t settings_get_raw_vstr(const char *key, vstr_t *vstr) { bool quoted; @@ -457,6 +509,16 @@ settings_err_t settings_get_obj(const char *key, mp_obj_t *value) { return SETTINGS_OK; } + // Not an integer, try float + #if MICROPY_PY_BUILTINS_FLOAT + mp_float_t float_val; + result = get_float(key, &float_val); + if (result == SETTINGS_OK) { + *value = mp_obj_new_float(float_val); + return SETTINGS_OK; + } + #endif + return SETTINGS_ERR_BAD_VALUE; } diff --git a/supervisor/shared/settings.h b/supervisor/shared/settings.h index eafa9962e9527..6d92d45d79a42 100644 --- a/supervisor/shared/settings.h +++ b/supervisor/shared/settings.h @@ -10,6 +10,7 @@ typedef enum { SETTINGS_OK = 0, + // The settings file could not be opened. SETTINGS_ERR_OPEN, SETTINGS_ERR_UNICODE, SETTINGS_ERR_LENGTH, @@ -20,7 +21,7 @@ typedef enum { // Read a string value from the settings file. // If it fits, the return value is 0-terminated. The passed-in buffer // may be modified even if an error is returned. Allocation free. -// An error that is not 'open' or 'not found' is printed on the repl. +// An error that is not SETTINGS_ERR_OPEN or SETTINGS_ERR_NOT_FOUND is printed on the repl. // Returns an error if the value is not a quoted string. settings_err_t settings_get_str(const char *key, char *value, size_t value_len); @@ -38,13 +39,23 @@ settings_err_t settings_get_int(const char *key, mp_int_t *value); // An error that is not 'open' or 'not found' is printed on the repl. settings_err_t settings_get_bool(const char *key, bool *value); +#if MICROPY_PY_BUILTINS_FLOAT +// Read a float value from the settings file. +// Returns SETTINGS_OK and sets value to the read value. Returns +// SETTINGS_ERR_... if the value was not a float. allocation-free. +// If any error code is returned, value is guaranteed not modified. +// An error that is not SETTINGS_ERR_OPEN or SETTINGS_ERR_NOT_FOUND is printed on the repl. +settings_err_t settings_get_float(const char *key, mp_float_t *value); +#endif + // Read a value from the settings file and return as parsed Python object. // Returns SETTINGS_OK and sets value to a parsed Python object from the RHS value: // - Quoted strings return as str // - Bare "true" or "false" return as bool // - Valid integers return as int +// - Valid floats (containing '.' or 'e'/'E') return as float // Returns SETTINGS_ERR_... if the value is not parseable as one of these types. -// An error that is not 'open' or 'not found' is printed on the repl. +// An error that is not SETTINGS_ERR_OPEN or SETTINGS_ERR_NOT_FOUND is printed on the repl. settings_err_t settings_get_obj(const char *key, mp_obj_t *value); // Read the raw value as a string, whether quoted or bare. diff --git a/tests/circuitpython-manual/settings/code.py b/tests/circuitpython-manual/settings/code.py new file mode 100644 index 0000000000000..bebed3d11ae9e --- /dev/null +++ b/tests/circuitpython-manual/settings/code.py @@ -0,0 +1,66 @@ +# Test supervisor.get_setting() parsing of settings.toml values. +# Copy settings.toml to the root of the board, then run this script. +# All lines should print PASS. + +import math +import supervisor + + +def check(key, expected): + got = supervisor.get_setting(key) + if type(got) is not type(expected): + print( + f"FAIL {key}: expected type {type(expected).__name__}, got {type(got).__name__} ({got!r})" + ) + return + if isinstance(expected, float) and math.isnan(expected): + ok = math.isnan(got) + else: + ok = got == expected + if ok: + print(f"PASS {key}") + else: + print(f"FAIL {key}: expected {expected!r}, got {got!r}") + + +check("str_val", "hello") +check("int_val", 42) +check("neg_int_val", -7) +check("hex_int_val", 31) +check("bool_true", True) +check("bool_false", False) +check("float_val", 3.14) +check("neg_float_val", -1.5) +check("sci_float_val", 6.626e-34) +# math.nan and math.inf are not always available +check("pos_inf", float("inf")) +check("neg_inf", -float("inf")) +check("pos_nan", float("nan")) + + +def check_bad(key): + try: + got = supervisor.get_setting(key) + print(f"FAIL {key}: expected ValueError, got {got!r}") + except ValueError: + print(f"PASS {key} raises ValueError") + + +check_bad("bad_word") # unquoted non-boolean word +check_bad("bad_float_junk") # float with trailing garbage +check_bad("bad_inf_junk") # inf with trailing characters +check_bad("bad_lone_sign") # bare + with nothing after it + +# Missing key returns default +got = supervisor.get_setting("no_such_key", "default") +if got == "default": + print("PASS missing key default") +else: + print(f"FAIL missing key default: got {got!r}") + +# Invalid value raises ValueError +try: + supervisor.get_setting("str_val") # quoted string is valid + print("PASS str via get_setting") +except ValueError: + print("FAIL str via get_setting raised ValueError unexpectedly") diff --git a/tests/circuitpython-manual/settings/settings.toml b/tests/circuitpython-manual/settings/settings.toml new file mode 100644 index 0000000000000..5df2d104afe0d --- /dev/null +++ b/tests/circuitpython-manual/settings/settings.toml @@ -0,0 +1,16 @@ +str_val = "hello" +bad_word = hello +bad_float_junk = 1.2abc +bad_inf_junk = infoo +bad_lone_sign = + +int_val = 42 +neg_int_val = -7 +hex_int_val = 0x1F +bool_true = true +bool_false = false +float_val = 3.14 +neg_float_val = -1.5 +sci_float_val = 6.626e-34 +pos_inf = inf +neg_inf = -inf +pos_nan = nan From 017e88013d2c1cd472bd46e66587aa5fbc866250 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Sun, 3 May 2026 09:26:45 -0400 Subject: [PATCH 2/4] docs/shared_bindings_matrix: stdout gets mixed up when print-SETTING runs in parallel --- docs/shared_bindings_matrix.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/shared_bindings_matrix.py b/docs/shared_bindings_matrix.py index 8fbee4beea5b5..2f9eb7967881b 100644 --- a/docs/shared_bindings_matrix.py +++ b/docs/shared_bindings_matrix.py @@ -200,6 +200,8 @@ def get_settings_from_makefile(port_dir, board_name): contents = subprocess.run( [ "make", + # Don't let make run in parallel; it can mix up the output from the various "print-" targets. + "-j1", "-C", port_dir, "-f", From ef2777a6dc8c408c0b5e67a9b585cb8a5e10edb7 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Sun, 3 May 2026 09:37:35 -0400 Subject: [PATCH 3/4] docs/rstjinja.py: use raw string for regexp --- docs/rstjinja.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rstjinja.py b/docs/rstjinja.py index 04c855a1a4049..ab940dc713454 100644 --- a/docs/rstjinja.py +++ b/docs/rstjinja.py @@ -5,7 +5,7 @@ def render_with_jinja(docname, source): - if re.search("^\s*.. jinja$", source[0], re.M): + if re.search(r"^\s*.. jinja$", source[0], re.M): return True return False From 92d17a812b07e921d16e226e1943c2bed501d5a0 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Sun, 3 May 2026 09:38:33 -0400 Subject: [PATCH 4/4] shared-bindings/supervisor/__init__.c: fix doc typo --- shared-bindings/supervisor/__init__.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared-bindings/supervisor/__init__.c b/shared-bindings/supervisor/__init__.c index 1200679f2e2a3..a331249998a4f 100644 --- a/shared-bindings/supervisor/__init__.c +++ b/shared-bindings/supervisor/__init__.c @@ -379,8 +379,8 @@ MP_DEFINE_CONST_FUN_OBJ_KW(supervisor_set_usb_identification_obj, 0, supervisor_ //| - ``str``: Double-quoted string. //| The string may include Unicode characters, and ``\\u`` Unicode escapes. Backslash-escaped characters //| ``\\b``, ``\\r``, ``\\n``, ``\\t``, ``\\v``, ``\\v`` are also allowed. -//| - ``int``: signed or unsigned integer -//| - ``bool``: lower-case `true`` or ``false``. +//| - ``int``: signed or unsigned integer. +//| - ``bool``: lower-case ``true`` or ``false``. //| The values are returned as Python ``True`` or ``False`` values. //| - ``float``: signed or unsigned decimal number with a ``.`` or exponent, or ``inf``, ``inf``, ``nan``. //|