From baae361af1befe79d654adbe0c0ab3a587ef40a5 Mon Sep 17 00:00:00 2001 From: Luiz Villa Date: Wed, 29 Jul 2026 16:27:38 +0200 Subject: [PATCH 1/7] Add metaData.md progress tracker for spin.metaData implementation Checklist of the planned commit sequence so an interrupted implementation can resume from where it stopped. Co-Authored-By: Claude Sonnet 5 --- metaData.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 metaData.md diff --git a/metaData.md b/metaData.md new file mode 100644 index 00000000..3051ee69 --- /dev/null +++ b/metaData.md @@ -0,0 +1,30 @@ +# spin.metaData implementation progress + +Tracks progress of the `spin.metaData` feature (persist board/shield serial +numbers, versions, shield password, and 5 extra slots to flash via NVS). +Full design/rationale: see the plan this was generated from +(`based-on-the-owntech-cheerful-gizmo.md`). + +This file is a scratch progress tracker, not user-facing documentation — +safe to delete once all commits below are checked off and the feature has +landed. + +## Commits + +- [ ] **Commit 1** — flash driver: add `BOARD_METADATA` category to + `nvs_category_t` + `nvs_storage_get_free_space()` diagnostic + (`zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.h`, + `nvs_storage.c`) +- [ ] **Commit 2** — new `MetaDataAPI` class + (`zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h`, `.cpp`) +- [ ] **Commit 3** — wire `MetaDataAPI` into `SpinAPI` as `spin.metaData` + (`SpinAPI.h`, `SpinAPI.cpp`, `zephyr/modules/owntech_spin_api/zephyr/CMakeLists.txt`) +- [ ] **Commit 4** — test harness `main.cpp` with serial command menu + (`h`/`w`/`r`/`c`/`f`) (`src/main.cpp`) +- [ ] **Commit 5** — automated `pyserial` test script + (`owntech/scripts/test_metadata_nvs.py`) + +## Status log + +(Each commit appends a short entry here when it lands: what was done, +files touched, any deviation from the plan.) From 92ed39c70be3ca1b241bbbf5d0debe77596cc5a5 Mon Sep 17 00:00:00 2001 From: Luiz Villa Date: Wed, 29 Jul 2026 16:27:38 +0200 Subject: [PATCH 2/7] flash: add BOARD_METADATA category and free-space diagnostic Adds a new NVS category for board/shield metadata (spin.metaData) and a nvs_storage_get_free_space() helper to measure remaining budget in the shared 4KB storage partition, since ADC calibration and safety thresholds already compete for that space. Co-Authored-By: Claude Sonnet 5 --- metaData.md | 8 +++++- .../zephyr/public_api/nvs_storage.c | 11 ++++++++ .../zephyr/public_api/nvs_storage.h | 26 ++++++++++++++----- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/metaData.md b/metaData.md index 3051ee69..711d13f8 100644 --- a/metaData.md +++ b/metaData.md @@ -11,7 +11,7 @@ landed. ## Commits -- [ ] **Commit 1** — flash driver: add `BOARD_METADATA` category to +- [x] **Commit 1** — flash driver: add `BOARD_METADATA` category to `nvs_category_t` + `nvs_storage_get_free_space()` diagnostic (`zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.h`, `nvs_storage.c`) @@ -28,3 +28,9 @@ landed. (Each commit appends a short entry here when it lands: what was done, files touched, any deviation from the plan.) + +- **Commit 1 done**: added `BOARD_METADATA = 0x0400` to `nvs_category_t` + and a new `nvs_storage_get_free_space()` diagnostic (wraps Zephyr's + `nvs_calc_free_space(&fs)`) to + `zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.h` + and `nvs_storage.c`. No deviation from plan. diff --git a/zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.c b/zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.c index 4d0780d9..fb31cc21 100644 --- a/zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.c +++ b/zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.c @@ -273,3 +273,14 @@ uint16_t nvs_storage_get_version_in_nvs() return storage_version_in_nvs; } + +int32_t nvs_storage_get_free_space() +{ + if (initialized == false) + { + int8_t error = _nvs_storage_init(); + if (error != 0) return error; + } + + return nvs_calc_free_space(&fs); +} diff --git a/zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.h b/zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.h index 08d284c3..b4676975 100644 --- a/zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.h +++ b/zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.h @@ -38,15 +38,17 @@ extern "C" { /* Types definition */ /** - * @brief Defines the NVS categories - * + * @brief Defines the NVS categories + * * - `VERSION` = 0x0100 - * + * * - `ADC_CALIBRATION` = 0x0200 - * + * * - `MEASURE_THRESHOLD` = 0x0300 - * - * + * + * - `BOARD_METADATA` = 0x0400 + * + * * @note Must be on the upper half of the 2-bytes value, hence end with 00 */ typedef enum @@ -54,6 +56,7 @@ typedef enum VERSION = 0x0100, ADC_CALIBRATION = 0x0200, MEASURE_THRESHOLD = 0x0300, + BOARD_METADATA = 0x0400, }nvs_category_t; /** @@ -118,6 +121,17 @@ uint16_t nvs_storage_get_current_version(); */ uint16_t nvs_storage_get_version_in_nvs(); +/** + * @brief Get the amount of free space left in the NVS partition. + * + * Useful to check how much of the storage budget remains before + * writing new data, since the NVS partition is shared by several + * modules (ADC calibration, safety thresholds, board metadata, ...). + * + * @return Free space in bytes on success, negative value on error. + */ +int32_t nvs_storage_get_free_space(); + #ifdef __cplusplus } From 378452f202a8c8da577635635667b3c935af8f3e Mon Sep 17 00:00:00 2001 From: Luiz Villa Date: Wed, 29 Jul 2026 16:27:38 +0200 Subject: [PATCH 3/7] spin_api: add MetaDataAPI to persist board/shield identity data Adds spin/shield serial numbers, spin/shield versions (major/minor/rev), a shield password, and 5 generic extra slots on top of the shared nvs_storage_store_data/retrieve_data API, following the same one-key-per- field pattern already used for ADC calibration and safety thresholds. Not yet wired into the public spin.metaData surface. Co-Authored-By: Claude Sonnet 5 --- metaData.md | 14 +- .../zephyr/src/MetaDataAPI.cpp | 220 +++++++++++++++ .../owntech_spin_api/zephyr/src/MetaDataAPI.h | 258 ++++++++++++++++++ 3 files changed, 491 insertions(+), 1 deletion(-) create mode 100644 zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.cpp create mode 100644 zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h diff --git a/metaData.md b/metaData.md index 711d13f8..f49cb38c 100644 --- a/metaData.md +++ b/metaData.md @@ -15,7 +15,7 @@ landed. `nvs_category_t` + `nvs_storage_get_free_space()` diagnostic (`zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.h`, `nvs_storage.c`) -- [ ] **Commit 2** — new `MetaDataAPI` class +- [x] **Commit 2** — new `MetaDataAPI` class (`zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h`, `.cpp`) - [ ] **Commit 3** — wire `MetaDataAPI` into `SpinAPI` as `spin.metaData` (`SpinAPI.h`, `SpinAPI.cpp`, `zephyr/modules/owntech_spin_api/zephyr/CMakeLists.txt`) @@ -34,3 +34,15 @@ files touched, any deviation from the plan.) `nvs_calc_free_space(&fs)`) to `zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.h` and `nvs_storage.c`. No deviation from plan. +- **Commit 2 done**: added `MetaDataAPI.h`/`.cpp` under + `zephyr/modules/owntech_spin_api/zephyr/src/` with the 10-field + get/set API (spin/shield serials, spin/shield versions as raw + major/minor/rev bytes, shield password, 5 extra slots) plus + `clearAllMetaData()`. Field sub-addressing (`META_SPIN_SERIAL`, etc.) + is a private enum inside the .cpp, combined with `BOARD_METADATA` as + `BOARD_METADATA | field_id`, matching the `ADC_CALIBRATION` bit-packing + precedent in `data_conversion.cpp`. `clearAllMetaData()` deletes each + of the 10 keys individually via `nvs_storage_store_data(id, ptr, 0)` + (Zephyr's `nvs_write` treats a 0-length write as a delete), so it never + touches the other modules' data in the shared NVS partition. No + deviation from plan. diff --git a/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.cpp b/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.cpp new file mode 100644 index 00000000..260d48df --- /dev/null +++ b/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.cpp @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2026-present LAAS-CNRS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1 + */ + +/* + * @date 2026 + * + * @author Luiz Villa + */ + + +/* Current class header */ +#include "MetaDataAPI.h" + +/* Low-level flash storage API */ +#include "nvs_storage.h" + + +/** + * Sub-addressing of the individual metadata fields within the + * BOARD_METADATA NVS category, following the same bit-packing convention + * used for ADC_CALIBRATION in data_conversion.cpp: the category occupies + * the upper byte of the 16-bit NVS key, the field id the lower byte. + */ +enum : uint16_t +{ + META_SPIN_SERIAL = 0x00, + META_SHIELD_SERIAL = 0x01, + META_SPIN_VERSION = 0x02, + META_SHIELD_VERSION = 0x03, + META_SHIELD_PASSWORD = 0x04, + META_EXTRA_0 = 0x05, +}; + +static const uint16_t VERSION_FIELD_LEN = 3; /* major, minor, rev */ + +int8_t MetaDataAPI::setSpinSerialNumber(const char* serial) +{ + int ret = nvs_storage_store_data(BOARD_METADATA | META_SPIN_SERIAL, + serial, SPIN_SERIAL_LEN); + return (ret < 0) ? -1 : 0; +} + +int8_t MetaDataAPI::getSpinSerialNumber(char* buffer, uint8_t buffer_size) +{ + if (buffer_size < SPIN_SERIAL_LEN) + { + return -2; + } + + int ret = nvs_storage_retrieve_data(BOARD_METADATA | META_SPIN_SERIAL, + buffer, buffer_size); + return (ret < 0) ? -1 : ret; +} + +int8_t MetaDataAPI::setShieldSerialNumber(const char* serial) +{ + int ret = nvs_storage_store_data(BOARD_METADATA | META_SHIELD_SERIAL, + serial, SHIELD_SERIAL_LEN); + return (ret < 0) ? -1 : 0; +} + +int8_t MetaDataAPI::getShieldSerialNumber(char* buffer, uint8_t buffer_size) +{ + if (buffer_size < SHIELD_SERIAL_LEN) + { + return -2; + } + + int ret = nvs_storage_retrieve_data(BOARD_METADATA | META_SHIELD_SERIAL, + buffer, buffer_size); + return (ret < 0) ? -1 : ret; +} + +int8_t MetaDataAPI::setSpinVersion(uint8_t major, uint8_t minor, uint8_t rev) +{ + uint8_t version[VERSION_FIELD_LEN] = {major, minor, rev}; + + int ret = nvs_storage_store_data(BOARD_METADATA | META_SPIN_VERSION, + version, VERSION_FIELD_LEN); + return (ret < 0) ? -1 : 0; +} + +int8_t MetaDataAPI::getSpinVersion(uint8_t* major, uint8_t* minor, uint8_t* rev) +{ + uint8_t version[VERSION_FIELD_LEN]; + + int ret = nvs_storage_retrieve_data(BOARD_METADATA | META_SPIN_VERSION, + version, VERSION_FIELD_LEN); + if (ret < 0) + { + return -1; + } + + *major = version[0]; + *minor = version[1]; + *rev = version[2]; + return 0; +} + +int8_t MetaDataAPI::setShieldVersion(uint8_t major, uint8_t minor, uint8_t rev) +{ + uint8_t version[VERSION_FIELD_LEN] = {major, minor, rev}; + + int ret = nvs_storage_store_data(BOARD_METADATA | META_SHIELD_VERSION, + version, VERSION_FIELD_LEN); + return (ret < 0) ? -1 : 0; +} + +int8_t MetaDataAPI::getShieldVersion(uint8_t* major, uint8_t* minor, uint8_t* rev) +{ + uint8_t version[VERSION_FIELD_LEN]; + + int ret = nvs_storage_retrieve_data(BOARD_METADATA | META_SHIELD_VERSION, + version, VERSION_FIELD_LEN); + if (ret < 0) + { + return -1; + } + + *major = version[0]; + *minor = version[1]; + *rev = version[2]; + return 0; +} + +int8_t MetaDataAPI::setShieldPassword(const char* password) +{ + int ret = nvs_storage_store_data(BOARD_METADATA | META_SHIELD_PASSWORD, + password, SHIELD_PASSWORD_LEN); + return (ret < 0) ? -1 : 0; +} + +int8_t MetaDataAPI::getShieldPassword(char* buffer, uint8_t buffer_size) +{ + if (buffer_size < SHIELD_PASSWORD_LEN) + { + return -2; + } + + int ret = nvs_storage_retrieve_data(BOARD_METADATA | META_SHIELD_PASSWORD, + buffer, buffer_size); + return (ret < 0) ? -1 : ret; +} + +int8_t MetaDataAPI::setExtraData(uint8_t index, const uint8_t* data, uint8_t data_size) +{ + if (index >= METADATA_EXTRA_COUNT) + { + return -3; + } + + if (data_size > METADATA_EXTRA_MAX_LEN) + { + return -2; + } + + uint16_t field_id = BOARD_METADATA | (META_EXTRA_0 + index); + int ret = nvs_storage_store_data(field_id, data, data_size); + return (ret < 0) ? -1 : 0; +} + +int8_t MetaDataAPI::getExtraData(uint8_t index, uint8_t* buffer, uint8_t buffer_size) +{ + if (index >= METADATA_EXTRA_COUNT) + { + return -3; + } + + uint16_t field_id = BOARD_METADATA | (META_EXTRA_0 + index); + int ret = nvs_storage_retrieve_data(field_id, buffer, buffer_size); + return (ret < 0) ? -1 : ret; +} + +int8_t MetaDataAPI::clearAllMetaData() +{ + static const uint16_t metadata_field_ids[] = { + META_SPIN_SERIAL, + META_SHIELD_SERIAL, + META_SPIN_VERSION, + META_SHIELD_VERSION, + META_SHIELD_PASSWORD, + META_EXTRA_0 + 0, + META_EXTRA_0 + 1, + META_EXTRA_0 + 2, + META_EXTRA_0 + 3, + META_EXTRA_0 + 4, + }; + + /* Writing with a size of 0 deletes the entry (see Zephyr's + * nvs_write() documentation), without touching any other module's + * data in the shared NVS partition. */ + uint8_t dummy = 0; + int8_t result = 0; + for (uint16_t field_id : metadata_field_ids) + { + int ret = nvs_storage_store_data(BOARD_METADATA | field_id, &dummy, 0); + if (ret < 0) + { + result = -1; + } + } + + return result; +} diff --git a/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h b/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h new file mode 100644 index 00000000..6b36e0fb --- /dev/null +++ b/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2026-present LAAS-CNRS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1 + */ + +/* + * @date 2026 + * + * @author Luiz Villa + */ + + +#ifndef METADATAAPI_H_ +#define METADATAAPI_H_ + + +/* Stdlib */ +#include + +/** + * Constants definitions + */ + +/* Fixed length, in bytes, of a serial number field (13 raw ASCII + * characters, not null-terminated). */ +static const uint8_t SPIN_SERIAL_LEN = 13; +static const uint8_t SHIELD_SERIAL_LEN = 13; + +/* Fixed length, in bytes, of the shield password field (3 raw ASCII + * characters, not null-terminated). */ +static const uint8_t SHIELD_PASSWORD_LEN = 3; + +/* Number of generic extra metadata slots, and max size of each. */ +static const uint8_t METADATA_EXTRA_COUNT = 5; +static const uint8_t METADATA_EXTRA_MAX_LEN = 16; + +/** + * Class definition + */ + +/** + * @brief Persist board/shield identity data (serial numbers, versions, + * shield password, and generic extra slots) to flash. + * + * @note This is NOT secure storage: data is written in plain form to the + * NVS partition, with no secure element or read-protection involved. + * The shield password field is only meant to gate casual shield + * mismatches, not to protect a real secret. + */ +class MetaDataAPI +{ +public: + + /** + * @brief Store the Spin board serial number in persistent memory. + * + * @param[in] serial Pointer to a buffer of exactly `SPIN_SERIAL_LEN` + * (13) raw ASCII bytes. The buffer does not need to + * be null-terminated, and no null terminator is + * written; if fewer than 13 meaningful characters + * are needed, the caller must pad the buffer itself + * (e.g. with spaces or zeros) up to 13 bytes. + * + * @return `0` if the serial number was correctly stored, + * `-1` if there was an underlying storage error. + */ + int8_t setSpinSerialNumber(const char* serial); + + /** + * @brief Retrieve the Spin board serial number from persistent memory. + * + * @param[in] buffer Buffer to receive the 13 raw ASCII bytes. + * @param[in] buffer_size Size of `buffer`, must be at least + * `SPIN_SERIAL_LEN` (13). + * + * @return Number of bytes read (13) on success, negative value on error: + * + * - `-1`: underlying storage error (nothing stored yet, corrupted + * data, or version mismatch), + * + * - `-2`: provided buffer is smaller than `SPIN_SERIAL_LEN`. + */ + int8_t getSpinSerialNumber(char* buffer, uint8_t buffer_size); + + /** + * @brief Store the shield serial number in persistent memory. + * + * @param[in] serial Pointer to a buffer of exactly `SHIELD_SERIAL_LEN` + * (13) raw ASCII bytes. Same padding rules as + * `setSpinSerialNumber()`. + * + * @return `0` if the serial number was correctly stored, + * `-1` if there was an underlying storage error. + */ + int8_t setShieldSerialNumber(const char* serial); + + /** + * @brief Retrieve the shield serial number from persistent memory. + * + * @param[in] buffer Buffer to receive the 13 raw ASCII bytes. + * @param[in] buffer_size Size of `buffer`, must be at least + * `SHIELD_SERIAL_LEN` (13). + * + * @return Number of bytes read (13) on success, negative value on error: + * + * - `-1`: underlying storage error, + * + * - `-2`: provided buffer is smaller than `SHIELD_SERIAL_LEN`. + */ + int8_t getShieldSerialNumber(char* buffer, uint8_t buffer_size); + + /** + * @brief Store the Spin board hardware version in persistent memory. + * + * @param[in] major Major version number. + * @param[in] minor Minor version number. + * @param[in] rev Revision number. + * + * @return `0` if the version was correctly stored, + * `-1` if there was an underlying storage error. + */ + int8_t setSpinVersion(uint8_t major, uint8_t minor, uint8_t rev); + + /** + * @brief Retrieve the Spin board hardware version from persistent + * memory. + * + * @param[out] major Pointer to receive the major version number. + * @param[out] minor Pointer to receive the minor version number. + * @param[out] rev Pointer to receive the revision number. + * + * @return `0` if the version was correctly retrieved, + * `-1` if there was an underlying storage error (nothing + * stored yet, corrupted data, or version mismatch). + */ + int8_t getSpinVersion(uint8_t* major, uint8_t* minor, uint8_t* rev); + + /** + * @brief Store the shield hardware version in persistent memory. + * + * @param[in] major Major version number. + * @param[in] minor Minor version number. + * @param[in] rev Revision number. + * + * @return `0` if the version was correctly stored, + * `-1` if there was an underlying storage error. + */ + int8_t setShieldVersion(uint8_t major, uint8_t minor, uint8_t rev); + + /** + * @brief Retrieve the shield hardware version from persistent memory. + * + * @param[out] major Pointer to receive the major version number. + * @param[out] minor Pointer to receive the minor version number. + * @param[out] rev Pointer to receive the revision number. + * + * @return `0` if the version was correctly retrieved, + * `-1` if there was an underlying storage error. + */ + int8_t getShieldVersion(uint8_t* major, uint8_t* minor, uint8_t* rev); + + /** + * @brief Store the shield password in persistent memory. + * + * @note This is not a secure secret store: the password is written in + * plain form to flash, retrievable via `getShieldPassword()`. + * It is only meant to gate casual shield/board mismatches. + * + * @param[in] password Pointer to a buffer of exactly + * `SHIELD_PASSWORD_LEN` (3) raw ASCII bytes. + * + * @return `0` if the password was correctly stored, + * `-1` if there was an underlying storage error. + */ + int8_t setShieldPassword(const char* password); + + /** + * @brief Retrieve the shield password from persistent memory. + * + * @param[in] buffer Buffer to receive the 3 raw ASCII bytes. + * @param[in] buffer_size Size of `buffer`, must be at least + * `SHIELD_PASSWORD_LEN` (3). + * + * @return Number of bytes read (3) on success, negative value on error: + * + * - `-1`: underlying storage error, + * + * - `-2`: provided buffer is smaller than `SHIELD_PASSWORD_LEN`. + */ + int8_t getShieldPassword(char* buffer, uint8_t buffer_size); + + /** + * @brief Store one of the generic extra metadata slots. + * + * @param[in] index Slot index, in `[0, METADATA_EXTRA_COUNT - 1]`. + * @param[in] data Pointer to the data to store. + * @param[in] data_size Size of `data` in bytes, at most + * `METADATA_EXTRA_MAX_LEN` (16). + * + * @return `0` if the data was correctly stored, negative value on error: + * + * - `-1`: underlying storage error, + * + * - `-2`: `data_size` exceeds `METADATA_EXTRA_MAX_LEN`, + * + * - `-3`: `index` is out of range. + */ + int8_t setExtraData(uint8_t index, const uint8_t* data, uint8_t data_size); + + /** + * @brief Retrieve one of the generic extra metadata slots. + * + * @param[in] index Slot index, in `[0, METADATA_EXTRA_COUNT - 1]`. + * @param[in] buffer Buffer to receive the stored data. + * @param[in] buffer_size Size of `buffer` in bytes. + * + * @return Number of bytes read on success, negative value on error: + * + * - `-1`: underlying storage error, + * + * - `-2`: provided buffer is smaller than the stored data, + * + * - `-3`: `index` is out of range. + */ + int8_t getExtraData(uint8_t index, uint8_t* buffer, uint8_t buffer_size); + + /** + * @brief Erase all board/shield metadata fields (serial numbers, + * versions, password, and all extra slots). + * + * @note This only erases the metadata fields owned by this class. It + * does not affect ADC calibration data or safety thresholds, + * which live under different NVS categories in the same + * partition. + * + * @return `0` if all fields were correctly erased, + * `-1` if there was an underlying storage error. + */ + int8_t clearAllMetaData(); + +}; + + +#endif /* METADATAAPI_H_ */ From 4dd086902fc4243a3808a6e0b94fc301bc1d811e Mon Sep 17 00:00:00 2001 From: Luiz Villa Date: Wed, 29 Jul 2026 16:27:38 +0200 Subject: [PATCH 4/7] spin_api: expose MetaDataAPI as spin.metaData Wires the new MetaDataAPI class into SpinAPI following the same static- member composition pattern used for spin.data, spin.gpio, etc. Co-Authored-By: Claude Sonnet 5 --- metaData.md | 12 +++++++++++- .../modules/owntech_spin_api/zephyr/CMakeLists.txt | 1 + .../owntech_spin_api/zephyr/public_api/SpinAPI.cpp | 2 ++ .../owntech_spin_api/zephyr/public_api/SpinAPI.h | 7 +++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/metaData.md b/metaData.md index f49cb38c..442baa62 100644 --- a/metaData.md +++ b/metaData.md @@ -17,7 +17,7 @@ landed. `nvs_storage.c`) - [x] **Commit 2** — new `MetaDataAPI` class (`zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h`, `.cpp`) -- [ ] **Commit 3** — wire `MetaDataAPI` into `SpinAPI` as `spin.metaData` +- [x] **Commit 3** — wire `MetaDataAPI` into `SpinAPI` as `spin.metaData` (`SpinAPI.h`, `SpinAPI.cpp`, `zephyr/modules/owntech_spin_api/zephyr/CMakeLists.txt`) - [ ] **Commit 4** — test harness `main.cpp` with serial command menu (`h`/`w`/`r`/`c`/`f`) (`src/main.cpp`) @@ -46,3 +46,13 @@ files touched, any deviation from the plan.) (Zephyr's `nvs_write` treats a 0-length write as a delete), so it never touches the other modules' data in the shared NVS partition. No deviation from plan. +- **Commit 3 done**: wired `MetaDataAPI` into `SpinAPI` as `spin.metaData` + — added the include + `static MetaDataAPI metaData;` member in + `SpinAPI.h`, the `MetaDataAPI SpinAPI::metaData;` definition in + `SpinAPI.cpp`, and `src/MetaDataAPI.cpp` to the unconditional + `zephyr_library_sources(...)` list in + `zephyr/modules/owntech_spin_api/zephyr/CMakeLists.txt`. No new Kconfig + needed (`CONFIG_OWNTECH_SPIN_API` already depends on + `CONFIG_OWNTECH_FLASH`). Left the pre-existing, unrelated local + modification to the top-level `zephyr/CMakeLists.txt` untouched. No + deviation from plan. diff --git a/zephyr/modules/owntech_spin_api/zephyr/CMakeLists.txt b/zephyr/modules/owntech_spin_api/zephyr/CMakeLists.txt index e6abe3ba..623cd1b6 100644 --- a/zephyr/modules/owntech_spin_api/zephyr/CMakeLists.txt +++ b/zephyr/modules/owntech_spin_api/zephyr/CMakeLists.txt @@ -18,6 +18,7 @@ if(CONFIG_OWNTECH_SPIN_API) src/DacHAL.cpp src/DataAPI.cpp src/LedHAL.cpp + src/MetaDataAPI.cpp src/PwmHAL.cpp src/TimerHAL.cpp ) diff --git a/zephyr/modules/owntech_spin_api/zephyr/public_api/SpinAPI.cpp b/zephyr/modules/owntech_spin_api/zephyr/public_api/SpinAPI.cpp index 72fc44dc..32393c47 100644 --- a/zephyr/modules/owntech_spin_api/zephyr/public_api/SpinAPI.cpp +++ b/zephyr/modules/owntech_spin_api/zephyr/public_api/SpinAPI.cpp @@ -40,6 +40,8 @@ DacHAL SpinAPI::dac; DataAPI SpinAPI::data; +MetaDataAPI SpinAPI::metaData; + CompHAL SpinAPI::comp; PwmHAL SpinAPI::pwm; diff --git a/zephyr/modules/owntech_spin_api/zephyr/public_api/SpinAPI.h b/zephyr/modules/owntech_spin_api/zephyr/public_api/SpinAPI.h index 7974c90e..509911dd 100644 --- a/zephyr/modules/owntech_spin_api/zephyr/public_api/SpinAPI.h +++ b/zephyr/modules/owntech_spin_api/zephyr/public_api/SpinAPI.h @@ -34,6 +34,7 @@ #include "../src/DataAPI.h" #include "../src/GpioHAL.h" #include "../src/LedHAL.h" +#include "../src/MetaDataAPI.h" #include "../src/PwmHAL.h" #include "../src/TimerHAL.h" @@ -99,6 +100,12 @@ class SpinAPI */ static DataAPI data; + /** + * @brief Persist board/shield identity data (serial numbers, + * versions, shield password, extra slots) to flash. + */ + static MetaDataAPI metaData; + }; From be6c7b235c74a6917f05aba239ba292ced2e4a3f Mon Sep 17 00:00:00 2001 From: Luiz Villa Date: Wed, 29 Jul 2026 18:10:48 +0200 Subject: [PATCH 5/7] spin_api: require explicit size for MetaDataAPI set* functions setSpinSerialNumber, setShieldSerialNumber, and setShieldPassword trusted their fixed-length constants regardless of how much data the caller actually provided, so a shorter buffer (e.g. "ABC") caused nvs_storage_store_data to read past its end. Callers must now pass the buffer size explicitly, which is checked before touching NVS. Addresses review feedback from @Ayoub-Farah on PR #157. --- .../zephyr/src/MetaDataAPI.cpp | 21 ++++++- .../owntech_spin_api/zephyr/src/MetaDataAPI.h | 61 ++++++++++++------- 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.cpp b/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.cpp index 260d48df..32f174de 100644 --- a/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.cpp +++ b/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.cpp @@ -49,8 +49,13 @@ enum : uint16_t static const uint16_t VERSION_FIELD_LEN = 3; /* major, minor, rev */ -int8_t MetaDataAPI::setSpinSerialNumber(const char* serial) +int8_t MetaDataAPI::setSpinSerialNumber(const char* serial, uint8_t serial_size) { + if (serial_size != SPIN_SERIAL_LEN) + { + return -2; + } + int ret = nvs_storage_store_data(BOARD_METADATA | META_SPIN_SERIAL, serial, SPIN_SERIAL_LEN); return (ret < 0) ? -1 : 0; @@ -68,8 +73,13 @@ int8_t MetaDataAPI::getSpinSerialNumber(char* buffer, uint8_t buffer_size) return (ret < 0) ? -1 : ret; } -int8_t MetaDataAPI::setShieldSerialNumber(const char* serial) +int8_t MetaDataAPI::setShieldSerialNumber(const char* serial, uint8_t serial_size) { + if (serial_size != SHIELD_SERIAL_LEN) + { + return -2; + } + int ret = nvs_storage_store_data(BOARD_METADATA | META_SHIELD_SERIAL, serial, SHIELD_SERIAL_LEN); return (ret < 0) ? -1 : 0; @@ -139,8 +149,13 @@ int8_t MetaDataAPI::getShieldVersion(uint8_t* major, uint8_t* minor, uint8_t* re return 0; } -int8_t MetaDataAPI::setShieldPassword(const char* password) +int8_t MetaDataAPI::setShieldPassword(const char* password, uint8_t password_size) { + if (password_size != SHIELD_PASSWORD_LEN) + { + return -2; + } + int ret = nvs_storage_store_data(BOARD_METADATA | META_SHIELD_PASSWORD, password, SHIELD_PASSWORD_LEN); return (ret < 0) ? -1 : 0; diff --git a/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h b/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h index 6b36e0fb..5b3ef3d0 100644 --- a/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h +++ b/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h @@ -68,17 +68,24 @@ class MetaDataAPI /** * @brief Store the Spin board serial number in persistent memory. * - * @param[in] serial Pointer to a buffer of exactly `SPIN_SERIAL_LEN` - * (13) raw ASCII bytes. The buffer does not need to - * be null-terminated, and no null terminator is - * written; if fewer than 13 meaningful characters - * are needed, the caller must pad the buffer itself - * (e.g. with spaces or zeros) up to 13 bytes. - * - * @return `0` if the serial number was correctly stored, - * `-1` if there was an underlying storage error. + * @param[in] serial Pointer to a buffer of exactly + * `SPIN_SERIAL_LEN` (13) raw ASCII bytes. The + * buffer does not need to be null-terminated, + * and no null terminator is written; if fewer + * than 13 meaningful characters are needed, the + * caller must pad the buffer itself (e.g. with + * spaces or zeros) up to 13 bytes. + * @param[in] serial_size Size of `serial` in bytes, must be exactly + * `SPIN_SERIAL_LEN` (13). + * + * @return `0` if the serial number was correctly stored, negative + * value on error: + * + * - `-1`: underlying storage error, + * + * - `-2`: `serial_size` is not exactly `SPIN_SERIAL_LEN`. */ - int8_t setSpinSerialNumber(const char* serial); + int8_t setSpinSerialNumber(const char* serial, uint8_t serial_size); /** * @brief Retrieve the Spin board serial number from persistent memory. @@ -99,14 +106,20 @@ class MetaDataAPI /** * @brief Store the shield serial number in persistent memory. * - * @param[in] serial Pointer to a buffer of exactly `SHIELD_SERIAL_LEN` - * (13) raw ASCII bytes. Same padding rules as - * `setSpinSerialNumber()`. + * @param[in] serial Pointer to a buffer of exactly + * `SHIELD_SERIAL_LEN` (13) raw ASCII bytes. + * Same padding rules as `setSpinSerialNumber()`. + * @param[in] serial_size Size of `serial` in bytes, must be exactly + * `SHIELD_SERIAL_LEN` (13). * - * @return `0` if the serial number was correctly stored, - * `-1` if there was an underlying storage error. + * @return `0` if the serial number was correctly stored, negative + * value on error: + * + * - `-1`: underlying storage error, + * + * - `-2`: `serial_size` is not exactly `SHIELD_SERIAL_LEN`. */ - int8_t setShieldSerialNumber(const char* serial); + int8_t setShieldSerialNumber(const char* serial, uint8_t serial_size); /** * @brief Retrieve the shield serial number from persistent memory. @@ -180,13 +193,19 @@ class MetaDataAPI * plain form to flash, retrievable via `getShieldPassword()`. * It is only meant to gate casual shield/board mismatches. * - * @param[in] password Pointer to a buffer of exactly - * `SHIELD_PASSWORD_LEN` (3) raw ASCII bytes. + * @param[in] password Pointer to a buffer of exactly + * `SHIELD_PASSWORD_LEN` (3) raw ASCII bytes. + * @param[in] password_size Size of `password` in bytes, must be + * exactly `SHIELD_PASSWORD_LEN` (3). * - * @return `0` if the password was correctly stored, - * `-1` if there was an underlying storage error. + * @return `0` if the password was correctly stored, negative value + * on error: + * + * - `-1`: underlying storage error, + * + * - `-2`: `password_size` is not exactly `SHIELD_PASSWORD_LEN`. */ - int8_t setShieldPassword(const char* password); + int8_t setShieldPassword(const char* password, uint8_t password_size); /** * @brief Retrieve the shield password from persistent memory. From 0144465c8bdc1b0d5d735201b44a5c3b7057cf61 Mon Sep 17 00:00:00 2001 From: Luiz Villa Date: Wed, 29 Jul 2026 18:33:07 +0200 Subject: [PATCH 6/7] Removes the metaData.md from it --- metaData.md | 58 ----------------------------------------------------- 1 file changed, 58 deletions(-) delete mode 100644 metaData.md diff --git a/metaData.md b/metaData.md deleted file mode 100644 index 442baa62..00000000 --- a/metaData.md +++ /dev/null @@ -1,58 +0,0 @@ -# spin.metaData implementation progress - -Tracks progress of the `spin.metaData` feature (persist board/shield serial -numbers, versions, shield password, and 5 extra slots to flash via NVS). -Full design/rationale: see the plan this was generated from -(`based-on-the-owntech-cheerful-gizmo.md`). - -This file is a scratch progress tracker, not user-facing documentation — -safe to delete once all commits below are checked off and the feature has -landed. - -## Commits - -- [x] **Commit 1** — flash driver: add `BOARD_METADATA` category to - `nvs_category_t` + `nvs_storage_get_free_space()` diagnostic - (`zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.h`, - `nvs_storage.c`) -- [x] **Commit 2** — new `MetaDataAPI` class - (`zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h`, `.cpp`) -- [x] **Commit 3** — wire `MetaDataAPI` into `SpinAPI` as `spin.metaData` - (`SpinAPI.h`, `SpinAPI.cpp`, `zephyr/modules/owntech_spin_api/zephyr/CMakeLists.txt`) -- [ ] **Commit 4** — test harness `main.cpp` with serial command menu - (`h`/`w`/`r`/`c`/`f`) (`src/main.cpp`) -- [ ] **Commit 5** — automated `pyserial` test script - (`owntech/scripts/test_metadata_nvs.py`) - -## Status log - -(Each commit appends a short entry here when it lands: what was done, -files touched, any deviation from the plan.) - -- **Commit 1 done**: added `BOARD_METADATA = 0x0400` to `nvs_category_t` - and a new `nvs_storage_get_free_space()` diagnostic (wraps Zephyr's - `nvs_calc_free_space(&fs)`) to - `zephyr/modules/owntech_flash_driver/zephyr/public_api/nvs_storage.h` - and `nvs_storage.c`. No deviation from plan. -- **Commit 2 done**: added `MetaDataAPI.h`/`.cpp` under - `zephyr/modules/owntech_spin_api/zephyr/src/` with the 10-field - get/set API (spin/shield serials, spin/shield versions as raw - major/minor/rev bytes, shield password, 5 extra slots) plus - `clearAllMetaData()`. Field sub-addressing (`META_SPIN_SERIAL`, etc.) - is a private enum inside the .cpp, combined with `BOARD_METADATA` as - `BOARD_METADATA | field_id`, matching the `ADC_CALIBRATION` bit-packing - precedent in `data_conversion.cpp`. `clearAllMetaData()` deletes each - of the 10 keys individually via `nvs_storage_store_data(id, ptr, 0)` - (Zephyr's `nvs_write` treats a 0-length write as a delete), so it never - touches the other modules' data in the shared NVS partition. No - deviation from plan. -- **Commit 3 done**: wired `MetaDataAPI` into `SpinAPI` as `spin.metaData` - — added the include + `static MetaDataAPI metaData;` member in - `SpinAPI.h`, the `MetaDataAPI SpinAPI::metaData;` definition in - `SpinAPI.cpp`, and `src/MetaDataAPI.cpp` to the unconditional - `zephyr_library_sources(...)` list in - `zephyr/modules/owntech_spin_api/zephyr/CMakeLists.txt`. No new Kconfig - needed (`CONFIG_OWNTECH_SPIN_API` already depends on - `CONFIG_OWNTECH_FLASH`). Left the pre-existing, unrelated local - modification to the top-level `zephyr/CMakeLists.txt` untouched. No - deviation from plan. From 61de6cce0f4bc11163249ceacc9e909745a87a2a Mon Sep 17 00:00:00 2001 From: Luiz Villa Date: Wed, 29 Jul 2026 18:33:22 +0200 Subject: [PATCH 7/7] Adds the -2 return for the getExtraData function --- zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.cpp | 5 +++++ zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.cpp b/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.cpp index 32f174de..bb47a54d 100644 --- a/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.cpp +++ b/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.cpp @@ -197,6 +197,11 @@ int8_t MetaDataAPI::getExtraData(uint8_t index, uint8_t* buffer, uint8_t buffer_ return -3; } + if (buffer_size < METADATA_EXTRA_MAX_LEN) + { + return -2; + } + uint16_t field_id = BOARD_METADATA | (META_EXTRA_0 + index); int ret = nvs_storage_retrieve_data(field_id, buffer, buffer_size); return (ret < 0) ? -1 : ret; diff --git a/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h b/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h index 5b3ef3d0..fb79e141 100644 --- a/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h +++ b/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h @@ -245,13 +245,16 @@ class MetaDataAPI * * @param[in] index Slot index, in `[0, METADATA_EXTRA_COUNT - 1]`. * @param[in] buffer Buffer to receive the stored data. - * @param[in] buffer_size Size of `buffer` in bytes. + * @param[in] buffer_size Size of `buffer` in bytes, must be at least + * `METADATA_EXTRA_MAX_LEN` (16), since the + * actual stored size for this slot is not + * known ahead of the read. * * @return Number of bytes read on success, negative value on error: * * - `-1`: underlying storage error, * - * - `-2`: provided buffer is smaller than the stored data, + * - `-2`: `buffer_size` is smaller than `METADATA_EXTRA_MAX_LEN`, * * - `-3`: `index` is out of range. */