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 } 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; + }; 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..bb47a54d --- /dev/null +++ b/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.cpp @@ -0,0 +1,240 @@ +/* + * 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, 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; +} + +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, 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; +} + +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, 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; +} + +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; + } + + 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; +} + +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..fb79e141 --- /dev/null +++ b/zephyr/modules/owntech_spin_api/zephyr/src/MetaDataAPI.h @@ -0,0 +1,280 @@ +/* + * 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. + * @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, uint8_t serial_size); + + /** + * @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()`. + * @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, negative + * value on error: + * + * - `-1`: underlying storage error, + * + * - `-2`: `serial_size` is not exactly `SHIELD_SERIAL_LEN`. + */ + int8_t setShieldSerialNumber(const char* serial, uint8_t serial_size); + + /** + * @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. + * @param[in] password_size Size of `password` in bytes, must be + * exactly `SHIELD_PASSWORD_LEN` (3). + * + * @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, uint8_t password_size); + + /** + * @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, 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`: `buffer_size` is smaller than `METADATA_EXTRA_MAX_LEN`, + * + * - `-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_ */