diff --git a/ConfigFile.cpp b/ConfigFile.cpp new file mode 100644 index 0000000..098c212 --- /dev/null +++ b/ConfigFile.cpp @@ -0,0 +1,370 @@ +#include +#include +#include +#include "ConfigFile.h" +#include "Utils.h" + +extern Preferences pref; + +#define SHADE_HDR_VER 1 +#define SHADE_HDR_SIZE 16 +#define SHADE_REC_SIZE 176 + +bool ConfigFile::begin(const char* filename, bool readOnly) { + this->file = LittleFS.open(filename, readOnly ? "r" : "w"); + this->_opened = true; + return true; +} +void ConfigFile::end() { + if(this->isOpen()) { + if(!this->readOnly) this->file.flush(); + this->file.close(); + } + this->_opened = false; +} +bool ConfigFile::isOpen() { return this->_opened; } +bool ConfigFile::seekChar(const char val) { + if(!this->isOpen()) return false; + char ch; + do { + ch = this->readChar('\0'); + if(ch == '\0') return false; + } while(ch != val); + return true; +} +bool ConfigFile::writeSeparator() {return this->writeChar(CFG_VALUE_SEP); } +bool ConfigFile::writeRecordEnd() { return this->writeChar(CFG_REC_END); } +bool ConfigFile::writeHeader() { return this->writeHeader(this->header); } +bool ConfigFile::writeHeader(const config_header_t &hdr) { + if(!this->isOpen()) return false; + this->writeUInt8(hdr.version); + this->writeUInt8(hdr.length); + this->writeUInt8(hdr.recordSize); + this->writeUInt8(hdr.records, CFG_REC_END); + return true; +} +bool ConfigFile::readHeader() { + if(!this->isOpen()) return false; + //if(this->file.position() != 0) this->file.seek(0, SeekSet); + Serial.printf("Reading header at %u\n", this->file.position()); + this->header.version = this->readUInt8(this->header.version); + this->header.length = this->readUInt8(0); + this->header.recordSize = this->readUInt8(this->header.recordSize); + this->header.records = this->readUInt8(this->header.records); + Serial.printf("version:%u len:%u size:%u recs:%u pos:%d\n", this->header.version, this->header.length, this->header.recordSize, this->header.records, this->file.position()); + return true; +} +bool ConfigFile::seekRecordByIndex(uint16_t ndx) { + if(!this->file) { + return false; + } + if(((this->header.recordSize * ndx) + this->header.length) > this->file.size()) return false; +} +bool ConfigFile::readString(char *buff, size_t len) { + if(!this->file) return false; + memset(buff, 0x00, len); + uint16_t i = 0; + while(i < len) { + uint8_t val; + if(this->file.read(&val, 1) == 1) { + switch(val) { + case CFG_REC_END: + case CFG_VALUE_SEP: + _rtrim(buff); + return true; + } + buff[i++] = val; + if(i == len) { + _rtrim(buff); + return true; + } + } + else + return false; + } + _rtrim(buff); + return true; +} +bool ConfigFile::writeString(const char *val, size_t len, const char tok) { + if(!this->isOpen()) return false; + int slen = strlen(val); + if(slen > 0) + if(this->file.write((uint8_t *)val, slen) != slen) return false; + // Now we need to pad the end of the string so that it is of a fixed length. + while(slen < len - 1) { + this->file.write(' '); + slen++; + } + // 255 = len = 4 slen = 3 + if(tok != CFG_TOK_NONE) + return this->writeChar(tok); + return true; +} +bool ConfigFile::writeChar(const char val) { + if(!this->isOpen()) return false; + if(this->file.write(static_cast(val)) == 1) return true; + return false; +} +bool ConfigFile::writeUInt8(const uint8_t val, const char tok) { + char buff[4]; + snprintf(buff, sizeof(buff), "%3u", val); + return this->writeString(buff, sizeof(buff), tok); +} +bool ConfigFile::writeUInt16(const uint16_t val, const char tok) { + char buff[6]; + snprintf(buff, sizeof(buff), "%5u", val); + return this->writeString(buff, sizeof(buff), tok); +} +bool ConfigFile::writeUInt32(const uint32_t val, const char tok) { + char buff[11]; + snprintf(buff, sizeof(buff), "%10u", val); + return this->writeString(buff, sizeof(buff), tok); +} +bool ConfigFile::writeFloat(const float val, const uint8_t prec, const char tok) { + char buff[20]; + snprintf(buff, sizeof(buff), "%*.*f", 7 + prec, prec, val); + return this->writeString(buff, 8 + prec, tok); +} +bool ConfigFile::writeBool(const bool val, const char tok) { + return this->writeString(val ? "true" : "false", 6, tok); +} + +char ConfigFile::readChar(const char defVal) { + uint8_t ch; + if(this->file.read(&ch, 1) == 1) return (char)ch; + return defVal; +} +uint8_t ConfigFile::readUInt8(const uint8_t defVal) { + char buff[4]; + if(this->readString(buff, sizeof(buff))) + return static_cast(atoi(buff)); + return defVal; +} +uint16_t ConfigFile::readUInt16(const uint16_t defVal) { + char buff[6]; + if(this->readString(buff, sizeof(buff))) + return static_cast(atoi(buff)); + return defVal; +} +uint32_t ConfigFile::readUInt32(const uint32_t defVal) { + char buff[11]; + if(this->readString(buff, sizeof(buff))) + return static_cast(atoi(buff)); + return defVal; +} +float ConfigFile::readFloat(const float defVal) { + char buff[25]; + if(this->readString(buff, sizeof(buff))) + return atof(buff); + return defVal; +} +bool ConfigFile::readBool(const bool defVal) { + char buff[6]; + if(this->readString(buff, sizeof(buff))) { + switch(buff[0]) { + case 't': + case 'T': + case '1': + return true; + default: + return false; + } + } + return defVal; +} + +bool ShadeConfigFile::seekRecordById(uint8_t id) { + if(this->isOpen()) return false; + this->file.seek(this->header.length, SeekSet); // Start at the beginning of the file after the header. + uint8_t i = 0; + while(i < SOMFY_MAX_SHADES) { + uint32_t pos = this->file.position(); + uint8_t len = this->readUInt8(this->header.recordSize); + uint8_t cid = this->readUInt8(255); + if(cid == id) { + this->file.seek(pos, SeekSet); + return true; + } + pos += len; + this->file.seek(pos, SeekSet); + } + return false; +} +bool ShadeConfigFile::begin(bool readOnly) { return this->begin("/shades.cfg", readOnly); } +bool ShadeConfigFile::begin(const char *filename, bool readOnly) { return ConfigFile::begin(filename, readOnly); } +void ShadeConfigFile::end() { ConfigFile::end(); } +bool ShadeConfigFile::save(SomfyShadeController *s) { + this->header.version = SHADE_HDR_VER; + this->header.recordSize = SHADE_REC_SIZE; + this->header.length = SHADE_HDR_SIZE; + this->header.records = SOMFY_MAX_SHADES; + this->writeHeader(); + for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { + SomfyShade *shade = &s->shades[i]; + this->writeShadeRecord(shade); + } + return true; +} +bool ShadeConfigFile::validate() { + this->readHeader(); + if(this->header.version < 1) { + Serial.print("Invalid Header Version:"); + Serial.println(this->header.version); + return false; + } + if(this->header.recordSize < 100) { + Serial.print("Invalid Record Size:"); + Serial.println(this->header.recordSize); + return false; + } + if(this->header.records != 32) { + Serial.print("Invalid Record Count:"); + Serial.println(this->header.records); + return false; + } + if(this->file.position() != this->header.length) { + Serial.printf("File not positioned at %u end of header: %d\n", this->header.length, this->file.position()); + return false; + } + // We should know the file size based upon the record information in the header + if(this->file.size() != this->header.length + (this->header.recordSize * this->header.records)) { + Serial.printf("File size is not correct should be %d and got %d", this->header.length + (this->header.recordSize * this->header.records), this->file.size()); + } + // Next check to see if the records match the header length. + uint8_t recs = 0; + uint32_t startPos = this->file.position(); + while(recs < this->header.records) { + uint32_t pos = this->file.position(); + if(!this->seekChar(CFG_REC_END)) { + Serial.printf("Failed to find the record end %d\n", recs); + return false; + } + if(this->file.position() - pos != this->header.recordSize) { + Serial.printf("Record length is %d and should be %d\n", this->file.position() - pos, this->header.recordSize); + return false; + } + recs++; + } + this->file.seek(startPos, SeekSet); + return true; +} +bool ShadeConfigFile::load(SomfyShadeController *s, const char *filename) { + ShadeConfigFile file; + if(file.begin(filename, true)) { + bool success = file.loadFile(s, filename); + file.end(); + return success; + } + return false; +} +bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) { + bool opened = false; + if(!this->isOpen()) { + Serial.println("Opening shade config file"); + this->begin(filename, true); + opened = true; + } + else { + //this->file.seek(0, SeekSet); + } + if(!this->validate()) { + Serial.println("Shade config file invalid!"); + if(opened) this->end(); + return false; + } + // We should be valid so start reading. + pref.begin("ShadeCodes"); + for(uint8_t i = 0; i < this->header.records; i++) { + SomfyShade *shade = &s->shades[i]; + shade->setShadeId(this->readUInt8(255)); + shade->paired = this->readBool(false); + shade->shadeType = static_cast(this->readUInt8(0)); + shade->setRemoteAddress(this->readUInt32(0)); + this->readString(shade->name, sizeof(shade->name)); + shade->hasTilt = this->readBool(false); + shade->upTime = this->readUInt32(shade->upTime); + shade->downTime = this->readUInt32(shade->downTime); + shade->tiltTime = this->readUInt32(shade->tiltTime); + for(uint8_t j = 0; j < SOMFY_MAX_LINKED_REMOTES; j++) { + SomfyLinkedRemote *rem = &shade->linkedRemotes[j]; + rem->setRemoteAddress(this->readUInt32(0)); + if(rem->getRemoteAddress() != 0) rem->lastRollingCode = pref.getUShort(rem->getRemotePrefId(), 0); + } + shade->lastRollingCode = this->readUInt16(0); + if(shade->getRemoteAddress() != 0) shade->lastRollingCode = max(pref.getUShort(shade->getRemotePrefId(), shade->lastRollingCode), shade->lastRollingCode); + shade->myPos = this->readUInt8(255); + shade->currentPos = this->readFloat(0); + shade->currentTiltPos = this->readFloat(0); + shade->tiltPosition = (uint8_t)floor(shade->currentTiltPos * 100); + shade->position = (uint8_t)floor(shade->currentPos * 100); + shade->target = shade->position; + shade->tiltTarget = shade->tiltPosition; + } + pref.end(); + if(opened) { + Serial.println("Closing shade config file"); + this->end(); + } + return true; +} +bool ShadeConfigFile::writeShadeRecord(SomfyShade *shade) { + this->writeUInt8(shade->getShadeId()); + this->writeBool(shade->paired); + this->writeUInt8(static_cast(shade->shadeType)); + this->writeUInt32(shade->getRemoteAddress()); + this->writeString(shade->name, sizeof(shade->name)); + this->writeBool(shade->hasTilt); + this->writeUInt32(shade->upTime); + this->writeUInt32(shade->downTime); + this->writeUInt32(shade->tiltTime); + for(uint8_t j = 0; j < SOMFY_MAX_LINKED_REMOTES; j++) { + SomfyLinkedRemote *rem = &shade->linkedRemotes[j]; + this->writeUInt32(rem->getRemoteAddress()); + } + this->writeUInt16(shade->lastRollingCode); + this->writeUInt8(shade->myPos); + this->writeFloat(shade->currentPos, 5); + this->writeFloat(shade->currentTiltPos, 5, CFG_REC_END); + return true; +} +bool ShadeConfigFile::exists() { return LittleFS.exists("/shades.cfg"); } +bool ShadeConfigFile::getAppVersion(appver_t &ver) { + char app[15]; + if(!LittleFS.exists("/appversion")) return false; + File f = LittleFS.open("/appversion", "r"); + size_t fsize = f.size(); + memset(app, 0x00, sizeof(app)); + f.read((uint8_t *)app, sizeof(app) - 1); + f.close(); + // Now lets parse this pig. + memset(&ver, 0x00, sizeof(appver_t)); + char num[3]; + uint8_t i = 0; + for(uint8_t j = 0; j < 3 && i < strlen(app); j++) { + char ch = app[i++]; + if(ch != '.') + num[j] = ch; + else + break; + } + ver.major = static_cast(atoi(num) & 0xFF); + memset(num, 0x00, sizeof(num)); + for(uint8_t j = 0; j < 3 && i < strlen(app); j++) { + char ch = app[i++]; + if(ch != '.') + num[j] = ch; + else + break; + } + ver.minor = static_cast(atoi(num) & 0xFF); + memset(num, 0x00, sizeof(num)); + for(uint8_t j = 0; j < 3 && i < strlen(app); j++) { + char ch = app[i++]; + if(ch != '.') + num[j] = ch; + else + break; + } + ver.build = static_cast(atoi(num) & 0xFF); + return true; +} diff --git a/ConfigFile.h b/ConfigFile.h new file mode 100644 index 0000000..2ffb292 --- /dev/null +++ b/ConfigFile.h @@ -0,0 +1,66 @@ +#include +#include +#include "Somfy.h" +#ifndef configfile_h +#define configfile_h + +#define CFG_VALUE_SEP ',' +#define CFG_REC_END '\n' +#define CFG_TOK_NONE 0x00 + +typedef struct config_header_t { + uint8_t version = 1; + uint16_t recordSize = 0; + uint16_t records = 0; + uint8_t length = 0; +}; +class ConfigFile { + protected: + File file; + bool readOnly = false; + bool begin(const char *filename, bool readOnly = false); + uint32_t startRecPos = 0; + bool _opened = false; + public: + config_header_t header; + bool save(); + void end(); + bool isOpen(); + bool seekRecordByIndex(uint16_t ndx); + bool readHeader(); + bool seekChar(const char val); + bool writeHeader(const config_header_t &header); + bool writeHeader(); + bool writeSeparator(); + bool writeRecordEnd(); + bool writeChar(const char val); + bool writeUInt8(const uint8_t val, const char tok = CFG_VALUE_SEP); + bool writeUInt16(const uint16_t val, const char tok = CFG_VALUE_SEP); + bool writeUInt32(const uint32_t val, const char tok = CFG_VALUE_SEP); + bool writeBool(const bool val, const char tok = CFG_VALUE_SEP); + bool writeFloat(const float val, const uint8_t prec, const char tok = CFG_VALUE_SEP); + bool readString(char *buff, size_t len); + bool writeString(const char *val, size_t len, const char tok = CFG_VALUE_SEP); + char readChar(const char defVal = '\0'); + uint8_t readUInt8(const uint8_t defVal = 0); + uint16_t readUInt16(const uint16_t defVal = 0); + uint32_t readUInt32(const uint32_t defVal = 0); + bool readBool(const bool defVal = false); + float readFloat(const float defVal = 0.00); +}; +class ShadeConfigFile : public ConfigFile { + protected: + bool writeShadeRecord(SomfyShade *shade); + public: + static bool getAppVersion(appver_t &ver); + static bool exists(); + static bool load(SomfyShadeController *somfy, const char *filename = "/shades.cfg"); + bool begin(const char *filename, bool readOnly = false); + bool begin(bool readOnly = false); + bool save(SomfyShadeController *sofmy); + bool loadFile(SomfyShadeController *somfy, const char *filename = "/shades.cfg"); + void end(); + bool seekRecordById(uint8_t id); + bool validate(); +}; +#endif; diff --git a/ConfigSettings.cpp b/ConfigSettings.cpp index d1f4289..94222e1 100644 --- a/ConfigSettings.cpp +++ b/ConfigSettings.cpp @@ -47,8 +47,6 @@ bool BaseSettings::parseIPAddress(JsonObject &obj, const char *prop, IPAddress * } return true; } - - int BaseSettings::parseValueInt(JsonObject &obj, const char *prop, int defVal) { if(obj.containsKey(prop)) return obj[prop]; return defVal; diff --git a/ConfigSettings.h b/ConfigSettings.h index b2749a0..bf0978c 100644 --- a/ConfigSettings.h +++ b/ConfigSettings.h @@ -3,7 +3,7 @@ #ifndef configsettings_h #define configsettings_h -#define FW_VERSION "v1.3.2" +#define FW_VERSION "v1.4.0" enum DeviceStatus { DS_OK = 0, DS_ERROR = 1, diff --git a/Network.cpp b/Network.cpp index 027e6d1..2308909 100644 --- a/Network.cpp +++ b/Network.cpp @@ -123,10 +123,10 @@ void Network::setConnected(conn_types connType) { Serial.print(" "); Serial.print(ETH.linkSpeed()); Serial.println("Mbps"); + char buf[128]; + snprintf(buf, sizeof(buf), "{\"connected\":true,\"speed\":%d,\"fullduplex\":%s}", ETH.linkSpeed(), ETH.fullDuplex() ? "true" : "false"); + sockEmit.sendToClients("ethernet", buf); } - char buf[128]; - snprintf(buf, sizeof(buf), "{\"connected\":true,\"speed\":%d,\"fullduplex\":%s}", ETH.linkSpeed(), ETH.fullDuplex() ? "true" : "false"); - sockEmit.sendToClients("ethernet", buf); } else { Serial.println(); diff --git a/Somfy.cpp b/Somfy.cpp index 56e6381..eb8acb5 100644 --- a/Somfy.cpp +++ b/Somfy.cpp @@ -7,12 +7,14 @@ #include "Somfy.h" #include "Sockets.h" #include "MQTT.h" +#include "ConfigFile.h" extern Preferences pref; extern SomfyShadeController somfy; extern SocketEmitter sockEmit; extern MQTTClass mqtt; + uint8_t rxmode = 0; // Indicates whether the radio is in receive mode. Just to ensure there isn't more than one interrupt hooked. #define SYMBOL 640 #if defined(ESP8266) @@ -245,13 +247,16 @@ SomfyShade *SomfyShadeController::findShadeByRemoteAddress(uint32_t address) { } return nullptr; } -bool SomfyShadeController::begin() { - // Load up all the configuration data. - //Serial.printf("sizeof(SomfyShade) = %d\n", sizeof(SomfyShade)); - pref.begin("Shades"); +bool SomfyShadeController::loadLegacy() { + Serial.println("Loading Legacy shades using NVS"); + pref.begin("Shades", true); pref.getBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds)); pref.end(); - //this->transceiver.begin(); + for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) { + if(i != 0) DEBUG_SOMFY.print(","); + DEBUG_SOMFY.print(this->m_shadeIds[i]); + } + DEBUG_SOMFY.println(); sortArray(this->m_shadeIds, sizeof(this->m_shadeIds)); #ifdef DEBUG_SOMFY for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) { @@ -281,12 +286,63 @@ bool SomfyShadeController::begin() { } Serial.println(); #endif - pref.begin("Shades"); - pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds)); - pref.end(); + if(!this->useNVS()) { + pref.begin("Shades"); + pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds)); + pref.end(); + } + this->commit(); + return true; +} +bool SomfyShadeController::begin() { + // Load up all the configuration data. + ShadeConfigFile::getAppVersion(this->appVersion); + Serial.printf("App Version:%u.%u.%u\n", this->appVersion.major, this->appVersion.minor, this->appVersion.build); + if(!this->useNVS()) { // At 1.4 we started using the configuration file. If the file doesn't exist then booh. + // We need to remove all the extraeneous data from NVS for the shades. From here on out we + // will rely on the shade configuration. + Serial.println("No longer using NVS"); + if(ShadeConfigFile::exists()) { + ShadeConfigFile::load(this); + } + else { + this->loadLegacy(); + } + pref.begin("Shades"); + if(pref.isKey("shadeIds")) { + pref.getBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds)); + pref.clear(); // Delete all the keys. + } + pref.end(); + for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) { + // Start deleting the keys for the shades. + if(this->m_shadeIds[i] == 255) continue; + char shadeKey[15]; + sprintf(shadeKey, "SomfyShade%u", this->m_shadeIds[i]); + pref.begin(shadeKey); + pref.clear(); + pref.end(); + } + } + else if(ShadeConfigFile::exists()) { + Serial.println("shades.cfg exists so we are using that"); + ShadeConfigFile::load(this); + } + else { + Serial.println("Starting clean"); + this->loadLegacy(); + } this->transceiver.begin(); return true; } +void SomfyShadeController::commit() { + ShadeConfigFile file; + file.begin(); + file.save(this); + file.end(); + this->isDirty = false; + this->lastCommit = millis(); +} SomfyShade * SomfyShadeController::getShadeById(uint8_t shadeId) { for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { if(this->shades[i].getShadeId() == shadeId) return &this->shades[i]; @@ -304,41 +360,85 @@ bool SomfyShade::linkRemote(uint32_t address, uint16_t rollingCode) { } for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { if(this->linkedRemotes[i].getRemoteAddress() == 0) { - char shadeKey[15]; this->linkedRemotes[i].setRemoteAddress(address); this->linkedRemotes[i].setRollingCode(rollingCode); - snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId()); - pref.begin(shadeKey); - uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES]; - memset(linkedAddresses, 0x00, sizeof(linkedAddresses)); - uint8_t j = 0; - for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { - SomfyLinkedRemote lremote = this->linkedRemotes[i]; - if(lremote.getRemoteAddress() != 0) linkedAddresses[j++] = lremote.getRemoteAddress(); + if(somfy.useNVS()) { + uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES]; + memset(linkedAddresses, 0x00, sizeof(linkedAddresses)); + uint8_t j = 0; + for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { + SomfyLinkedRemote lremote = this->linkedRemotes[i]; + if(lremote.getRemoteAddress() != 0) linkedAddresses[j++] = lremote.getRemoteAddress(); + } + char shadeKey[15]; + snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId()); + pref.begin(shadeKey); + pref.putBytes("linkedAddr", linkedAddresses, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES); + pref.end(); } - pref.putBytes("linkedAddr", linkedAddresses, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES); - pref.end(); + this->commit(); return true; } } return false; } +void SomfyShade::commit() { somfy.commit(); } +void SomfyShade::commitShadePosition() { + somfy.isDirty = true; + char shadeKey[15]; + if(somfy.useNVS()) { + snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId); + Serial.print("Writing current shade position: "); + Serial.println(this->currentPos, 4); + pref.begin(shadeKey); + pref.putFloat("currentPos", this->currentPos); + pref.end(); + } +} +void SomfyShade::commitMyPosition() { + somfy.isDirty = true; + if(somfy.useNVS()) { + char shadeKey[15]; + snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId); + Serial.print("Writing my shade position:"); + Serial.print(this->myPos); + Serial.println("%"); + pref.begin(shadeKey); + pref.putUShort("myPos", this->myPos); + pref.end(); + } +} +void SomfyShade::commitTiltPosition() { + somfy.isDirty = true; + if(somfy.useNVS()) { + char shadeKey[15]; + snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId); + Serial.print("Writing current shade tilt position: "); + Serial.println(this->currentTiltPos, 4); + pref.begin(shadeKey); + pref.putFloat("currentTiltPos", this->currentTiltPos); + pref.end(); + } +} bool SomfyShade::unlinkRemote(uint32_t address) { for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { if(this->linkedRemotes[i].getRemoteAddress() == address) { - char shadeKey[15]; this->linkedRemotes[i].setRemoteAddress(0); - snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId()); - pref.begin(shadeKey); - uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES]; - memset(linkedAddresses, 0x00, sizeof(linkedAddresses)); - uint8_t j = 0; - for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { - SomfyLinkedRemote lremote = this->linkedRemotes[i]; - if(lremote.getRemoteAddress() != 0) linkedAddresses[j++] = lremote.getRemoteAddress(); + if(somfy.useNVS()) { + char shadeKey[15]; + snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId()); + uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES]; + memset(linkedAddresses, 0x00, sizeof(linkedAddresses)); + uint8_t j = 0; + for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { + SomfyLinkedRemote lremote = this->linkedRemotes[i]; + if(lremote.getRemoteAddress() != 0) linkedAddresses[j++] = lremote.getRemoteAddress(); + } + pref.begin(shadeKey); + pref.putBytes("linkedAddr", linkedAddresses, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES); + pref.end(); } - pref.putBytes("linkedAddr", linkedAddresses, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES); - pref.end(); + this->commit(); return true; } } @@ -505,13 +605,7 @@ void SomfyShade::checkMovement() { } } if(currDir != this->direction && this->direction == 0) { - char shadeKey[15]; - snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId); - Serial.print("Writing current shade position: "); - Serial.println(this->currentPos, 4); - pref.begin(shadeKey); - pref.putFloat("currentPos", this->currentPos); - pref.end(); + this->commitShadePosition(); if(this->settingMyPos) { delay(200); // Set this position before sending the command. If you don't the processFrame function @@ -521,23 +615,11 @@ void SomfyShade::checkMovement() { SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS); this->settingMyPos = false; this->seekingMyPos = false; - Serial.print("Committing My Position: "); - Serial.print(this->myPos); - Serial.println("%"); - - pref.begin(shadeKey); - pref.putUShort("myPos", this->myPos); - pref.end(); + this->commitMyPosition(); } } if(currTiltDir != this->tiltDirection && this->tiltDirection == 0) { - char shadeKey[15]; - snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId); - Serial.print("Writing current shade tilt position: "); - Serial.println(this->currentTiltPos, 4); - pref.begin(shadeKey); - pref.putFloat("currentTiltPos", this->currentTiltPos); - pref.end(); + this->commitTiltPosition(); } if(currDir != this->direction || currPos != this->position || currTiltDir != this->tiltDirection || currTiltPos != this->tiltPosition) { // We need to emit on the socket that our state has changed. @@ -555,19 +637,36 @@ void SomfyShade::load() { // Now load up each of the shades into memory. //Serial.print("key:"); //Serial.println(shadeKey); - pref.begin(shadeKey); + + pref.begin(shadeKey, !somfy.useNVS()); pref.getString("name", this->name, sizeof(this->name)); this->paired = pref.getBool("paired", false); - this->upTime = pref.getUShort("upTime", 10000); - this->downTime = pref.getUShort("downTime", 10000); - this->setRemoteAddress(pref.getULong("remoteAddress", 0)); + if(pref.isKey("upTime") && pref.getType("upTime") != PreferenceType::PT_U32) { + // We need to convert these to 32 bits because earlier versions did not support this. + this->upTime = static_cast(pref.getUShort("upTime", 1000)); + this->downTime = static_cast(pref.getUShort("downTime", 1000)); + this->tiltTime = static_cast(pref.getUShort("tiltTime", 7000)); + if(somfy.useNVS()) { + pref.remove("upTime"); + pref.putUInt("upTime", this->upTime); + pref.remove("downTime"); + pref.putUInt("downTime", this->downTime); + pref.remove("tiltTime"); + pref.putUInt("tiltTime", this->tiltTime); + } + } + else { + this->upTime = pref.getUInt("upTime", this->upTime); + this->downTime = pref.getUInt("downTime", this->downTime); + this->tiltTime = pref.getUInt("tiltTime", this->tiltTime); + } + this->setRemoteAddress(pref.getUInt("remoteAddress", 0)); this->currentPos = pref.getFloat("currentPos", 0); this->position = (uint8_t)floor(this->currentPos * 100); this->target = this->position; this->myPos = pref.getUShort("myPos", this->myPos); this->hasTilt = pref.getBool("hasTilt", false); this->shadeType = static_cast(pref.getChar("shadeType", static_cast(this->shadeType))); - this->tiltTime = pref.getUShort("tiltTime", 3000); this->currentTiltPos = pref.getFloat("currentTiltPos", 0); this->tiltPosition = (uint8_t)floor(this->currentTiltPos * 100); this->tiltTarget = this->tiltPosition; @@ -591,7 +690,6 @@ void SomfyShade::load() { lremote.lastRollingCode = pref.getUShort(lremote.getRemotePrefId(), 0); } pref.end(); - } void SomfyShade::publish() { if(mqtt.connected()) { @@ -698,10 +796,7 @@ void SomfyShade::processWaitingFrame() { this->myPos = 255; else this->myPos = this->position; - Serial.print(this->name); - Serial.print(" MY POSITION SET TO:"); - Serial.print(this->myPos); - Serial.println("%"); + this->commitMyPosition(); this->lastFrame.processed = true; this->emitState(); } @@ -755,6 +850,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { // This is an internal tilt command. Serial.println("Processing Tilt UP..."); this->setTiltMovement(-1); + this->lastFrame.processed = true; return; } else { @@ -777,6 +873,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { // This is an internal tilt command. Serial.println("Processing Tilt DOWN..."); this->setTiltMovement(1); + this->lastFrame.processed = true; return; } else { @@ -833,13 +930,7 @@ void SomfyShade::setTiltMovement(int8_t dir) { this->tiltStart = 0; this->tiltDirection = dir; if(currDir != dir) { - char shadeKey[15]; - snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId); - Serial.print("Writing current shade position:"); - Serial.println(this->currentTiltPos, 4); - pref.begin(shadeKey); - pref.putFloat("currentTiltPos", this->currentTiltPos); - pref.end(); + this->commitTiltPosition(); } } else if(this->direction != dir) { @@ -852,7 +943,6 @@ void SomfyShade::setTiltMovement(int8_t dir) { this->emitState(); } } - void SomfyShade::setMovement(int8_t dir) { int8_t currDir = this->direction; if(dir == 0) { @@ -861,13 +951,7 @@ void SomfyShade::setMovement(int8_t dir) { this->moveStart = 0; this->direction = dir; if(currDir != dir) { - char shadeKey[15]; - snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId); - Serial.print("Writing current shade position:"); - Serial.println(this->currentPos, 4); - pref.begin(shadeKey); - pref.putFloat("currentPos", this->currentPos); - pref.end(); + this->commitShadePosition(); } } else if(this->direction != dir) { @@ -895,18 +979,11 @@ void SomfyShade::setMyPosition(uint8_t target) { } else { this->sendCommand(somfy_commands::My, SETMY_REPEATS); - char shadeKey[15]; - snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId); if(target == this->myPos) this->myPos = 255; else this->myPos = target; - Serial.print("Writing my shade position:"); - Serial.print(this->myPos); - Serial.println("%"); - pref.begin(shadeKey); - pref.putUShort("myPos", this->myPos); - pref.end(); + this->commitMyPosition(); this->emitState(); } } @@ -1005,29 +1082,34 @@ void SomfyShade::moveToTarget(uint8_t target) { SomfyRemote::sendCommand(cmd); } bool SomfyShade::save() { - char shadeKey[15]; - snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId()); - pref.begin(shadeKey); - pref.putString("name", this->name); - pref.putBool("hasTilt", this->hasTilt); - pref.putBool("paired", this->paired); - pref.putUShort("upTime", this->upTime); - pref.putUShort("downTime", this->downTime); - pref.putUShort("tiltTime", this->tiltTime); - pref.putULong("remoteAddress", this->getRemoteAddress()); - pref.putFloat("currentPos", this->currentPos); - pref.putFloat("currentTiltPos", this->currentTiltPos); - pref.putUShort("myPos", this->myPos); - pref.putChar("shadeType", static_cast(this->shadeType)); - uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES]; - memset(linkedAddresses, 0x00, sizeof(linkedAddresses)); - uint8_t j = 0; - for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { - SomfyLinkedRemote lremote = this->linkedRemotes[i]; - if(lremote.getRemoteAddress() != 0) linkedAddresses[j++] = lremote.getRemoteAddress(); + if(somfy.useNVS()) { + char shadeKey[15]; + snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId()); + pref.begin(shadeKey); + pref.clear(); + pref.putChar("shadeType", static_cast(this->shadeType)); + pref.putUInt("remoteAddress", this->getRemoteAddress()); + pref.putString("name", this->name); + pref.putBool("hasTilt", this->hasTilt); + pref.putBool("paired", this->paired); + pref.putUInt("upTime", this->upTime); + pref.putUInt("downTime", this->downTime); + pref.putUInt("tiltTime", this->tiltTime); + pref.putFloat("currentPos", this->currentPos); + pref.putFloat("currentTiltPos", this->currentTiltPos); + pref.putUShort("myPos", this->myPos); + uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES]; + memset(linkedAddresses, 0x00, sizeof(linkedAddresses)); + uint8_t j = 0; + for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { + SomfyLinkedRemote lremote = this->linkedRemotes[i]; + if(lremote.getRemoteAddress() != 0) linkedAddresses[j++] = lremote.getRemoteAddress(); + } + pref.remove("linkedAddr"); + pref.putBytes("linkedAddr", linkedAddresses, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES); + pref.end(); } - pref.putBytes("linkedAddr", linkedAddresses, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES); - pref.end(); + this->commit(); return true; } bool SomfyShade::fromJSON(JsonObject &obj) { @@ -1152,7 +1234,6 @@ uint8_t SomfyShadeController::getNextShadeId() { return i; } } - return 255; } uint8_t SomfyShadeController::shadeCount() { @@ -1188,40 +1269,61 @@ SomfyShade *SomfyShadeController::addShade(JsonObject &obj) { } SomfyShade *SomfyShadeController::addShade() { uint8_t shadeId = this->getNextShadeId(); - SomfyShade *shade = nullptr; - for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { - if(this->shades[i].getShadeId() == 255) { - shade = &this->shades[i]; - break; - } - } + // 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. + // There is no apparent way around this. In the future we might actually add an indexer + // to it for sorting later. + if(shadeId == 255) return nullptr; + SomfyShade *shade = &this->shades[shadeId - 1]; if(shade) { shade->setShadeId(shadeId); - for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) { - this->m_shadeIds[i] = this->shades[i].getShadeId(); - } - sortArray(this->m_shadeIds, sizeof(this->m_shadeIds)); - uint8_t id = 0; - // This little diddy is about a bug I had previously that left duplicates in the - // sorted array. So we will walk the sorted array until we hit a duplicate where the previous - // value == the current value. Set it to 255 then sort the array again. - // 1,1,2,2,3,3,255... - bool hadDups = false; - for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) { - if(this->m_shadeIds[i] == 255) break; - if(id == this->m_shadeIds[i]) { - id = this->m_shadeIds[i]; - this->m_shadeIds[i] = 255; - hadDups = true; + this->isDirty = true; + if(this->useNVS()) { + for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) { + this->m_shadeIds[i] = this->shades[i].getShadeId(); } - else { - id = this->m_shadeIds[i]; + sortArray(this->m_shadeIds, sizeof(this->m_shadeIds)); + uint8_t id = 0; + // This little diddy is about a bug I had previously that left duplicates in the + // sorted array. So we will walk the sorted array until we hit a duplicate where the previous + // value == the current value. Set it to 255 then sort the array again. + // 1,1,2,2,3,3,255... + bool hadDups = false; + for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) { + if(this->m_shadeIds[i] == 255) break; + if(id == this->m_shadeIds[i]) { + id = this->m_shadeIds[i]; + this->m_shadeIds[i] = 255; + hadDups = true; + } + else { + id = this->m_shadeIds[i]; + } } + if(hadDups) sortArray(this->m_shadeIds, sizeof(this->m_shadeIds)); + pref.begin("Shades"); + pref.remove("shadeIds"); + int x = pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds)); + Serial.printf("WROTE %d bytes to shadeIds\n", x); + pref.end(); + for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) { + if(i != 0) Serial.print(","); + else Serial.print("Shade Ids: "); + Serial.print(this->m_shadeIds[i]); + } + Serial.println(); + pref.begin("Shades"); + pref.getBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds)); + Serial.print("LENGTH:"); + Serial.println(pref.getBytesLength("shadeIds")); + pref.end(); + for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) { + if(i != 0) Serial.print(","); + else Serial.print("Shade Ids: "); + Serial.print(this->m_shadeIds[i]); + } + Serial.println(); } - if(hadDups) sortArray(this->m_shadeIds, sizeof(this->m_shadeIds)); - pref.begin("Shades"); - pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds)); - pref.end(); } return shade; } @@ -1231,6 +1333,7 @@ void SomfyRemote::sendCommand(somfy_commands cmd, uint8_t repeat) { frame.remoteAddress = this->getRemoteAddress(); frame.cmd = cmd; frame.repeats = repeat; + this->lastRollingCode = frame.rollingCode; somfy.sendFrame(frame, repeat); somfy.processFrame(frame, true); } @@ -1261,18 +1364,24 @@ bool SomfyShadeController::deleteShade(uint8_t shadeId) { this->shades[i].setShadeId(255); } } - for(uint8_t i = 0; i < sizeof(this->m_shadeIds) - 1; i++) { - if(this->m_shadeIds[i] == shadeId) { - this->m_shadeIds[i] = 255; + if(this->useNVS()) { + for(uint8_t i = 0; i < sizeof(this->m_shadeIds) - 1; i++) { + if(this->m_shadeIds[i] == shadeId) { + this->m_shadeIds[i] = 255; + } } + + //qsort(this->m_shadeIds, sizeof(this->m_shadeIds)/sizeof(this->m_shadeIds[0]), sizeof(this->m_shadeIds[0]), sort_asc); + sortArray(this->m_shadeIds, sizeof(this->m_shadeIds)); + + pref.begin("Shades"); + pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds)); + pref.end(); } - //qsort(this->m_shadeIds, sizeof(this->m_shadeIds)/sizeof(this->m_shadeIds[0]), sizeof(this->m_shadeIds[0]), sort_asc); - sortArray(this->m_shadeIds, sizeof(this->m_shadeIds)); - pref.begin("Shades"); - pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds)); - pref.end(); + this->commit(); return true; } +bool SomfyShadeController::loadShadesFile(const char *filename) { return ShadeConfigFile::load(this, filename); } uint16_t SomfyRemote::getNextRollingCode() { pref.begin("ShadeCodes"); uint16_t code = pref.getUShort(this->m_remotePrefId, 0); @@ -1315,15 +1424,6 @@ bool SomfyShadeController::toJSON(JsonObject &obj) { this->transceiver.toJSON(oradio); JsonArray arr = obj.createNestedArray("shades"); this->toJSON(arr); - /* - for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { - SomfyShade &shade = this->shades[i]; - if(shade.getShadeId() != 255) { - JsonObject oshade = arr.createNestedObject(); - shade.toJSON(oshade); - } - } - */ return true; } bool SomfyShadeController::toJSON(JsonArray &arr) { @@ -1341,6 +1441,10 @@ void SomfyShadeController::loop() { for(uint8_t i; i < SOMFY_MAX_SHADES; i++) { if(this->shades[i].getShadeId() != 255) this->shades[i].checkMovement(); } + // Only commit the file once per second. + if(this->isDirty && millis() - this->lastCommit > 1000) { + this->commit(); + } } SomfyLinkedRemote::SomfyLinkedRemote() {} @@ -1552,9 +1656,6 @@ bool Transceiver::fromJSON(JsonObject& obj) { bool Transceiver::save() { this->config.save(); this->config.apply(); - //ELECHOUSE_cc1101.setRxBW(this->config.rxBandwidth); // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz. - //ELECHOUSE_cc1101.setPA(this->config.txPower); // Set TxPower. The following settings are possible depending on the frequency band. (-30 -20 -15 -10 -6 0 5 7 10 11 12) Default is max! - //ELECHOUSE_cc1101.setDeviation(this->config.deviation); return true; } bool Transceiver::end() { @@ -1570,15 +1671,18 @@ void transceiver_config_t::fromJSON(JsonObject& obj) { if(obj.containsKey("RXPin")) this->RXPin = obj["RXPin"]; if(obj.containsKey("SCKPin")) this->SCKPin = obj["SCKPin"]; if(obj.containsKey("TXPin")) this->TXPin = obj["TXPin"]; + if(obj.containsKey("rxBandwidth")) this->rxBandwidth = obj["rxBandwidth"]; // float + if(obj.containsKey("frequency")) this->frequency = obj["frequency"]; // float + if(obj.containsKey("deviation")) this->deviation = obj["deviation"]; // float + if(obj.containsKey("enabled")) this->enabled = obj["enabled"]; + if(obj.containsKey("txPower")) this->txPower = obj["txPower"]; + + /* if (obj.containsKey("internalCCMode")) this->internalCCMode = obj["internalCCMode"]; if (obj.containsKey("modulationMode")) this->modulationMode = obj["modulationMode"]; - if (obj.containsKey("frequency")) this->frequency = obj["frequency"]; // float - if (obj.containsKey("deviation")) this->deviation = obj["deviation"]; // float if (obj.containsKey("channel")) this->channel = obj["channel"]; if (obj.containsKey("channelSpacing")) this->channelSpacing = obj["channelSpacing"]; // float - if (obj.containsKey("rxBandwidth")) this->rxBandwidth = obj["rxBandwidth"]; // float if (obj.containsKey("dataRate")) this->dataRate = obj["dataRate"]; // float - if (obj.containsKey("txPower")) this->txPower = obj["txPower"]; if (obj.containsKey("syncMode")) this->syncMode = obj["syncMode"]; if (obj.containsKey("syncWordHigh")) this->syncWordHigh = obj["syncWordHigh"]; if (obj.containsKey("syncWordLow")) this->syncWordLow = obj["syncWordLow"]; @@ -1597,7 +1701,7 @@ void transceiver_config_t::fromJSON(JsonObject& obj) { if (obj.containsKey("pqtThreshold")) this->pqtThreshold = obj["pqtThreshold"]; if (obj.containsKey("appendStatus")) this->appendStatus = obj["appendStatus"]; if (obj.containsKey("printBuffer")) this->printBuffer = obj["printBuffer"]; - if(obj.containsKey("enabled")) this->enabled = obj["enabled"]; + */ Serial.printf("SCK:%u MISO:%u MOSI:%u CSN:%u RX:%u TX:%u\n", this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin, this->RXPin, this->TXPin); } void transceiver_config_t::toJSON(JsonObject& obj) { @@ -1608,15 +1712,16 @@ void transceiver_config_t::toJSON(JsonObject& obj) { obj["MOSIPin"] = this->MOSIPin; obj["MISOPin"] = this->MISOPin; obj["CSNPin"] = this->CSNPin; - obj["internalCCMode"] = this->internalCCMode; - obj["modulationMode"] = this->modulationMode; + obj["rxBandwidth"] = this->rxBandwidth; // float obj["frequency"] = this->frequency; // float obj["deviation"] = this->deviation; // float + obj["txPower"] = this->txPower; + /* + obj["internalCCMode"] = this->internalCCMode; + obj["modulationMode"] = this->modulationMode; obj["channel"] = this->channel; obj["channelSpacing"] = this->channelSpacing; // float - obj["rxBandwidth"] = this->rxBandwidth; // float obj["dataRate"] = this->dataRate; // float - obj["txPower"] = this->txPower; obj["syncMode"] = this->syncMode; obj["syncWordHigh"] = this->syncWordHigh; obj["syncWordLow"] = this->syncWordLow; @@ -1635,6 +1740,7 @@ void transceiver_config_t::toJSON(JsonObject& obj) { obj["pqtThreshold"] = this->pqtThreshold; obj["appendStatus"] = this->appendStatus; obj["printBuffer"] = somfy.transceiver.printBuffer; + */ obj["enabled"] = this->enabled; obj["radioInit"] = this->radioInit; Serial.print("Serialize Radio JSON "); @@ -1642,6 +1748,7 @@ void transceiver_config_t::toJSON(JsonObject& obj) { } void transceiver_config_t::save() { pref.begin("CC1101"); + pref.clear(); pref.putUChar("type", this->type); pref.putUChar("TXPin", this->TXPin); pref.putUChar("RXPin", this->RXPin); @@ -1649,10 +1756,15 @@ void transceiver_config_t::save() { pref.putUChar("MOSIPin", this->MOSIPin); pref.putUChar("MISOPin", this->MISOPin); pref.putUChar("CSNPin", this->CSNPin); - pref.putBool("internalCCMode", this->internalCCMode); - pref.putUChar("modulationMode", this->modulationMode); pref.putFloat("frequency", this->frequency); // float pref.putFloat("deviation", this->deviation); // float + pref.putFloat("rxBandwidth", this->rxBandwidth); // float + pref.putBool("enabled", this->enabled); + pref.putBool("radioInit", true); + + /* + pref.putBool("internalCCMode", this->internalCCMode); + pref.putUChar("modulationMode", this->modulationMode); pref.putUChar("channel", this->channel); pref.putFloat("channelSpacing", this->channelSpacing); // float pref.putFloat("rxBandwidth", this->rxBandwidth); // float @@ -1675,13 +1787,18 @@ void transceiver_config_t::save() { pref.putUChar("minPreambleBytes", this->minPreambleBytes); pref.putUChar("pqtThreshold", this->pqtThreshold); pref.putBool("appendStatus", this->appendStatus); - pref.putBool("enabled", this->enabled); - pref.putBool("radioInit", true); + */ pref.end(); Serial.print("Save Radio Settings "); Serial.printf("SCK:%u MISO:%u MOSI:%u CSN:%u RX:%u TX:%u\n", this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin, this->RXPin, this->TXPin); } +void transceiver_config_t::removeNVSKey(const char *key) { + if(pref.isKey(key)) { + Serial.printf("Removing NVS Key: CC1101.%s\n", key); + pref.remove(key); + } +} void transceiver_config_t::load() { pref.begin("CC1101"); this->type = pref.getUChar("type", 56); @@ -1691,35 +1808,37 @@ void transceiver_config_t::load() { this->MOSIPin = pref.getUChar("MOSIPin", this->MOSIPin); this->MISOPin = pref.getUChar("MISOPin", this->MISOPin); this->CSNPin = pref.getUChar("CSNPin", this->CSNPin); - this->internalCCMode = pref.getBool("internalCCMode", this->internalCCMode); - this->modulationMode = pref.getUChar("modulationMode", this->modulationMode); this->frequency = pref.getFloat("frequency", this->frequency); // float this->deviation = pref.getFloat("deviation", this->deviation); // float - this->channel = pref.getUChar("channel", this->channel); - this->channelSpacing = pref.getFloat("channelSpacing", this->channelSpacing); // float - this->rxBandwidth = pref.getFloat("rxBandwidth", this->rxBandwidth); // float - this->dataRate = pref.getFloat("dataRate", this->dataRate); // float - this->txPower = pref.getChar("txPower", this->txPower); - this->syncMode = pref.getUChar("syncMode", this->syncMode); - this->syncWordHigh = pref.getUShort("syncWordHigh", this->syncWordHigh); - this->syncWordLow = pref.getUShort("syncWordLow", this->syncWordLow); - this->addrCheckMode = pref.getUChar("addrCheckMode", this->addrCheckMode); - this->checkAddr = pref.getUChar("checkAddr", this->checkAddr); - this->dataWhitening = pref.getBool("dataWhitening", this->dataWhitening); - this->pktFormat = pref.getUChar("pktFormat", this->pktFormat); - this->pktLengthMode = pref.getUChar("pktLengthMode", this->pktLengthMode); - this->pktLength = pref.getUChar("pktLength", this->pktLength); - this->useCRC = pref.getBool("useCRC", this->useCRC); - this->autoFlushCRC = pref.getBool("autoFlushCRC", this->autoFlushCRC); - this->disableDCFilter = pref.getBool("disableDCFilter", this->disableDCFilter); - this->enableManchester = pref.getBool("enableManchester", this->enableManchester); - this->enableFEC = pref.getBool("enableFEC", this->enableFEC); - this->minPreambleBytes = pref.getUChar("minPreambleBytes", this->minPreambleBytes); - this->pqtThreshold = pref.getUChar("pqtThreshold", this->pqtThreshold); - this->appendStatus = pref.getBool("appendStatus", this->appendStatus); this->enabled = pref.getBool("enabled", this->enabled); + this->txPower = pref.getChar("txPower", this->txPower); + this->rxBandwidth = pref.getFloat("rxBandwidth", this->rxBandwidth); + + + this->removeNVSKey("internalCCMode"); + this->removeNVSKey("modulationMode"); + this->removeNVSKey("channel"); + this->removeNVSKey("channelSpacing"); + this->removeNVSKey("dataRate"); + this->removeNVSKey("syncMode"); + this->removeNVSKey("syncWordHigh"); + this->removeNVSKey("syncWordLow"); + this->removeNVSKey("addrCheckMode"); + this->removeNVSKey("checkAddr"); + this->removeNVSKey("dataWhitening"); + this->removeNVSKey("pktFormat"); + this->removeNVSKey("pktLengthMode"); + this->removeNVSKey("pktLength"); + this->removeNVSKey("useCRC"); + this->removeNVSKey("autoFlushCRC"); + this->removeNVSKey("disableDCFilter"); + this->removeNVSKey("enableManchester"); + this->removeNVSKey("enableFEC"); + this->removeNVSKey("minPreambleBytes"); + this->removeNVSKey("pqtThreshold"); + this->removeNVSKey("appendStatus"); pref.end(); - this->printBuffer = somfy.transceiver.printBuffer; + //this->printBuffer = somfy.transceiver.printBuffer; } void transceiver_config_t::apply() { somfy.transceiver.disableReceive(); @@ -1733,7 +1852,7 @@ void transceiver_config_t::apply() { this->radioInit = false; pref.end(); if(!radioInit) return; - Serial.print("Applying Initializing radio settings "); + Serial.print("Applying radio settings "); Serial.printf("Setting Data Pins RX:%u TX:%u\n", this->RXPin, this->TXPin); ELECHOUSE_cc1101.setGDO(this->TXPin, this->RXPin); Serial.printf("Setting SPI Pins SCK:%u MISO:%u MOSI:%u CSN:%u\n", this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin); @@ -1742,6 +1861,7 @@ void transceiver_config_t::apply() { ELECHOUSE_cc1101.Init(); ELECHOUSE_cc1101.setMHZ(this->frequency); // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet. ELECHOUSE_cc1101.setRxBW(this->rxBandwidth); // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz. + ELECHOUSE_cc1101.setDeviation(this->deviation); ELECHOUSE_cc1101.setPA(this->txPower); // Set TxPower. The following settings are possible depending on the frequency band. (-30 -20 -15 -10 -6 0 5 7 10 11 12) Default is max! //ELECHOUSE_cc1101.setCCMode(this->internalCCMode); // set config for internal transmission mode. //ELECHOUSE_cc1101.setModulation(this->modulationMode); // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK. diff --git a/Somfy.h b/Somfy.h index ee5daba..0528e9e 100644 --- a/Somfy.h +++ b/Somfy.h @@ -4,6 +4,12 @@ #define SOMFY_MAX_SHADES 32 #define SOMFY_MAX_LINKED_REMOTES 5 +typedef struct appver_t { + uint8_t major; + uint8_t minor; + uint8_t build; +}; + enum class somfy_commands : byte { My = 0x1, Up = 0x2, @@ -99,9 +105,9 @@ class SomfyShade : public SomfyRemote { char name[21] = ""; void setShadeId(uint8_t id) { shadeId = id; } uint8_t getShadeId() { return shadeId; } - uint16_t upTime = 10000; - uint16_t downTime = 10000; - uint16_t tiltTime = 5000; + uint32_t upTime = 10000; + uint32_t downTime = 10000; + uint32_t tiltTime = 7000; bool save(); bool isIdle(); void checkMovement(); @@ -121,6 +127,10 @@ class SomfyShade : public SomfyRemote { void moveToMyPosition(); void processWaitingFrame(); void publish(); + void commit(); + void commitShadePosition(); + void commitTiltPosition(); + void commitMyPosition(); }; typedef struct transceiver_config_t { @@ -134,15 +144,16 @@ typedef struct transceiver_config_t { uint8_t MISOPin = 19; uint8_t CSNPin = 5; bool radioInit = false; - bool internalCCMode = false; // Use internal transmission mode FIFO buffers. - byte modulationMode = 2; // Modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK. float frequency = 433.42; // Basic frequency float deviation = 47.60; // Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz. + float rxBandwidth = 812.5; // Receive bandwidth in kHz. Value from 58.03 to 812.50. Default is 99.97kHz. + int8_t txPower = 10; // Transmission power {-30, -20, -15, -10, -6, 0, 5, 7, 10, 11, 12}. Default is 12. +/* + bool internalCCMode = false; // Use internal transmission mode FIFO buffers. + byte modulationMode = 2; // Modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK. uint8_t channel = 0; // The channel number from 0 to 255 float channelSpacing = 199.95; // Channel spacing in multiplied by the channel number and added to the base frequency in kHz. 25.39 to 405.45. Default 199.95 - float rxBandwidth = 812.5; // Receive bandwidth in kHz. Value from 58.03 to 812.50. Default is 99.97kHz. float dataRate = 99.97; // The data rate in kBaud. 0.02 to 1621.83 Default is 99.97. - int8_t txPower = 10; // Transmission power {-30, -20, -15, -10, -6, 0, 5, 7, 10, 11, 12}. Default is 12. uint8_t syncMode = 0; // 0=No preamble/sync, // 1=16 sync word bits detected, // 2=16/16 sync words bits detected. @@ -186,11 +197,13 @@ typedef struct transceiver_config_t { // decreases the bounter by 8 each time a bit is received that is the same as the lats bit. A threshold of 4 PQT for this counter is used to gate sync word detection. // When PQT = 0 a sync word is always accepted. bool appendStatus = false; // Appends the RSSI and LQI values to the TX packed as well as the CRC. + */ void fromJSON(JsonObject& obj); void toJSON(JsonObject& obj); void save(); void load(); void apply(); + void removeNVSKey(const char *key); }; class Transceiver { private: @@ -218,7 +231,11 @@ class Transceiver { class SomfyShadeController { protected: uint8_t m_shadeIds[SOMFY_MAX_SHADES]; + uint32_t lastCommit = 0; public: + appver_t appVersion; + bool useNVS() { return !(this->appVersion.major > 1 || this->appVersion.minor >= 4); } + bool isDirty = false; uint32_t startingAddress; uint8_t getNextShadeId(); uint32_t getNextRemoteAddress(uint8_t shadeId); @@ -242,6 +259,9 @@ class SomfyShadeController { void emitState(uint8_t num = 255); void publish(); void processWaitingFrame(); + void commit(); + bool loadShadesFile(const char *filename); + bool loadLegacy(); }; #endif diff --git a/SomfyController.ino.esp32.bin b/SomfyController.ino.esp32.bin index bc516d2..1e4f107 100644 Binary files a/SomfyController.ino.esp32.bin and b/SomfyController.ino.esp32.bin differ diff --git a/SomfyController.littlefs.bin b/SomfyController.littlefs.bin index baf50d5..9f47ab9 100644 Binary files a/SomfyController.littlefs.bin and b/SomfyController.littlefs.bin differ diff --git a/Web.cpp b/Web.cpp index b55bfc7..8d52a52 100644 --- a/Web.cpp +++ b/Web.cpp @@ -271,6 +271,86 @@ void Web::begin() { server.streamFile(file, _encoding_html); file.close(); }); + server.on("/shades.cfg", []() { + webServer.sendCORSHeaders(); + // Load the index html page from the data directory. + Serial.println("Loading file shades.cfg"); + File file = LittleFS.open("/shades.cfg", "r"); + if (!file) { + Serial.println("Error opening shades.cfg"); + server.send(500, _encoding_text, "shades.cfg"); + } + server.streamFile(file, _encoding_text); + file.close(); + + }); + server.on("/shades.tmp", []() { + webServer.sendCORSHeaders(); + // Load the index html page from the data directory. + Serial.println("Loading file shades.cfg"); + File file = LittleFS.open("/shades.tmp", "r"); + if (!file) { + Serial.println("Error opening shades.tmp"); + server.send(500, _encoding_text, "shades.tmp"); + } + server.streamFile(file, _encoding_text); + file.close(); + }); + + server.on("/backup", []() { + webServer.sendCORSHeaders(); + char filename[120]; + Timestamp ts; + char * iso = ts.getISOTime(); + // Replace the invalid characters as quickly as we can. + for(uint8_t i = 0; i < strlen(iso); i++) { + switch(iso[i]) { + case '.': + // Just trim off the ms. + iso[i] = '\0'; + break; + case ':': + iso[i] = '_'; + break; + } + } + snprintf(filename, sizeof(filename), "attachment; filename=\"ESPSomfyRTS %s.backup\"", iso); + Serial.println(filename); + server.sendHeader(F("Content-Disposition"), filename); + Serial.println("Saving current shade information"); + somfy.commit(); + File file = LittleFS.open("/shades.cfg", "r"); + if (!file) { + Serial.println("Error opening shades.cfg"); + server.send(500, _encoding_text, "shades.cfg"); + } + server.streamFile(file, _encoding_text); + file.close(); + }); + server.on("/restore", HTTP_POST, []() { + webServer.sendCORSHeaders(); + server.sendHeader("Connection", "close"); + server.send(200, _encoding_json, "{\"status\":\"Success\",\"desc\":\"Restoring Shade settings\"}"); + }, []() { + HTTPUpload& upload = server.upload(); + if (upload.status == UPLOAD_FILE_START) { + Serial.printf("Restore: %s\n", upload.filename.c_str()); + // Begin by opening a new temporary file. + File fup = LittleFS.open("/shades.tmp", "w"); + fup.close(); + } + else if (upload.status == UPLOAD_FILE_WRITE) { + File fup = LittleFS.open("/shades.tmp", "a"); + fup.write(upload.buf, upload.currentSize); + fup.close(); + } + else if (upload.status == UPLOAD_FILE_END) { + // TODO: Do some validation of the file. + Serial.println("Validating restore"); + // Go through the uploaded file to determine if it is valid. + somfy.loadShadesFile("/shades.tmp"); + } + }); server.on("/index.js", []() { webServer.sendCacheHeaders(604800); webServer.sendCORSHeaders(); @@ -324,6 +404,20 @@ void Web::begin() { server.streamFile(file, "image/png"); file.close(); }); + server.on("/icon.png", []() { + webServer.sendCacheHeaders(604800); + webServer.sendCORSHeaders(); + + // Load the index html page from the data directory. + Serial.println("Loading file favicon.png"); + File file = LittleFS.open("/icon.png", "r"); + if (!file) { + Serial.println("Error opening data/favicon.png"); + server.send(500, _encoding_text, "Unable to open data/icons.css"); + } + server.streamFile(file, "image/png"); + file.close(); + }); server.onNotFound([]() { Serial.print("Request 404:"); HTTPMethod method = server.method(); @@ -385,7 +479,7 @@ void Web::begin() { SomfyShade* shade; if (method == HTTP_POST || method == HTTP_PUT) { Serial.println("Adding a shade"); - DynamicJsonDocument doc(256); + DynamicJsonDocument doc(512); DeserializationError err = deserializeJson(doc, server.arg("plain")); if (err) { switch (err.code()) { @@ -410,7 +504,7 @@ void Web::begin() { Serial.println("Adding shade"); shade = somfy.addShade(obj); if (shade) { - DynamicJsonDocument sdoc(256); + DynamicJsonDocument sdoc(512); JsonObject sobj = sdoc.to(); shade->toJSON(sobj); serializeJson(sdoc, g_content); @@ -592,7 +686,7 @@ void Web::begin() { shade->moveToTiltTarget(target); else shade->sendTiltCommand(command); - DynamicJsonDocument sdoc(256); + DynamicJsonDocument sdoc(512); JsonObject sobj = sdoc.to(); shade->toJSON(sobj); serializeJson(sdoc, g_content); @@ -656,7 +750,7 @@ void Web::begin() { shade->moveToTarget(target); else shade->sendCommand(command); - DynamicJsonDocument sdoc(256); + DynamicJsonDocument sdoc(512); JsonObject sobj = sdoc.to(); shade->toJSON(sobj); serializeJson(sdoc, g_content); @@ -711,7 +805,7 @@ void Web::begin() { if(target == 255) target = shade->myPos; if(target >= 0 && target <= 100) shade->setMyPosition(target); - DynamicJsonDocument sdoc(256); + DynamicJsonDocument sdoc(512); JsonObject sobj = sdoc.to(); shade->toJSON(sobj); serializeJson(sdoc, g_content); @@ -761,7 +855,7 @@ void Web::begin() { } else { shade->setRollingCode(rollingCode); - StaticJsonDocument<256> doc; + DynamicJsonDocument doc(512); JsonObject obj = doc.to(); shade->toJSON(obj); serializeJson(doc, g_content); @@ -776,7 +870,7 @@ void Web::begin() { uint8_t shadeId = 255; if (server.hasArg("plain")) { // Its coming in the body. - StaticJsonDocument<129> doc; + DynamicJsonDocument doc(512); DeserializationError err = deserializeJson(doc, server.arg("plain")); if (err) { switch (err.code()) { @@ -804,10 +898,10 @@ void Web::begin() { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to pair\"}")); } else { - shade->sendCommand(somfy_commands::Prog, 4); + shade->sendCommand(somfy_commands::Prog, 7); shade->paired = true; shade->save(); - StaticJsonDocument<256> doc; + DynamicJsonDocument doc(512); JsonObject obj = doc.to(); shade->toJSON(obj); serializeJson(doc, g_content); @@ -822,7 +916,7 @@ void Web::begin() { uint8_t shadeId = 255; if (server.hasArg("plain")) { // Its coming in the body. - StaticJsonDocument<129> doc; + DynamicJsonDocument doc(512); DeserializationError err = deserializeJson(doc, server.arg("plain")); if (err) { switch (err.code()) { @@ -850,10 +944,10 @@ void Web::begin() { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to unpair\"}")); } else { - shade->sendCommand(somfy_commands::Prog, 4); + shade->sendCommand(somfy_commands::Prog, 7); shade->paired = false; shade->save(); - StaticJsonDocument<256> doc; + DynamicJsonDocument doc(512); JsonObject obj = doc.to(); shade->toJSON(obj); serializeJson(doc, g_content); @@ -867,7 +961,7 @@ void Web::begin() { if (method == HTTP_PUT || method == HTTP_POST) { // We are updating an existing shade by adding a linked remote. if (server.hasArg("plain")) { - DynamicJsonDocument doc(256); + DynamicJsonDocument doc(512); DeserializationError err = deserializeJson(doc, server.arg("plain")); if (err) { switch (err.code()) { @@ -914,7 +1008,7 @@ void Web::begin() { // We are updating an existing shade by adding a linked remote. if (server.hasArg("plain")) { Serial.println("Linking a remote"); - DynamicJsonDocument doc(256); + DynamicJsonDocument doc(512); DeserializationError err = deserializeJson(doc, server.arg("plain")); if (err) { switch (err.code()) { @@ -1026,6 +1120,29 @@ void Web::begin() { } } }); + server.on("/updateShadeConfig", HTTP_POST, []() { + webServer.sendCORSHeaders(); + server.sendHeader("Connection", "close"); + server.send(200, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Updating Shade Config: \"}"); + }, []() { + HTTPUpload& upload = server.upload(); + if (upload.status == UPLOAD_FILE_START) { + Serial.printf("Update: shades.cfg\n"); + File fup = LittleFS.open("/shades.tmp", "w"); + fup.close(); + } + else if (upload.status == UPLOAD_FILE_WRITE) { + /* flashing littlefs to ESP*/ + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + File fup = LittleFS.open("/shades.tmp", "a"); + fup.write(upload.buf, upload.currentSize); + fup.close(); + } + } + else if (upload.status == UPLOAD_FILE_END) { + somfy.loadShadesFile("/shades.tmp"); + } + }); server.on("/updateApplication", HTTP_POST, []() { webServer.sendCORSHeaders(); server.sendHeader("Connection", "close"); @@ -1041,7 +1158,7 @@ void Web::begin() { } } else if (upload.status == UPLOAD_FILE_WRITE) { - /* flashing firmware to ESP*/ + /* flashing littlefs to ESP*/ if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); } @@ -1049,6 +1166,7 @@ void Web::begin() { else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { //true to set the size to the current progress Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); + somfy.commit(); } else { Update.printError(Serial); diff --git a/data/appversion b/data/appversion new file mode 100644 index 0000000..e21e727 --- /dev/null +++ b/data/appversion @@ -0,0 +1 @@ +1.4.0 \ No newline at end of file diff --git a/data/icon.png b/data/icon.png new file mode 100644 index 0000000..cbc072e Binary files /dev/null and b/data/icon.png differ diff --git a/data/index.html b/data/index.html index 2b54f4d..c39a8f2 100644 --- a/data/index.html +++ b/data/index.html @@ -3,15 +3,15 @@ - - + + - + -
+
Radio Not Initialized
-

ESPSomfy RTS

+

ESPSomfy RTS