fix: give every NVS user its own Preferences handle

loop() and the async web server task shared one global Preferences.  A
begin() from one task failed while the other had a namespace open, and the
clear() or remove() after it then hit the other task's namespace: saving the
radio settings while a command committed its rolling code could erase every
rolling code.

Each function now opens NVS with a local Preferences.  NVS serializes access
internally and the destructor closes the handle, so no lock is added (see
docs/adr/0001).  No new tests: nothing here builds natively, and deleting the
global makes any missed use a compile error.

The read-then-commit races on rolling codes across tasks remain and are
tracked in docs/KNOWN_ISSUES.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-12 20:51:05 +02:00
parent 4135f21ee7
commit c1b47f898c
6 changed files with 94 additions and 27 deletions

View file

@ -57,3 +57,26 @@ Unlike the `/controller` HTTP crash, this path does **not** abort — there is n
- Not the same code path as the `/controller` crash. Solve independently. - Not the same code path as the `/controller` crash. Solve independently.
- Worth grepping for `JsonSockEvent` usages that iterate collections (see references in `Somfy.cpp`, `ESPNetwork.cpp`, `GitOTA.cpp`) to identify the most at-risk events. - Worth grepping for `JsonSockEvent` usages that iterate collections (see references in `Somfy.cpp`, `ESPNetwork.cpp`, `GitOTA.cpp`) to identify the most at-risk events.
## Rolling codes written from two tasks
**Status:** open
**Location:** `SomfyRemote::sendCommandAfter` and `setRollingCode` in [src/Somfy.cpp](../src/Somfy.cpp), the `/setRollingCode` handlers in [src/Web.cpp](../src/Web.cpp), and `ShadeConfigFile::readShadeRecord`/`readGroupRecord` in [src/ConfigFile.cpp](../src/ConfigFile.cpp).
### Summary
Sending a command reads the next rolling code, queues the frame, then commits the code (`peekNextRollingCode` → `submitTx` → `commitRollingCode`), all on `loop()`. `/setRollingCode` and a shades restore change the same remote's `lastRollingCode` and its `ShadeCodes` NVS key from the async web server task, with no lock against that sequence. Each NVS write is safe on its own, since every function opens its own handle (see [ADR-0001](adr/0001-local-nvs-handles-no-wrapper.md)), but the read-then-commit sequence is not.
### Concrete failure modes
1. `/setRollingCode` lands between the peek and the commit of a command. The command's commit overwrites the code just set, so the set has no effect.
2. A restore reads `ShadeCodes` while a command commits a newer code. The restored shade can come back with the older code, and the motor rejects its frames until the code passes the newer one.
### Fix options (pick one later)
- **(a) Run these handlers on `loop()`.** Queue `/setRollingCode` and restore through the web command queue like shade commands, so `loop()` is the only task that touches a remote's rolling code. Part of making `loop()` the only owner of the shade model.
- **(b) A rolling-code lock.** Hold a mutex across peek → queue → commit, and in `setRollingCode` and restore. Narrower, but it adds a lock to the command path.
### Related
- Same root cause as [Concurrent mutation during chunked `/controller` response](#concurrent-mutation-during-chunked-controller-response): the web server task changes the shade model that `loop()` owns.

View file

@ -0,0 +1,12 @@
# Each function opens NVS with its own Preferences handle, with no lock or wrapper
A single global `Preferences pref` was used from both `loop()` and the async web server task. A `begin()` from one task could fail while the other task had a namespace open, and its `clear()` or `remove()` then hit the wrong namespace (a radio settings save could erase every rolling code). We replaced the global with a local `Preferences` in each function that uses NVS. We did not put an app-level lock or a wrapper module around it, because ESP-IDF's NVS API already serializes access internally (`nvs::Lock`), each `Preferences` holds its own handle, and its destructor calls `end()`.
## Considered Options
- **Global handle plus a mutex-backed `NvsSession`.** Rejected: the lock would only protect the shared handle, which is the actual bug, and it would block `loop()`'s rolling-code writes while `ConfigFile` keeps `ShadeCodes` open across LittleFS reads. Remove the shared handle and nothing is left for the lock to do.
- **An `NvsStore` seam with typed accessors and an in-memory adapter for native tests.** Rejected: it would have exactly one real adapter, so the seam would be hypothetical, and deleting it would just give back `Preferences`. The only NVS logic worth testing (the next rolling code) is already tested natively through `RollingCode.h`, which takes its loader as a parameter.
## Consequences
Separate handles make each NVS operation safe on its own, but not a read-then-write sequence across tasks (rolling-code peek/commit in `loop()` against `/setRollingCode` or a restore on the web server task). Those races belong to the model, not to NVS, and are tracked in `docs/KNOWN_ISSUES.md`.

View file

@ -8,7 +8,6 @@
static const char *TAG = "ConfigFile"; static const char *TAG = "ConfigFile";
extern Preferences pref;
#define SHADE_HDR_VER 24 #define SHADE_HDR_VER 24
#define SHADE_HDR_SIZE 76 #define SHADE_HDR_SIZE 76
@ -739,6 +738,7 @@ bool ShadeConfigFile::readSettingsRecord() {
return true; return true;
} }
bool ShadeConfigFile::readGroupRecord(SomfyGroup *group) { bool ShadeConfigFile::readGroupRecord(SomfyGroup *group) {
Preferences pref;
pref.begin("ShadeCodes"); pref.begin("ShadeCodes");
uint32_t startPos = this->file.position(); uint32_t startPos = this->file.position();
group->setGroupId(this->readUInt8(255)); group->setGroupId(this->readUInt8(255));
@ -802,6 +802,7 @@ bool ShadeConfigFile::readRoomRecord(SomfyRoom *room) {
} }
bool ShadeConfigFile::readShadeRecord(SomfyShade *shade) { bool ShadeConfigFile::readShadeRecord(SomfyShade *shade) {
Preferences pref;
pref.begin("ShadeCodes"); pref.begin("ShadeCodes");
uint32_t startPos = this->file.position(); uint32_t startPos = this->file.position();
shade->setShadeId(this->readUInt8(255)); shade->setShadeId(this->readUInt8(255));

View file

@ -10,7 +10,6 @@
static const char *TAG = "Config"; static const char *TAG = "Config";
Preferences pref;
void restore_options_t::fromJSON(JsonObject &obj) { void restore_options_t::fromJSON(JsonObject &obj) {
if(!obj["shades"].isNull()) this->shades = obj["shades"]; if(!obj["shades"].isNull()) this->shades = obj["shades"];
@ -190,6 +189,7 @@ bool ConfigSettings::begin() {
return true; return true;
} }
bool ConfigSettings::load() { bool ConfigSettings::load() {
Preferences pref;
this->fwVersion.parse(FW_VERSION); this->fwVersion.parse(FW_VERSION);
this->getAppVersion(); this->getAppVersion();
pref.begin("CFG"); pref.begin("CFG");
@ -224,6 +224,7 @@ bool ConfigSettings::getAppVersion() {
return true; return true;
} }
bool ConfigSettings::save() { bool ConfigSettings::save() {
Preferences pref;
pref.begin("CFG"); pref.begin("CFG");
pref.putString("hostname", this->hostname); pref.putString("hostname", this->hostname);
pref.putBool("ssdpBroadcast", this->ssdpBroadcast); pref.putBool("ssdpBroadcast", this->ssdpBroadcast);
@ -318,6 +319,7 @@ bool MQTTSettings::fromJSON(JsonObject &obj) {
return true; return true;
} }
bool MQTTSettings::save() { bool MQTTSettings::save() {
Preferences pref;
pref.begin("MQTT"); pref.begin("MQTT");
pref.clear(); pref.clear();
pref.putString("protocol", this->protocol); pref.putString("protocol", this->protocol);
@ -333,6 +335,7 @@ bool MQTTSettings::save() {
return true; return true;
} }
bool MQTTSettings::load() { bool MQTTSettings::load() {
Preferences pref;
pref.begin("MQTT"); pref.begin("MQTT");
pref.getString("protocol", this->protocol, sizeof(this->protocol)); pref.getString("protocol", this->protocol, sizeof(this->protocol));
pref.getString("hostname", this->hostname, sizeof(this->hostname)); pref.getString("hostname", this->hostname, sizeof(this->hostname));
@ -362,6 +365,7 @@ bool NTPSettings::begin() {
return true; return true;
} }
bool NTPSettings::save() { bool NTPSettings::save() {
Preferences pref;
pref.begin("NTP"); pref.begin("NTP");
pref.clear(); pref.clear();
pref.putString("ntpServer", this->ntpServer); pref.putString("ntpServer", this->ntpServer);
@ -374,6 +378,7 @@ bool NTPSettings::save() {
return true; return true;
} }
bool NTPSettings::load() { bool NTPSettings::load() {
Preferences pref;
pref.begin("NTP"); pref.begin("NTP");
pref.getString("ntpServer", this->ntpServer, sizeof(this->ntpServer)); pref.getString("ntpServer", this->ntpServer, sizeof(this->ntpServer));
pref.getString("posixZone", this->posixZone, sizeof(this->posixZone)); pref.getString("posixZone", this->posixZone, sizeof(this->posixZone));
@ -425,6 +430,7 @@ bool IPSettings::toJSON(JsonObject &obj) {
return true; return true;
} }
bool IPSettings::save() { bool IPSettings::save() {
Preferences pref;
pref.begin("IP"); pref.begin("IP");
pref.clear(); pref.clear();
pref.putBool("dhcp", this->dhcp); pref.putBool("dhcp", this->dhcp);
@ -437,6 +443,7 @@ bool IPSettings::save() {
return true; return true;
} }
bool IPSettings::load() { bool IPSettings::load() {
Preferences pref;
pref.begin("IP"); pref.begin("IP");
this->dhcp = pref.getBool("dhcp", true); this->dhcp = pref.getBool("dhcp", true);
char buff[16]; char buff[16];
@ -485,6 +492,7 @@ bool SecuritySettings::toJSON(JsonObject &obj) {
return true; return true;
} }
bool SecuritySettings::save() { bool SecuritySettings::save() {
Preferences pref;
pref.begin("SEC"); pref.begin("SEC");
pref.clear(); pref.clear();
pref.putChar("type", static_cast<uint8_t>(this->type)); pref.putChar("type", static_cast<uint8_t>(this->type));
@ -496,6 +504,7 @@ bool SecuritySettings::save() {
return true; return true;
} }
bool SecuritySettings::load() { bool SecuritySettings::load() {
Preferences pref;
pref.begin("SEC"); pref.begin("SEC");
this->type = static_cast<security_types>(pref.getChar("type", 0)); this->type = static_cast<security_types>(pref.getChar("type", 0));
if(pref.isKey("username")) pref.getString("username", this->username, sizeof(this->username)); if(pref.isKey("username")) pref.getString("username", this->username, sizeof(this->username));
@ -530,6 +539,7 @@ bool WifiSettings::toJSON(JsonObject &obj) {
return true; return true;
} }
bool WifiSettings::save() { bool WifiSettings::save() {
Preferences pref;
pref.begin("WIFI"); pref.begin("WIFI");
pref.clear(); pref.clear();
pref.putString("ssid", this->ssid); pref.putString("ssid", this->ssid);
@ -540,6 +550,7 @@ bool WifiSettings::save() {
return true; return true;
} }
bool WifiSettings::load() { bool WifiSettings::load() {
Preferences pref;
pref.begin("WIFI"); pref.begin("WIFI");
pref.getString("ssid", this->ssid, sizeof(this->ssid)); pref.getString("ssid", this->ssid, sizeof(this->ssid));
pref.getString("passphrase", this->passphrase, sizeof(this->passphrase)); pref.getString("passphrase", this->passphrase, sizeof(this->passphrase));
@ -623,6 +634,7 @@ bool EthernetSettings::usesPin(uint8_t pin) {
return false; return false;
} }
bool EthernetSettings::save() { bool EthernetSettings::save() {
Preferences pref;
pref.begin("ETH"); pref.begin("ETH");
pref.clear(); pref.clear();
pref.putChar("boardType", this->boardType); pref.putChar("boardType", this->boardType);
@ -636,6 +648,7 @@ bool EthernetSettings::save() {
return true; return true;
} }
bool EthernetSettings::load() { bool EthernetSettings::load() {
Preferences pref;
pref.begin("ETH"); pref.begin("ETH");
this->boardType = pref.getChar("boardType", this->boardType); this->boardType = pref.getChar("boardType", this->boardType);
this->phyType = static_cast<eth_phy_type_t>(pref.getChar("phyType", ETH_PHY_LAN8720)); this->phyType = static_cast<eth_phy_type_t>(pref.getChar("phyType", ETH_PHY_LAN8720));

View file

@ -20,7 +20,6 @@
static const char *TAG = "Somfy"; static const char *TAG = "Somfy";
extern Preferences pref;
extern SomfyShadeController somfy; extern SomfyShadeController somfy;
extern SocketEmitter sockEmit; extern SocketEmitter sockEmit;
extern ConfigSettings settings; extern ConfigSettings settings;
@ -154,6 +153,7 @@ void SomfyShadeController::updateGroupFlags() {
} }
#ifdef USE_NVS #ifdef USE_NVS
bool SomfyShadeController::loadLegacy() { bool SomfyShadeController::loadLegacy() {
Preferences pref;
ESP_LOGI(TAG, "Loading Legacy shades using NVS"); ESP_LOGI(TAG, "Loading Legacy shades using NVS");
pref.begin("Shades", true); pref.begin("Shades", true);
pref.getBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds)); pref.getBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
@ -203,6 +203,7 @@ bool SomfyShadeController::loadLegacy() {
} }
#endif #endif
bool SomfyShadeController::begin() { bool SomfyShadeController::begin() {
Preferences pref;
// Load up all the configuration data. // Load up all the configuration data.
//ShadeConfigFile::getAppVersion(this->appVersion); //ShadeConfigFile::getAppVersion(this->appVersion);
ESP_LOGI(TAG, "App Version:%u.%u.%u", settings.appVersion.major, settings.appVersion.minor, settings.appVersion.build); ESP_LOGI(TAG, "App Version:%u.%u.%u", settings.appVersion.major, settings.appVersion.minor, settings.appVersion.build);
@ -357,6 +358,7 @@ void SomfyGroup::clear() {
memset(&this->linkedShades, 0x00, sizeof(this->linkedShades)); memset(&this->linkedShades, 0x00, sizeof(this->linkedShades));
} }
bool SomfyShade::linkRemote(uint32_t address, uint16_t rollingCode) { bool SomfyShade::linkRemote(uint32_t address, uint16_t rollingCode) {
Preferences pref;
// Check to see if the remote is already linked. If it is // Check to see if the remote is already linked. If it is
// just return true after setting the rolling code // just return true after setting the rolling code
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
@ -409,6 +411,7 @@ bool SomfyGroup::linkShade(uint8_t shadeId) {
} }
void SomfyShade::commit() { somfy.commit(); } void SomfyShade::commit() { somfy.commit(); }
void SomfyShade::commitShadePosition() { void SomfyShade::commitShadePosition() {
Preferences pref;
somfy.isDirty = true; somfy.isDirty = true;
#ifdef USE_NVS #ifdef USE_NVS
char shadeKey[15]; char shadeKey[15];
@ -422,6 +425,7 @@ void SomfyShade::commitShadePosition() {
#endif #endif
} }
void SomfyShade::commitMyPosition() { void SomfyShade::commitMyPosition() {
Preferences pref;
somfy.isDirty = true; somfy.isDirty = true;
#ifdef USE_NVS #ifdef USE_NVS
if(somfy.useNVS()) { if(somfy.useNVS()) {
@ -435,6 +439,7 @@ void SomfyShade::commitMyPosition() {
#endif #endif
} }
void SomfyShade::commitTiltPosition() { void SomfyShade::commitTiltPosition() {
Preferences pref;
somfy.isDirty = true; somfy.isDirty = true;
#ifdef USE_NVS #ifdef USE_NVS
if(somfy.useNVS()) { if(somfy.useNVS()) {
@ -448,6 +453,7 @@ void SomfyShade::commitTiltPosition() {
#endif #endif
} }
bool SomfyShade::unlinkRemote(uint32_t address) { bool SomfyShade::unlinkRemote(uint32_t address) {
Preferences pref;
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
if(this->linkedRemotes[i].getRemoteAddress() == address) { if(this->linkedRemotes[i].getRemoteAddress() == address) {
this->linkedRemotes[i].setRemoteAddress(0); this->linkedRemotes[i].setRemoteAddress(0);
@ -917,6 +923,7 @@ void SomfyShade::checkMovement() {
} }
#ifdef USE_NVS #ifdef USE_NVS
void SomfyShade::load() { void SomfyShade::load() {
Preferences pref;
char shadeKey[15]; char shadeKey[15];
uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES]; uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES];
memset(linkedAddresses, 0x00, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES); memset(linkedAddresses, 0x00, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES);
@ -2708,6 +2715,7 @@ bool SomfyShade::moveToTarget(float pos, float tilt) {
return true; return true;
} }
bool SomfyShade::save() { bool SomfyShade::save() {
Preferences pref;
#ifdef USE_NVS #ifdef USE_NVS
if(somfy.useNVS()) { if(somfy.useNVS()) {
char shadeKey[15]; char shadeKey[15];
@ -3285,6 +3293,7 @@ SomfyShade *SomfyShadeController::addShade(JsonObject &obj) {
return shade; return shade;
} }
SomfyShade *SomfyShadeController::addShade() { SomfyShade *SomfyShadeController::addShade() {
Preferences pref;
uint8_t shadeId = this->getNextShadeId(); uint8_t shadeId = this->getNextShadeId();
// So the next shade id will be the first one we run into with an id of 255 so // So the next shade id will be the first one we run into with an id of 255 so
// if it gets deleted in the middle then it will get the first slot that is empty. // if it gets deleted in the middle then it will get the first slot that is empty.
@ -3527,6 +3536,7 @@ uint32_t SomfyShadeController::sendFrame(somfy_frame_t &frame, uint8_t repeat, u
return this->transceiver.submitTx(job, priority, delayMs); return this->transceiver.submitTx(job, priority, delayMs);
} }
bool SomfyShadeController::deleteShade(uint8_t shadeId) { bool SomfyShadeController::deleteShade(uint8_t shadeId) {
Preferences pref;
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
if(this->shades[i].getShadeId() == shadeId) { if(this->shades[i].getShadeId() == shadeId) {
shades[i].emitState("shadeRemoved"); shades[i].emitState("shadeRemoved");
@ -3594,6 +3604,7 @@ bool SomfyShadeController::loadShadesFile(const char *filename) { return ShadeCo
// it has been queued. After that lastRollingCode holds what NVS does. // it has been queued. After that lastRollingCode holds what NVS does.
uint16_t SomfyRemote::peekNextRollingCode() { uint16_t SomfyRemote::peekNextRollingCode() {
return nextRollingCode(this->m_rollingCodeSynced, this->lastRollingCode, [this]() { return nextRollingCode(this->m_rollingCodeSynced, this->lastRollingCode, [this]() {
Preferences pref;
if(!pref.begin("ShadeCodes")) { if(!pref.begin("ShadeCodes")) {
ESP_LOGE(TAG, "peekNextRollingCode() pref.begin(ShadeCodes) FAILED"); ESP_LOGE(TAG, "peekNextRollingCode() pref.begin(ShadeCodes) FAILED");
} }
@ -3603,6 +3614,7 @@ uint16_t SomfyRemote::peekNextRollingCode() {
}); });
} }
void SomfyRemote::commitRollingCode(uint16_t code) { void SomfyRemote::commitRollingCode(uint16_t code) {
Preferences pref;
if(!pref.begin("ShadeCodes")) { if(!pref.begin("ShadeCodes")) {
ESP_LOGE(TAG, "commitRollingCode() pref.begin(ShadeCodes) FAILED"); ESP_LOGE(TAG, "commitRollingCode() pref.begin(ShadeCodes) FAILED");
} }
@ -3617,6 +3629,7 @@ uint16_t SomfyRemote::p_lastRollingCode(uint16_t code) {
return old; return old;
} }
uint16_t SomfyRemote::setRollingCode(uint16_t code) { uint16_t SomfyRemote::setRollingCode(uint16_t code) {
Preferences pref;
if(this->lastRollingCode != code) { if(this->lastRollingCode != code) {
pref.begin("ShadeCodes"); pref.begin("ShadeCodes");
pref.putUShort(this->m_remotePrefId, code); pref.putUShort(this->m_remotePrefId, code);
@ -4295,6 +4308,7 @@ void transceiver_config_t::toJSON(JsonObject& obj) {
} }
*/ */
void transceiver_config_t::save() { void transceiver_config_t::save() {
Preferences pref;
pref.begin("CC1101"); pref.begin("CC1101");
pref.clear(); pref.clear();
pref.putUChar("type", this->type); pref.putUChar("type", this->type);
@ -4343,13 +4357,14 @@ void transceiver_config_t::save() {
ESP_LOGI(TAG, "Save Radio Settings SCK:%u MISO:%u MOSI:%u CSN:%u RX:%u TX:%u", this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin, this->RXPin, this->TXPin); ESP_LOGI(TAG, "Save Radio Settings SCK:%u MISO:%u MOSI:%u CSN:%u RX:%u TX:%u", this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin, this->RXPin, this->TXPin);
} }
void transceiver_config_t::removeNVSKey(const char *key) { void transceiver_config_t::removeNVSKey(Preferences &pref, const char *key) {
if(pref.isKey(key)) { if(pref.isKey(key)) {
ESP_LOGD(TAG, "Removing NVS Key: CC1101.%s", key); ESP_LOGD(TAG, "Removing NVS Key: CC1101.%s", key);
pref.remove(key); pref.remove(key);
} }
} }
void transceiver_config_t::load() { void transceiver_config_t::load() {
Preferences pref;
esp_chip_info_t ci; esp_chip_info_t ci;
esp_chip_info(&ci); esp_chip_info(&ci);
switch(ci.model) { switch(ci.model) {
@ -4413,32 +4428,33 @@ void transceiver_config_t::load() {
this->rxBandwidth = pref.getFloat("rxBandwidth", this->rxBandwidth); this->rxBandwidth = pref.getFloat("rxBandwidth", this->rxBandwidth);
this->proto = static_cast<radio_proto>(pref.getChar("proto", static_cast<uint8_t>(this->proto))); this->proto = static_cast<radio_proto>(pref.getChar("proto", static_cast<uint8_t>(this->proto)));
this->noiseDetection = pref.getBool("noiseDetect", false); this->noiseDetection = pref.getBool("noiseDetect", false);
this->removeNVSKey("internalCCMode"); this->removeNVSKey(pref, "internalCCMode");
this->removeNVSKey("modulationMode"); this->removeNVSKey(pref, "modulationMode");
this->removeNVSKey("channel"); this->removeNVSKey(pref, "channel");
this->removeNVSKey("channelSpacing"); this->removeNVSKey(pref, "channelSpacing");
this->removeNVSKey("dataRate"); this->removeNVSKey(pref, "dataRate");
this->removeNVSKey("syncMode"); this->removeNVSKey(pref, "syncMode");
this->removeNVSKey("syncWordHigh"); this->removeNVSKey(pref, "syncWordHigh");
this->removeNVSKey("syncWordLow"); this->removeNVSKey(pref, "syncWordLow");
this->removeNVSKey("addrCheckMode"); this->removeNVSKey(pref, "addrCheckMode");
this->removeNVSKey("checkAddr"); this->removeNVSKey(pref, "checkAddr");
this->removeNVSKey("dataWhitening"); this->removeNVSKey(pref, "dataWhitening");
this->removeNVSKey("pktFormat"); this->removeNVSKey(pref, "pktFormat");
this->removeNVSKey("pktLengthMode"); this->removeNVSKey(pref, "pktLengthMode");
this->removeNVSKey("pktLength"); this->removeNVSKey(pref, "pktLength");
this->removeNVSKey("useCRC"); this->removeNVSKey(pref, "useCRC");
this->removeNVSKey("autoFlushCRC"); this->removeNVSKey(pref, "autoFlushCRC");
this->removeNVSKey("disableDCFilter"); this->removeNVSKey(pref, "disableDCFilter");
this->removeNVSKey("enableManchester"); this->removeNVSKey(pref, "enableManchester");
this->removeNVSKey("enableFEC"); this->removeNVSKey(pref, "enableFEC");
this->removeNVSKey("minPreambleBytes"); this->removeNVSKey(pref, "minPreambleBytes");
this->removeNVSKey("pqtThreshold"); this->removeNVSKey(pref, "pqtThreshold");
this->removeNVSKey("appendStatus"); this->removeNVSKey(pref, "appendStatus");
pref.end(); pref.end();
//this->printBuffer = somfy.transceiver.printBuffer; //this->printBuffer = somfy.transceiver.printBuffer;
} }
void transceiver_config_t::apply() { void transceiver_config_t::apply() {
Preferences pref;
// A transmission in flight stops after its current frame so the radio can // A transmission in flight stops after its current frame so the radio can
// be reconfigured. Queued frames are already encoded and go out with the // be reconfigured. Queued frames are already encoded and go out with the
// new settings. // new settings.

View file

@ -1,5 +1,6 @@
#ifndef SOMFY_H #ifndef SOMFY_H
#define SOMFY_H #define SOMFY_H
#include <Preferences.h>
#include "ConfigSettings.h" #include "ConfigSettings.h"
#include "WResp.h" #include "WResp.h"
@ -421,7 +422,8 @@ struct transceiver_config_t {
void save(); void save();
void load(); void load();
void apply(); void apply();
void removeNVSKey(const char *key); // Removes a key no longer used from the open CC1101 namespace.
void removeNVSKey(Preferences &pref, const char *key);
}; };
class Transceiver { class Transceiver {
private: private: