diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index cb92a4f..fe6096d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,8 +7,8 @@ on: env: ARDUINO_BOARD_MANAGER_ADDITIONAL_URLS: "https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json" ARDUINO_CLI_VERSION: "0.x" - ARDUINO_ESP32_VERSION: "2.0.14" - ARDUINO_JSON_VERSION: "6.21.3" + ARDUINO_ESP32_VERSION: "2.0.17" + ARDUINO_JSON_VERSION: "6.21.5" ESPTOOL_VERSION: "4.7" LITTLEFS_VERSION: "v2.5.1" MKLITTLEFS_VERSION: "3.1.0" @@ -30,17 +30,17 @@ jobs: GITHUB_TOKEN: ${{ github.token }} - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Checkout mklittlefs - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: earlephilhower/mklittlefs path: mklittlefs ref: ${{ env.MKLITTLEFS_VERSION }} - name: Checkout LittleFS - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: littlefs-project/littlefs path: mklittlefs/littlefs @@ -55,14 +55,14 @@ jobs: ./mklittlefs/mklittlefs --create data --size 1441792 SomfyController.littlefs.bin - name: Upload binaries - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: LittleFS path: SomfyController.littlefs.bin retention-days: 5 - name: Upload LittleFS - uses: shogo82148/actions-upload-release-asset@v1.7.2 + uses: shogo82148/actions-upload-release-asset@v1.7.5 with: github_token: ${{ github.token }} upload_url: ${{ steps.get_release.outputs.upload_url }} @@ -83,30 +83,32 @@ jobs: - board: esp32 addr_bootloader: 0x1000 chip: ESP32 - fqbn: esp32:esp32:esp32:PartitionScheme=default,FlashMode=qio,FlashFreq=80,DebugLevel=none - # esp32:esp32:esp32wrover:PartitionScheme=default,FlashMode=qio,FlashFreq=80,UploadSpeed=921600,DebugLevel=none,EraseFlash=none + fqbn: esp32:esp32:esp32wrover:PartitionScheme=default,FlashMode=qio,FlashFreq=80,UploadSpeed=921600,DebugLevel=none,EraseFlash=none + # esp32:esp32:esp32wrover:PartitionScheme=default,FlashMode=qio,FlashFreq=80,UploadSpeed=921600,DebugLevel=none,EraseFlash=none name: ESP32 obname: SomfyController.onboard.esp32.bin fwname: SomfyController.ino.esp32.bin - board: esp32c3 addr_bootloader: 0x0 chip: ESP32-C3 - fqbn: esp32:esp32:esp32c3 + fqbn: esp32:esp32:esp32c3:JTAGAdapter=default,CDCOnBoot=cdc,PartitionScheme=default,CPUFreq=160,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none,EraseFlash=none + # esp32:esp32:esp32c3:JTAGAdapter=default,CDCOnBoot=default,PartitionScheme=default,CPUFreq=160,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none,EraseFlash=none name: ESP32C3 obname: SomfyController.onboard.esp32c3.bin fwname: SomfyController.ino.esp32c3.bin - board: esp32s2 addr_bootloader: 0x1000 chip: ESP32-S2 - fqbn: esp32:esp32:esp32s2 + fqbn: esp32:esp32:esp32s2:JTAGAdapter=default,CDCOnBoot=cdc,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PSRAM=disabled,PartitionScheme=default,CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none,EraseFlash=none + # esp32:esp32:esp32s2:JTAGAdapter=default,CDCOnBoot=default,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PSRAM=disabled,PartitionScheme=default,CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none,EraseFlash=none name: ESP32S2 obname: SomfyController.onboard.esp32s2.bin fwname: SomfyController.ino.esp32s2.bin - board: esp32s3 addr_bootloader: 0x0 chip: ESP32-S3 - fqbn: esp32:esp32:esp32s3:JTAGAdapter=default,USBMode=hwcdc,CDCOnBoot=cdc,DebugLevel=none,FlashMode=qio,PartitionScheme=default - # esp32:esp32:esp32s3:JTAGAdapter=default,PSRAM=disabled,FlashMode=qio,FlashSize=4M,LoopCore=1,EventsCore=1,USBMode=hwcdc,CDCOnBoot=cdc,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PartitionScheme=default,CPUFreq=240,UploadSpeed=921600,DebugLevel=none,EraseFlash=none + fqbn: esp32:esp32:esp32s3:JTAGAdapter=default,PSRAM=disabled,FlashMode=qio,FlashSize=4M,LoopCore=1,EventsCore=1,USBMode=hwcdc,CDCOnBoot=cdc,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PartitionScheme=default,CPUFreq=240,UploadSpeed=921600,DebugLevel=none,EraseFlash=none + # esp32:esp32:esp32s3:JTAGAdapter=default,PSRAM=disabled,FlashMode=qio,FlashSize=4M,LoopCore=1,EventsCore=1,USBMode=hwcdc,CDCOnBoot=cdc,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PartitionScheme=default,CPUFreq=240,UploadSpeed=921600,DebugLevel=none,EraseFlash=none name: ESP32S3 fwname: SomfyController.ino.esp32s3.bin obname: SomfyController.onboard.esp32s3.bin @@ -118,17 +120,17 @@ jobs: GITHUB_TOKEN: ${{ github.token }} - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: SomfyController - name: Get LittleFS - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: LittleFS - name: Install Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} @@ -173,7 +175,7 @@ jobs: 0x290000 SomfyController.littlefs.bin - name: Upload Firmware ${{ matrix.name }} - uses: shogo82148/actions-upload-release-asset@v1.7.2 + uses: shogo82148/actions-upload-release-asset@v1.7.5 with: github_token: ${{ github.token }} upload_url: ${{ steps.get_release.outputs.upload_url }} @@ -185,7 +187,7 @@ jobs: zip ${{ matrix.obname }}.zip ./${{ matrix.obname }} - name: Upload Onboard ${{ matrix.name }} - uses: shogo82148/actions-upload-release-asset@v1.7.2 + uses: shogo82148/actions-upload-release-asset@v1.7.5 # env: # GITHUB_TOKEN: ${{ github.token }} with: diff --git a/.gitignore b/.gitignore index 601a4c0..be96788 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ esp32s3.svd debug.cfg SomfyController.ino.XIAO_ESP32S3.bin +SomfyController.ino.esp32c3.bin +SomfyController.ino.esp32s2.bin +.vscode/settings.json diff --git a/ConfigFile.cpp b/ConfigFile.cpp index 8bcab04..e515bed 100644 --- a/ConfigFile.cpp +++ b/ConfigFile.cpp @@ -7,10 +7,10 @@ extern Preferences pref; -#define SHADE_HDR_VER 22 +#define SHADE_HDR_VER 24 #define SHADE_HDR_SIZE 76 #define SHADE_REC_SIZE 276 -#define GROUP_REC_SIZE 194 +#define GROUP_REC_SIZE 200 #define TRANS_REC_SIZE 74 #define ROOM_REC_SIZE 29 #define REPEATER_REC_SIZE 77 @@ -125,6 +125,29 @@ bool ConfigFile::readString(char *buff, size_t len) { _rtrim(buff); return true; } +bool ConfigFile::skipValue(size_t len) { + if(!this->file) return false; + uint8_t quotes = 0; + uint8_t j = 0; + while(j < len) { + uint8_t val; + j++; + if(this->file.read(&val, 1) == 1) { + switch(val) { + case CFG_VALUE_SEP: + if(quotes >= 2 || quotes == 0) return true; + break; + case CFG_REC_END: + return true; + case CFG_TOK_QUOTE: + quotes++; + break; + } + } + else return false; + } + return true; +} bool ConfigFile::readVarString(char *buff, size_t len) { if(!this->file) return false; memset(buff, 0x00, len); @@ -460,7 +483,7 @@ bool ShadeConfigFile::validate() { if(this->header.version >= 21) { recs = 0; while(recs < this->header.repeaterRecords) { - uint32_t pos = this->file.position(); + //uint32_t pos = this->file.position(); if(!this->seekChar(CFG_REC_END)) { Serial.printf("Failed to find the repeater record end %d\n", recs); } @@ -564,8 +587,8 @@ bool ShadeConfigFile::restoreFile(SomfyShadeController *s, const char *filename, else { this->file.seek(this->file.position() + this->header.settingsRecordSize, SeekSet); } - if(opts.network) { - this->readNetRecord(); + if(opts.network || opts.mqtt) { + this->readNetRecord(opts); } else { this->file.seek(this->file.position() + this->header.netRecordSize, SeekSet); @@ -583,44 +606,68 @@ bool ShadeConfigFile::restoreFile(SomfyShadeController *s, const char *filename, settings.WIFI.save(); settings.Ethernet.save(); } + if(opts.mqtt) settings.MQTT.save(); return true; } -bool ShadeConfigFile::readNetRecord() { +bool ShadeConfigFile::readNetRecord(restore_options_t &opts) { if(this->header.netRecordSize > 0) { uint32_t startPos = this->file.position(); - Serial.println("Reading network settings from file..."); - settings.connType = static_cast(this->readUInt8(static_cast(conn_types::unset))); - settings.IP.dhcp = this->readBool(true); - char ip[24]; - this->readVarString(ip, sizeof(ip)); - settings.IP.ip.fromString(ip); - this->readVarString(ip, sizeof(ip)); - settings.IP.gateway.fromString(ip); - this->readVarString(ip, sizeof(ip)); - settings.IP.subnet.fromString(ip); - this->readVarString(ip, sizeof(ip)); - settings.IP.dns1.fromString(ip); - this->readVarString(ip, sizeof(ip)); - settings.IP.dns2.fromString(ip); + if(opts.network) { + Serial.println("Reading network settings from file..."); + settings.connType = static_cast(this->readUInt8(static_cast(conn_types_t::unset))); + settings.IP.dhcp = this->readBool(true); + char ip[24]; + this->readVarString(ip, sizeof(ip)); + settings.IP.ip.fromString(ip); + this->readVarString(ip, sizeof(ip)); + settings.IP.gateway.fromString(ip); + this->readVarString(ip, sizeof(ip)); + settings.IP.subnet.fromString(ip); + this->readVarString(ip, sizeof(ip)); + settings.IP.dns1.fromString(ip); + this->readVarString(ip, sizeof(ip)); + settings.IP.dns2.fromString(ip); + } + else { + this->skipValue(4); // connType + this->skipValue(6); // dhcp flag + this->skipValue(24); // ip + this->skipValue(24); // gateway + this->skipValue(24); // subnet + this->skipValue(24); // dns1 + this->skipValue(24); // dns2 + } if(this->header.version >= 22) { - this->readVarString(settings.MQTT.protocol, sizeof(settings.MQTT.protocol)); - this->readVarString(settings.MQTT.hostname, sizeof(settings.MQTT.hostname)); - settings.MQTT.port = this->readUInt16(1883); - settings.MQTT.pubDisco = this->readBool(false); - this->readVarString(settings.MQTT.rootTopic, sizeof(settings.MQTT.rootTopic)); - this->readVarString(settings.MQTT.discoTopic, sizeof(settings.MQTT.discoTopic)); + if(opts.mqtt) { + this->readVarString(settings.MQTT.protocol, sizeof(settings.MQTT.protocol)); + this->readVarString(settings.MQTT.hostname, sizeof(settings.MQTT.hostname)); + settings.MQTT.port = this->readUInt16(1883); + settings.MQTT.pubDisco = this->readBool(false); + this->readVarString(settings.MQTT.rootTopic, sizeof(settings.MQTT.rootTopic)); + this->readVarString(settings.MQTT.discoTopic, sizeof(settings.MQTT.discoTopic)); + } + else { + this->skipValue(sizeof(settings.MQTT.protocol)); + this->skipValue(sizeof(settings.MQTT.hostname)); + this->skipValue(6); // Port + this->skipValue(6); // pubDisco + this->skipValue(sizeof(settings.MQTT.rootTopic)); + this->skipValue(sizeof(settings.MQTT.discoTopic)); + } } // Now lets check to see if we are the same board. If we are then we will restore // the ethernet phy settings. - if(strncmp(settings.serverId, this->header.serverId, sizeof(settings.serverId)) == 0) { - Serial.println("Restoring Ethernet adapter settings"); - settings.Ethernet.boardType = this->readUInt8(1); - settings.Ethernet.phyType = static_cast(this->readUInt8(0)); - settings.Ethernet.CLKMode = static_cast(this->readUInt8(0)); - settings.Ethernet.phyAddress = this->readInt8(1); - settings.Ethernet.PWRPin = this->readInt8(1); - settings.Ethernet.MDCPin = this->readInt8(16); - settings.Ethernet.MDIOPin = this->readInt8(23); + if(opts.network) { + if(strncmp(settings.serverId, this->header.serverId, sizeof(settings.serverId)) == 0) { + Serial.println("Restoring Ethernet adapter settings"); + settings.Ethernet.boardType = this->readUInt8(1); + settings.Ethernet.phyType = static_cast(this->readUInt8(0)); + settings.Ethernet.CLKMode = static_cast(this->readUInt8(0)); + settings.Ethernet.phyAddress = this->readInt8(1); + settings.Ethernet.PWRPin = this->readInt8(1); + settings.Ethernet.MDCPin = this->readInt8(16); + settings.Ethernet.MDIOPin = this->readInt8(23); + } } if(this->file.position() != startPos + this->header.netRecordSize) { Serial.println("Reading to end of network record"); @@ -681,12 +728,9 @@ bool ShadeConfigFile::readGroupRecord(SomfyGroup *group) { this->readString(group->name, sizeof(group->name)); group->proto = static_cast(this->readUInt8(0)); group->bitLength = this->readUInt8(56); - if(group->getRemoteAddress() != 0) { - uint16_t rc = pref.getUShort(group->getRemotePrefId(), 0); - group->lastRollingCode = max(rc, group->lastRollingCode); - if(rc < group->lastRollingCode) pref.putUShort(group->getRemotePrefId(), group->lastRollingCode); - } + if(this->header.version == 23) group->lastRollingCode = this->readUInt16(0); uint8_t lsd = 0; + memset(group->linkedShades, 0x00, sizeof(group->linkedShades)); for(uint8_t j = 0; j < SOMFY_MAX_GROUPED_SHADES; j++) { uint8_t shadeId = this->readUInt8(0); // Do this to eliminate gaps. @@ -700,6 +744,13 @@ bool ShadeConfigFile::readGroupRecord(SomfyGroup *group) { else group->compressLinkedShadeIds(); if(this->header.version >= 18) group->flipCommands = this->readBool(false); if(this->header.version >= 19) group->roomId = this->readUInt8(0); + if(this->header.version >= 24) group->lastRollingCode = this->readUInt16(0); + if(group->getRemoteAddress() != 0) { + uint16_t rc = pref.getUShort(group->getRemotePrefId(), 0); + group->lastRollingCode = max(rc, group->lastRollingCode); + if(rc < group->lastRollingCode) pref.putUShort(group->getRemotePrefId(), group->lastRollingCode); + } + pref.end(); if(this->file.position() != startPos + this->header.groupRecordSize) { Serial.println("Reading to end of group record"); @@ -710,7 +761,7 @@ bool ShadeConfigFile::readGroupRecord(SomfyGroup *group) { bool ShadeConfigFile::readRepeaterRecord(SomfyShadeController *s) { uint32_t startPos = this->file.position(); - for(uint8_t i; i < SOMFY_MAX_REPEATERS; i++) { + for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) { s->linkRepeater(this->readUInt32(0)); } if(this->file.position() != startPos + this->header.repeaterRecordSize) { @@ -884,7 +935,8 @@ bool ShadeConfigFile::writeGroupRecord(SomfyGroup *group) { this->writeUInt8(group->repeats); this->writeUInt8(group->sortOrder); this->writeBool(group->flipCommands); - this->writeUInt8(group->roomId, CFG_REC_END); + this->writeUInt8(group->roomId); + this->writeUInt16(group->lastRollingCode, CFG_REC_END); return true; } bool ShadeConfigFile::writeRepeaterRecord(SomfyShadeController *s) { diff --git a/ConfigFile.h b/ConfigFile.h index be603b1..5ad9281 100644 --- a/ConfigFile.h +++ b/ConfigFile.h @@ -55,6 +55,7 @@ class ConfigFile { bool writeFloat(const float val, const uint8_t prec, const char tok = CFG_VALUE_SEP); bool readString(char *buff, size_t len); bool readVarString(char *buff, size_t len); + bool skipValue(size_t len); bool writeString(const char *val, size_t len, const char tok = CFG_VALUE_SEP); bool writeVarString(const char *val, const char tok = CFG_VALUE_SEP); char readChar(const char defVal = '\0'); @@ -79,7 +80,7 @@ class ShadeConfigFile : public ConfigFile { bool readShadeRecord(SomfyShade *shade); bool readGroupRecord(SomfyGroup *group); bool readSettingsRecord(); - bool readNetRecord(); + bool readNetRecord(restore_options_t &opts); bool readTransRecord(transceiver_config_t &cfg); public: static bool exists(); diff --git a/ConfigSettings.cpp b/ConfigSettings.cpp index 5b452ce..5deec5e 100644 --- a/ConfigSettings.cpp +++ b/ConfigSettings.cpp @@ -15,6 +15,7 @@ void restore_options_t::fromJSON(JsonObject &obj) { if(obj.containsKey("network")) this->network = obj["network"]; if(obj.containsKey("transceiver")) this->transceiver = obj["transceiver"]; if(obj.containsKey("repeaters")) this->repeaters = obj["repeaters"]; + if(obj.containsKey("mqtt")) this->mqtt = obj["mqtt"]; } int8_t appver_t::compare(appver_t &ver) { if(this->major == ver.major && this->minor == ver.minor && this->build == ver.build) return 0; @@ -84,6 +85,20 @@ bool appver_t::toJSON(JsonObject &obj) { obj["suffix"] = this->suffix; return true; } +void appver_t::toJSON(JsonResponse &json) { + json.addElem("name", this->name); + json.addElem("major", this->major); + json.addElem("minor", this->minor); + json.addElem("build", this->build); + json.addElem("suffix", this->suffix); +} +void appver_t::toJSON(JsonSockEvent *json) { + json->addElem("name", this->name); + json->addElem("major", this->major); + json->addElem("minor", this->minor); + json->addElem("build", this->build); + json->addElem("suffix", this->suffix); +} bool BaseSettings::load() { return true; } bool BaseSettings::loadFile(const char *filename) { @@ -190,12 +205,12 @@ bool ConfigSettings::load() { pref.getString("hostname", this->hostname, sizeof(this->hostname)); this->ssdpBroadcast = pref.getBool("ssdpBroadcast", true); this->checkForUpdate = pref.getBool("checkForUpdate", true); - this->connType = static_cast(pref.getChar("connType", 0x00)); + this->connType = static_cast(pref.getChar("connType", 0x00)); //Serial.printf("Preference GFG Free Entries: %d\n", pref.freeEntries()); pref.end(); - if(this->connType == conn_types::unset) { + if(this->connType == conn_types_t::unset) { // We are doing this to convert the data from previous versions. - this->connType = conn_types::wifi; + this->connType = conn_types_t::wifi; pref.begin("WIFI"); pref.getString("hostname", this->hostname, sizeof(this->hostname)); this->ssdpBroadcast = pref.getBool("ssdpBroadcast", true); @@ -234,11 +249,19 @@ bool ConfigSettings::toJSON(JsonObject &obj) { obj["checkForUpdate"] = this->checkForUpdate; return true; } +void ConfigSettings::toJSON(JsonResponse &json) { + json.addElem("ssdpBroadcast", this->ssdpBroadcast); + json.addElem("hostname", this->hostname); + json.addElem("connType", static_cast(this->connType)); + json.addElem("chipModel", this->chipModel); + json.addElem("checkForUpdate", this->checkForUpdate); +} + bool ConfigSettings::requiresAuth() { return this->Security.type != security_types::None; } bool ConfigSettings::fromJSON(JsonObject &obj) { if(obj.containsKey("ssdpBroadcast")) this->ssdpBroadcast = obj["ssdpBroadcast"]; if(obj.containsKey("hostname")) this->parseValueString(obj, "hostname", this->hostname, sizeof(this->hostname)); - if(obj.containsKey("connType")) this->connType = static_cast(obj["connType"].as()); + if(obj.containsKey("connType")) this->connType = static_cast(obj["connType"].as()); if(obj.containsKey("checkForUpdate")) this->checkForUpdate = obj["checkForUpdate"]; return true; } @@ -246,8 +269,8 @@ void ConfigSettings::print() { this->Security.print(); Serial.printf("Connection Type: %u\n", (unsigned int) this->connType); this->NTP.print(); - if(this->connType == conn_types::wifi || this->connType == conn_types::unset) this->WIFI.print(); - if(this->connType == conn_types::ethernet || this->connType == conn_types::ethernetpref) this->Ethernet.print(); + if(this->connType == conn_types_t::wifi || this->connType == conn_types_t::unset) this->WIFI.print(); + if(this->connType == conn_types_t::ethernet || this->connType == conn_types_t::ethernetpref) this->Ethernet.print(); } void ConfigSettings::emitSockets() {} void ConfigSettings::emitSockets(uint8_t num) {} @@ -285,6 +308,18 @@ bool MQTTSettings::begin() { this->load(); return true; } +void MQTTSettings::toJSON(JsonResponse &json) { + json.addElem("enabled", this->enabled); + json.addElem("pubDisco", this->pubDisco); + json.addElem("protocol", this->protocol); + json.addElem("hostname", this->hostname); + json.addElem("port", (uint32_t)this->port); + json.addElem("username", this->username); + json.addElem("password", this->password); + json.addElem("rootTopic", this->rootTopic); + json.addElem("discoTopic", this->discoTopic); +} + bool MQTTSettings::toJSON(JsonObject &obj) { obj["enabled"] = this->enabled; obj["pubDisco"] = this->pubDisco; @@ -383,6 +418,11 @@ bool NTPSettings::fromJSON(JsonObject &obj) { this->parseValueString(obj, "posixZone", this->posixZone, sizeof(this->posixZone)); return true; } +void NTPSettings::toJSON(JsonResponse &json) { + json.addElem("ntpServer", this->ntpServer); + json.addElem("posixZone", this->posixZone); +} + bool NTPSettings::toJSON(JsonObject &obj) { obj["ntpServer"] = this->ntpServer; obj["posixZone"] = this->posixZone; @@ -419,6 +459,16 @@ bool IPSettings::toJSON(JsonObject &obj) { obj["dns2"] = this->dns2 == ipEmpty ? "" : this->dns2.toString(); return true; } +void IPSettings::toJSON(JsonResponse &json) { + IPAddress ipEmpty(0,0,0,0); + json.addElem("dhcp", this->dhcp); + json.addElem("ip", this->ip.toString().c_str()); + json.addElem("gateway", this->gateway.toString().c_str()); + json.addElem("subnet", this->subnet.toString().c_str()); + json.addElem("dns1", this->dns1.toString().c_str()); + json.addElem("dns2", this->dns2.toString().c_str()); +} + bool IPSettings::save() { pref.begin("IP"); pref.clear(); @@ -479,6 +529,14 @@ bool SecuritySettings::toJSON(JsonObject &obj) { obj["permissions"] = this->permissions; return true; } +void SecuritySettings::toJSON(JsonResponse &json) { + json.addElem("type", static_cast(this->type)); + json.addElem("username", this->username); + json.addElem("password", this->password); + json.addElem("pin", this->pin); + json.addElem("permissions", this->permissions); +} + bool SecuritySettings::save() { pref.begin("SEC"); pref.clear(); @@ -521,18 +579,31 @@ bool WifiSettings::begin() { bool WifiSettings::fromJSON(JsonObject &obj) { this->parseValueString(obj, "ssid", this->ssid, sizeof(this->ssid)); this->parseValueString(obj, "passphrase", this->passphrase, sizeof(this->passphrase)); + if(obj.containsKey("roaming")) this->roaming = obj["roaming"]; + if(obj.containsKey("hidden")) this->hidden = obj["hidden"]; return true; } bool WifiSettings::toJSON(JsonObject &obj) { obj["ssid"] = this->ssid; obj["passphrase"] = this->passphrase; + obj["roaming"] = this->roaming; + obj["hidden"] = this->hidden; return true; } +void WifiSettings::toJSON(JsonResponse &json) { + json.addElem("ssid", this->ssid); + json.addElem("passphrase", this->passphrase); + json.addElem("roaming", this->roaming); + json.addElem("hidden", this->hidden); +} + bool WifiSettings::save() { pref.begin("WIFI"); pref.clear(); pref.putString("ssid", this->ssid); pref.putString("passphrase", this->passphrase); + pref.putBool("roaming", this->roaming); + pref.putBool("hidden", this->hidden); pref.end(); return true; } @@ -542,6 +613,8 @@ bool WifiSettings::load() { pref.getString("passphrase", this->passphrase, sizeof(this->passphrase)); this->ssid[sizeof(this->ssid) - 1] = '\0'; this->passphrase[sizeof(this->passphrase) - 1] = '\0'; + this->roaming = pref.getBool("roaming", true); + this->hidden = pref.getBool("hidden", false); pref.end(); return true; } @@ -571,7 +644,7 @@ void WifiSettings::print() { Serial.println("]"); } void WifiSettings::printNetworks() { - int n = WiFi.scanNetworks(false, true); + int n = WiFi.scanNetworks(false, false); Serial.print("Scanned "); Serial.print(n); Serial.println(" Networks..."); @@ -590,6 +663,7 @@ void WifiSettings::printNetworks() { Serial.print(WiFi.BSSIDstr(i)); Serial.println(); } + } bool WifiSettings::ssidExists(const char *ssid) { int n = WiFi.scanNetworks(false, true); @@ -623,6 +697,16 @@ bool EthernetSettings::toJSON(JsonObject &obj) { obj["MDIOPin"] = this->MDIOPin; return true; } +void EthernetSettings::toJSON(JsonResponse &json) { + json.addElem("boardType", this->boardType); + json.addElem("phyAddress", this->phyAddress); + json.addElem("CLKMode", static_cast(this->CLKMode)); + json.addElem("phyType", static_cast(this->phyType)); + json.addElem("PWRPin", this->PWRPin); + json.addElem("MDCPin", this->MDCPin); + json.addElem("MDIOPin", this->MDIOPin); +} + bool EthernetSettings::usesPin(uint8_t pin) { if((this->CLKMode == 0 || this->CLKMode == 1) && pin == 0) return true; else if(this->CLKMode == 2 && pin == 16) return true; diff --git a/ConfigSettings.h b/ConfigSettings.h index 88bf3db..350db96 100644 --- a/ConfigSettings.h +++ b/ConfigSettings.h @@ -2,8 +2,16 @@ #include #ifndef configsettings_h #define configsettings_h +#include "WResp.h" +#define FW_VERSION "v2.4.7" +enum class conn_types_t : byte { + unset = 0x00, + wifi = 0x01, + ethernet = 0x02, + ethernetpref = 0x03, + ap = 0x04 +}; -#define FW_VERSION "v2.4.0" enum DeviceStatus { DS_OK = 0, DS_ERROR = 1, @@ -15,6 +23,7 @@ struct restore_options_t { bool network = false; bool transceiver = false; bool repeaters = false; + bool mqtt = false; void fromJSON(JsonObject &obj); }; struct appver_t { @@ -25,6 +34,8 @@ struct appver_t { char suffix[4] = ""; void parse(const char *ver); bool toJSON(JsonObject &obj); + void toJSON(JsonResponse &json); + void toJSON(JsonSockEvent *json); int8_t compare(appver_t &ver); void copy(appver_t &ver); }; @@ -35,6 +46,7 @@ class BaseSettings { bool loadFile(const char* filename); bool fromJSON(JsonObject &obj); bool toJSON(JsonObject &obj); + void toJSON(JsonResponse &json); bool parseIPAddress(JsonObject &obj, const char *prop, IPAddress *); bool parseValueString(JsonObject &obj, const char *prop, char *dest, size_t size); int parseValueInt(JsonObject &obj, const char *prop, int defVal); @@ -49,6 +61,7 @@ class NTPSettings: BaseSettings { char posixZone[64] = ""; bool fromJSON(JsonObject &obj); bool toJSON(JsonObject &obj); + void toJSON(JsonResponse &json); bool apply(); bool begin(); bool save(); @@ -58,12 +71,15 @@ class NTPSettings: BaseSettings { class WifiSettings: BaseSettings { public: WifiSettings(); + bool roaming = true; + bool hidden = false; char ssid[65] = ""; char passphrase[65] = ""; //bool ssdpBroadcast = true; bool begin(); bool fromJSON(JsonObject &obj); bool toJSON(JsonObject &obj); + void toJSON(JsonResponse &json); String mapEncryptionType(int type); bool ssidExists(const char *ssid); void printNetworks(); @@ -86,6 +102,7 @@ class EthernetSettings: BaseSettings { bool begin(); bool fromJSON(JsonObject &obj); bool toJSON(JsonObject &obj); + void toJSON(JsonResponse &json); bool load(); bool save(); void print(); @@ -103,6 +120,7 @@ class IPSettings: BaseSettings { bool begin(); bool fromJSON(JsonObject &obj); bool toJSON(JsonObject &obj); + void toJSON(JsonResponse &json); bool load(); bool save(); void print(); @@ -127,6 +145,7 @@ class SecuritySettings: BaseSettings { bool load(); void print(); bool toJSON(JsonObject &obj); + void toJSON(JsonResponse &json); bool fromJSON(JsonObject &obj); }; class MQTTSettings: BaseSettings { @@ -144,21 +163,16 @@ class MQTTSettings: BaseSettings { bool save(); bool load(); bool toJSON(JsonObject &obj); + void toJSON(JsonResponse &json); bool fromJSON(JsonObject &obj); }; -enum class conn_types : byte { - unset = 0x00, - wifi = 0x01, - ethernet = 0x02, - ethernetpref = 0x03 -}; class ConfigSettings: BaseSettings { public: static void printAvailHeap(); char serverId[10] = ""; char hostname[32] = "ESPSomfyRTS"; char chipModel[10] = "ESP32"; - conn_types connType = conn_types::unset; + conn_types_t connType = conn_types_t::unset; appver_t fwVersion; appver_t appVersion; bool ssdpBroadcast = true; @@ -173,6 +187,7 @@ class ConfigSettings: BaseSettings { bool requiresAuth(); bool fromJSON(JsonObject &obj); bool toJSON(JsonObject &obj); + void toJSON(JsonResponse &json); bool begin(); bool save(); bool load(); @@ -184,5 +199,4 @@ class ConfigSettings: BaseSettings { uint16_t calcNetRecSize(); bool getAppVersion(); }; - #endif diff --git a/GitOTA.cpp b/GitOTA.cpp index 9a0ec04..49ea3c5 100644 --- a/GitOTA.cpp +++ b/GitOTA.cpp @@ -2,12 +2,17 @@ #include #include #include +#include +#include "ConfigSettings.h" #include "GitOTA.h" #include "Utils.h" -#include "ConfigSettings.h" #include "Sockets.h" #include "Somfy.h" #include "Web.h" +#include "WResp.h" +#include "Network.h" + + extern ConfigSettings settings; @@ -15,6 +20,8 @@ extern SocketEmitter sockEmit; extern SomfyShadeController somfy; extern rebootDelay_t rebootDelay; extern Web webServer; +extern Network net; + #define MAX_BUFF_SIZE 4096 @@ -65,19 +72,21 @@ void GitRelease::setAssetProperty(const char *key, const char *val) { } } } -bool GitRelease::toJSON(JsonObject &obj) { +void GitRelease::toJSON(JsonResponse &json) { Timestamp ts; - obj["id"] = this->id; - obj["name"] = this->name; - obj["date"] = ts.getISOTime(this->releaseDate); - obj["draft"] = this->draft; - obj["preRelease"] = this->preRelease; - obj["main"] = this->main; - obj["hasFS"] = this->hasFS; - obj["hwVersions"] = this->hwVersions; - JsonObject ver = obj.createNestedObject("version"); - this->version.toJSON(ver); - return true; + char buff[20]; + sprintf(buff, "%llu", this->id); + json.addElem("id", buff); + json.addElem("name", this->name); + json.addElem("date", ts.getISOTime(this->releaseDate)); + json.addElem("draft", this->draft); + json.addElem("preRelease", this->preRelease); + json.addElem("main", this->main); + json.addElem("hasFS", this->hasFS); + json.addElem("hwVersions", this->hwVersions); + json.beginObject("version"); + this->version.toJSON(json); + json.endObject(); } #define ERR_CLIENT_OFFSET -50 @@ -97,16 +106,17 @@ int16_t GitRepo::getReleases(uint8_t num) { strcpy(main->version.name, "main"); strcpy(main->name, "Main"); strcpy(main->hwVersions, "32,s3"); - HTTPClient *https = new HTTPClient(); - https->setReuse(false); - if(https->begin(sclient, url)) { - int httpCode = https->GET(); + HTTPClient https; + https.setReuse(false); + if(https.begin(sclient, url)) { + esp_task_wdt_reset(); + int httpCode = https.GET(); Serial.printf("[HTTPS] GET... code: %d\n", httpCode); if(httpCode > 0) { - int len = https->getSize(); + int len = https.getSize(); Serial.printf("[HTTPS] GET... code: %d - %d\n", httpCode, len); if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { - WiFiClient *stream = https->getStreamPtr(); + WiFiClient *stream = https.getStreamPtr(); uint8_t buff[128] = {0}; char jsonElem[32] = ""; char jsonValue[128] = ""; @@ -117,9 +127,10 @@ int16_t GitRepo::getReleases(uint8_t num) { bool inValue = false; bool awaitValue = false; bool inAss = false; - while(https->connected() && (len > 0 || len == -1) && ndx < count) { + while(https.connected() && (len > 0 || len == -1) && ndx < count) { size_t size = stream->available(); if(size) { + esp_task_wdt_reset(); int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); //Serial.write(buff, c); if(len > 0) len -= c; @@ -208,31 +219,32 @@ int16_t GitRepo::getReleases(uint8_t num) { } } else { - https->end(); + https.end(); sclient.stop(); - delete https; return httpCode; } } - https->end(); - delete https; + https.end(); + sclient.stop(); } - sclient.stop(); settings.printAvailHeap(); return 0; } -bool GitRepo::toJSON(JsonObject &obj) { - JsonObject fw = obj.createNestedObject("fwVersion"); - settings.fwVersion.toJSON(fw); - JsonObject app = obj.createNestedObject("appVersion"); - settings.appVersion.toJSON(app); - JsonArray arr = obj.createNestedArray("releases"); +void GitRepo::toJSON(JsonResponse &json) { + json.beginObject("fwVersion"); + settings.fwVersion.toJSON(json); + json.endObject(); + json.beginObject("appVersion"); + settings.appVersion.toJSON(json); + json.endObject(); + json.beginArray("releases"); for(uint8_t i = 0; i < GIT_MAX_RELEASES + 1; i++) { if(this->releases[i].id == 0) continue; - JsonObject o = arr.createNestedObject(); - this->releases[i].toJSON(o); + json.beginObject(); + this->releases[i].toJSON(json); + json.endObject(); } - return true; + json.endArray(); } #define UPDATE_ERR_OFFSET 20 #define ERR_DOWNLOAD_HTTP -40 @@ -240,12 +252,10 @@ bool GitRepo::toJSON(JsonObject &obj) { #define ERR_DOWNLOAD_CONNECTION -42 void GitUpdater::loop() { + if(!net.connected()) return; if(this->status == GIT_STATUS_READY) { - //if(this->lastCheck == 0) - //this->lastCheck = millis(); - //else if(settings.checkForUpdate && - //(this->lastCheck + 14400000 < millis() || this->lastCheck == 0) && !rebootDelay.reboot) { // 4 hours + (millis() > net.connectTime + 60000) && // Wait a minute before checking after connection. (this->lastCheck + 86400000 < millis() || this->lastCheck == 0) && !rebootDelay.reboot) { // 1 day this->checkForUpdate(); } @@ -269,6 +279,7 @@ void GitUpdater::loop() { void GitUpdater::checkForUpdate() { if(this->status != 0) return; // If we are already checking. Serial.println("Check github for updates..."); + this->status = GIT_STATUS_CHECK; settings.printAvailHeap(); this->lastCheck = millis(); @@ -299,42 +310,59 @@ void GitUpdater::setCurrentRelease(GitRepo &repo) { } this->emitUpdateCheck(); } -void GitUpdater::toJSON(JsonObject &obj) { - obj["available"] = this->updateAvailable; - obj["status"] = this->status; - obj["error"] = this->error; - obj["cancelled"] = this->cancelled; - obj["checkForUpdate"] = settings.checkForUpdate; - obj["inetAvailable"] = this->inetAvailable; - JsonObject fw = obj.createNestedObject("fwVersion"); - settings.fwVersion.toJSON(fw); - JsonObject app = obj.createNestedObject("appVersion"); - settings.appVersion.toJSON(app); - JsonObject latest = obj.createNestedObject("latest"); - this->latest.toJSON(latest); +void GitUpdater::toJSON(JsonResponse &json) { + json.addElem("available", this->updateAvailable); + json.addElem("status", this->status); + json.addElem("error", (int32_t)this->error); + json.addElem("cancelled", this->cancelled); + json.addElem("checkForUpdate", settings.checkForUpdate); + json.addElem("inetAvailable", this->inetAvailable); + json.beginObject("fwVersion"); + settings.fwVersion.toJSON(json); + json.endObject(); + json.beginObject("appVersion"); + settings.appVersion.toJSON(json); + json.endObject(); + json.beginObject("latest"); + this->latest.toJSON(json); + json.endObject(); } void GitUpdater::emitUpdateCheck(uint8_t num) { - ClientSocketEvent evt("fwStatus"); - DynamicJsonDocument doc(512); - JsonObject obj = doc.to(); - this->toJSON(obj); - if(num == 255) - sockEmit.sendToClients("fwStatus", doc); - else - sockEmit.sendToClient(num, "fwStatus", doc); + JsonSockEvent *json = sockEmit.beginEmit("fwStatus"); + json->beginObject(); + json->addElem("available", this->updateAvailable); + json->addElem("status", this->status); + json->addElem("error", (int32_t)this->error); + json->addElem("cancelled", this->cancelled); + json->addElem("checkForUpdate", settings.checkForUpdate); + json->addElem("inetAvailable", this->inetAvailable); + json->beginObject("fwVersion"); + settings.fwVersion.toJSON(json); + json->endObject(); + json->beginObject("appVersion"); + settings.appVersion.toJSON(json); + json->endObject(); + json->beginObject("latest"); + this->latest.toJSON(json); + json->endObject(); + json->endObject(); + sockEmit.endEmit(num); } int GitUpdater::checkInternet() { int err = 500; uint32_t t = millis(); - WiFiClientSecure client; - client.setInsecure(); - client.setHandshakeTimeout(3); - HTTPClient *https = new HTTPClient(); - https->setReuse(false); - if(https->begin(client, "https://github.com/rstrouse/ESPSomfy-RTS")) { - https->setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); - https->setTimeout(5000); - int httpCode = https->sendRequest("HEAD"); + WiFiClientSecure sclient; + sclient.setInsecure(); + sclient.setHandshakeTimeout(3); + esp_task_wdt_reset(); + HTTPClient https; + https.setReuse(false); + if(https.begin(sclient, "https://github.com/rstrouse/ESPSomfy-RTS")) { + https.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); + https.setTimeout(3000); + esp_task_wdt_reset(); + int httpCode = https.sendRequest("HEAD"); + esp_task_wdt_reset(); if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY || httpCode == HTTP_CODE_FOUND) { err = 0; Serial.printf("Internet is Available: %ldms\n", millis() - t); @@ -345,18 +373,30 @@ int GitUpdater::checkInternet() { Serial.printf("Internet is Unavailable: %d: %ldms\n", err, millis() - t); this->inetAvailable = false; } - https->end(); + https.end(); + sclient.stop(); } - client.stop(); - delete https; + esp_task_wdt_reset(); return err; } void GitUpdater::emitDownloadProgress(size_t total, size_t loaded, const char *evt) { this->emitDownloadProgress(255, total, loaded, evt); } void GitUpdater::emitDownloadProgress(uint8_t num, size_t total, size_t loaded, const char *evt) { + JsonSockEvent *json = sockEmit.beginEmit(evt); + json->beginObject(); + json->addElem("ver", this->targetRelease); + json->addElem("part", (int32_t)this->partition); + json->addElem("file", this->currentFile); + json->addElem("total", (uint32_t)total); + json->addElem("loaded", (uint32_t)loaded); + json->addElem("error", (uint32_t)this->error); + json->endObject(); + sockEmit.endEmit(num); + /* char buf[420]; snprintf(buf, sizeof(buf), "{\"ver\":\"%s\",\"part\":%d,\"file\":\"%s\",\"total\":%d,\"loaded\":%d, \"error\":%d}", this->targetRelease, this->partition, this->currentFile, total, loaded, this->error); if(num >= 255) sockEmit.sendToClients(evt, buf); else sockEmit.sendToClient(num, evt, buf); + */ sockEmit.loop(); webServer.loop(); } @@ -431,119 +471,118 @@ bool GitUpdater::recoverFilesystem() { } bool GitUpdater::endUpdate() { return true; } int8_t GitUpdater::downloadFile() { - WiFiClientSecure *client = new WiFiClientSecure; Serial.printf("Begin update %s\n", this->currentFile); - if(client) { - client->setInsecure(); - HTTPClient https; - char url[196]; - sprintf(url, "%s%s", this->baseUrl, this->currentFile); - Serial.println(url); - if(https.begin(*client, url)) { - https.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); - Serial.print("[HTTPS] GET...\n"); - int httpCode = https.GET(); - if(httpCode > 0) { - size_t len = https.getSize(); - size_t total = 0; - uint8_t pct = 0; - Serial.printf("[HTTPS] GET... code: %d - %d\n", httpCode, len); - if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY || httpCode == HTTP_CODE_FOUND) { - WiFiClient *stream = https.getStreamPtr(); - if(!Update.begin(len, this->partition)) { - Serial.println("Update Error detected!!!!!"); - Update.printError(Serial); - https.end(); - return -(Update.getError() + UPDATE_ERR_OFFSET); - } - uint8_t *buff = (uint8_t *)malloc(MAX_BUFF_SIZE); - if(buff) { - this->emitDownloadProgress(len, total); - int timeouts = 0; - while(https.connected() && (len > 0 || len == -1) && total < len) { - size_t size = stream->available(); - if(size) { - if(this->cancelled && !this->lockFS) { - Update.abort(); - https.end(); - free(buff); - return -(Update.getError() + UPDATE_ERR_OFFSET); - } - int c = stream->readBytes(buff, ((size > MAX_BUFF_SIZE) ? MAX_BUFF_SIZE : size)); - total += c; - //Serial.println(total); - if (Update.write(buff, c) != c) { + WiFiClientSecure sclient; + sclient.setInsecure(); + HTTPClient https; + char url[196]; + sprintf(url, "%s%s", this->baseUrl, this->currentFile); + Serial.println(url); + esp_task_wdt_reset(); + if(https.begin(sclient, url)) { + https.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); + Serial.print("[HTTPS] GET...\n"); + int httpCode = https.GET(); + if(httpCode > 0) { + size_t len = https.getSize(); + size_t total = 0; + uint8_t pct = 0; + Serial.printf("[HTTPS] GET... code: %d - %d\n", httpCode, len); + if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY || httpCode == HTTP_CODE_FOUND) { + WiFiClient *stream = https.getStreamPtr(); + if(!Update.begin(len, this->partition)) { + Serial.println("Update Error detected!!!!!"); + Update.printError(Serial); + https.end(); + return -(Update.getError() + UPDATE_ERR_OFFSET); + } + uint8_t *buff = (uint8_t *)malloc(MAX_BUFF_SIZE); + if(buff) { + this->emitDownloadProgress(len, total); + int timeouts = 0; + while(https.connected() && (len > 0 || len == -1) && total < len) { + size_t size = stream->available(); + esp_task_wdt_reset(); + if(size) { + timeouts = 0; + if(this->cancelled && !this->lockFS) { + Update.abort(); + free(buff); + https.end(); + return -(Update.getError() + UPDATE_ERR_OFFSET); + } + int c = stream->readBytes(buff, ((size > MAX_BUFF_SIZE) ? MAX_BUFF_SIZE : size)); + total += c; + //Serial.println(total); + if (Update.write(buff, c) != c) { + Update.printError(Serial); + Serial.printf("Upload of %s aborted invalid size %d\n", url, c); + free(buff); + https.end(); + sclient.stop(); + return -(Update.getError() + UPDATE_ERR_OFFSET); + } + // Calculate the percentage. + uint8_t p = (uint8_t)floor(((float)total / (float)len) * 100.0f); + if(p != pct) { + pct = p; + Serial.printf("LEN:%d TOTAL:%d %d%%\n", len, total, pct); + this->emitDownloadProgress(len, total); + } + delay(1); + if(total >= len) { + if(!Update.end(true)) { + Serial.println("Error downloading update..."); Update.printError(Serial); - Serial.printf("Upload of %s aborted invalid size %d\n", url, c); - free(buff); - https.end(); - return -(Update.getError() + UPDATE_ERR_OFFSET); } - // Calculate the percentage. - uint8_t p = (uint8_t)floor(((float)total / (float)len) * 100.0f); - if(p != pct) { - pct = p; - Serial.printf("LEN:%d TOTAL:%d %d%%\n", len, total, pct); - this->emitDownloadProgress(len, total); + else { + Serial.println("Update.end Called..."); } - delay(1); - if(total >= len) { - if(!Update.end(true)) { - Serial.println("Error downloading update..."); - Update.printError(Serial); - } - else { - Serial.println("Update.end Called..."); - } - https.end(); - } - } - else { - timeouts++; - if(timeouts >= 500) { - Update.abort(); - https.end(); - free(buff); - Serial.println("Stream timeout!!!"); - return -43; - } - sockEmit.loop(); - webServer.loop(); - delay(100); + https.end(); + sclient.stop(); } } - free(buff); - if(len > total) { - Update.abort(); - somfy.commit(); - Serial.println("Error downloading file!!!"); - return -42; - + else { + timeouts++; + if(timeouts >= 500) { + Update.abort(); + https.end(); + free(buff); + Serial.println("Stream timeout!!!"); + return -43; + } + sockEmit.loop(); + webServer.loop(); + delay(100); } - else - Serial.printf("Update %s complete\n", this->currentFile); - } - else { - // TODO: memory allocation error. - Serial.println("Unable to allocate memory for update!!!"); + free(buff); + if(len > total) { + Update.abort(); + somfy.commit(); + Serial.println("Error downloading file!!!"); + return -42; } + else + Serial.printf("Update %s complete\n", this->currentFile); } else { - Serial.printf("Invalid HTTP Code... %d", httpCode); - return httpCode; + // TODO: memory allocation error. + Serial.println("Unable to allocate memory for update!!!"); } - } - else { - Serial.printf("Invalid HTTP Code: %d\n", httpCode); } - - if(https.connected()) https.end(); - Serial.printf("End update %s\n", this->currentFile); - + else { + Serial.printf("Invalid HTTP Code... %d", httpCode); + return httpCode; + } + } + else { + Serial.printf("Invalid HTTP Code: %d\n", httpCode); } - client->stop(); - delete client; + https.end(); + sclient.stop(); + Serial.printf("End update %s\n", this->currentFile); } + esp_task_wdt_reset(); return 0; } diff --git a/GitOTA.h b/GitOTA.h index 8a4019b..f79aaea 100644 --- a/GitOTA.h +++ b/GitOTA.h @@ -4,6 +4,7 @@ #include #include #include "ConfigSettings.h" +#include "WResp.h" #define GIT_MAX_RELEASES 5 #define GIT_STATUS_READY 0 @@ -27,14 +28,13 @@ class GitRelease { appver_t version; void setReleaseProperty(const char *key, const char *val); void setAssetProperty(const char *key, const char *val); - bool toJSON(JsonObject &obj); - + void toJSON(JsonResponse &json); }; class GitRepo { public: int16_t getReleases(uint8_t num = GIT_MAX_RELEASES); GitRelease releases[GIT_MAX_RELEASES + 1]; - bool toJSON(JsonObject &obj); + void toJSON(JsonResponse &json); }; class GitUpdater { public: @@ -58,7 +58,7 @@ class GitUpdater { void setFirmwareFile(); void setCurrentRelease(GitRepo &repo); void loop(); - void toJSON(JsonObject &obj); + void toJSON(JsonResponse &json); bool recoverFilesystem(); int checkInternet(); void emitUpdateCheck(uint8_t num=255); diff --git a/MQTT.cpp b/MQTT.cpp index 65eb63d..55f693a 100644 --- a/MQTT.cpp +++ b/MQTT.cpp @@ -1,8 +1,9 @@ #include #include #include -#include "MQTT.h" +#include #include "ConfigSettings.h" +#include "MQTT.h" #include "Somfy.h" #include "Network.h" #include "Utils.h" @@ -34,12 +35,16 @@ void MQTTClass::reset() { this->connect(); } bool MQTTClass::loop() { - if(settings.MQTT.enabled && !rebootDelay.reboot && !this->suspended && !mqttClient.connected()) - this->connect(); + if(settings.MQTT.enabled && !rebootDelay.reboot && !this->suspended && !mqttClient.connected()) { + esp_task_wdt_reset(); + if(!this->connected() && net.connected()) this->connect(); + } + esp_task_wdt_reset(); if(settings.MQTT.enabled) mqttClient.loop(); return true; } void MQTTClass::receive(const char *topic, byte*payload, uint32_t length) { + esp_task_wdt_reset(); // Make sure we do not reboot here. Serial.print("MQTT Topic:"); Serial.print(topic); Serial.print(" payload:"); @@ -178,8 +183,10 @@ void MQTTClass::receive(const char *topic, byte*payload, uint32_t length) { } } } + esp_task_wdt_reset(); // Make sure we do not reboot here. } bool MQTTClass::connect() { + esp_task_wdt_reset(); // Make sure we do not reboot here. if(mqttClient.connected()) { if(!settings.MQTT.enabled || this->suspended) return this->disconnect(); @@ -195,6 +202,7 @@ bool MQTTClass::connect() { char lwtTopic[128] = "status"; if(strlen(settings.MQTT.rootTopic) > 0) snprintf(lwtTopic, sizeof(lwtTopic), "%s/status", settings.MQTT.rootTopic); + esp_task_wdt_reset(); if(mqttClient.connect(this->clientId, settings.MQTT.username, settings.MQTT.password, lwtTopic, 0, true, "offline")) { Serial.print("Successfully connected MQTT client "); Serial.println(this->clientId); @@ -219,8 +227,9 @@ bool MQTTClass::connect() { this->subscribe("groups/+/sunFlag/set"); this->subscribe("groups/+/sunny/set"); this->subscribe("groups/+/windy/set"); - mqttClient.setCallback(MQTTClass::receive); + Serial.println("MQTT Startup Completed"); + esp_task_wdt_reset(); this->lastConnect = millis(); return true; } @@ -272,6 +281,7 @@ bool MQTTClass::unsubscribe(const char *topic) { } bool MQTTClass::subscribe(const char *topic) { if(mqttClient.connected()) { + esp_task_wdt_reset(); // Make sure we do not reboot here. char top[128]; if(strlen(settings.MQTT.rootTopic) > 0) snprintf(top, sizeof(top), "%s/%s", settings.MQTT.rootTopic, topic); @@ -290,6 +300,7 @@ bool MQTTClass::publish(const char *topic, const char *payload, bool retain) { snprintf(top, sizeof(top), "%s/%s", settings.MQTT.rootTopic, topic); else strlcpy(top, topic, sizeof(top)); + esp_task_wdt_reset(); // Make sure we do not reboot here. mqttClient.publish(top, payload, retain); return true; } @@ -299,18 +310,6 @@ bool MQTTClass::publish(const char *topic, uint32_t val, bool retain) { snprintf(g_content, sizeof(g_content), "%u", val); return this->publish(topic, g_content, retain); } -bool MQTTClass::publish(const char *topic, JsonDocument &doc, bool retain) { - serializeJson(doc, g_content, sizeof(g_content)); - return this->publish(topic, g_content, retain); -} -bool MQTTClass::publish(const char *topic, JsonArray &arr, bool retain) { - serializeJson(arr, g_content, sizeof(g_content)); - return this->publish(topic, g_content, retain); -} -bool MQTTClass::publish(const char *topic, JsonObject &obj, bool retain) { - serializeJson(obj, g_content, sizeof(g_content)); - return this->publish(topic, g_content, retain); -} bool MQTTClass::unpublish(const char *topic) { if(mqttClient.connected()) { char top[128]; @@ -318,6 +317,7 @@ bool MQTTClass::unpublish(const char *topic) { snprintf(top, sizeof(top), "%s/%s", settings.MQTT.rootTopic, topic); else strlcpy(top, topic, sizeof(top)); + esp_task_wdt_reset(); // Make sure we do not reboot here. mqttClient.publish(top, (const uint8_t *)"", 0, true); return true; } @@ -332,6 +332,7 @@ bool MQTTClass::publishBuffer(const char *topic, uint8_t *data, uint16_t len, bo uint16_t offset = 0; uint16_t to_write = len; uint16_t buff_len; + esp_task_wdt_reset(); // Make sure we do not reboot here. mqttClient.beginPublish(topic, len, retain); do { buff_len = to_write; diff --git a/MQTT.h b/MQTT.h index 073c943..fbc4839 100644 --- a/MQTT.h +++ b/MQTT.h @@ -17,9 +17,6 @@ class MQTTClass { void reset(); bool unpublish(const char *topic); bool publish(const char *topic, const char *payload, bool retain = false); - bool publish(const char *topic, JsonDocument &doc, bool retain = false); - bool publish(const char *topic, JsonArray &arr, bool retain = false); - bool publish(const char *topic, JsonObject &obj, bool retain = false); bool publish(const char *topic, uint8_t val, bool retain = false); bool publish(const char *topic, int8_t val, bool retain = false); bool publish(const char *topic, uint32_t val, bool retain = false); diff --git a/Network.cpp b/Network.cpp index 4242e57..ad8f2e1 100644 --- a/Network.cpp +++ b/Network.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "ConfigSettings.h" #include "Network.h" #include "Web.h" @@ -15,122 +16,245 @@ extern SocketEmitter sockEmit; extern MQTTClass mqtt; extern rebootDelay_t rebootDelay; extern Network net; +extern SomfyShadeController somfy; +static unsigned long _lastHeapEmit = 0; + +static bool _apScanning = false; +static uint32_t _lastMaxHeap = 0; +static uint32_t _lastHeap = 0; int connectRetries = 0; void Network::end() { - sockEmit.end(); SSDP.end(); mqtt.end(); + sockEmit.end(); delay(100); } bool Network::setup() { + WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); + WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); WiFi.persistent(false); - if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true); - if(settings.connType == conn_types::wifi || settings.connType == conn_types::unset) { + WiFi.setAutoReconnect(false); + WiFi.onEvent(this->networkEvent); + this->disconnectTime = millis(); + if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true, true); + if(settings.connType == conn_types_t::wifi || settings.connType == conn_types_t::unset) { WiFi.persistent(false); + if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname); Serial.print("WiFi Mode: "); Serial.println(WiFi.getMode()); WiFi.mode(WIFI_STA); - settings.WIFI.printNetworks(); } sockEmit.begin(); - if(!this->connect()) this->openSoftAP(); return true; } +conn_types_t Network::preferredConnType() { + switch(settings.connType) { + case conn_types_t::wifi: + return settings.WIFI.ssid[0] != '\0' ? conn_types_t::wifi : conn_types_t::ap; + case conn_types_t::unset: + case conn_types_t::ap: + return conn_types_t::ap; + case conn_types_t::ethernetpref: + return settings.WIFI.ssid[0] != '\0' && (!ETH.linkUp() && this->ethStarted) ? conn_types_t::wifi : conn_types_t::ethernet; + case conn_types_t::ethernet: + return ETH.linkUp() || !this->ethStarted ? conn_types_t::ethernet : conn_types_t::ap; + default: + return settings.connType; + } +} void Network::loop() { - if(millis() - this->lastEmit > 1500) { - this->lastEmit = millis(); - if(!this->softAPOpened) { - while(!this->connect()) { - // If we lost our connection - connectRetries++; - if(connectRetries > 100) { - if(!this->connected()) this->openSoftAP(); - break; - } - sockEmit.loop(); - } - connectRetries = 0; + // ORDER OF OPERATIONS: + // ---------------------------------------------- + // 1. If we are in the middle of a connection process we need to simply bail after the connect method. The + // connect method will take care of our target connection for us. + // 2. Check to see what type of target connection we need. + // a. If this is an ethernet target then the connection needs to perform a fallback if applicable. + // b. If this is a wifi target then we need to first check to see if the SSID is available. + // c. If an SSID has not been set then we need to turn on the Soft AP. + // 3. If the Soft AP is open and the target is either wifi, ethernet, or ethernetpref then + // we need to shut it down if there are no connections and the preferred connection is available. + // a. Ethernet: Check for an active ethernet connection. We cannot rely on linkup because the PHY will + // report that the link is up when no IP address is being served. + // b. WiFi: Perform synchronous scan for APs related to the SSID. If the SSID can be found then perform + // the connection process for the WiFi connection. + // c. SoftAP: This condition retains the Soft AP because no other connection method is available. + conn_types_t ctype = this->preferredConnType(); + this->connect(ctype); // Connection timeout handled in connect function as well as the opening of the Soft AP if needed. + if(this->connecting()) return; // If we are currently attempting to connect to something then we need to bail here. + if(_apScanning) { + if(settings.WIFI.hidden || // This user has elected to use a hidden AP. + (this->connected() && !settings.WIFI.roaming) || // We are already connected and should not be roaming. + (this->softAPOpened && WiFi.softAPgetStationNum() != 0) || // The Soft AP is open and a user is connected. + (ctype != conn_types_t::wifi)) { // The Ethernet link is up so we should ignore this scan. + Serial.println("Cancelling WiFi STA Scan..."); + _apScanning = false; + WiFi.scanDelete(); } - this->emitSockets(); + else { + int16_t n = WiFi.scanComplete(); + if( n >= 0) { // If the scan is complete but the WiFi isn't ready this can return 0. + uint8_t bssid[6]; + int32_t channel = 0; + if(this->getStrongestAP(settings.WIFI.ssid, bssid, &channel)) { + if(!WiFi.BSSID() || memcmp(bssid, WiFi.BSSID(), sizeof(bssid)) != 0) { + if(!this->connected()) { + Serial.printf("Connecting to AP %02X:%02X:%02X:%02X:%02X:%02X CH: %d\n", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], channel); + this->connectWiFi(bssid, channel); + } + else { + Serial.printf("Found stronger AP %02X:%02X:%02X:%02X:%02X:%02X CH: %d\n", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], channel); + this->changeAP(bssid, channel); + } + } + } + _apScanning = false; + } + } + } + if(!this->connecting() && !settings.WIFI.hidden) { + if((this->softAPOpened && WiFi.softAPgetStationNum() == 0) || + (!this->connected() && ctype == conn_types_t::wifi)) { + // If the Soft AP is opened and there are no clients connected then we need to scan for an AP. If + // our target exists we will exit out of the Soft AP and start that connection. We are also + // going to continuously scan when there is no connection and our preferred connection is wifi. + if(ctype == conn_types_t::wifi) { + // Scan for an AP but only if we are not already scanning. + if(!_apScanning && WiFi.scanNetworks(true, false, true, 300, 0, settings.WIFI.ssid) == -1) { + _apScanning = true; + } + } + } + else if(this->connected() && ctype == conn_types_t::wifi && settings.WIFI.roaming) { + // Periodically look for a roaming AP. + if(millis() > SSID_SCAN_INTERVAL + this->lastWifiScan) { + //Serial.println("Started scan for access points"); + if(!_apScanning && WiFi.scanNetworks(true, false, true, 300, 0, settings.WIFI.ssid) == -1) { + _apScanning = true; + this->lastWifiScan = millis(); + } + } + } + } + if(millis() - this->lastEmit > 1500) { + // Post our connection status if needed. this->lastEmit = millis(); - if(!this->connected()) return; + if(this->connected()) { + this->emitSockets(); + this->lastEmit = millis(); + } + esp_task_wdt_reset(); // Make sure we do not reboot here. } - if(this->connected() && millis() - this->lastMDNS > 60000) { - // We are doing this every 60 seconds because of the BS related to - // the MDNS library. The original library required manual updates - // to the MDNS or it would lose its hostname after 2 minutes. - if(this->lastMDNS != 0) MDNS.setInstanceName(settings.hostname); - this->lastMDNS = millis(); - } - if(settings.ssdpBroadcast) { + + sockEmit.loop(); + mqtt.loop(); + if(settings.ssdpBroadcast && this->connected()) { if(!SSDP.isStarted) SSDP.begin(); if(SSDP.isStarted) SSDP.loop(); } else if(!settings.ssdpBroadcast && SSDP.isStarted) SSDP.end(); - mqtt.loop(); +} +bool Network::changeAP(const uint8_t *bssid, const int32_t channel) { + esp_task_wdt_reset(); // Make sure we do not reboot here. + if(SSDP.isStarted) SSDP.end(); + mqtt.disconnect(); + //sockEmit.end(); + WiFi.disconnect(false, true); + this->connType = conn_types_t::unset; + this->_connecting = true; + this->connectStart = millis(); + WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase, channel, bssid); + this->connectStart = millis(); + return false; } void Network::emitSockets() { - if(this->needsBroadcast || abs(abs(WiFi.RSSI()) - abs(this->lastRSSI)) > 1 || WiFi.channel() != this->lastChannel) { + this->emitHeap(); + if(this->needsBroadcast || + (this->connType == conn_types_t::wifi && (abs(abs(WiFi.RSSI()) - abs(this->lastRSSI)) > 1 || WiFi.channel() != this->lastChannel))) { this->emitSockets(255); sockEmit.loop(); this->needsBroadcast = false; } } void Network::emitSockets(uint8_t num) { - char buf[128]; - if(this->connType == conn_types::ethernet) { - snprintf(buf, sizeof(buf), "{\"connected\":%s,\"speed\":%d,\"fullduplex\":%s}", this->connected() ? "true" : "false", ETH.linkSpeed(), ETH.fullDuplex() ? "true" : "false"); - if(num == 255) - sockEmit.sendToClients("ethernet", buf); - else - sockEmit.sendToClient(num, "ethernet", buf); + if(this->connType == conn_types_t::ethernet) { + JsonSockEvent *json = sockEmit.beginEmit("ethernet"); + json->beginObject(); + json->addElem("connected", this->connected()); + json->addElem("speed", ETH.linkSpeed()); + json->addElem("fullduplex", ETH.fullDuplex()); + json->endObject(); + sockEmit.endEmit(num); } else { if(WiFi.status() == WL_CONNECTED) { - snprintf(buf, sizeof(buf), "{\"ssid\":\"%s\",\"strength\":%d,\"channel\":%d}", WiFi.SSID().c_str(), WiFi.RSSI(), WiFi.channel()); - if(num == 255) - sockEmit.sendToClients("wifiStrength", buf); - else - sockEmit.sendToClient(num, "wifiStrength", buf); + JsonSockEvent *json = sockEmit.beginEmit("wifiStrength"); + json->beginObject(); + json->addElem("ssid", WiFi.SSID().c_str()); + json->addElem("strength", (int32_t)WiFi.RSSI()); + json->addElem("channel", (int32_t)this->channel); + json->endObject(); + sockEmit.endEmit(num); this->lastRSSI = WiFi.RSSI(); this->lastChannel = WiFi.channel(); } else { - if(num == 255) { - sockEmit.sendToClients("wifiStrength", "{\"ssid\":\"\", \"strength\":-100,\"channel\":-1}"); - sockEmit.sendToClients("ethernet", "{\"connected\":false,\"speed\":0,\"fullduplex\":false}"); - } - else { - sockEmit.sendToClient(num, "wifiStrength", "{\"ssid\":\"\", \"strength\":-100,\"channel\":-1}"); - sockEmit.sendToClient(num, "ethernet", "{\"connected\":false,\"speed\":0,\"fullduplex\":false}"); - } + JsonSockEvent *json = sockEmit.beginEmit("wifiStrength"); + json->beginObject(); + json->addElem("ssid", ""); + json->addElem("strength", (int8_t)-100); + json->addElem("channel", (int8_t)-1); + json->endObject(); + sockEmit.endEmit(num); + + json = sockEmit.beginEmit("ethernet"); + json->beginObject(); + json->addElem("connected", false); + json->addElem("speed", (uint8_t)0); + json->addElem("fullduplex", false); + json->endObject(); + sockEmit.endEmit(num); this->lastRSSI = -100; this->lastChannel = -1; } } + this->emitHeap(num); } -void Network::setConnected(conn_types connType) { +void Network::setConnected(conn_types_t connType) { + esp_task_wdt_reset(); this->connType = connType; this->connectTime = millis(); connectRetries = 0; - if(this->connType == conn_types::wifi) { - if(this->softAPOpened) { + Serial.println("Setting connected..."); + if(this->connType == conn_types_t::wifi) { + if(this->softAPOpened && WiFi.softAPgetStationNum() == 0) { WiFi.softAPdisconnect(true); WiFi.mode(WIFI_STA); } + this->_connecting = false; + this->ssid = WiFi.SSID(); + this->mac = WiFi.BSSIDstr(); + this->strength = WiFi.RSSI(); + this->channel = WiFi.channel(); + this->connectAttempts++; } - else if(this->connType == conn_types::ethernet) { + else if(this->connType == conn_types_t::ethernet) { if(this->softAPOpened) { Serial.println("Disonnecting from SoftAP"); WiFi.softAPdisconnect(true); WiFi.mode(WIFI_OFF); } + this->connectAttempts++; + this->_connecting = false; this->wifiFallback = false; } + // NET: Begin this in the startup. + //sockEmit.begin(); + esp_task_wdt_reset(); + if(this->connectAttempts == 1) { Serial.println(); - if(this->connType == conn_types::wifi) { + if(this->connType == conn_types_t::wifi) { Serial.print("Successfully Connected to WiFi!!!!"); Serial.print(WiFi.localIP()); Serial.print(" ("); @@ -160,9 +284,15 @@ void Network::setConnected(conn_types connType) { settings.IP.dns1 = ETH.dnsIP(0); settings.IP.dns2 = ETH.dnsIP(1); } - char buf[128]; - snprintf(buf, sizeof(buf), "{\"connected\":true,\"speed\":%d,\"fullduplex\":%s}", ETH.linkSpeed(), ETH.fullDuplex() ? "true" : "false"); - sockEmit.sendToClients("ethernet", buf); + esp_task_wdt_reset(); + JsonSockEvent *json = sockEmit.beginEmit("ethernet"); + json->beginObject(); + json->addElem("connected", this->connected()); + json->addElem("speed", ETH.linkSpeed()); + json->addElem("fullduplex", ETH.fullDuplex()); + json->endObject(); + sockEmit.endEmit(); + esp_task_wdt_reset(); } } else { @@ -170,7 +300,7 @@ void Network::setConnected(conn_types connType) { Serial.print("Reconnected after "); Serial.print(1.0 * (millis() - this->connectStart)/1000); Serial.print("sec IP: "); - if(this->connType == conn_types::wifi) { + if(this->connType == conn_types_t::wifi) { Serial.print(WiFi.localIP()); Serial.print(" "); Serial.print(this->mac); @@ -205,7 +335,7 @@ void Network::setConnected(conn_types connType) { if(strlen(settings.chipModel) == 0) SSDP.setModelNumber(0, "ESP32"); else { char sModel[20] = ""; - snprintf(sModel, sizeof(sModel), "ESP32-%S", settings.chipModel); + snprintf(sModel, sizeof(sModel), "ESP32-%s", settings.chipModel); SSDP.setModelNumber(0, sModel); } SSDP.setModelURL(0, "https://github.com/rstrouse/ESPSomfy-RTS"); @@ -213,9 +343,10 @@ void Network::setConnected(conn_types connType) { SSDP.setManufacturerURL(0, "https://github.com/rstrouse"); SSDP.setURL(0, "/"); SSDP.setActive(0, true); + esp_task_wdt_reset(); if(MDNS.begin(settings.hostname)) { Serial.printf("MDNS Responder Started: serverId=%s\n", settings.serverId); - //MDNS.addService("http", "tcp", 80); + MDNS.addService("http", "tcp", 80); //MDNS.addServiceTxt("http", "tcp", "board", "ESP32"); //MDNS.addServiceTxt("http", "tcp", "model", "ESPSomfyRTS"); @@ -225,73 +356,76 @@ void Network::setConnected(conn_types connType) { MDNS.addServiceTxt("espsomfy_rts", "tcp", "version", String(settings.fwVersion.name)); } if(settings.ssdpBroadcast) { + esp_task_wdt_reset(); SSDP.begin(); } else if(SSDP.isStarted) SSDP.end(); + esp_task_wdt_reset(); this->emitSockets(); settings.printAvailHeap(); + this->needsBroadcast = true; } bool Network::connectWired() { - //if(this->connType == conn_types::ethernet && ETH.linkUp()) { if(ETH.linkUp()) { - this->disconnected = 0; - this->wifiFallback = false; + // If the ethernet link is re-established then we need to shut down wifi. + if(WiFi.status() == WL_CONNECTED) { + //sockEmit.end(); + WiFi.disconnect(true); + WiFi.mode(WIFI_OFF); + } + if(this->connType != conn_types_t::ethernet) this->setConnected(conn_types_t::ethernet); return true; } + else if(this->ethStarted) { + // There is no wired connection so we need to fallback if appropriate. + if(settings.connType == conn_types_t::ethernetpref && settings.WIFI.ssid[0] != '\0') + return this->connectWiFi(); + } if(this->connectAttempts > 0) { Serial.printf("Ethernet Connection Lost... %d Reconnecting ", this->connectAttempts); Serial.println(this->mac); } else Serial.println("Connecting to Wired Ethernet"); - this->connectAttempts++; + this->_connecting = true; + this->connTarget = conn_types_t::ethernet; + this->connType = conn_types_t::unset; if(!this->ethStarted) { - this->ethStarted = true; - WiFi.mode(WIFI_OFF); - WiFi.onEvent(this->networkEvent); - if(settings.hostname[0] != '\0') ETH.setHostname(settings.hostname); - Serial.print("Set hostname to:"); - Serial.println(ETH.getHostname()); - if(!ETH.begin(settings.Ethernet.phyAddress, settings.Ethernet.PWRPin, settings.Ethernet.MDCPin, settings.Ethernet.MDIOPin, settings.Ethernet.phyType, settings.Ethernet.CLKMode)) { - Serial.println("Ethernet Begin failed"); - if(settings.connType == conn_types::ethernetpref) { - this->wifiFallback = true; - return connectWiFi(); - } - return false; + // Currently the ethernet module will leak memory if you call begin more than once. + this->ethStarted = true; + WiFi.mode(WIFI_OFF); + if(settings.hostname[0] != '\0') + ETH.setHostname(settings.hostname); + else + ETH.setHostname("ESPSomfy-RTS"); + Serial.print("Set hostname to:"); + Serial.println(ETH.getHostname()); + if(!ETH.begin(settings.Ethernet.phyAddress, settings.Ethernet.PWRPin, settings.Ethernet.MDCPin, settings.Ethernet.MDIOPin, settings.Ethernet.phyType, settings.Ethernet.CLKMode)) { + Serial.println("Ethernet Begin failed"); + this->ethStarted = false; + if(settings.connType == conn_types_t::ethernetpref) { + this->wifiFallback = true; + return connectWiFi(); } - else { - if(!settings.IP.dhcp) { - if(!ETH.config(settings.IP.ip, settings.IP.gateway, settings.IP.subnet, settings.IP.dns1, settings.IP.dns2)) { - Serial.println("Unable to configure static IP address...."); - ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); - } - } - else - ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); - - uint32_t wait = millis(); - while(millis() - wait < 14000) { - if(this->connected()) return true; - delay(500); - } - if(settings.connType == conn_types::ethernetpref) { - this->wifiFallback = true; - return connectWiFi(); + return false; + } + else { + if(!settings.IP.dhcp) { + if(!ETH.config(settings.IP.ip, settings.IP.gateway, settings.IP.subnet, settings.IP.dns1, settings.IP.dns2)) { + Serial.println("Unable to configure static IP address...."); + ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); } } + else + ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); + } } - int retries = 0; - while(retries++ < 100) { - delay(100); - if(this->connected()) return true; - } - if(this->connectAttempts > 10) this->wifiFallback = true; - return false; + this->connectStart = millis(); + return true; } void Network::updateHostname() { if(settings.hostname[0] != '\0' && this->connected()) { - if(this->connType == conn_types::ethernet && + if(this->connType == conn_types_t::ethernet && strcmp(settings.hostname, ETH.getHostname()) != 0) { Serial.printf("Updating host name to %s...\n", settings.hostname); ETH.setHostname(settings.hostname); @@ -306,12 +440,49 @@ void Network::updateHostname() { } } } -bool Network::connectWiFi() { - if(settings.WIFI.ssid[0] != '\0') { - if(WiFi.status() == WL_CONNECTED && WiFi.SSID().compareTo(settings.WIFI.ssid) == 0) { +bool Network::connectWiFi(const uint8_t *bssid, const int32_t channel) { + if(this->softAPOpened && WiFi.softAPgetStationNum() > 0) { + // There is a client connected to the soft AP. We do not want to close out the connection. While both the + // Soft AP and a wifi connection can coexist on ESP32 the performance is abysmal. + WiFi.disconnect(false); + this->_connecting = false; + this->connType = conn_types_t::unset; + return true; + } + WiFi.setSleep(false); + if(!settings.IP.dhcp) { + if(!WiFi.config(settings.IP.ip, settings.IP.gateway, settings.IP.subnet, settings.IP.dns1, settings.IP.dns2)) + WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); + } + else + WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); + if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname); + delay(100); + + if(bssid && channel > 0) { + if(WiFi.status() == WL_CONNECTED && WiFi.SSID().compareTo(settings.WIFI.ssid) == 0 + && WiFi.channel() == channel) { this->disconnected = 0; return true; } + this->connTarget = conn_types_t::wifi; + this->connType = conn_types_t::unset; + Serial.println("WiFi begin..."); + this->_connecting = true; + WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase, channel, bssid); + this->connectStart = millis(); + } + else if(settings.WIFI.ssid[0] != '\0') { + if(WiFi.status() == WL_CONNECTED && WiFi.SSID().compareTo(settings.WIFI.ssid) == 0) { + // If we are connected to the target SSID then just return. + this->disconnected = 0; + this->_connecting = true; + return true; + } + if(this->_connecting) return true; + this->_connecting = true; + this->connTarget = conn_types_t::wifi; + this->connType = conn_types_t::unset; if(this->connectAttempts > 0) { Serial.print("Connection Lost..."); Serial.print(this->mac); @@ -322,94 +493,50 @@ bool Network::connectWiFi() { Serial.println("dbm) "); } else Serial.println("Connecting to AP"); - this->connectAttempts++; - this->connectStart = millis(); - WiFi.setSleep(false); - WiFi.mode(WIFI_MODE_NULL); - WiFi.onEvent(this->networkEvent); - - if(!settings.IP.dhcp) { - if(!WiFi.config(settings.IP.ip, settings.IP.gateway, settings.IP.subnet, settings.IP.dns1, settings.IP.dns2)) - WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); - } - else - WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); delay(100); // There is also another method simply called hostname() but this is legacy for esp8266. if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname); Serial.print("Set hostname to:"); Serial.println(WiFi.getHostname()); - WiFi.mode(WIFI_STA); - WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase); - delay(100); - int retries = 0; - while(retries < 100) { - switch(WiFi.status()) { - case WL_SCAN_COMPLETED: - Serial.println("Status: Scan Completed"); - break; - case WL_CONNECT_FAILED: - if(this->connectAttempts == 1) Serial.println(); - Serial.println("WiFi Module connection failed"); - return false; - case WL_DISCONNECTED: - break; - case WL_IDLE_STATUS: - Serial.print("*"); - break; - case WL_CONNECTED: - //WiFi.hostname(settings.hostname); - this->ssid = WiFi.SSID(); - this->mac = WiFi.BSSIDstr(); - this->strength = WiFi.RSSI(); - this->channel = WiFi.channel(); - this->setConnected(conn_types::wifi); - WiFi.setSleep(false); - return true; - case WL_NO_SHIELD: - Serial.println("Connection failed - WiFi module not found"); - return false; - case WL_NO_SSID_AVAIL: - Serial.print(" Connection failed the SSID "); - Serial.print(settings.WIFI.ssid); - Serial.println(" could not be found"); - return false; - default: - break; - } - delay(500); - if(connectAttempts == 1) Serial.print("*"); - retries++; + WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); + WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); + uint8_t _bssid[6]; + int32_t _channel = 0; + if(!settings.WIFI.hidden && this->getStrongestAP(settings.WIFI.ssid, _bssid, &_channel)) { + Serial.printf("Found strongest AP %02X:%02X:%02X:%02X:%02X:%02X CH:%d\n", _bssid[0], _bssid[1], _bssid[2], _bssid[3], _bssid[4], _bssid[5], _channel); + WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase, _channel, _bssid); } - if(this->connectAttempts != 1) { - int st = this->getStrengthBySSID(settings.WIFI.ssid); - Serial.print("("); - Serial.print(st); - Serial.print("dBm) "); - Serial.println("Failed"); - //if(disconnected > 0 && st == -100) settings.WIFI.PrintNetworks(); - disconnected++; + else + // If the user has the hidden flag set just connect to whatever the AP gives us. + WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase); + } + this->connectStart = millis(); + return true; +} +bool Network::connect(conn_types_t ctype) { + esp_task_wdt_reset(); + if(this->connecting()) return true; + if(this->disconnectTime == 0) this->disconnectTime = millis(); + if(ctype == conn_types_t::ethernet && this->connType != conn_types_t::ethernet) { + // Here we need to call the connect to ethernet. + this->connectWired(); + } + else if(ctype == conn_types_t::ap || (!this->connected() && millis() > this->disconnectTime + CONNECT_TIMEOUT)) { + if(!this->softAPOpened && !this->openingSoftAP) { + this->disconnectTime = millis(); + this->openSoftAP(); + } + else if(this->softAPOpened && !this->openingSoftAP && + (ctype == conn_types_t::wifi && this->connType != conn_types_t::wifi && settings.WIFI.hidden)) { + // When thge softAP is open then we need to try to connect to wifi repeatedly if the user connects to a hidden SSID. + this->connectWiFi(); } } - return false; -} -bool Network::connect() { - if(settings.connType == conn_types::unset) return true; - else if(settings.connType == conn_types::ethernet || (settings.connType == conn_types::ethernetpref)) { - bool bConnected = this->connectWired(); - if(!bConnected && settings.connType == conn_types::ethernetpref && settings.WIFI.ssid[0] != '\0') - bConnected = this->connectWiFi(); - return bConnected; + else if((ctype == conn_types_t::wifi && this->connType != conn_types_t::wifi && settings.WIFI.hidden)) { + this->connectWiFi(); } - return this->connectWiFi(); -} -int Network::getStrengthByMac(const char *macAddr) { - int n = WiFi.scanNetworks(true); - for(int i = 0; i < n; i++) { - if (WiFi.BSSIDstr(i).compareTo(macAddr) == 0) - return WiFi.RSSI(i); - } - return -100; + + return true; } uint32_t Network::getChipId() { uint32_t chipId = 0; @@ -419,154 +546,166 @@ uint32_t Network::getChipId() { } return chipId; } -int Network::getStrengthBySSID(const char *ssid) { - int32_t strength = -100; - int n = WiFi.scanNetworks(false, true); - for(int i = 0; i < n; i++) { - if(WiFi.SSID(i).compareTo(ssid) == 0) strength = max(WiFi.RSSI(i), strength); - } - if(strength == -100) { - Serial.print("Could not find network ["); - Serial.print(ssid); - Serial.print("] Scanned "); - Serial.print(n); - Serial.println(" Networks..."); - String network; - for(int i = 0; i < n; i++) { - //WiFi.getNetworkInfo(i, network, encType, RSSI, BSSID, channel, isHidden); - if(network.compareTo(this->ssid) == 0) Serial.print("*"); - else Serial.print(" "); - Serial.print(i); - Serial.print(": "); - Serial.print(WiFi.SSID(i).c_str()); - Serial.print(" ("); - Serial.print(WiFi.RSSI(i)); - Serial.print("dBm) CH:"); - Serial.print(WiFi.channel(i)); - Serial.print(" MAC:"); - Serial.print(WiFi.BSSIDstr(i).c_str()); - Serial.println(); +bool Network::getStrongestAP(const char *ssid, uint8_t *bssid, int32_t *channel) { + // The new AP must be at least 10dbm greater. + int32_t strength = this->connected() ? WiFi.RSSI() + 10 : -127; + int32_t chan = -1; + memset(bssid, 0x00, 6); + esp_task_wdt_delete(NULL); + int16_t n = WiFi.scanComplete(); + //int16_t n = this->connected() ? WiFi.scanComplete() : WiFi.scanNetworks(false, false, false, 300, 0, ssid); + esp_task_wdt_add(NULL); + for(int16_t i = 0; i < n; i++) { + if(WiFi.SSID(i).compareTo(ssid) == 0) { + if(WiFi.RSSI(i) > strength) { + strength = WiFi.RSSI(i); + memcpy(bssid, WiFi.BSSID(i), 6); + *channel = chan = WiFi.channel(i); + } } - } - return strength; + } + WiFi.scanDelete(); + return chan > 0; } bool Network::openSoftAP() { + if(this->softAPOpened || this->openingSoftAP) return true; + if(this->connected()) WiFi.disconnect(false); + this->openingSoftAP = true; Serial.println(); Serial.println("Turning the HotSpot On"); - WiFi.disconnect(true); - WiFi.hostname("ESPSomfy RTS"); - WiFi.mode(WIFI_AP_STA); - delay(100); - WiFi.softAP("ESPSomfy RTS", ""); - Serial.println("Initializing AP for credentials modification"); - Serial.println(); - Serial.print("SoftAP IP: "); - Serial.println(WiFi.softAPIP()); - //pinMode(D0, INPUT_PULLUP); - long startTime = millis(); - int c = 0; - - while (!this->connected()) - { - int clients = WiFi.softAPgetStationNum(); - webServer.loop(); - if(millis() - this->lastEmit > 1500) { - //if(this->connect()) {} - this->lastEmit = millis(); - this->emitSockets(); - if(clients > 0) - Serial.print(clients); - else - Serial.print("."); - c++; - } - sockEmit.loop(); - if(rebootDelay.reboot && millis() > rebootDelay.rebootTime) { - this->end(); - ESP.restart(); - break; - } - - // If no clients have connected in 3 minutes from starting this server reboot this pig. This will - // force a reboot cycle until we have some response. That is unless the SSID has been cleared. - if(clients == 0 && - (strlen(settings.WIFI.ssid) > 0 || settings.connType == conn_types::ethernet || settings.connType == conn_types::ethernetpref) && - millis() - startTime > 3 * 60000) { - Serial.println(); - Serial.println("Stopping AP Mode"); - WiFi.softAPdisconnect(true); - return false; - } - if(c == 100) { - Serial.println(); - c = 0; - } - yield(); - } + esp_task_wdt_reset(); // Make sure we do not reboot here. + WiFi.softAP(strlen(settings.hostname) > 0 ? settings.hostname : "ESPSomfy RTS", ""); + delay(200); return true; } bool Network::connected() { - if(this->connType == conn_types::unset) return false; - else if(this->connType == conn_types::wifi) return WiFi.status() == WL_CONNECTED; - else if(this->connType == conn_types::ethernet) return ETH.linkUp(); - else return this->connType != conn_types::unset; + if(this->connecting()) return false; + else if(this->connType == conn_types_t::unset) return false; + else if(this->connType == conn_types_t::wifi) return WiFi.status() == WL_CONNECTED; + else if(this->connType == conn_types_t::ethernet) return ETH.linkUp(); + else return this->connType != conn_types_t::unset; return false; } +bool Network::connecting() { + if(this->_connecting && millis() > this->connectStart + CONNECT_TIMEOUT) this->_connecting = false; + return this->_connecting; +} +void Network::clearConnecting() { this->_connecting = false; } void Network::networkEvent(WiFiEvent_t event) { switch(event) { - case ARDUINO_EVENT_ETH_START: - Serial.println("Ethernet Started"); - if(settings.hostname[0] != '\0') - ETH.setHostname(settings.hostname); - else - ETH.setHostname("ESPSomfy-RTS"); + case ARDUINO_EVENT_WIFI_READY: Serial.println("(evt) WiFi interface ready"); break; + case ARDUINO_EVENT_WIFI_SCAN_DONE: + Serial.printf("(evt) Completed scan for access points (%d)\n", WiFi.scanComplete()); + //Serial.println("(evt) Completed scan for access points"); + net.lastWifiScan = millis(); break; + case ARDUINO_EVENT_WIFI_STA_START: + Serial.println("WiFi station mode started"); + if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname); + break; + case ARDUINO_EVENT_WIFI_STA_STOP: Serial.println("(evt) WiFi clients stopped"); break; + case ARDUINO_EVENT_WIFI_STA_CONNECTED: Serial.println("(evt) Connected to WiFi STA access point"); break; + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + Serial.printf("(evt) Disconnected from WiFi STA access point. Connecting: %d\n", net.connecting()); + net.connType = conn_types_t::unset; + net.disconnectTime = millis(); + net.clearConnecting(); + break; + case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: Serial.println("(evt) Authentication mode of STA access point has changed"); break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + Serial.print("(evt) Got WiFi STA IP: "); + Serial.println(WiFi.localIP()); + net.connType = conn_types_t::wifi; + net.connectTime = millis(); + net.setConnected(conn_types_t::wifi); + break; + case ARDUINO_EVENT_WIFI_STA_LOST_IP: Serial.println("Lost IP address and IP address is reset to 0"); break; case ARDUINO_EVENT_ETH_GOT_IP: // If the Wifi is connected then drop that connection if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true); Serial.print("Got Ethernet IP "); Serial.println(ETH.localIP()); - net.mac = ETH.macAddress(); - net.setConnected(conn_types::ethernet); + net.connectTime = millis(); + net.connType = conn_types_t::ethernet; + if(settings.IP.dhcp) { + settings.IP.ip = ETH.localIP(); + settings.IP.subnet = ETH.subnetMask(); + settings.IP.gateway = ETH.gatewayIP(); + settings.IP.dns1 = ETH.dnsIP(0); + settings.IP.dns2 = ETH.dnsIP(1); + } + net.setConnected(conn_types_t::ethernet); break; -/* - case ARDUINO_EVENT_ETH_LOST_IP: - Serial.println("Ethernet Lost IP"); - sockEmit.sendToClients("ethernet", "{\"connected\":false, \"speed\":0,\"fullduplex\":false}"); - net.connType = conn_types::unset; - break; -*/ case ARDUINO_EVENT_ETH_CONNECTED: - Serial.print("Ethernet Connected "); - // We don't want to call setConnected if we do not have an IP address yet - if(ETH.localIP() != INADDR_NONE) - net.setConnected(conn_types::ethernet); + Serial.print("(evt) Ethernet Connected "); break; case ARDUINO_EVENT_ETH_DISCONNECTED: - Serial.println("Ethernet Disconnected"); - sockEmit.sendToClients("ethernet", "{\"connected\":false, \"speed\":0,\"fullduplex\":false}"); - net.connType = conn_types::unset; + Serial.println("(evt) Ethernet Disconnected"); + net.connType = conn_types_t::unset; + net.disconnectTime = millis(); + net.clearConnecting(); + break; + case ARDUINO_EVENT_ETH_START: + Serial.println("(evt) Ethernet Started"); + net.ethStarted = true; break; case ARDUINO_EVENT_ETH_STOP: - Serial.println("Ethernet Stopped"); - net.connType = conn_types::unset; + Serial.println("(evt) Ethernet Stopped"); + net.connType = conn_types_t::unset; + net.ethStarted = false; break; - case ARDUINO_EVENT_WIFI_AP_STOP: - Serial.println("WiFi AP Stopped"); - net.softAPOpened = false; - break; case ARDUINO_EVENT_WIFI_AP_START: - Serial.println("WiFi AP Started"); + Serial.print("(evt) WiFi SoftAP Started IP:"); + Serial.println(WiFi.softAPIP()); + net.openingSoftAP = false; net.softAPOpened = true; break; - case ARDUINO_EVENT_WIFI_STA_START: - if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname); - break; - case ARDUINO_EVENT_WIFI_STA_CONNECTED: - break; + case ARDUINO_EVENT_WIFI_AP_STOP: + if(!net.openingSoftAP) Serial.println("(evt) WiFi SoftAP Stopped"); + net.softAPOpened = false; + break; default: if(event > ARDUINO_EVENT_ETH_START) - Serial.printf("Unknown Ethernet Event %d\n", event); + Serial.printf("(evt) Unknown Ethernet Event %d\n", event); break; } } +void Network::emitHeap(uint8_t num) { + bool bEmit = false; + bool bTimeEmit = millis() - _lastHeapEmit > 15000; + bool bRoomEmit = false; + bool bValEmit = false; + if(num != 255 || this->needsBroadcast) bEmit = true; + if(millis() - _lastHeapEmit > 15000) bTimeEmit = true; + uint32_t freeHeap = ESP.getFreeHeap(); + uint32_t maxHeap = ESP.getMaxAllocHeap(); + uint32_t minHeap = ESP.getMinFreeHeap(); + if(abs((int)(freeHeap - _lastHeap)) > 1500) bValEmit = true; + if(abs((int)(maxHeap - _lastMaxHeap)) > 1500) bValEmit = true; + bRoomEmit = sockEmit.activeClients(0) > 0; + if(bValEmit) bTimeEmit = millis() - _lastHeapEmit > 7000; + if(bEmit || bTimeEmit || bRoomEmit || bValEmit) { + JsonSockEvent *json = sockEmit.beginEmit("memStatus"); + json->beginObject(); + json->addElem("max", maxHeap); + json->addElem("free", freeHeap); + json->addElem("min", minHeap); + json->addElem("total", ESP.getHeapSize()); + json->endObject(); + if(num == 255 && bTimeEmit && bValEmit) { + sockEmit.endEmit(num); + _lastHeapEmit = millis(); + _lastHeap = freeHeap; + _lastMaxHeap = maxHeap; + //Serial.printf("BROAD HEAP: Emit:%d TimeEmit:%d ValEmit:%d\n", bEmit, bTimeEmit, bValEmit); + } + else if(num != 255) { + sockEmit.endEmit(num); + //Serial.printf("TARGET HEAP %d: Emit:%d TimeEmit:%d ValEmit:%d\n", num, bEmit, bTimeEmit, bValEmit); + } + else if(bRoomEmit) { + sockEmit.endEmitRoom(0); + //Serial.printf("ROOM HEAP: Emit:%d TimeEmit:%d ValEmit:%d\n", bEmit, bTimeEmit, bValEmit); + } + } +} diff --git a/Network.h b/Network.h index 0a4b409..864966c 100644 --- a/Network.h +++ b/Network.h @@ -2,6 +2,11 @@ #ifndef Network_h #define Network_h + +//enum class conn_types_t : byte; + +#define CONNECT_TIMEOUT 20000 +#define SSID_SCAN_INTERVAL 60000 class Network { protected: unsigned long lastEmit = 0; @@ -9,34 +14,45 @@ class Network { int lastRSSI = 0; int lastChannel = 0; int linkSpeed = 0; - bool ethStarted = false; + bool _connecting = false; public: + unsigned long lastWifiScan = 0; + bool ethStarted = false; bool wifiFallback = false; bool softAPOpened = false; + bool openingSoftAP = false; bool needsBroadcast = true; - conn_types connType = conn_types::unset; + conn_types_t connType = conn_types_t::unset; + conn_types_t connTarget = conn_types_t::unset; bool connected(); + bool connecting(); + void clearConnecting(); + conn_types_t preferredConnType(); String ssid; String mac; int channel; int strength; int disconnected = 0; int connectAttempts = 0; - long connectStart = 0; - long connectTime = 0; + uint32_t disconnectTime = 0; + uint32_t connectStart = 0; + uint32_t connectTime = 0; bool openSoftAP(); - bool connect(); - bool connectWiFi(); + bool connect(conn_types_t ctype); + bool connectWiFi(const uint8_t *bssid = nullptr, const int32_t channel = -1); bool connectWired(); - void setConnected(conn_types connType); - int getStrengthByMac(const char *mac); - int getStrengthBySSID(const char *ssid); + void setConnected(conn_types_t connType); + bool getStrongestAP(const char *ssid, uint8_t *bssid, int32_t *channel); + bool changeAP(const uint8_t *bssid, const int32_t channel); + //int getStrengthByMac(const char *mac); + //int getStrengthBySSID(const char *ssid); void updateHostname(); bool setup(); void loop(); void end(); void emitSockets(); void emitSockets(uint8_t num); + void emitHeap(uint8_t num = 255); uint32_t getChipId(); static void networkEvent(WiFiEvent_t event); }; diff --git a/SSDP.cpp b/SSDP.cpp index 7e45612..f866865 100644 --- a/SSDP.cpp +++ b/SSDP.cpp @@ -32,7 +32,7 @@ static const char _ssdp_bye_template[] PROGMEM = "NTS: ssdp:byebye\r\n" "NT: %s\r\n" "USN: %s\r\n" - "BOOTID.UPNP.ORG: %ul\r\n" + "BOOTID.UPNP.ORG: %lu\r\n" "CONFIGID.UPNP.ORG: %d\r\n" "\r\n"; static const char _ssdp_packet_template[] PROGMEM = @@ -42,7 +42,7 @@ static const char _ssdp_packet_template[] PROGMEM = "USN: %s\r\n" // _uuid "%s: %s\r\n" // "NT" or "ST", _deviceType "LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL - "BOOTID.UPNP.ORG: %ul\r\n" + "BOOTID.UPNP.ORG: %lu\r\n" "CONFIGID.UPNP.ORG: %d\r\n" "\r\n"; static const char _ssdp_device_schema_template[] PROGMEM = @@ -160,8 +160,8 @@ void UPNPDeviceType::setChipId(uint32_t chipId) { (uint16_t)((chipId >> 8) & 0xff), (uint16_t)chipId & 0xff); } -SSDPClass::SSDPClass():sendQueue{false, INADDR_NONE, 0, nullptr, false, 0, ""} {} -SSDPClass::~SSDPClass() { end(); } +SSDPClass::SSDPClass():sendQueue{false, INADDR_NONE, 0, nullptr, false, 0, "", response_types_t::root} {} +SSDPClass::~SSDPClass() { end(); this->isStarted = false; } bool SSDPClass::begin() { for(int i = 0; i < SSDP_QUEUE_SIZE; i++) { this->sendQueue[i].waiting = false; @@ -209,6 +209,7 @@ void SSDPClass::end() { if(this->_server.connected()) { this->_sendByeBye(); this->_server.close(); + Serial.println("Disconnected from SSDP..."); } this->isStarted = false; // Clear out the last notified so if the user starts us up again it will notify @@ -216,8 +217,6 @@ void SSDPClass::end() { for(uint8_t i = 0; i < this->m_cdeviceTypes; i++) { this->deviceTypes[i].lastNotified = 0; } - - Serial.println("Disconnected from SSDP..."); } UPNPDeviceType* SSDPClass::getDeviceType(uint8_t ndx) { if(ndx < this->m_cdeviceTypes) return &this->deviceTypes[ndx]; return nullptr; } UPNPDeviceType* SSDPClass::findDeviceByType(char *devType) { @@ -407,7 +406,7 @@ void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, UPNPDeviceType *d, strcpy_P(pbuff, _ssdp_response_template); // Don't use ip.toString as this fragments the heap like no tomorrow. - int len = snprintf_P(buffer, sizeof(buffer)-1, + snprintf_P(buffer, sizeof(buffer)-1, _ssdp_packet_template, pbuff, this->_interval, @@ -418,28 +417,6 @@ void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, UPNPDeviceType *d, buffer[sizeof(buffer) - 1] = '\0'; this->_sendResponse(addr, port, buffer); free(pbuff); -/* -static const char _ssdp_packet_template[] PROGMEM = - "%s" // _ssdp_response_template / _ssdp_notify_template - "CACHE-CONTROL: max-age=%u\r\n" // _interval - "SERVER: Arduino/1.0 UPNP/1.1 %s/%s\r\n" // _modelName, _modelNumber - "USN: %s\r\n" // _uuid - "%s: %s\r\n" // "NT" or "ST", _deviceType - "LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL - "\r\n"; - - - - #ifdef DEBUG_SSDP - DEBUG_SSDP.print("Sending Response to "); - DEBUG_SSDP.print(IPAddress(addr)); - DEBUG_SSDP.print(":"); - DEBUG_SSDP.println(port); - DEBUG_SSDP.println(buffer); - #endif - - _server.writeTo((const uint8_t *)buffer, len, addr, port); - */ } void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, const char *buff) { #ifdef DEBUG_SSDP @@ -545,6 +522,7 @@ void SSDPClass::_sendNotify(UPNPDeviceType *d, bool root) { ip[0], ip[1], ip[2], ip[3], _port, d->schemaURL, this->bootId, this->configId); this->_sendNotify(buffer); d->lastNotified = millis(); + free(pbuff); } void SSDPClass::setActive(uint8_t ndx, bool isActive) { UPNPDeviceType *d = &this->deviceTypes[ndx]; diff --git a/Sockets.cpp b/Sockets.cpp index 5429acd..51765aa 100644 --- a/Sockets.cpp +++ b/Sockets.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "Sockets.h" #include "ConfigSettings.h" #include "Somfy.h" @@ -16,6 +17,9 @@ extern GitUpdater git; WebSocketsServer sockServer = WebSocketsServer(8080); +#define MAX_SOCK_RESPONSE 2048 +static char g_response[MAX_SOCK_RESPONSE]; + bool room_t::isJoined(uint8_t num) { for(uint8_t i = 0; i < sizeof(this->clients); i++) { if(this->clients[i] == num) return true; @@ -39,6 +43,9 @@ bool room_t::leave(uint8_t num) { } return true; } +void room_t::clear() { + memset(this->clients, 255, sizeof(this->clients)); +} uint8_t room_t::activeClients() { uint8_t n = 0; for(uint8_t i = 0; i < sizeof(this->clients); i++) { @@ -49,7 +56,9 @@ uint8_t room_t::activeClients() { /********************************************************************* * ClientSocketEvent class members ********************************************************************/ +/* void ClientSocketEvent::prepareMessage(const char *evt, const char *payload) { + if(strlen(payload) + 5 >= sizeof(this->msg)) Serial.printf("Socket buffer overflow %d > 2048\n", strlen(payload) + 5 + strlen(evt)); snprintf(this->msg, sizeof(this->msg), "42[%s,%s]", evt, payload); } void ClientSocketEvent::prepareMessage(const char *evt, JsonDocument &doc) { @@ -58,6 +67,7 @@ void ClientSocketEvent::prepareMessage(const char *evt, JsonDocument &doc) { serializeJson(doc, &this->msg[strlen(this->msg)], sizeof(this->msg) - strlen(this->msg) - 2); strcat(this->msg, "]"); } +*/ /********************************************************************* * SocketEmitter class members @@ -70,102 +80,60 @@ void SocketEmitter::begin() { sockServer.enableHeartbeat(20000, 10000, 3); sockServer.onEvent(this->wsEvent); Serial.println("Socket Server Started..."); - settings.printAvailHeap(); + //settings.printAvailHeap(); } void SocketEmitter::loop() { + this->initClients(); sockServer.loop(); } -/* -bool SocketEmitter::sendToClients(const char *evt, JsonObject &obj) { - serializeJson(obj, g_buffer, sizeof(g_buffer)); - return this->sendToClients(evt, g_buffer); +JsonSockEvent *SocketEmitter::beginEmit(const char *evt) { + this->json.beginEvent(&sockServer, evt, g_response, sizeof(g_response)); + return &this->json; } -bool SocketEmitter::sendToClient(uint8_t num, const char *evt, JsonObject &obj) { - serializeJson(obj, g_buffer, sizeof(g_buffer)); - return this->sendToClient(num, evt, g_buffer); -} -*/ -ClientSocketEvent::ClientSocketEvent() {} -ClientSocketEvent::ClientSocketEvent(const char *evt) { snprintf(this->msg, sizeof(this->msg), "42[%s,]", evt); } -ClientSocketEvent::ClientSocketEvent(const char *evt, const char *payload) { snprintf(this->msg, sizeof(this->msg), "42[%s,%s]", evt, payload); } -void ClientSocketEvent::appendMessage(const char *text) { - uint16_t len = strlen(this->msg); - this->msg[len - 1] = '\0'; - strcat(this->msg, text); - strcat(this->msg, "]"); -} -/* -void ClientSocketEvent::appendJSONElem(const char *elem) { - this->msg[strlen(this->msg) - 1] = '\0'; // Trim off the ending bracket. - uint16_t len = strlen(this->msg); - if(len > 0) { - if(this->msg[strlen(this->msg) - 1] == '{') strcat(this->msg, ','); +void SocketEmitter::endEmit(uint8_t num) { this->json.endEvent(num); sockServer.loop(); } +void SocketEmitter::endEmitRoom(uint8_t room) { + if(room < SOCK_MAX_ROOMS) { + room_t *r = &this->rooms[room]; + for(uint8_t i = 0; i < sizeof(r->clients); i++) { + if(r->clients[i] != 255) this->json.endEvent(r->clients[i]); + } } - strcat(this->msg, "\""); - strcat(this->msg, elem); - strcat(this->msg, "\":"); - strcat(this->msg, "]"); } -void ClientSocketEvent::appendJSON(const char *elem, const char *text, bool quoted) { - this->appendJSONElem(elem); - this->msg[strlen(this->msg) - 1] = '\0'; // Trim off the ending bracket. - if(quoted) strcat(this->msg, "\""); - strcat(this->msg, text); - if(quoted) strcat(this->msg, "\""); - strcat(this->msg, "]"); -} -void ClientSocketEvent::appendJSON(const char *elem, const bool b) { this->appendJSON(elem, b ? "true" : "false", false); } -void ClientSocketEvent::appendJSON(const char *elem, const uint8_t val) { - char buff[5]; - sprintf(buff, "%d", val); - this->appendJSON(elem, buff, false); -} -*/ - uint8_t SocketEmitter::activeClients(uint8_t room) { if(room < SOCK_MAX_ROOMS) return this->rooms[room].activeClients(); return 0; } -bool SocketEmitter::sendToRoom(uint8_t room, ClientSocketEvent *evt) { - if(room < SOCK_MAX_ROOMS) { - room_t *r = &this->rooms[room]; - for(uint8_t i = 0; i < sizeof(r->clients); i++) { - if(r->clients[i] != 255) this->sendToClient(r->clients[i], evt); +void SocketEmitter::initClients() { + for(uint8_t i = 0; i < sizeof(this->newClients); i++) { + uint8_t num = this->newClients[i]; + if(num != 255) { + if(sockServer.clientIsConnected(num)) { + Serial.printf("Initializing Socket Client %u\n", num); + esp_task_wdt_reset(); + settings.emitSockets(num); + somfy.emitState(num); + git.emitUpdateCheck(num); + net.emitSockets(num); + esp_task_wdt_reset(); + } + this->newClients[i] = 255; } - return true; } - return false; } -bool SocketEmitter::sendToClients(ClientSocketEvent *evt) { - if(evt->msg[strlen(evt->msg) - 1] != ']') strcat(evt->msg, "]"); - return sockServer.broadcastTXT(evt->msg); +void SocketEmitter::delayInit(uint8_t num) { + for(uint8_t i=0; i < sizeof(this->newClients); i++) { + if(this->newClients[i] == num) break; + else if(this->newClients[i] == 255) { + this->newClients[i] = num; + break; + } + } } -bool SocketEmitter::sendToClient(uint8_t num, ClientSocketEvent *evt) { - if(evt->msg[strlen(evt->msg) - 1] != ']') strcat(evt->msg, "]"); - return sockServer.sendTXT(num, evt->msg); +void SocketEmitter::end() { + sockServer.close(); + for(uint8_t i = 0; i < SOCK_MAX_ROOMS; i++) + this->rooms[i].clear(); } -bool SocketEmitter::sendToClients(const char *evt, const char *payload) { - if(settings.status == DS_FWUPDATE) return true; - this->evt.prepareMessage(evt, payload); - return sockServer.broadcastTXT(this->evt.msg); -} -bool SocketEmitter::sendToClient(uint8_t num, const char *evt, const char *payload) { - if(settings.status == DS_FWUPDATE) return true; - this->evt.prepareMessage(evt, payload); - return sockServer.sendTXT(num, this->evt.msg); -} -bool SocketEmitter::sendToClient(uint8_t num, const char *evt, JsonDocument &doc) { - if(settings.status == DS_FWUPDATE) return true; - this->evt.prepareMessage(evt, doc); - return sockServer.sendTXT(num, this->evt.msg); -} -bool SocketEmitter::sendToClients(const char *evt, JsonDocument &doc) { - if(settings.status == DS_FWUPDATE) return true; - this->evt.prepareMessage(evt, doc); - return sockServer.broadcastTXT(this->evt.msg); -} - -void SocketEmitter::end() { sockServer.close(); } void SocketEmitter::disconnect() { sockServer.disconnect(); } void SocketEmitter::wsEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) { switch(type) { @@ -190,12 +158,8 @@ void SocketEmitter::wsEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t Serial.printf("Socket [%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); // Send all the current shade settings to the client. sockServer.sendTXT(num, "Connected"); - sockServer.loop(); - settings.emitSockets(num); - somfy.emitState(num); - git.emitUpdateCheck(num); - net.emitSockets(num); - sockServer.loop(); + //sockServer.loop(); + sockEmit.delayInit(num); } break; case WStype_TEXT: diff --git a/Sockets.h b/Sockets.h index 4d1fc06..d76ab93 100644 --- a/Sockets.h +++ b/Sockets.h @@ -1,4 +1,5 @@ #include +#include "WResp.h" #ifndef sockets_h #define sockets_h @@ -6,45 +7,32 @@ #define ROOM_EMIT_FRAME 0 struct room_t { - uint8_t clients[5] = {255, 255, 255, 255}; + uint8_t clients[5] = {255, 255, 255, 255, 255}; uint8_t activeClients(); bool isJoined(uint8_t num); bool join(uint8_t num); bool leave(uint8_t num); -}; - - -class ClientSocketEvent { - public: - ClientSocketEvent(); - ClientSocketEvent(const char *evt); - ClientSocketEvent(const char *evt, const char *data); - char msg[2048]; - void prepareMessage(const char *evt, const char *data); - void prepareMessage(const char *evt, JsonDocument &doc); - void appendMessage(const char *text); - void appendElement(const char *elem, const char *val); - - + void clear(); }; class SocketEmitter { - ClientSocketEvent evt; - + protected: + uint8_t newclients = 0; + uint8_t newClients[5] = {255,255,255,255,255}; + void delayInit(uint8_t num); public: + JsonSockEvent json; + //ClientSocketEvent evt; room_t rooms[SOCK_MAX_ROOMS]; uint8_t activeClients(uint8_t room); + void initClients(); void startup(); void begin(); void loop(); void end(); void disconnect(); - bool sendToRoom(uint8_t room, ClientSocketEvent *evt); - bool sendToClients(ClientSocketEvent *evt); - bool sendToClient(uint8_t num, ClientSocketEvent *evt); - bool sendToClients(const char *evt, const char *data); - bool sendToClient(uint8_t num, const char *evt, const char *data); - bool sendToClients(const char *evt, JsonDocument &doc); - bool sendToClient(uint8_t num, const char *evt, JsonDocument &doc); + JsonSockEvent * beginEmit(const char *evt); + void endEmit(uint8_t num = 255); + void endEmitRoom(uint8_t num); static void wsEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length); }; #endif diff --git a/Somfy.cpp b/Somfy.cpp index 1bce68a..bffebd2 100644 --- a/Somfy.cpp +++ b/Somfy.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include "Utils.h" #include "ConfigSettings.h" #include "Somfy.h" @@ -56,6 +58,9 @@ somfy_commands translateSomfyCommand(const String& string) { else if (string.equalsIgnoreCase("Flag")) return somfy_commands::Flag; else if (string.equalsIgnoreCase("Sensor")) return somfy_commands::Sensor; else if (string.equalsIgnoreCase("Toggle")) return somfy_commands::Toggle; + else if (string.equalsIgnoreCase("Favorite")) return somfy_commands::Favorite; + else if (string.equalsIgnoreCase("Stop")) return somfy_commands::Stop; + else if (string.startsWith("fav") || string.startsWith("FAV")) return somfy_commands::Favorite; else if (string.startsWith("mud") || string.startsWith("MUD")) return somfy_commands::MyUpDown; else if (string.startsWith("md") || string.startsWith("MD")) return somfy_commands::MyDown; else if (string.startsWith("ud") || string.startsWith("UD")) return somfy_commands::UpDown; @@ -103,10 +108,23 @@ String translateSomfyCommand(const somfy_commands cmd) { return "Sensor"; case somfy_commands::Toggle: return "Toggle"; + case somfy_commands::Favorite: + return "Favorite"; + case somfy_commands::Stop: + return "Stop"; default: return "Unknown(" + String((uint8_t)cmd) + ")"; } } +byte somfy_frame_t::calc80Checksum(byte b0, byte b1, byte b2) { + byte cs80 = 0; + cs80 = (((b0 & 0xF0) >> 4) ^ ((b1 & 0xF0) >> 4)); + cs80 ^= ((b2 & 0xF0) >> 4); + cs80 ^= (b0 & 0x0F); + cs80 ^= (b1 & 0x0F); + return cs80; +} + void somfy_frame_t::decodeFrame(byte* frame) { byte decoded[10]; decoded[0] = frame[0]; @@ -147,13 +165,23 @@ void somfy_frame_t::decodeFrame(byte* frame) { // We reuse this memory address so we must reset the processed // flag. This will ensure we can see frames on the first beat. this->processed = false; - // Pull in the data for an 80-bit step command. - if(this->cmd == somfy_commands::StepDown) this->cmd = (somfy_commands)((decoded[1] >> 4) | ((decoded[8] & 0x08) << 4)); this->rollingCode = decoded[3] + (decoded[2] << 8); this->remoteAddress = (decoded[6] + (decoded[5] << 8) + (decoded[4] << 16)); this->valid = this->checksum == checksum && this->remoteAddress > 0 && this->remoteAddress < 16777215; if (this->cmd != somfy_commands::Sensor && this->valid) this->valid = (this->rollingCode > 0); + // Next lets process some of the RTS extensions for 80-bit frames + if(this->valid && this->proto == radio_proto::RTS && this->bitLength == 80) { + // Do a parity checksum on the 80 bit data. + if((decoded[9] & 0x0F) != this->calc80Checksum(decoded[7], decoded[8], decoded[9])) this->valid = false; + if(this->valid) { + // Translate extensions for stop and favorite. + if(this->cmd == somfy_commands::My) this->cmd = (somfy_commands)((decoded[1] >> 4) | ((decoded[8] & 0x0F) << 4)); + // Bit packing to get the step size prohibits translation on the byte. + else if(this->cmd == somfy_commands::StepDown) this->cmd = (somfy_commands)((decoded[1] >> 4) | ((decoded[8] & 0x08) << 4)); + } + } if (this->valid) { + // Check for valid command. switch (this->cmd) { //case somfy_commands::Unknown0: @@ -175,8 +203,12 @@ void somfy_frame_t::decodeFrame(byte* frame) { break; case somfy_commands::StepUp: case somfy_commands::StepDown: + // Decode the step size. + this->stepSize = ((decoded[8] & 0x07) << 4) | ((decoded[9] & 0xF0) >> 4); + break; case somfy_commands::Toggle: - // These must be 80 bit commands + case somfy_commands::Favorite: + case somfy_commands::Stop: break; default: this->valid = false; @@ -184,23 +216,7 @@ void somfy_frame_t::decodeFrame(byte* frame) { } } if(this->valid && this->encKey == 0) this->valid = false; - if (this->valid) { - /* - Serial.print("KEY:"); - Serial.print(this->encKey); - Serial.print(" ADDR:"); - Serial.print(this->remoteAddress); - Serial.print(" CMD:"); - Serial.print(translateSomfyCommand(this->cmd)); - Serial.print(" RCODE:"); - Serial.print(this->rollingCode); - Serial.print(" BITS:"); - Serial.print(this->bitLength); - Serial.print(" HWSYNC:"); - Serial.println(this->hwsync); - */ - } - else { + if (!this->valid) { Serial.print("INVALID FRAME "); Serial.print("KEY:"); Serial.print(this->encKey); @@ -241,8 +257,82 @@ void somfy_frame_t::decodeFrame(somfy_rx_t *rx) { this->rssi = ELECHOUSE_cc1101.getRssi(); this->decodeFrame(rx->payload); } +byte somfy_frame_t::encode80Byte7(byte start, uint8_t repeat) { + while((repeat * 4) + start > 255) repeat -= 15; + return start + (repeat * 4); +} +void somfy_frame_t::encode80BitFrame(byte *frame, uint8_t repeat) { + switch(this->cmd) { + // Step up and down commands encode the step size into the last 3 bytes. + case somfy_commands::StepUp: + if(repeat == 0) frame[1] = (static_cast(somfy_commands::StepDown) << 4) | (frame[1] & 0x0F); + if(this->stepSize == 0) this->stepSize = 1; + frame[7] = 132; // For simplicity this appears to be constant. + frame[8] = ((this->stepSize & 0x70) >> 4) | 0x38; + frame[9] = ((this->stepSize & 0x0F) << 4); + frame[9] |= this->calc80Checksum(frame[7], frame[8], frame[9]); + break; + case somfy_commands::StepDown: + if(repeat == 0) frame[1] = (static_cast(somfy_commands::StepDown) << 4) | (frame[1] & 0x0F); + if(this->stepSize == 0) this->stepSize = 1; + frame[7] = 132; // For simplicity this appears to be constant. + frame[8] = ((this->stepSize & 0x70) >> 4) | 0x30; + frame[9] = ((this->stepSize & 0x0F) << 4); + frame[9] |= this->calc80Checksum(frame[7], frame[8], frame[9]); + break; + case somfy_commands::Favorite: + if(repeat == 0) frame[1] = (static_cast(somfy_commands::My) << 4) | (frame[1] & 0x0F); + frame[7] = repeat > 0 ? 132 : 196; + frame[8] = 44; + frame[9] = 0x90; + frame[9] |= this->calc80Checksum(frame[7], frame[8], frame[9]); + break; + case somfy_commands::Stop: + if(repeat == 0) frame[1] = (static_cast(somfy_commands::My) << 4) | (frame[1] & 0x0F); + frame[7] = repeat > 0 ? 132 : 196; + frame[8] = 47; + frame[9] = 0xF0; + frame[9] |= this->calc80Checksum(frame[7], frame[8], frame[9]); + break; + case somfy_commands::Toggle: + frame[0] = 164; + frame[1] |= 0xF0; + frame[7] = this->encode80Byte7(196, repeat); + frame[8] = 0; + frame[9] = 0x10; + frame[9] |= this->calc80Checksum(frame[7], frame[8], frame[9]); + break; + case somfy_commands::Up: + frame[7] = this->encode80Byte7(196, repeat); + frame[8] = 32; + frame[9] = 0x00; + frame[9] |= this->calc80Checksum(frame[7], frame[8], frame[9]); + break; + case somfy_commands::Down: + frame[7] = this->encode80Byte7(196, repeat); + frame[8] = 44; + frame[9] = 0x80; + frame[9] |= this->calc80Checksum(frame[7], frame[8], frame[9]); + break; + case somfy_commands::Prog: + case somfy_commands::UpDown: + case somfy_commands::MyDown: + case somfy_commands::MyUp: + case somfy_commands::MyUpDown: + case somfy_commands::My: + frame[7] = this->encode80Byte7(196, repeat); + frame[8] = 0x00; + frame[9] = 0x10; + frame[9] |= this->calc80Checksum(frame[7], frame[8], frame[9]); + break; + + default: + break; + } +} void somfy_frame_t::encodeFrame(byte *frame) { const byte btn = static_cast(cmd); + this->valid = true; frame[0] = this->encKey; // Encryption key. Doesn't matter much frame[1] = (btn & 0x0F) << 4; // Which button did you press? The 4 LSB will be the checksum frame[2] = this->rollingCode >> 8; // Rolling code (big endian) @@ -330,34 +420,7 @@ void somfy_frame_t::encodeFrame(byte *frame) { } else { - switch(this->cmd) { - case somfy_commands::StepUp: - frame[7] = 132; - frame[8] = 56; - frame[9] = 22; - break; - case somfy_commands::StepDown: - frame[7] = 132; - frame[8] = 48; - frame[9] = 30; - break; - case somfy_commands::Toggle: - frame[0] = 164; - // This also needs to be a command val of 15. - frame[1] |= 0xF0; - frame[7] = 196; - frame[8] = 0; - frame[9] = 25; - break; - case somfy_commands::Prog: - //frame[0] = 0xA6; - frame[7] = 196; - frame[8] = 0; - frame[9] = 25; - break; - default: - break; - } + if(this->bitLength == 80) this->encode80BitFrame(&frame[0], this->repeats); } byte checksum = 0; @@ -389,6 +452,7 @@ void somfy_frame_t::print() { Serial.print(" CS:"); Serial.println(this->checksum); } +bool somfy_frame_t::isSynonym(somfy_frame_t &frame) { return this->remoteAddress == frame.remoteAddress && this->cmd != frame.cmd && this->rollingCode == frame.rollingCode; } bool somfy_frame_t::isRepeat(somfy_frame_t &frame) { return this->remoteAddress == frame.remoteAddress && this->cmd == frame.cmd && this->rollingCode == frame.rollingCode; } void somfy_frame_t::copy(somfy_frame_t &frame) { if(this->isRepeat(frame)) { @@ -397,8 +461,9 @@ void somfy_frame_t::copy(somfy_frame_t &frame) { this->lqi = frame.lqi; } else { + this->synonym = this->isSynonym(frame); this->valid = frame.valid; - this->processed = frame.processed; + if(!this->synonym) this->processed = frame.processed; this->rssi = frame.rssi; this->lqi = frame.lqi; this->cmd = frame.cmd; @@ -557,6 +622,7 @@ bool SomfyShadeController::begin() { } void SomfyShadeController::commit() { if(git.lockFS) return; + esp_task_wdt_reset(); // Make sure we don't reset inadvertently. ShadeConfigFile file; file.begin(); file.save(this); @@ -566,6 +632,7 @@ void SomfyShadeController::commit() { } void SomfyShadeController::writeBackup() { if(git.lockFS) return; + esp_task_wdt_reset(); // Make sure we don't reset inadvertently. ShadeConfigFile file; file.begin("/controller.backup", false); file.backup(this); @@ -643,8 +710,10 @@ void SomfyRoom::clear() { void SomfyGroup::clear() { this->setGroupId(255); this->setRemoteAddress(0); - this->repeats = 1; - memset(&this->linkedShades[0], 0x00, sizeof(this->linkedShades)); + this->repeats = 0; + this->roomId = 0; + this->name[0] = 0x00; + memset(&this->linkedShades, 0x00, sizeof(this->linkedShades)); } bool SomfyShade::linkRemote(uint32_t address, uint16_t rollingCode) { // Check to see if the remote is already linked. If it is @@ -829,10 +898,13 @@ bool SomfyShade::isAtTarget() { else if(this->tiltType == tilt_types::none) return fabs(this->currentPos - this->target) < epsilon; return fabs(this->currentPos - this->target) < epsilon && fabs(this->currentTiltPos - this->tiltTarget) < epsilon; } +bool SomfyRemote::simMy() { return (this->flags & static_cast(somfy_flags_t::SimMy)) > 0; } +void SomfyRemote::setSimMy(bool bSimMy) { bSimMy ? this->flags |= static_cast(somfy_flags_t::SimMy) : this->flags &= ~(static_cast(somfy_flags_t::SimMy)); } bool SomfyRemote::hasSunSensor() { return (this->flags & static_cast(somfy_flags_t::SunSensor)) > 0;} bool SomfyRemote::hasLight() { return (this->flags & static_cast(somfy_flags_t::Light)) > 0; } void SomfyRemote::setSunSensor(bool bHasSensor ) { bHasSensor ? this->flags |= static_cast(somfy_flags_t::SunSensor) : this->flags &= ~(static_cast(somfy_flags_t::SunSensor)); } void SomfyRemote::setLight(bool bHasLight ) { bHasLight ? this->flags |= static_cast(somfy_flags_t::Light) : this->flags &= ~(static_cast(somfy_flags_t::Light)); } + void SomfyGroup::updateFlags() { uint8_t oldFlags = this->flags; this->flags = 0; @@ -920,7 +992,7 @@ void SomfyShade::triggerGPIOs(somfy_frame_t &frame) { int8_t dir = 0; switch(frame.cmd) { case somfy_commands::My: - if(this->shadeType != shade_types::drycontact && this->shadeType != shade_types::garage1) { + if(this->shadeType != shade_types::drycontact && !this->isToggle()) { digitalWrite(this->gpioUp, p_off); digitalWrite(this->gpioDown, p_off); digitalWrite(this->gpioMy, p_on); @@ -929,7 +1001,7 @@ void SomfyShade::triggerGPIOs(somfy_frame_t &frame) { } break; case somfy_commands::Up: - if(this->shadeType != shade_types::drycontact && this->shadeType != shade_types::garage1 && this->shadeType != shade_types::drycontact2) { + if(this->shadeType != shade_types::drycontact && !this->isToggle() && this->shadeType != shade_types::drycontact2) { digitalWrite(this->gpioMy, p_off); digitalWrite(this->gpioDown, p_off); digitalWrite(this->gpioUp, p_on); @@ -939,7 +1011,7 @@ void SomfyShade::triggerGPIOs(somfy_frame_t &frame) { break; case somfy_commands::Toggle: case somfy_commands::Down: - if(this->shadeType != shade_types::drycontact && this->shadeType != shade_types::garage1 && this->shadeType != shade_types::drycontact2) { + if(this->shadeType != shade_types::drycontact && !this->isToggle() && this->shadeType != shade_types::drycontact2) { digitalWrite(this->gpioMy, p_off); digitalWrite(this->gpioUp, p_off); } @@ -948,7 +1020,7 @@ void SomfyShade::triggerGPIOs(somfy_frame_t &frame) { Serial.printf("UP: false, DOWN: true, MY: false\n"); break; case somfy_commands::MyUp: - if(this->shadeType != shade_types::drycontact && this->shadeType != shade_types::garage1 && this->shadeType != shade_types::drycontact2) { + if(this->shadeType != shade_types::drycontact && !this->isToggle() && this->shadeType != shade_types::drycontact2) { digitalWrite(this->gpioDown, p_off); digitalWrite(this->gpioMy, p_on); digitalWrite(this->gpioUp, p_on); @@ -956,7 +1028,7 @@ void SomfyShade::triggerGPIOs(somfy_frame_t &frame) { } break; case somfy_commands::MyDown: - if(this->shadeType != shade_types::drycontact && this->shadeType != shade_types::garage1 && this->shadeType != shade_types::drycontact2) { + if(this->shadeType != shade_types::drycontact && !this->isToggle() && this->shadeType != shade_types::drycontact2) { digitalWrite(this->gpioUp, p_off); digitalWrite(this->gpioMy, p_on); digitalWrite(this->gpioDown, p_on); @@ -964,7 +1036,7 @@ void SomfyShade::triggerGPIOs(somfy_frame_t &frame) { } break; case somfy_commands::MyUpDown: - if(this->shadeType != shade_types::drycontact && this->shadeType != shade_types::garage1 && this->shadeType != shade_types::drycontact2) { + if(this->shadeType != shade_types::drycontact && this->isToggle() && this->shadeType != shade_types::drycontact2) { digitalWrite(this->gpioUp, p_on); digitalWrite(this->gpioMy, p_on); digitalWrite(this->gpioDown, p_on); @@ -1432,6 +1504,9 @@ void SomfyShade::publishDisco() { case shade_types::lgate: case shade_types::cgate: case shade_types::rgate: + case shade_types::lgate1: + case shade_types::cgate1: + case shade_types::rgate1: case shade_types::ldrapery: case shade_types::rdrapery: case shade_types::cdrapery: @@ -1820,6 +1895,32 @@ float SomfyShade::p_myTiltPos(float pos) { void SomfyShade::emitState(const char *evt) { this->emitState(255, evt); } void SomfyShade::emitState(uint8_t num, const char *evt) { + JsonSockEvent *json = sockEmit.beginEmit(evt); + json->beginObject(); + json->addElem("shadeId", this->shadeId); + json->addElem("type", static_cast(this->shadeType)); + json->addElem("remoteAddress", (uint32_t)this->getRemoteAddress()); + json->addElem("name", this->name); + json->addElem("direction", this->direction); + json->addElem("position", this->transformPosition(this->currentPos)); + json->addElem("target", this->transformPosition(this->target)); + json->addElem("myPos", this->transformPosition(this->myPos)); + json->addElem("tiltType", static_cast(this->tiltType)); + json->addElem("flipCommands", this->flipCommands); + json->addElem("flipPosition", this->flipPosition); + json->addElem("flags", this->flags); + json->addElem("sunSensor", this->hasSunSensor()); + json->addElem("light", this->hasLight()); + json->addElem("sortOrder", this->sortOrder); + if(this->tiltType != tilt_types::none) { + json->addElem("tiltDirection", this->tiltDirection); + json->addElem("tiltTarget", this->transformPosition(this->tiltTarget)); + json->addElem("tiltPosition", this->transformPosition(this->currentTiltPos)); + json->addElem("myTiltPos", this->transformPosition(this->myTiltPos)); + } + json->endObject(); + sockEmit.endEmit(num); + /* char buf[420]; if(this->tiltType != tilt_types::none) snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"type\":%u,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d,\"myPos\":%d,\"myTiltPos\":%d,\"tiltType\":%u,\"tiltDirection\":%d,\"tiltTarget\":%d,\"tiltPosition\":%d,\"flipCommands\":%s,\"flipPosition\":%s,\"flags\":%d,\"sunSensor\":%s,\"light\":%s,\"sortOrder\":%d}", @@ -1834,9 +1935,21 @@ void SomfyShade::emitState(uint8_t num, const char *evt) { static_cast(this->tiltType), this->flipCommands ? "true" : "false", this->flipPosition ? "true": "false", this->flags, this->hasSunSensor() ? "true" : "false", this->hasLight() ? "true" : "false", this->sortOrder); if(num >= 255) sockEmit.sendToClients(evt, buf); else sockEmit.sendToClient(num, evt, buf); + */ } void SomfyShade::emitCommand(somfy_commands cmd, const char *source, uint32_t sourceAddress, const char *evt) { this->emitCommand(255, cmd, source, sourceAddress, evt); } void SomfyShade::emitCommand(uint8_t num, somfy_commands cmd, const char *source, uint32_t sourceAddress, const char *evt) { + JsonSockEvent *json = sockEmit.beginEmit(evt); + json->beginObject(); + json->addElem("shadeId", this->shadeId); + json->addElem("remoteAddress", (uint32_t)this->getRemoteAddress()); + json->addElem("cmd", translateSomfyCommand(cmd).c_str()); + json->addElem("source", source); + json->addElem("rcode", (uint32_t)this->lastRollingCode); + json->addElem("sourceAddress", (uint32_t)sourceAddress); + json->endObject(); + sockEmit.endEmit(num); + /* ClientSocketEvent e(evt); char buf[30]; snprintf(buf, sizeof(buf), "{\"shadeId\":%d", this->shadeId); @@ -1853,6 +1966,7 @@ void SomfyShade::emitCommand(uint8_t num, somfy_commands cmd, const char *source e.appendMessage(buf); if(num >= 255) sockEmit.sendToClients(&e); else sockEmit.sendToClient(num, &e); + */ if(mqtt.connected()) { this->publish("cmdSource", source); this->publish("cmdAddress", sourceAddress); @@ -1861,6 +1975,14 @@ void SomfyShade::emitCommand(uint8_t num, somfy_commands cmd, const char *source } void SomfyRoom::emitState(const char *evt) { this->emitState(255, evt); } void SomfyRoom::emitState(uint8_t num, const char *evt) { + JsonSockEvent *json = sockEmit.beginEmit(evt); + json->beginObject(); + json->addElem("roomId", this->roomId); + json->addElem("name", this->name); + json->addElem("sortOrder", this->sortOrder); + json->endObject(); + sockEmit.endEmit(num); + /* ClientSocketEvent e(evt); char buf[55]; uint8_t flags = 0; @@ -1872,10 +1994,31 @@ void SomfyRoom::emitState(uint8_t num, const char *evt) { e.appendMessage(buf); if(num >= 255) sockEmit.sendToClients(&e); else sockEmit.sendToClient(num, &e); + */ this->publish(); } void SomfyGroup::emitState(const char *evt) { this->emitState(255, evt); } void SomfyGroup::emitState(uint8_t num, const char *evt) { + uint8_t flags = 0; + JsonSockEvent *json = sockEmit.beginEmit(evt); + json->beginObject(); + json->addElem("groupId", this->groupId); + json->addElem("remoteAddress", (uint32_t)this->getRemoteAddress()); + json->addElem("name", this->name); + json->addElem("sunSensor", this->hasSunSensor()); + json->beginArray("shades"); + for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) { + if(this->linkedShades[i] != 255 && this->linkedShades[i] != 0) { + SomfyShade *shade = somfy.getShadeById(this->linkedShades[i]); + if(shade) json->addElem(this->linkedShades[i]); + flags |= shade->flags; + } + } + json->endArray(); + json->addElem("flags", flags); + json->endObject(); + sockEmit.endEmit(num); + /* ClientSocketEvent e(evt); char buf[55]; uint8_t flags = 0; @@ -1906,6 +2049,7 @@ void SomfyGroup::emitState(uint8_t num, const char *evt) { if(num >= 255) sockEmit.sendToClients(&e); else sockEmit.sendToClient(num, &e); + */ this->publish(); } int8_t SomfyShade::transformPosition(float fpos) { @@ -2013,8 +2157,12 @@ void SomfyShade::processWaitingFrame() { this->emitState(); } else if(this->isIdle()) { - if(this->myPos >= 0.0f && this->myPos <= 100.0f) this->p_target(this->myPos); - if(this->myTiltPos >= 0.0f && this->myTiltPos <= 100.0f) this->p_tiltTarget(this->myTiltPos); + if(this->simMy()) + this->moveToMyPosition(); // Call out like this (instead of move to target) so that we don't get some of the goofy tilt only problems. + else { + if(this->myPos >= 0.0f && this->myPos <= 100.0f) this->p_target(this->myPos); + if(this->myTiltPos >= 0.0f && this->myTiltPos <= 100.0f) this->p_tiltTarget(this->myTiltPos); + } this->setMovement(0); this->lastFrame.processed = true; this->emitCommand(cmd, "remote", this->lastFrame.remoteAddress); @@ -2247,7 +2395,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { break; case somfy_commands::My: if(this->shadeType == shade_types::drycontact2) return; - if(this->shadeType == shade_types::garage1) { + if(this->isToggle()) { // This is a one button device if(this->lastFrame.processed) return; this->lastFrame.processed = true; if(!this->isIdle()) this->p_target(this->currentPos); @@ -2275,14 +2423,16 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { this->lastFrame.await = curTime + 500; } else { + if(this->lastFrame.processed) return; Serial.println("Moving to My target"); this->lastFrame.processed = true; - if(this->myTiltPos >= 0.0f && this->myTiltPos >= 100.0f) this->p_tiltTarget(this->myTiltPos); + if(this->myTiltPos >= 0.0f && this->myTiltPos <= 100.0f) this->p_tiltTarget(this->myTiltPos); if(this->myPos >= 0.0f && this->myPos <= 100.0f && this->tiltType != tilt_types::tiltonly) this->p_target(this->myPos); this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); } } else { + if(this->lastFrame.processed) return; this->lastFrame.processed = true; if(!internal) { if(this->tiltType != tilt_types::tiltonly) this->p_target(this->currentPos); @@ -2300,6 +2450,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { // the motor must tilt in the direction first then move // so we have to calculate the target with this in mind. if(this->stepSize == 0) return; // Avoid divide by 0. + if(this->lastFrame.stepSize == 0) this->lastFrame.stepSize = 1; if(this->tiltType == tilt_types::integrated) { // With integrated tilt this is more involved than ne would think because the step command can be moving not just the tilt // but the lift. So a determination needs to be made as to whether we are currently moving and it should stop. @@ -2312,22 +2463,22 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { // Set the tilt position. This should stop the lift movement. this->p_target(this->currentPos); if(this->tiltTime == 0) return; // Avoid divide by 0. - this->p_tiltTarget(max(0.0f, this->currentTiltPos - (100.0f/(static_cast(this->tiltTime/static_cast(this->stepSize)))))); + this->p_tiltTarget(max(0.0f, this->currentTiltPos - (100.0f/(static_cast(this->tiltTime/static_cast(this->stepSize * this->lastFrame.stepSize)))))); } else { // We only have the lift to move. if(this->upTime == 0) return; // Avoid divide by 0. this->p_tiltTarget(this->currentTiltPos); - this->p_target(max(0.0f, this->currentPos - (100.0f/(static_cast(this->upTime/static_cast(this->stepSize)))))); + this->p_target(max(0.0f, this->currentPos - (100.0f/(static_cast(this->upTime/static_cast(this->stepSize * this->lastFrame.stepSize)))))); } } else if(this->tiltType == tilt_types::tiltonly) { if(this->tiltTime == 0 || this->stepSize == 0) return; - this->p_tiltTarget(max(0.0f, this->currentTiltPos - (100.0f/(static_cast(this->tiltTime/static_cast(this->stepSize)))))); + this->p_tiltTarget(max(0.0f, this->currentTiltPos - (100.0f/(static_cast(this->tiltTime/static_cast(this->stepSize * this->lastFrame.stepSize)))))); } else if(this->currentPos > 0.0f) { if(this->downTime == 0 || this->stepSize == 0) return; - this->p_target(max(0.0f, this->currentPos - (100.0f/(static_cast(this->upTime/static_cast(this->stepSize)))))); + this->p_target(max(0.0f, this->currentPos - (100.0f/(static_cast(this->upTime/static_cast(this->stepSize * this->lastFrame.stepSize)))))); } this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); break; @@ -2340,6 +2491,8 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { // the motor must tilt in the direction first then move // so we have to calculate the target with this in mind. if(this->stepSize == 0) return; // Avoid divide by 0. + if(this->lastFrame.stepSize == 0) this->lastFrame.stepSize = 1; + if(this->tiltType == tilt_types::integrated) { // With integrated tilt this is more involved than ne would think because the step command can be moving not just the tilt // but the lift. So a determination needs to be made as to whether we are currently moving and it should stop. @@ -2352,22 +2505,22 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { // Set the tilt position. This should stop the lift movement. this->p_target(this->currentPos); if(this->tiltTime == 0) return; // Avoid divide by 0. - this->p_tiltTarget(min(100.0f, this->currentTiltPos + (100.0f/(static_cast(this->tiltTime/static_cast(this->stepSize)))))); + this->p_tiltTarget(min(100.0f, this->currentTiltPos + (100.0f/(static_cast(this->tiltTime/static_cast(this->stepSize * this->lastFrame.stepSize)))))); } else { // We only have the lift to move. this->p_tiltTarget(this->currentTiltPos); if(this->downTime == 0) return; // Avoid divide by 0. - this->p_target(min(100.0f, this->currentPos + (100.0f/(static_cast(this->downTime/static_cast(this->stepSize)))))); + this->p_target(min(100.0f, this->currentPos + (100.0f/(static_cast(this->downTime/static_cast(this->stepSize* this->lastFrame.stepSize)))))); } } else if(this->tiltType == tilt_types::tiltonly) { if(this->tiltTime == 0 || this->stepSize == 0) return; - this->p_target(min(100.0f, this->currentTiltPos + (100.0f/(static_cast(this->tiltTime/static_cast(this->stepSize)))))); + this->p_target(min(100.0f, this->currentTiltPos + (100.0f/(static_cast(this->tiltTime/static_cast(this->stepSize * this->lastFrame.stepSize)))))); } else if(this->currentPos < 100.0f) { if(this->downTime == 0 || this->stepSize == 0) return; - this->p_target(min(100.0f, this->currentPos + (100.0f/(static_cast(this->downTime/static_cast(this->stepSize)))))); + this->p_target(min(100.0f, this->currentPos + (100.0f/(static_cast(this->downTime/static_cast(this->stepSize * this->lastFrame.stepSize)))))); } this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); break; @@ -2380,6 +2533,25 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { else this->p_target(this->lastMovement == -1 ? 100 : 0); this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); break; + case somfy_commands::Stop: + if(this->lastFrame.processed) return; + this->lastFrame.processed = true; + this->p_target(this->currentPos); + this->p_tiltTarget(this->currentTiltPos); + this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); + break; + case somfy_commands::Favorite: + if(this->lastFrame.processed) return; + this->lastFrame.processed = true; + if(this->simMy()) { + this->moveToMyPosition(); + } + else { + if(this->myTiltPos >= 0.0f && this->myTiltPos <= 100.0f) this->p_tiltTarget(this->myTiltPos); + if(this->myPos >= 0.0f && this->myPos <= 100.0f && this->tiltType != tilt_types::tiltonly) this->p_target(this->myPos); + this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); + } + break; default: dir = 0; break; @@ -2438,8 +2610,13 @@ void SomfyShade::processInternalCommand(somfy_commands cmd, uint8_t repeat) { case somfy_commands::My: if(this->isIdle()) { Serial.printf("Shade #%d is idle\n", this->getShadeId()); - if(this->myTiltPos >= 0.0f && this->myTiltPos <= 100.0f) this->p_tiltTarget(this->myTiltPos); - if(this->myPos >= 0.0f && this->myPos <= 100.0f && this->tiltType != tilt_types::tiltonly) this->p_target(this->myPos); + if(this->simMy()) { + this->moveToMyPosition(); + } + else { + if(this->myTiltPos >= 0.0f && this->myTiltPos <= 100.0f) this->p_tiltTarget(this->myTiltPos); + if(this->myPos >= 0.0f && this->myPos <= 100.0f && this->tiltType != tilt_types::tiltonly) this->p_target(this->myPos); + } } else { if(this->tiltType == tilt_types::tiltonly) { @@ -2702,10 +2879,15 @@ void SomfyShade::moveToMyPosition() { if(this->tiltType != tilt_types::tiltonly && this->myPos >= 0.0f && this->myPos <= 100.0f) this->p_target(this->myPos); if(this->myTiltPos >= 0.0f && this->myTiltPos <= 100.0f) this->p_tiltTarget(this->myTiltPos); this->settingPos = false; - SomfyRemote::sendCommand(somfy_commands::My, this->repeats); + if(this->simMy()) { + Serial.print("Moving to simulated favorite\n"); + this->moveToTarget(this->myPos, this->myTiltPos); + } + else + SomfyRemote::sendCommand(somfy_commands::My, this->repeats); } void SomfyShade::sendCommand(somfy_commands cmd) { this->sendCommand(cmd, this->repeats); } -void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat) { +void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize) { // This sendCommand function will always be called externally. sendCommand at the remote level // is expected to be called internally when the motor needs commanded. if(this->bitLength == 0) this->bitLength = somfy.transceiver.config.type; @@ -2746,7 +2928,7 @@ void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat) { } } else if(cmd == somfy_commands::My) { - if(this->shadeType == shade_types::garage1 || this->shadeType == shade_types::drycontact) + if(this->isToggle() || this->shadeType == shade_types::drycontact) SomfyRemote::sendCommand(cmd, repeat); else if(this->shadeType == shade_types::drycontact2) return; else if(this->isIdle()) { @@ -2760,22 +2942,23 @@ void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat) { } } else if(cmd == somfy_commands::Toggle) { - if(this->bitLength != 80) SomfyRemote::sendCommand(somfy_commands::My, repeat); + if(this->bitLength != 80) SomfyRemote::sendCommand(somfy_commands::My, repeat, stepSize); else SomfyRemote::sendCommand(somfy_commands::Toggle, repeat); } - else if(this->shadeType == shade_types::garage1 && cmd == somfy_commands::Prog) { - SomfyRemote::sendCommand(somfy_commands::Toggle, repeat); + else if(this->isToggle() && cmd == somfy_commands::Prog) { + SomfyRemote::sendCommand(somfy_commands::Toggle, repeat, stepSize); } else { - SomfyRemote::sendCommand(cmd, repeat); + SomfyRemote::sendCommand(cmd, repeat, stepSize); } } void SomfyGroup::sendCommand(somfy_commands cmd) { this->sendCommand(cmd, this->repeats); } -void SomfyGroup::sendCommand(somfy_commands cmd, uint8_t repeat) { +void SomfyGroup::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize) { // This sendCommand function will always be called externally. sendCommand at the remote level // is expected to be called internally when the motor needs commanded. if(this->bitLength == 0) this->bitLength = somfy.transceiver.config.type; - SomfyRemote::sendCommand(cmd, repeat); + SomfyRemote::sendCommand(cmd, repeat, stepSize); + switch(cmd) { case somfy_commands::My: this->p_direction(0); @@ -2789,6 +2972,7 @@ void SomfyGroup::sendCommand(somfy_commands cmd, uint8_t repeat) { default: break; } + for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) { if(this->linkedShades[i] != 0) { SomfyShade *shade = somfy.getShadeById(this->linkedShades[i]); @@ -2843,8 +3027,8 @@ void SomfyShade::moveToTiltTarget(float target) { } void SomfyShade::moveToTarget(float pos, float tilt) { somfy_commands cmd = somfy_commands::My; - if(this->shadeType == shade_types::garage1) { - // Overload this as we cannot seek a position on a garage door. + if(this->isToggle()) { + // Overload this as we cannot seek a position on a garage door or single button device. this->p_target(pos); this->p_currentPos(pos); this->emitState(); @@ -2926,12 +3110,24 @@ bool SomfyShade::save() { } bool SomfyRoom::save() { somfy.commit(); return true; } bool SomfyGroup::save() { somfy.commit(); return true; } +bool SomfyShade::isToggle() { + switch(this->shadeType) { + case shade_types::garage1: + case shade_types::lgate1: + case shade_types::cgate1: + case shade_types::rgate1: + return true; + default: + break; + } + return false; +} bool SomfyShade::usesPin(uint8_t pin) { if(this->proto != radio_proto::GP_Remote && this->proto != radio_proto::GP_Relay) return false; if(this->gpioDown == pin) return true; else if(this->shadeType == shade_types::drycontact) return this->gpioDown == pin; - else if(this->shadeType == shade_types::garage1) { + else if(this->isToggle()) { if(this->proto == radio_proto::GP_Relay && this->gpioUp == pin) return true; } else if(this->shadeType == shade_types::drycontact2) { @@ -2983,7 +3179,9 @@ int8_t SomfyShade::validateJSON(JsonObject &obj) { uint8_t upPin = obj.containsKey("gpioUp") ? obj["gpioUp"].as() : this->gpioUp; uint8_t downPin = obj.containsKey("gpioDown") ? obj["gpioDown"].as() : this->gpioDown; uint8_t myPin = obj.containsKey("gpioMy") ? obj["gpioMy"].as() : this->gpioMy; - if(type == shade_types::drycontact || (type == shade_types::garage1 && proto == radio_proto::GP_Remote)) upPin = myPin = 255; + if(type == shade_types::drycontact || + ((type == shade_types::garage1 || type == shade_types::lgate1 || type == shade_types::cgate1 || type == shade_types::rgate1) + && proto == radio_proto::GP_Remote)) upPin = myPin = 255; else if(type == shade_types::drycontact2) myPin = 255; if(proto == radio_proto::GP_Relay) myPin = 255; if(somfy.transceiver.config.enabled) { @@ -2992,7 +3190,7 @@ int8_t SomfyShade::validateJSON(JsonObject &obj) { (myPin != 255 && somfy.transceiver.usesPin(myPin))) ret = -10; } - if(settings.connType == conn_types::ethernet || settings.connType == conn_types::ethernetpref) { + if(settings.connType == conn_types_t::ethernet || settings.connType == conn_types_t::ethernetpref) { if((upPin != 255 && settings.Ethernet.usesPin(upPin)) || (downPin != 255 && somfy.transceiver.usesPin(downPin)) || (myPin != 255 && somfy.transceiver.usesPin(myPin))) @@ -3028,6 +3226,7 @@ int8_t SomfyShade::fromJSON(JsonObject &obj) { if(obj.containsKey("bitLength")) this->bitLength = obj["bitLength"]; if(obj.containsKey("proto")) this->proto = static_cast(obj["proto"].as()); if(obj.containsKey("sunSensor")) this->setSunSensor(obj["sunSensor"]); + if(obj.containsKey("simMy")) this->setSimMy(obj["simMy"]); if(obj.containsKey("light")) this->setLight(obj["light"]); if(obj.containsKey("gpioFlags")) this->gpioFlags = obj["gpioFlags"]; if(obj.containsKey("gpioLLTrigger")) { @@ -3110,22 +3309,73 @@ int8_t SomfyShade::fromJSON(JsonObject &obj) { } return err; } -bool SomfyShade::toJSONRef(JsonObject &obj) { - obj["shadeId"] = this->getShadeId(); - obj["roomId"] = this->roomId; - obj["name"] = this->name; - obj["remoteAddress"] = this->m_remoteAddress; - obj["paired"] = this->paired; - obj["shadeType"] = static_cast(this->shadeType); - obj["bitLength"] = this->bitLength; - obj["proto"] = static_cast(this->proto); - obj["flags"] = this->flags; - obj["sunSensor"] = this->hasSunSensor(); - obj["hasLight"] = this->hasLight(); - obj["repeats"] = this->repeats; - SomfyRemote::toJSON(obj); - return true; +void SomfyShade::toJSONRef(JsonResponse &json) { + json.addElem("shadeId", this->getShadeId()); + json.addElem("roomId", this->roomId); + json.addElem("name", this->name); + json.addElem("remoteAddress", (uint32_t)this->m_remoteAddress); + json.addElem("paired", this->paired); + json.addElem("shadeType", static_cast(this->shadeType)); + json.addElem("flipCommands", this->flipCommands); + json.addElem("flipPosition", this->flipCommands); + json.addElem("bitLength", this->bitLength); + json.addElem("proto", static_cast(this->proto)); + json.addElem("flags", this->flags); + json.addElem("sunSensor", this->hasSunSensor()); + json.addElem("hasLight", this->hasLight()); + json.addElem("repeats", this->repeats); + //SomfyRemote::toJSON(json); } + +void SomfyShade::toJSON(JsonResponse &json) { + json.addElem("shadeId", this->getShadeId()); + json.addElem("roomId", this->roomId); + json.addElem("name", this->name); + json.addElem("remoteAddress", (uint32_t)this->m_remoteAddress); + json.addElem("upTime", (uint32_t)this->upTime); + json.addElem("downTime", (uint32_t)this->downTime); + json.addElem("paired", this->paired); + json.addElem("lastRollingCode", (uint32_t)this->lastRollingCode); + json.addElem("position", this->transformPosition(this->currentPos)); + json.addElem("tiltType", static_cast(this->tiltType)); + json.addElem("tiltPosition", this->transformPosition(this->currentTiltPos)); + json.addElem("tiltDirection", this->tiltDirection); + json.addElem("tiltTime", (uint32_t)this->tiltTime); + json.addElem("stepSize", (uint32_t)this->stepSize); + json.addElem("tiltTarget", this->transformPosition(this->tiltTarget)); + json.addElem("target", this->transformPosition(this->target)); + json.addElem("myPos", this->transformPosition(this->myPos)); + json.addElem("myTiltPos", this->transformPosition(this->myTiltPos)); + json.addElem("direction", this->direction); + json.addElem("shadeType", static_cast(this->shadeType)); + json.addElem("bitLength", this->bitLength); + json.addElem("proto", static_cast(this->proto)); + json.addElem("flags", this->flags); + json.addElem("flipCommands", this->flipCommands); + json.addElem("flipPosition", this->flipPosition); + json.addElem("inGroup", this->isInGroup()); + json.addElem("sunSensor", this->hasSunSensor()); + json.addElem("light", this->hasLight()); + json.addElem("repeats", this->repeats); + json.addElem("sortOrder", this->sortOrder); + json.addElem("gpioUp", this->gpioUp); + json.addElem("gpioDown", this->gpioDown); + json.addElem("gpioMy", this->gpioMy); + json.addElem("gpioLLTrigger", ((this->gpioFlags & (uint8_t)gpio_flags_t::LowLevelTrigger) == 0) ? false : true); + json.addElem("simMy", this->simMy()); + json.beginArray("linkedRemotes"); + for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { + SomfyLinkedRemote &lremote = this->linkedRemotes[i]; + if(lremote.getRemoteAddress() != 0) { + json.beginObject(); + lremote.toJSON(json); + json.endObject(); + } + } + json.endArray(); +} + +/* bool SomfyShade::toJSON(JsonObject &obj) { //Serial.print("Serializing Shade:"); //Serial.print(this->getShadeId()); @@ -3178,17 +3428,25 @@ bool SomfyShade::toJSON(JsonObject &obj) { } return true; } +*/ bool SomfyRoom::fromJSON(JsonObject &obj) { if(obj.containsKey("name")) strlcpy(this->name, obj["name"], sizeof(this->name)); if(obj.containsKey("sortOrder")) this->sortOrder = obj["sortOrder"]; return true; } +/* bool SomfyRoom::toJSON(JsonObject &obj) { obj["roomId"] = this->roomId; obj["name"] = this->name; obj["sortOrder"] = this->sortOrder; return true; } +*/ +void SomfyRoom::toJSON(JsonResponse &json) { + json.addElem("roomId", this->roomId); + json.addElem("name", this->name); + json.addElem("sortOrder", this->sortOrder); +} bool SomfyGroup::fromJSON(JsonObject &obj) { if(obj.containsKey("name")) strlcpy(this->name, obj["name"], sizeof(this->name)); @@ -3211,6 +3469,51 @@ bool SomfyGroup::fromJSON(JsonObject &obj) { } return true; } +void SomfyGroup::toJSON(JsonResponse &json) { + this->updateFlags(); + json.addElem("groupId", this->getGroupId()); + json.addElem("roomId", this->roomId); + json.addElem("name", this->name); + json.addElem("remoteAddress", (uint32_t)this->m_remoteAddress); + json.addElem("lastRollingCode", (uint32_t)this->lastRollingCode); + json.addElem("bitLength", this->bitLength); + json.addElem("proto", static_cast(this->proto)); + json.addElem("sunSensor", this->hasSunSensor()); + json.addElem("flipCommands", this->flipCommands); + json.addElem("flags", this->flags); + json.addElem("repeats", this->repeats); + json.addElem("sortOrder", this->sortOrder); + json.beginArray("linkedShades"); + for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) { + uint8_t shadeId = this->linkedShades[i]; + if(shadeId > 0 && shadeId < 255) { + SomfyShade *shade = somfy.getShadeById(shadeId); + if(shade) { + json.beginObject(); + shade->toJSONRef(json); + json.endObject(); + } + } + } + json.endArray(); +} +void SomfyGroup::toJSONRef(JsonResponse &json) { + this->updateFlags(); + json.addElem("groupId", this->getGroupId()); + json.addElem("roomId", this->roomId); + json.addElem("name", this->name); + json.addElem("remoteAddress", (uint32_t)this->m_remoteAddress); + json.addElem("lastRollingCode", (uint32_t)this->lastRollingCode); + json.addElem("bitLength", this->bitLength); + json.addElem("proto", static_cast(this->proto)); + json.addElem("sunSensor", this->hasSunSensor()); + json.addElem("flipCommands", this->flipCommands); + json.addElem("flags", this->flags); + json.addElem("repeats", this->repeats); + json.addElem("sortOrder", this->sortOrder); +} + +/* bool SomfyGroup::toJSON(JsonObject &obj) { this->updateFlags(); obj["groupId"] = this->getGroupId(); @@ -3239,12 +3542,20 @@ bool SomfyGroup::toJSON(JsonObject &obj) { } return true; } +*/ + +void SomfyRemote::toJSON(JsonResponse &json) { + json.addElem("remoteAddress", (uint32_t)this->getRemoteAddress()); + json.addElem("lastRollingCode", (uint32_t)this->lastRollingCode); +} +/* bool SomfyRemote::toJSON(JsonObject &obj) { //obj["remotePrefId"] = this->getRemotePrefId(); obj["remoteAddress"] = this->getRemoteAddress(); obj["lastRollingCode"] = this->lastRollingCode; return true; } +*/ void SomfyRemote::setRemoteAddress(uint32_t address) { this->m_remoteAddress = address; snprintf(this->m_remotePrefId, sizeof(this->m_remotePrefId), "_%lu", (unsigned long)this->m_remoteAddress); } uint32_t SomfyRemote::getRemoteAddress() { return this->m_remoteAddress; } void SomfyShadeController::processFrame(somfy_frame_t &frame, bool internal) { @@ -3620,12 +3931,14 @@ void SomfyRemote::sendSensorCommand(int8_t isWindy, int8_t isSunny, uint8_t repe somfy.processFrame(this->lastFrame, true); } void SomfyRemote::sendCommand(somfy_commands cmd) { this->sendCommand(cmd, this->repeats); } -void SomfyRemote::sendCommand(somfy_commands cmd, uint8_t repeat) { +void SomfyRemote::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize) { this->lastFrame.rollingCode = this->getNextRollingCode(); this->lastFrame.remoteAddress = this->getRemoteAddress(); this->lastFrame.cmd = this->transformCommand(cmd); this->lastFrame.repeats = repeat; this->lastFrame.bitLength = this->bitLength; + this->lastFrame.stepSize = stepSize; + this->lastFrame.valid = true; // Match the encKey to the rolling code. These keys range from 160 to 175. this->lastFrame.encKey = 0xA0 | static_cast(this->lastFrame.rollingCode & 0x000F); this->lastFrame.proto = this->proto; @@ -3684,9 +3997,13 @@ void SomfyRemote::repeatFrame(uint8_t repeat) { somfy.transceiver.beginTransmit(); byte frm[10]; this->lastFrame.encodeFrame(frm); + this->lastFrame.repeats++; somfy.transceiver.sendFrame(frm, this->bitLength == 56 ? 2 : 12, this->bitLength); for(uint8_t i = 0; i < repeat; i++) { + this->lastFrame.repeats++; + if(this->lastFrame.bitLength == 80) this->lastFrame.encode80BitFrame(&frm[0], this->lastFrame.repeats); somfy.transceiver.sendFrame(frm, this->bitLength == 56 ? 7 : 6, this->bitLength); + esp_task_wdt_reset(); } somfy.transceiver.endTransmit(); //somfy.processFrame(this->lastFrame, true); @@ -3696,25 +4013,12 @@ void SomfyShadeController::sendFrame(somfy_frame_t &frame, uint8_t repeat) { byte frm[10]; frame.encodeFrame(frm); this->transceiver.sendFrame(frm, frame.bitLength == 56 ? 2 : 12, frame.bitLength); - // Transform the repeat bytes - switch(frame.cmd) { - case somfy_commands::StepUp: - case somfy_commands::StepDown: - break; - case somfy_commands::Prog: - frm[7] = 132; - frm[9] = 63; - break; - case somfy_commands::Toggle: - frm[7] = 136; - frm[9] = 34; - break; - default: - frm[9] = 46; - break; - } for(uint8_t i = 0; i < repeat; i++) { + // For each 80-bit frame we need to adjust the byte encoding for the + // silence. + if(frame.bitLength == 80) frame.encode80BitFrame(&frm[0], i + 1); this->transceiver.sendFrame(frm, frame.bitLength == 56 ? 7 : 6, frame.bitLength); + esp_task_wdt_reset(); } this->transceiver.endTransmit(); } @@ -3807,6 +4111,28 @@ uint16_t SomfyRemote::setRollingCode(uint16_t code) { } return code; } +void SomfyShadeController::toJSONRooms(JsonResponse &json) { + for(uint8_t i = 0; i < SOMFY_MAX_ROOMS; i++) { + SomfyRoom *room = &this->rooms[i]; + if(room->roomId != 0) { + json.beginObject(); + room->toJSON(json); + json.endObject(); + } + } +} +void SomfyShadeController::toJSONShades(JsonResponse &json) { + for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { + SomfyShade &shade = this->shades[i]; + if(shade.getShadeId() != 255) { + json.beginObject(); + shade.toJSON(json); + json.endObject(); + } + } +} + +/* bool SomfyShadeController::toJSON(DynamicJsonDocument &doc) { doc["maxRooms"] = SOMFY_MAX_ROOMS; doc["maxShades"] = SOMFY_MAX_SHADES; @@ -3836,23 +4162,7 @@ bool SomfyShadeController::toJSON(JsonObject &obj) { this->toJSONGroups(arrGroups); return true; } -bool SomfyShadeController::toJSONRepeaters(JsonArray &arr) { - for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) { - if(somfy.repeaters[i] != 0) arr.add(somfy.repeaters[i]); - } - return true; -} -bool SomfyShadeController::toJSONRooms(JsonArray &arr) { - for(uint8_t i = 0; i < SOMFY_MAX_ROOMS; i++) { - SomfyRoom &room = this->rooms[i]; - if(room.roomId != 0) { - JsonObject oroom = arr.createNestedObject(); - room.toJSON(oroom); - } - } - return true; -} bool SomfyShadeController::toJSONShades(JsonArray &arr) { for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { @@ -3874,6 +4184,22 @@ bool SomfyShadeController::toJSONGroups(JsonArray &arr) { } return true; } +*/ +void SomfyShadeController::toJSONGroups(JsonResponse &json) { + for(uint8_t i = 0; i < SOMFY_MAX_GROUPS; i++) { + SomfyGroup &group = this->groups[i]; + if(group.getGroupId() != 255) { + json.beginObject(); + group.toJSON(json); + json.endObject(); + } + } +} +void SomfyShadeController::toJSONRepeaters(JsonResponse &json) { + for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) { + if(somfy.repeaters[i] != 0) json.addElem((uint32_t)somfy.repeaters[i]); + } +} void SomfyShadeController::loop() { this->transceiver.loop(); for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { @@ -4079,7 +4405,7 @@ void RECEIVE_ATTR Transceiver::handleReceive() { else if (duration > tempo_synchro_sw_min && duration < tempo_synchro_sw_max && somfy_rx.cpt_synchro_hw >= 4) { // If we have a full hardware sync then we should look for the software sync. If we have a software sync // bit and enough hardware sync bits then we should start receiving data. It turns out that a 56 bit packet - // with give 4 or 14 bits of hardware sync. An 80 bit packet give 12 or 24 bits of hw sync. Early on + // with give 4 or 14 bits of hardware sync. An 80 bit packet gives 12, 13 or 24 bits of hw sync. Early on // I had some shorter and longer hw syncs but I can no longer repeat this. memset(somfy_rx.payload, 0x00, sizeof(somfy_rx.payload)); somfy_rx.previous_bit = 0x00; @@ -4088,8 +4414,11 @@ void RECEIVE_ATTR Transceiver::handleReceive() { // Keep an eye on this as it is possible that we might get fewer or more synchro bits. if (somfy_rx.cpt_synchro_hw <= 7) somfy_rx.bit_length = 56; else if (somfy_rx.cpt_synchro_hw == 14) somfy_rx.bit_length = 56; + else if (somfy_rx.cpt_synchro_hw == 13) somfy_rx.bit_length = 80; // The RS485 device sends this sync. else if (somfy_rx.cpt_synchro_hw == 12) somfy_rx.bit_length = 80; else if (somfy_rx.cpt_synchro_hw > 17) somfy_rx.bit_length = 80; + else somfy_rx.bit_length = 56; + //somfy_rx.bit_length = 80; somfy_rx.status = receiving_data; } else { @@ -4244,10 +4573,21 @@ void Transceiver::endFrequencyScan() { } } void Transceiver::emitFrequencyScan(uint8_t num) { + JsonSockEvent *json = sockEmit.beginEmit("frequencyScan"); + json->beginObject(); + json->addElem("scanning", rxmode == 3); + json->addElem("testFreq", currFreq); + json->addElem("testRSSI", (int32_t)currRSSI); + json->addElem("frequency", markFreq); + json->addElem("RSSI", (int32_t)markRSSI); + json->endObject(); + sockEmit.endEmit(num); + /* char buf[420]; snprintf(buf, sizeof(buf), "{\"scanning\":%s,\"testFreq\":%f,\"testRSSI\":%d,\"frequency\":%f,\"RSSI\":%d}", rxmode == 3 ? "true" : "false", currFreq, currRSSI, markFreq, markRSSI); if(num >= 255) sockEmit.sendToClients("frequencyScan", buf); else sockEmit.sendToClient(num, "frequencyScan", buf); + */ } bool Transceiver::receive(somfy_rx_t *rx) { // Check to see if there is anything in the buffer @@ -4262,6 +4602,29 @@ bool Transceiver::receive(somfy_rx_t *rx) { } void Transceiver::emitFrame(somfy_frame_t *frame, somfy_rx_t *rx) { if(sockEmit.activeClients(ROOM_EMIT_FRAME) > 0) { + JsonSockEvent *json = sockEmit.beginEmit("remoteFrame"); + json->beginObject(); + json->addElem("encKey", frame->encKey); + json->addElem("address", (uint32_t)frame->remoteAddress); + json->addElem("rcode", (uint32_t)frame->rollingCode); + json->addElem("command", translateSomfyCommand(frame->cmd).c_str()); + json->addElem("rssi", (int32_t)frame->rssi); + json->addElem("bits", rx->bit_length); + json->addElem("proto", static_cast(frame->proto)); + json->addElem("valid", frame->valid); + json->addElem("sync", frame->hwsync); + if(frame->cmd == somfy_commands::StepUp || frame->cmd == somfy_commands::StepDown) + json->addElem("stepSize", frame->stepSize); + json->beginArray("pulses"); + if(rx) { + for(uint16_t i = 0; i < rx->pulseCount; i++) { + json->addElem((uint32_t)rx->pulses[i]); + } + } + json->endArray(); + json->endObject(); + sockEmit.endEmitRoom(ROOM_EMIT_FRAME); + /* ClientSocketEvent evt("remoteFrame"); char buf[30]; snprintf(buf, sizeof(buf), "{\"encKey\":%d,", frame->encKey); @@ -4291,12 +4654,14 @@ void Transceiver::emitFrame(somfy_frame_t *frame, somfy_rx_t *rx) { } evt.appendMessage("]}"); sockEmit.sendToRoom(ROOM_EMIT_FRAME, &evt); + */ } } void Transceiver::clearReceived(void) { //packet_received = false; //memset(receive_buffer, 0x00, sizeof(receive_buffer)); if(this->config.enabled) + //attachInterrupt(interruptPin, handleReceive, FALLING); attachInterrupt(interruptPin, handleReceive, CHANGE); } void Transceiver::enableReceive(void) { @@ -4307,6 +4672,7 @@ void Transceiver::enableReceive(void) { pinMode(this->config.RXPin, INPUT); interruptPin = digitalPinToInterrupt(this->config.RXPin); ELECHOUSE_cc1101.SetRx(); + //attachInterrupt(interruptPin, handleReceive, FALLING); attachInterrupt(interruptPin, handleReceive, CHANGE); Serial.printf("Enabled receive on Pin #%d Timing: %ld\n", this->config.RXPin, millis() - timing); } @@ -4317,12 +4683,19 @@ void Transceiver::disableReceive(void) { interruptPin = 0; } +void Transceiver::toJSON(JsonResponse& json) { + json.beginObject("config"); + this->config.toJSON(json); + json.endObject(); +} +/* bool Transceiver::toJSON(JsonObject& obj) { - Serial.println("Setting Transceiver Json"); + //Serial.println("Setting Transceiver Json"); JsonObject objConfig = obj.createNestedObject("config"); this->config.toJSON(objConfig); return true; } +*/ bool Transceiver::fromJSON(JsonObject& obj) { if (obj.containsKey("config")) { JsonObject objConfig = obj["config"]; @@ -4352,7 +4725,7 @@ bool Transceiver::end() { return true; } void transceiver_config_t::fromJSON(JsonObject& obj) { - Serial.print("Deserialize Radio JSON "); + //Serial.print("Deserialize Radio JSON "); if(obj.containsKey("type")) this->type = obj["type"]; if(obj.containsKey("CSNPin")) this->CSNPin = obj["CSNPin"]; if(obj.containsKey("MISOPin")) this->MISOPin = obj["MISOPin"]; @@ -4393,6 +4766,23 @@ void transceiver_config_t::fromJSON(JsonObject& obj) { */ 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(JsonResponse &json) { + json.addElem("type", this->type); + json.addElem("TXPin", this->TXPin); + json.addElem("RXPin", this->RXPin); + json.addElem("SCKPin", this->SCKPin); + json.addElem("MOSIPin", this->MOSIPin); + json.addElem("MISOPin", this->MISOPin); + json.addElem("CSNPin", this->CSNPin); + json.addElem("rxBandwidth", this->rxBandwidth); // float + json.addElem("frequency", this->frequency); // float + json.addElem("deviation", this->deviation); // float + json.addElem("txPower", this->txPower); + json.addElem("proto", static_cast(this->proto)); + json.addElem("enabled", this->enabled); + json.addElem("radioInit", this->radioInit); +} +/* void transceiver_config_t::toJSON(JsonObject& obj) { obj["type"] = this->type; obj["TXPin"] = this->TXPin; @@ -4406,36 +4796,35 @@ void transceiver_config_t::toJSON(JsonObject& obj) { obj["deviation"] = this->deviation; // float obj["txPower"] = this->txPower; obj["proto"] = static_cast(this->proto); - /* - obj["internalCCMode"] = this->internalCCMode; - obj["modulationMode"] = this->modulationMode; - obj["channel"] = this->channel; - obj["channelSpacing"] = this->channelSpacing; // float - obj["dataRate"] = this->dataRate; // float - obj["syncMode"] = this->syncMode; - obj["syncWordHigh"] = this->syncWordHigh; - obj["syncWordLow"] = this->syncWordLow; - obj["addrCheckMode"] = this->addrCheckMode; - obj["checkAddr"] = this->checkAddr; - obj["dataWhitening"] = this->dataWhitening; - obj["pktFormat"] = this->pktFormat; - obj["pktLengthMode"] = this->pktLengthMode; - obj["pktLength"] = this->pktLength; - obj["useCRC"] = this->useCRC; - obj["autoFlushCRC"] = this->autoFlushCRC; - obj["disableDCFilter"] = this->disableDCFilter; - obj["enableManchester"] = this->enableManchester; - obj["enableFEC"] = this->enableFEC; - obj["minPreambleBytes"] = this->minPreambleBytes; - obj["pqtThreshold"] = this->pqtThreshold; - obj["appendStatus"] = this->appendStatus; - obj["printBuffer"] = somfy.transceiver.printBuffer; - */ + //obj["internalCCMode"] = this->internalCCMode; + //obj["modulationMode"] = this->modulationMode; + //obj["channel"] = this->channel; + //obj["channelSpacing"] = this->channelSpacing; // float + //obj["dataRate"] = this->dataRate; // float + //obj["syncMode"] = this->syncMode; + //obj["syncWordHigh"] = this->syncWordHigh; + //obj["syncWordLow"] = this->syncWordLow; + //obj["addrCheckMode"] = this->addrCheckMode; + //obj["checkAddr"] = this->checkAddr; + //obj["dataWhitening"] = this->dataWhitening; + //obj["pktFormat"] = this->pktFormat; + //obj["pktLengthMode"] = this->pktLengthMode; + //obj["pktLength"] = this->pktLength; + //obj["useCRC"] = this->useCRC; + //obj["autoFlushCRC"] = this->autoFlushCRC; + //obj["disableDCFilter"] = this->disableDCFilter; + //obj["enableManchester"] = this->enableManchester; + //obj["enableFEC"] = this->enableFEC; + //obj["minPreambleBytes"] = this->minPreambleBytes; + //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 "); - 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); + //Serial.print("Serialize Radio JSON "); + //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::save() { pref.begin("CC1101"); pref.clear(); diff --git a/Somfy.h b/Somfy.h index 414beec..bc5c99b 100644 --- a/Somfy.h +++ b/Somfy.h @@ -1,6 +1,7 @@ #ifndef SOMFY_H #define SOMFY_H #include "ConfigSettings.h" +#include "WResp.h" #define SOMFY_MAX_SHADES 32 #define SOMFY_MAX_GROUPS 16 @@ -46,6 +47,8 @@ enum class somfy_commands : byte { RTWProto = 0xF, // RTW Protocol // Command extensions for 80 bit frames StepUp = 0x8B, + Favorite = 0xC1, + Stop = 0xF1 }; enum class group_types : byte { channel = 0x00 @@ -64,7 +67,10 @@ enum class shade_types : byte { drycontact2 = 0x0A, lgate = 0x0B, cgate = 0x0C, - rgate = 0x0D + rgate = 0x0D, + lgate1 = 0x0E, + cgate1 = 0x0F, + rgate1 = 0x10 }; enum class tilt_types : byte { none = 0x00, @@ -154,7 +160,8 @@ enum class somfy_flags_t : byte { Light = 0x08, Windy = 0x10, Sunny = 0x20, - Lighted = 0x40 + Lighted = 0x40, + SimMy = 0x80 }; enum class gpio_flags_t : byte { LowLevelTrigger = 0x01 @@ -167,6 +174,7 @@ struct somfy_relay_t { struct somfy_frame_t { bool valid = false; bool processed = false; + bool synonym = false; radio_proto proto = radio_proto::RTS; int rssi = 0; byte lqi = 0x0; @@ -180,11 +188,16 @@ struct somfy_frame_t { uint32_t await = 0; uint8_t bitLength = 56; uint16_t pulseCount = 0; + uint8_t stepSize = 0; void print(); + void encode80BitFrame(byte *frame, uint8_t repeat); + byte calc80Checksum(byte b0, byte b1, byte b2); + byte encode80Byte7(byte start, uint8_t repeat); void encodeFrame(byte *frame); void decodeFrame(byte* frame); void decodeFrame(somfy_rx_t *rx); bool isRepeat(somfy_frame_t &f); + bool isSynonym(somfy_frame_t &f); void copy(somfy_frame_t &f); }; @@ -196,7 +209,7 @@ class SomfyRoom { void clear(); bool save(); bool fromJSON(JsonObject &obj); - bool toJSON(JsonObject &obj); + void toJSON(JsonResponse &json); void emitState(const char *evt = "roomState"); void emitState(uint8_t num, const char *evt = "roomState"); void publish(); @@ -226,17 +239,19 @@ class SomfyRemote { uint8_t repeats = 1; virtual bool isLastCommand(somfy_commands cmd); char *getRemotePrefId() {return m_remotePrefId;} - virtual bool toJSON(JsonObject &obj); + virtual void toJSON(JsonResponse &json); virtual void setRemoteAddress(uint32_t address); virtual uint32_t getRemoteAddress(); virtual uint16_t getNextRollingCode(); virtual uint16_t setRollingCode(uint16_t code); bool hasSunSensor(); bool hasLight(); + bool simMy(); void setSunSensor(bool bHasSensor); void setLight(bool bHasLight); + void setSimMy(bool bSimMy); virtual void sendCommand(somfy_commands cmd); - virtual void sendCommand(somfy_commands cmd, uint8_t repeat); + virtual void sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0); void sendSensorCommand(int8_t isWindy, int8_t isSunny, uint8_t repeat); void repeatFrame(uint8_t repeat); virtual uint16_t p_lastRollingCode(uint16_t code); @@ -277,10 +292,8 @@ class SomfyShade : public SomfyRemote { #ifdef USE_NVS void load(); #endif - //somfy_tx_queue_t txQueue; float currentPos = 0.0f; float currentTiltPos = 0.0f; - //uint16_t movement = 0; int8_t lastMovement = 0; int8_t direction = 0; // 0 = stopped, 1=down, -1=up. int8_t tiltDirection = 0; // 0=stopped, 1=clockwise, -1=counter clockwise @@ -291,9 +304,10 @@ class SomfyShade : public SomfyRemote { SomfyLinkedRemote linkedRemotes[SOMFY_MAX_LINKED_REMOTES]; bool paired = false; int8_t validateJSON(JsonObject &obj); - bool toJSONRef(JsonObject &obj); + void toJSONRef(JsonResponse &json); int8_t fromJSON(JsonObject &obj); - bool toJSON(JsonObject &obj) override; + void toJSON(JsonResponse &json) override; + char name[21] = ""; void setShadeId(uint8_t id) { shadeId = id; } uint8_t getShadeId() { return shadeId; } @@ -311,11 +325,12 @@ class SomfyShade : public SomfyRemote { void setMovement(int8_t dir); void setTarget(float target); bool isAtTarget(); + bool isToggle(); void moveToTarget(float pos, float tilt = -1.0f); void moveToTiltTarget(float target); void sendTiltCommand(somfy_commands cmd); void sendCommand(somfy_commands cmd); - void sendCommand(somfy_commands cmd, uint8_t repeat); + void sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0); bool linkRemote(uint32_t remoteAddress, uint16_t rollingCode = 0); bool unlinkRemote(uint32_t remoteAddress); void emitState(const char *evt = "shadeState"); @@ -377,7 +392,10 @@ class SomfyGroup : public SomfyRemote { bool save(); void clear(); bool fromJSON(JsonObject &obj); - bool toJSON(JsonObject &obj); + //bool toJSON(JsonObject &obj); + void toJSON(JsonResponse &json); + void toJSONRef(JsonResponse &json); + bool linkShade(uint8_t shadeId); bool unlinkShade(uint8_t shadeId); bool hasShadeId(uint8_t shadeId); @@ -391,7 +409,7 @@ class SomfyGroup : public SomfyRemote { void emitState(const char *evt = "groupState"); void emitState(uint8_t num, const char *evt = "groupState"); void sendCommand(somfy_commands cmd); - void sendCommand(somfy_commands cmd, uint8_t repeat); + void sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0); int8_t p_direction(int8_t dir); bool publish(const char *topic, uint8_t val, bool retain = false); bool publish(const char *topic, int8_t val, bool retain = false); @@ -466,7 +484,8 @@ struct transceiver_config_t { 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 toJSON(JsonObject& obj); + void toJSON(JsonResponse& json); void save(); void load(); void apply(); @@ -480,7 +499,8 @@ class Transceiver { public: transceiver_config_t config; bool printBuffer = false; - bool toJSON(JsonObject& obj); + //bool toJSON(JsonObject& obj); + void toJSON(JsonResponse& json); bool fromJSON(JsonObject& obj); bool save(); bool begin(); @@ -537,12 +557,10 @@ class SomfyShadeController { SomfyGroup groups[SOMFY_MAX_GROUPS]; bool linkRepeater(uint32_t address); bool unlinkRepeater(uint32_t address); - bool toJSON(DynamicJsonDocument &doc); - bool toJSON(JsonObject &obj); - bool toJSONRooms(JsonArray &arr); - bool toJSONShades(JsonArray &arr); - bool toJSONGroups(JsonArray &arr); - bool toJSONRepeaters(JsonArray &arr); + void toJSONShades(JsonResponse &json); + void toJSONRooms(JsonResponse &json); + void toJSONGroups(JsonResponse &json); + void toJSONRepeaters(JsonResponse &json); uint8_t repeaterCount(); uint8_t roomCount(); uint8_t shadeCount(); diff --git a/SomfyController.ino b/SomfyController.ino index 804c6fa..464d179 100644 --- a/SomfyController.ino +++ b/SomfyController.ino @@ -1,5 +1,6 @@ #include #include +#include #include "ConfigSettings.h" #include "Network.h" #include "Web.h" @@ -18,6 +19,7 @@ SomfyShadeController somfy; MQTTClass mqtt; GitUpdater git; +uint32_t oldheap = 0; void setup() { Serial.begin(115200); Serial.println(); @@ -35,34 +37,50 @@ void setup() { net.setup(); somfy.begin(); //git.checkForUpdate(); + esp_task_wdt_init(7, true); //enable panic so ESP32 restarts + esp_task_wdt_add(NULL); //add current thread to WDT watch + } void loop() { // put your main code here, to run repeatedly: + //uint32_t heap = ESP.getFreeHeap(); if(rebootDelay.reboot && millis() > rebootDelay.rebootTime) { Serial.print("Rebooting after "); Serial.print(rebootDelay.rebootTime); Serial.println("ms"); + net.end(); ESP.restart(); + return; } uint32_t timing = millis(); + net.loop(); if(millis() - timing > 100) Serial.printf("Timing Net: %ldms\n", millis() - timing); timing = millis(); + esp_task_wdt_reset(); somfy.loop(); if(millis() - timing > 100) Serial.printf("Timing Somfy: %ldms\n", millis() - timing); timing = millis(); - if(net.connected()) { - if(!rebootDelay.reboot) git.loop(); + esp_task_wdt_reset(); + if(net.connected() || net.softAPOpened) { + if(!rebootDelay.reboot && net.connected() && !net.softAPOpened) { + git.loop(); + esp_task_wdt_reset(); + } webServer.loop(); - if(millis() - timing > 200) Serial.printf("Timing WebServer: %ldms\n", millis() - timing); + esp_task_wdt_reset(); + if(millis() - timing > 100) Serial.printf("Timing WebServer: %ldms\n", millis() - timing); + esp_task_wdt_reset(); timing = millis(); sockEmit.loop(); if(millis() - timing > 100) Serial.printf("Timing Socket: %ldms\n", millis() - timing); + esp_task_wdt_reset(); timing = millis(); } if(rebootDelay.reboot && millis() > rebootDelay.rebootTime) { net.end(); ESP.restart(); } + esp_task_wdt_reset(); } diff --git a/SomfyController.ino.esp32.bin b/SomfyController.ino.esp32.bin index ca229da..bb87d58 100644 Binary files a/SomfyController.ino.esp32.bin and b/SomfyController.ino.esp32.bin differ diff --git a/SomfyController.ino.esp32s3.bin b/SomfyController.ino.esp32s3.bin index 8dd001b..76f303c 100644 Binary files a/SomfyController.ino.esp32s3.bin and b/SomfyController.ino.esp32s3.bin differ diff --git a/SomfyController.littlefs.bin b/SomfyController.littlefs.bin index 3fd9d09..98d9f83 100644 Binary files a/SomfyController.littlefs.bin and b/SomfyController.littlefs.bin differ diff --git a/Utils.cpp b/Utils.cpp index ece3c49..5d461a3 100644 --- a/Utils.cpp +++ b/Utils.cpp @@ -9,13 +9,13 @@ unsigned long Timestamp::epoch() { struct tm tmNow; time_t now; - if(!getLocalTime(&tmNow)) return 0; + if(!getLocalTime(&tmNow,50)) return 0; time(&now); return now; } time_t Timestamp::now() { struct tm tmNow; - getLocalTime(&tmNow); + getLocalTime(&tmNow,50); return mktime(&tmNow); } time_t Timestamp::getUTC() { @@ -36,7 +36,14 @@ time_t Timestamp::mkUTCTime(struct tm *dt) { return tsBadLocal + tsLocalOffset; } time_t Timestamp::parseUTCTime(const char *buff) { - struct tm dt = {0}; + struct tm dt; + dt.tm_hour = 0; + dt.tm_mday = 0; + dt.tm_mon = 0; + dt.tm_year = 0; + dt.tm_wday = 0; + dt.tm_yday = 0; + dt.tm_isdst = false; char num[5]; uint8_t i = 0; memset(num, 0x00, sizeof(num)); diff --git a/Utils.h b/Utils.h index b31e3ab..51a29b2 100644 --- a/Utils.h +++ b/Utils.h @@ -9,6 +9,7 @@ [[maybe_unused]] static void SETCHARPROP(char *prop, const char *value, size_t size) {strncpy(prop, value, size); prop[size - 1] = '\0';} +/* namespace util { // Createa a custom to_string function. C++ can be annoying // with all the trailing 0s on number formats. @@ -23,6 +24,8 @@ namespace util { return str; } } +*/ + static void _ltrim(char *str) { int s = 0, j, k = 0; int e = strlen(str); diff --git a/WResp.cpp b/WResp.cpp new file mode 100644 index 0000000..e44e3ba --- /dev/null +++ b/WResp.cpp @@ -0,0 +1,204 @@ +#include "WResp.h" +void JsonSockEvent::beginEvent(WebSocketsServer *server, const char *evt, char *buff, size_t buffSize) { + this->server = server; + this->buff = buff; + this->buffSize = buffSize; + this->_nocomma = true; + this->_closed = false; + snprintf(this->buff, buffSize, "42[%s,", evt); +} +void JsonSockEvent::closeEvent() { + if(!this->_closed) { + if(strlen(this->buff) < buffSize) strcat(this->buff, "]"); + else this->buff[buffSize - 1] = ']'; + } + this->_nocomma = true; + this->_closed = true; +} +void JsonSockEvent::endEvent(uint8_t num) { + this->closeEvent(); + if(num == 255) this->server->broadcastTXT(this->buff); + else this->server->sendTXT(num, this->buff); +} +void JsonSockEvent::_safecat(const char *val, bool escape) { + size_t len = (escape ? this->calcEscapedLength(val) : strlen(val)) + strlen(this->buff); + if(escape) len += 2; + if(len >= this->buffSize) { + Serial.printf("Socket exceeded buffer size %d - %d\n", this->buffSize, len); + Serial.println(this->buff); + return; + } + if(escape) strcat(this->buff, "\""); + if(escape) this->escapeString(val, &this->buff[strlen(this->buff)]); + else strcat(this->buff, val); + if(escape) strcat(this->buff, "\""); +} +void JsonResponse::beginResponse(WebServer *server, char *buff, size_t buffSize) { + this->server = server; + this->buff = buff; + this->buffSize = buffSize; + this->buff[0] = 0x00; + this->_nocomma = true; + server->setContentLength(CONTENT_LENGTH_UNKNOWN); +} +void JsonResponse::endResponse() { + if(strlen(buff)) this->send(); + server->sendContent("", 0); +} +void JsonResponse::send() { + if(!this->_headersSent) server->send_P(200, "application/json", this->buff); + else server->sendContent(this->buff); + //Serial.printf("Sent %d bytes %d\n", strlen(this->buff), this->buffSize); + this->buff[0] = 0x00; + this->_headersSent = true; +} +void JsonResponse::_safecat(const char *val, bool escape) { + size_t len = (escape ? this->calcEscapedLength(val) : strlen(val)) + strlen(this->buff); + if(escape) len += 2; + if(len >= this->buffSize) { + this->send(); + } + if(escape) strcat(this->buff, "\""); + if(escape) this->escapeString(val, &this->buff[strlen(this->buff)]); + else strcat(this->buff, val); + if(escape) strcat(this->buff, "\""); +} + +void JsonFormatter::beginObject(const char *name) { + if(name && strlen(name) > 0) this->appendElem(name); + else if(!this->_nocomma) this->_safecat(","); + this->_safecat("{"); + this->_objects++; + this->_nocomma = true; +} +void JsonFormatter::endObject() { + //if(strlen(this->buff) + 1 > this->buffSize - 1) this->send(); + this->_safecat("}"); + this->_objects--; + this->_nocomma = false; +} +void JsonFormatter::beginArray(const char *name) { + if(name && strlen(name) > 0) this->appendElem(name); + else if(!this->_nocomma) this->_safecat(","); + this->_safecat("["); + this->_arrays++; + this->_nocomma = true; +} +void JsonFormatter::endArray() { + //if(strlen(this->buff) + 1 > this->buffSize - 1) this->send(); + this->_safecat("]"); + this->_arrays--; + this->_nocomma = false; +} + +void JsonFormatter::appendElem(const char *name) { + if(!this->_nocomma) this->_safecat(","); + if(name && strlen(name) > 0) { + this->_safecat(name, true); + this->_safecat(":"); + } + this->_nocomma = false; +} + +void JsonFormatter::addElem(const char *name, const char *val) { + if(!val) return; + this->appendElem(name); + this->_safecat(val, true); +} +void JsonFormatter::addElem(const char *val) { this->addElem(nullptr, val); } +void JsonFormatter::addElem(float fval) { sprintf(this->_numbuff, "%.4f", fval); this->_appendNumber(nullptr); } +void JsonFormatter::addElem(int8_t nval) { sprintf(this->_numbuff, "%d", nval); this->_appendNumber(nullptr); } +void JsonFormatter::addElem(uint8_t nval) { sprintf(this->_numbuff, "%u", nval); this->_appendNumber(nullptr); } +void JsonFormatter::addElem(int32_t nval) { sprintf(this->_numbuff, "%ld", (long)nval); this->_appendNumber(nullptr); } +void JsonFormatter::addElem(uint32_t nval) { sprintf(this->_numbuff, "%lu", (unsigned long)nval); this->_appendNumber(nullptr); } + +/* +void JsonFormatter::addElem(int16_t nval) { sprintf(this->_numbuff, "%d", nval); this->_appendNumber(nullptr); } +void JsonFormatter::addElem(uint16_t nval) { sprintf(this->_numbuff, "%u", nval); this->_appendNumber(nullptr); } +void JsonFormatter::addElem(int64_t lval) { sprintf(this->_numbuff, "%lld", (long long)lval); this->_appendNumber(nullptr); } +void JsonFormatter::addElem(uint64_t lval) { sprintf(this->_numbuff, "%llu", (unsigned long long)lval); this->_appendNumber(nullptr); } +*/ +void JsonFormatter::addElem(bool bval) { strcpy(this->_numbuff, bval ? "true" : "false"); this->_appendNumber(nullptr); } + +void JsonFormatter::addElem(const char *name, float fval) { sprintf(this->_numbuff, "%.4f", fval); this->_appendNumber(name); } +void JsonFormatter::addElem(const char *name, int8_t nval) { sprintf(this->_numbuff, "%d", nval); this->_appendNumber(name); } +void JsonFormatter::addElem(const char *name, uint8_t nval) { sprintf(this->_numbuff, "%u", nval); this->_appendNumber(name); } +void JsonFormatter::addElem(const char *name, int32_t nval) { sprintf(this->_numbuff, "%ld", (long)nval); this->_appendNumber(name); } +void JsonFormatter::addElem(const char *name, uint32_t nval) { sprintf(this->_numbuff, "%lu", (unsigned long)nval); this->_appendNumber(name); } + +/* +void JsonFormatter::addElem(const char *name, int16_t nval) { sprintf(this->_numbuff, "%d", nval); this->_appendNumber(name); } +void JsonFormatter::addElem(const char *name, uint16_t nval) { sprintf(this->_numbuff, "%u", nval); this->_appendNumber(name); } +void JsonFormatter::addElem(const char *name, int64_t lval) { sprintf(this->_numbuff, "%lld", (long long)lval); this->_appendNumber(name); } +void JsonFormatter::addElem(const char *name, uint64_t lval) { sprintf(this->_numbuff, "%llu", (unsigned long long)lval); this->_appendNumber(name); } +*/ +void JsonFormatter::addElem(const char *name, bool bval) { strcpy(this->_numbuff, bval ? "true" : "false"); this->_appendNumber(name); } + +void JsonFormatter::_safecat(const char *val, bool escape) { + size_t len = (escape ? this->calcEscapedLength(val) : strlen(val)) + strlen(this->buff); + if(escape) len += 2; + if(len >= this->buffSize) { + return; + } + if(escape) strcat(this->buff, "\""); + if(escape) this->escapeString(val, &this->buff[strlen(this->buff)]); + else strcat(this->buff, val); + if(escape) strcat(this->buff, "\""); +} +void JsonFormatter::_appendNumber(const char *name) { this->appendElem(name); this->_safecat(this->_numbuff); } +uint32_t JsonFormatter::calcEscapedLength(const char *raw) { + uint32_t len = 0; + for(size_t i = strlen(raw); i > 0; i--) { + switch(raw[i]) { + case '"': + case '/': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + case '\\': + len += 2; + break; + default: + len++; + break; + } + } + return len; +} +void JsonFormatter::escapeString(const char *raw, char *escaped) { + for(uint32_t i = 0; i < strlen(raw); i++) { + switch(raw[i]) { + case '"': + strcat(escaped, "\\\""); + break; + case '/': + strcat(escaped, "\\/"); + break; + case '\b': + strcat(escaped, "\\b"); + break; + case '\f': + strcat(escaped, "\\f"); + break; + case '\n': + strcat(escaped, "\\n"); + break; + case '\r': + strcat(escaped, "\\r"); + break; + case '\t': + strcat(escaped, "\\t"); + break; + case '\\': + strcat(escaped, "\\\\"); + break; + default: + size_t len = strlen(escaped); + escaped[len] = raw[i]; + escaped[len+1] = 0x00; + break; + } + } +} diff --git a/WResp.h b/WResp.h new file mode 100644 index 0000000..4bda5d5 --- /dev/null +++ b/WResp.h @@ -0,0 +1,74 @@ +#include +#include +#include "Somfy.h" +#ifndef wresp_h +#define wresp_h + +class JsonFormatter { + protected: + char *buff; + size_t buffSize; + bool _headersSent = false; + uint8_t _objects = 0; + uint8_t _arrays = 0; + bool _nocomma = true; + char _numbuff[25] = {0}; + virtual void _safecat(const char *val, bool escape = false); + void _appendNumber(const char *name); + public: + void escapeString(const char *raw, char *escaped); + uint32_t calcEscapedLength(const char *raw); + void beginObject(const char *name = nullptr); + void endObject(); + void beginArray(const char *name = nullptr); + void endArray(); + void appendElem(const char *name = nullptr); + + void addElem(const char* val); + void addElem(float fval); + void addElem(int8_t nval); + void addElem(uint8_t nval); + /* + void addElem(int32_t nval); + void addElem(int16_t nval); + void addElem(uint16_t nval); + void addElem(unsigned int nval); + */ + void addElem(int32_t lval); + void addElem(uint32_t lval); + void addElem(bool bval); + + void addElem(const char* name, float fval); + void addElem(const char* name, int8_t nval); + void addElem(const char* name, uint8_t nval); + /* + void addElem(const char* name, int nval); + void addElem(const char* name, int16_t nval); + void addElem(const char* name, uint16_t nval); + void addElem(const char* name, unsigned int nval); + */ + void addElem(const char* name, int32_t lval); + void addElem(const char* name, uint32_t lval); + void addElem(const char* name, bool bval); + void addElem(const char *name, const char *val); +}; +class JsonResponse : public JsonFormatter { + protected: + void _safecat(const char *val, bool escape = false) override; + public: + WebServer *server; + void beginResponse(WebServer *server, char *buff, size_t buffSize); + void endResponse(); + void send(); +}; +class JsonSockEvent : public JsonFormatter { + protected: + bool _closed = false; + void _safecat(const char *val, bool escape = false) override; + public: + WebSocketsServer *server = nullptr; + void beginEvent(WebSocketsServer *server, const char *evt, char *buff, size_t buffSize); + void endEvent(uint8_t clientNum = 255); + void closeEvent(); +}; +#endif diff --git a/Web.cpp b/Web.cpp index 71b08f6..425d1b2 100644 --- a/Web.cpp +++ b/Web.cpp @@ -2,13 +2,15 @@ #include #include #include +#include #include "mbedtls/md.h" #include "ConfigSettings.h" #include "ConfigFile.h" -#include "Web.h" #include "Utils.h" #include "SSDP.h" #include "Somfy.h" +#include "WResp.h" +#include "Web.h" #include "MQTT.h" #include "GitOTA.h" #include "Network.h" @@ -23,7 +25,7 @@ extern GitUpdater git; extern Network net; //#define WEB_MAX_RESPONSE 34768 -#define WEB_MAX_RESPONSE 8192 +#define WEB_MAX_RESPONSE 4096 static char g_content[WEB_MAX_RESPONSE]; @@ -213,7 +215,7 @@ void Web::handleStreamFile(WebServer &server, const char *filename, const char * } webServer.sendCORSHeaders(server); if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - + esp_task_wdt_reset(); // Load the index html page from the data directory. Serial.print("Loading file "); Serial.println(filename); @@ -223,62 +225,11 @@ void Web::handleStreamFile(WebServer &server, const char *filename, const char * Serial.println(filename); server.send(500, _encoding_text, "Error opening file"); } + esp_task_wdt_delete(NULL); server.streamFile(file, encoding); file.close(); -} -void Web::chunkRoomsResponse(WebServer &server, const char * elem) { - uint8_t ndx = 0; - if(elem && strlen(elem) > 0) { - sprintf(g_content, "\"%s\"", elem); - server.sendContent(g_content); - } - for(uint8_t i = 0; i < SOMFY_MAX_ROOMS; i++) { - if(somfy.rooms[i].roomId != 0) { - DynamicJsonDocument doc(512); - JsonObject obj = doc.to(); - somfy.rooms[i].toJSON(obj); - strcpy(g_content, ndx++ != 0 ? "," : "["); - serializeJson(doc, &g_content[strlen(g_content)], sizeof(g_content) - strlen(g_content)); - server.sendContent(g_content); - } - } - server.sendContent(ndx == 0 ? "[]" : "]"); -} -void Web::chunkShadesResponse(WebServer &server, const char * elem) { - uint8_t ndx = 0; - if(elem && strlen(elem) > 0) { - sprintf(g_content, "\"%s\"", elem); - server.sendContent(g_content); - } - for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { - if(somfy.shades[i].getShadeId() != 255) { - DynamicJsonDocument doc(1024); - JsonObject obj = doc.to(); - somfy.shades[i].toJSON(obj); - strcpy(g_content, ndx++ != 0 ? "," : "["); - serializeJson(doc, &g_content[strlen(g_content)], sizeof(g_content) - strlen(g_content)); - server.sendContent(g_content); - } - } - server.sendContent(ndx == 0 ? "[]" : "]"); -} -void Web::chunkGroupsResponse(WebServer &server, const char * elem) { - uint8_t ndx = 0; - if(elem && strlen(elem) > 0) { - sprintf(g_content, "\"%s\"", elem); - server.sendContent(g_content); - } - for(uint8_t i = 0; i < SOMFY_MAX_GROUPS; i++) { - if(somfy.groups[i].getGroupId() != 255) { - DynamicJsonDocument doc(1024); - JsonObject obj = doc.to(); - somfy.groups[i].toJSON(obj); - strcpy(g_content, ndx++ != 0 ? "," : "["); - serializeJson(doc, &g_content[strlen(g_content)], sizeof(g_content) - strlen(g_content)); - server.sendContent(g_content); - } - } - server.sendContent(ndx == 0 ? "[]" : "]"); + esp_task_wdt_add(NULL); + esp_task_wdt_reset(); } void Web::handleController(WebServer &server) { webServer.sendCORSHeaders(server); @@ -286,69 +237,64 @@ void Web::handleController(WebServer &server) { HTTPMethod method = server.method(); settings.printAvailHeap(); if (method == HTTP_POST || method == HTTP_GET) { - server.setContentLength(CONTENT_LENGTH_UNKNOWN); - // Alright lets chunk our response. - snprintf(g_content, sizeof(g_content), "{\"maxRooms\":%d,\"maxShades\":%d,\"maxGroups\":%d,\"maxGroupedShades\":%d,\"maxLinkedRemotes\":%d,\"startingAddress\":%d,\"transceiver\":", - SOMFY_MAX_ROOMS, SOMFY_MAX_SHADES, SOMFY_MAX_GROUPS, SOMFY_MAX_GROUPED_SHADES, SOMFY_MAX_LINKED_REMOTES, somfy.startingAddress); - server.send_P(200, _encoding_json, g_content); - { - DynamicJsonDocument doc(1024); - JsonObject trans = doc.to(); - somfy.transceiver.toJSON(trans); - serializeJson(doc, g_content); - server.sendContent(g_content); - } - { - DynamicJsonDocument doc(512); - JsonObject fw = doc.to(); - git.toJSON(fw); - server.sendContent(",\"version\":"); - serializeJson(doc, g_content); - server.sendContent(g_content); - } - server.sendContent(",\"rooms\":"); - this->chunkRoomsResponse(server); - server.sendContent(",\"shades\":"); - this->chunkShadesResponse(server); - server.sendContent(",\"groups\":"); - this->chunkGroupsResponse(server); - server.sendContent(",\"repeaters\":"); - { - DynamicJsonDocument doc(512); - JsonArray r = doc.to(); - somfy.toJSONRepeaters(r); - serializeJson(doc, g_content); - server.sendContent(g_content); - } - server.sendContent("}"); - server.sendContent("", 0); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + resp.addElem("maxRooms", (uint8_t)SOMFY_MAX_ROOMS); + resp.addElem("maxShades", (uint8_t)SOMFY_MAX_SHADES); + resp.addElem("maxGroups", (uint8_t)SOMFY_MAX_GROUPS); + resp.addElem("maxGroupedShades", (uint8_t)SOMFY_MAX_GROUPED_SHADES); + resp.addElem("maxLinkedRemotes", (uint8_t)SOMFY_MAX_LINKED_REMOTES); + resp.addElem("startingAddress", (uint32_t)somfy.startingAddress); + resp.beginObject("transceiver"); + somfy.transceiver.toJSON(resp); + resp.endObject(); + resp.beginObject("version"); + git.toJSON(resp); + resp.endObject(); + resp.beginArray("rooms"); + somfy.toJSONRooms(resp); + resp.endArray(); + resp.beginArray("shades"); + somfy.toJSONShades(resp); + resp.endArray(); + resp.beginArray("groups"); + somfy.toJSONGroups(resp); + resp.endArray(); + resp.beginArray("repeaters"); + somfy.toJSONRepeaters(resp); + resp.endArray(); + resp.endObject(); + resp.endResponse(); } else server.send(404, _encoding_text, _response_404); } void Web::handleLoginContext(WebServer &server) { webServer.sendCORSHeaders(server); if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - DynamicJsonDocument doc(512); - JsonObject obj = doc.to(); - obj["type"] = static_cast(settings.Security.type); - obj["permissions"] = settings.Security.permissions; - obj["serverId"] = settings.serverId; - obj["version"] = settings.fwVersion.name; - obj["model"] = "ESPSomfyRTS"; - obj["hostname"] = settings.hostname; - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + resp.addElem("type", static_cast(settings.Security.type)); + resp.addElem("permissions", settings.Security.permissions); + resp.addElem("serverId", settings.serverId); + resp.addElem("version", settings.fwVersion.name); + resp.addElem("model", "ESPSomfyRTS"); + resp.addElem("hostname", settings.hostname); + resp.endObject(); + resp.endResponse(); } void Web::handleGetRepeaters(WebServer &server) { webServer.sendCORSHeaders(server); if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } HTTPMethod method = server.method(); if (method == HTTP_POST || method == HTTP_GET) { - DynamicJsonDocument doc(512); - JsonArray r = doc.to(); - somfy.toJSONRepeaters(r); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginArray(); + somfy.toJSONRepeaters(resp); + resp.endArray(); + resp.endResponse(); } else server.send(404, _encoding_text, _response_404); } @@ -357,10 +303,12 @@ void Web::handleGetRooms(WebServer &server) { if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } HTTPMethod method = server.method(); if (method == HTTP_POST || method == HTTP_GET) { - server.setContentLength(CONTENT_LENGTH_UNKNOWN); - server.send_P(200, _encoding_json, " "); - this->chunkRoomsResponse(server); - server.sendContent("", 0); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginArray(); + somfy.toJSONRooms(resp); + resp.endArray(); + resp.endResponse(); } else server.send(404, _encoding_text, _response_404); } @@ -369,10 +317,12 @@ void Web::handleGetShades(WebServer &server) { if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } HTTPMethod method = server.method(); if (method == HTTP_POST || method == HTTP_GET) { - server.setContentLength(CONTENT_LENGTH_UNKNOWN); - server.send_P(200, _encoding_json, " "); - this->chunkShadesResponse(server); - server.sendContent("", 0); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginArray(); + somfy.toJSONShades(resp); + resp.endArray(); + resp.endResponse(); } else server.send(404, _encoding_text, _response_404); } @@ -381,10 +331,12 @@ void Web::handleGetGroups(WebServer &server) { if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } HTTPMethod method = server.method(); if (method == HTTP_POST || method == HTTP_GET) { - server.setContentLength(CONTENT_LENGTH_UNKNOWN); - server.send_P(200, _encoding_json, " "); - this->chunkGroupsResponse(server); - server.sendContent("", 0); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginArray(); + somfy.toJSONGroups(resp); + resp.endArray(); + resp.endResponse(); } else server.send(404, _encoding_text, _response_404); } @@ -394,6 +346,7 @@ void Web::handleShadeCommand(WebServer& server) { HTTPMethod method = server.method(); uint8_t shadeId = 255; uint8_t target = 255; + uint8_t stepSize = 0; int8_t repeat = -1; somfy_commands command = somfy_commands::My; if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { @@ -402,10 +355,11 @@ void Web::handleShadeCommand(WebServer& server) { if (server.hasArg("command")) command = translateSomfyCommand(server.arg("command")); else if (server.hasArg("target")) target = atoi(server.arg("target").c_str()); if (server.hasArg("repeat")) repeat = atoi(server.arg("repeat").c_str()); + if(server.hasArg("stepSize")) stepSize = atoi(server.arg("stepSize").c_str()); } else if (server.hasArg("plain")) { Serial.println("Sending Shade Command"); - DynamicJsonDocument doc(256); + DynamicJsonDocument doc(512); DeserializationError err = deserializeJson(doc, server.arg("plain")); if (err) { this->handleDeserializationError(server, err); @@ -423,25 +377,25 @@ void Web::handleShadeCommand(WebServer& server) { target = obj["target"].as(); } if (obj.containsKey("repeat")) repeat = obj["repeat"].as(); + if(obj.containsKey("stepSize")) stepSize = obj["stepSize"].as(); } } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); SomfyShade* shade = somfy.getShadeById(shadeId); if (shade) { - Serial.print("Received:"); - Serial.println(server.arg("plain")); - // Send the command to the shade. - if (target <= 100) - shade->moveToTarget(shade->transformPosition(target)); - else if (repeat > 0) - shade->sendCommand(command, repeat); - else - shade->sendCommand(command); - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + Serial.print("Received:"); + Serial.println(server.arg("plain")); + // Send the command to the shade. + if (target <= 100) + shade->moveToTarget(shade->transformPosition(target)); + else + shade->sendCommand(command, repeat > 0 ? repeat : shade->repeats, stepSize); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + shade->toJSONRef(resp); + resp.endObject(); + resp.endResponse(); } else { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); @@ -456,6 +410,7 @@ void Web::handleRepeatCommand(WebServer& server) { if (method == HTTP_OPTIONS) { server.send(200, "OK"); return; } uint8_t shadeId = 255; uint8_t groupId = 255; + uint8_t stepSize = 0; int8_t repeat = -1; somfy_commands command = somfy_commands::My; if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { @@ -463,8 +418,9 @@ void Web::handleRepeatCommand(WebServer& server) { else if(server.hasArg("groupId")) groupId = atoi(server.arg("groupId").c_str()); if(server.hasArg("command")) command = translateSomfyCommand(server.arg("command")); if(server.hasArg("repeat")) repeat = atoi(server.arg("repeat").c_str()); + if(server.hasArg("stepSize")) stepSize = atoi(server.arg("stepSize").c_str()); if(shadeId == 255 && groupId == 255 && server.hasArg("plain")) { - DynamicJsonDocument doc(256); + DynamicJsonDocument doc(512); DeserializationError err = deserializeJson(doc, server.arg("plain")); if (err) { this->handleDeserializationError(server, err); @@ -474,6 +430,7 @@ void Web::handleRepeatCommand(WebServer& server) { JsonObject obj = doc.as(); if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; if(obj.containsKey("groupId")) groupId = obj["groupId"]; + if(obj.containsKey("stepSize")) stepSize = obj["stepSize"]; if (obj.containsKey("command")) { String scmd = obj["command"]; command = translateSomfyCommand(scmd); @@ -481,8 +438,8 @@ void Web::handleRepeatCommand(WebServer& server) { if (obj.containsKey("repeat")) repeat = obj["repeat"].as(); } } - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); + //DynamicJsonDocument sdoc(512); + //JsonObject sobj = sdoc.to(); if(shadeId != 255) { SomfyShade *shade = somfy.getShadeById(shadeId); if(!shade) { @@ -492,14 +449,17 @@ void Web::handleRepeatCommand(WebServer& server) { if(shade->shadeType == shade_types::garage1 && command == somfy_commands::Prog) command = somfy_commands::Toggle; if(!shade->isLastCommand(command)) { // We are going to send this as a new command. - shade->sendCommand(command, repeat >= 0 ? repeat : shade->repeats); + shade->sendCommand(command, repeat >= 0 ? repeat : shade->repeats, stepSize); } else { shade->repeatFrame(repeat >= 0 ? repeat : shade->repeats); } - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginArray(); + shade->toJSONRef(resp); + resp.endArray(); + resp.endResponse(); } else if(groupId != 255) { SomfyGroup * group = somfy.getGroupById(groupId); @@ -509,13 +469,20 @@ void Web::handleRepeatCommand(WebServer& server) { } if(!group->isLastCommand(command)) { // We are going to send this as a new command. - group->sendCommand(command, repeat >= 0 ? repeat : group->repeats); + group->sendCommand(command, repeat >= 0 ? repeat : group->repeats, stepSize); } else group->repeatFrame(repeat >= 0 ? repeat : group->repeats); - group->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + group->toJSONRef(resp); + resp.endObject(); + resp.endResponse(); + + //group->toJSON(sobj); + //serializeJson(sdoc, g_content); + //server.send(200, _encoding_json, g_content); } } else { @@ -527,6 +494,7 @@ void Web::handleGroupCommand(WebServer &server) { if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } HTTPMethod method = server.method(); uint8_t groupId = 255; + uint8_t stepSize = 0; int8_t repeat = -1; somfy_commands command = somfy_commands::My; if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { @@ -534,6 +502,7 @@ void Web::handleGroupCommand(WebServer &server) { groupId = atoi(server.arg("groupId").c_str()); if (server.hasArg("command")) command = translateSomfyCommand(server.arg("command")); if(server.hasArg("repeat")) repeat = atoi(server.arg("repeat").c_str()); + if(server.hasArg("stepSize")) stepSize = atoi(server.arg("stepSize").c_str()); } else if (server.hasArg("plain")) { Serial.println("Sending Group Command"); @@ -546,12 +515,16 @@ void Web::handleGroupCommand(WebServer &server) { else { JsonObject obj = doc.as(); if (obj.containsKey("groupId")) groupId = obj["groupId"]; - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group id was supplied.\"}")); + else { + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group id was supplied.\"}")); + return; + } if (obj.containsKey("command")) { String scmd = obj["command"]; command = translateSomfyCommand(scmd); } if(obj.containsKey("repeat")) repeat = obj["repeat"].as(); + if(obj.containsKey("stepSize")) stepSize = obj["stepSize"].as(); } } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group object supplied.\"}")); @@ -560,13 +533,13 @@ void Web::handleGroupCommand(WebServer &server) { Serial.print("Received:"); Serial.println(server.arg("plain")); // Send the command to the group. - if(repeat > 0) group->sendCommand(command, repeat); - else group->sendCommand(command); - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - group->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + group->sendCommand(command, repeat >= 0 ? repeat : group->repeats, stepSize); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + group->toJSONRef(resp); + resp.endObject(); + resp.endResponse(); } else { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group with the specified id not found.\"}")); @@ -619,11 +592,12 @@ void Web::handleTiltCommand(WebServer &server) { shade->moveToTiltTarget(shade->transformPosition(target)); else shade->sendTiltCommand(command); - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + shade->toJSONRef(resp); + resp.endObject(); + resp.endResponse(); } else { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); @@ -641,11 +615,12 @@ void Web::handleRoom(WebServer &server) { int roomId = atoi(server.arg("roomId").c_str()); SomfyRoom* room = somfy.getRoomById(roomId); if (room) { - DynamicJsonDocument doc(512); - JsonObject obj = doc.to(); - room->toJSON(obj); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + room->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Room Id not found.\"}")); } @@ -671,11 +646,12 @@ void Web::handleRoom(WebServer &server) { uint8_t err = room->fromJSON(obj); if(err == 0) { room->save(); - DynamicJsonDocument sdoc(2048); - JsonObject sobj = sdoc.to(); - room->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + room->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else { snprintf(g_content, sizeof(g_content), "{\"status\":\"DATA\",\"desc\":\"Data Error.\", \"code\":%d}", err); @@ -701,11 +677,12 @@ void Web::handleShade(WebServer &server) { int shadeId = atoi(server.arg("shadeId").c_str()); SomfyShade* shade = somfy.getShadeById(shadeId); if (shade) { - DynamicJsonDocument doc(2048); - JsonObject obj = doc.to(); - shade->toJSON(obj); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + shade->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}")); } @@ -731,11 +708,12 @@ void Web::handleShade(WebServer &server) { uint8_t err = shade->fromJSON(obj); if(err == 0) { shade->save(); - DynamicJsonDocument sdoc(2048); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + shade->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else { snprintf(g_content, sizeof(g_content), "{\"status\":\"DATA\",\"desc\":\"Data Error.\", \"code\":%d}", err); @@ -761,11 +739,12 @@ void Web::handleGroup(WebServer &server) { int groupId = atoi(server.arg("groupId").c_str()); SomfyGroup* group = somfy.getGroupById(groupId); if (group) { - DynamicJsonDocument doc(2048); - JsonObject obj = doc.to(); - group->toJSON(obj); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + group->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group Id not found.\"}")); } @@ -790,11 +769,12 @@ void Web::handleGroup(WebServer &server) { if (group) { group->fromJSON(obj); group->save(); - DynamicJsonDocument sdoc(2048); - JsonObject sobj = sdoc.to(); - group->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + group->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group Id not found.\"}")); } @@ -810,32 +790,40 @@ void Web::handleDiscovery(WebServer &server) { HTTPMethod method = apiServer.method(); if (method == HTTP_POST || method == HTTP_GET) { Serial.println("Discovery Requested"); - server.setContentLength(CONTENT_LENGTH_UNKNOWN); - // Alright lets chunk our response. char connType[10] = "Unknown"; - if(net.connType == conn_types::ethernet) strcpy(connType, "Ethernet"); - else if(net.connType == conn_types::wifi) strcpy(connType, "Wifi"); - snprintf(g_content, sizeof(g_content), "{\"serverId\":\"%s\",\"version\":\"%s\",\"latest\":\"%s\",\"model\":\"%s\",\"hostname\":\"%s\",\"authType\":%d,\"permissions\":%d,\"chipModel\":\"%s\",\"connType\":\"%s\",\"checkForUpdate\":%s", - settings.serverId, settings.fwVersion.name, git.latest.name, "ESPSomfyRTS", settings.hostname, static_cast(settings.Security.type), settings.Security.permissions, settings.chipModel, connType, settings.checkForUpdate ? "true" : "false"); - server.send_P(200, _encoding_json, g_content); - /* - if(net.connType == conn_types::ethernet) { - snprintf(g_content, sizeof(g_content), ",\"ethernet\":{\"connected\":true,\"speed\":%d,\"fullduplex\":%s}", ETH.linkSpeed(), ETH.fullDuplex() ? "true" : "false"); - server.sendContent(g_content); - } - else { - snprintf(g_content, sizeof(g_content), ",\"wifi\":{\"ssid\":\"%s\",\"strength\":%d,\"channel\":%d}", WiFi.SSID().c_str(), WiFi.RSSI(), WiFi.channel()); - server.sendContent(g_content); - } - */ - server.sendContent(",\"rooms\":"); - this->chunkRoomsResponse(server); - server.sendContent(",\"shades\":"); - this->chunkShadesResponse(server); - server.sendContent(",\"groups\":"); - this->chunkGroupsResponse(server); - server.sendContent("}"); - server.sendContent("", 0); + if(net.connType == conn_types_t::ethernet) strcpy(connType, "Ethernet"); + else if(net.connType == conn_types_t::wifi) strcpy(connType, "Wifi"); + + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + resp.addElem("serverId", settings.serverId); + resp.addElem("version", settings.fwVersion.name); + resp.addElem("latest", git.latest.name); + resp.addElem("model", "ESPSomfyRTS"); + resp.addElem("hostname", settings.hostname); + resp.addElem("authType", static_cast(settings.Security.type)); + resp.addElem("permissions", settings.Security.permissions); + resp.addElem("chipModel", settings.chipModel); + resp.addElem("connType", connType); + resp.addElem("checkForUpdate", settings.checkForUpdate); + resp.beginObject("memory"); + resp.addElem("max", ESP.getMaxAllocHeap()); + resp.addElem("free", ESP.getFreeHeap()); + resp.addElem("min", ESP.getMinFreeHeap()); + resp.addElem("total", ESP.getHeapSize()); + resp.endObject(); + resp.beginArray("rooms"); + somfy.toJSONRooms(resp); + resp.endArray(); + resp.beginArray("shades"); + somfy.toJSONShades(resp); + resp.endArray(); + resp.beginArray("groups"); + somfy.toJSONGroups(resp); + resp.endArray(); + resp.endObject(); + resp.endResponse(); net.needsBroadcast = true; } else @@ -902,11 +890,12 @@ void Web::handleSetPositions(WebServer &server) { if(pos >= 0) shade->target = shade->currentPos = pos; if(tiltPos >= 0 && shade->tiltType != tilt_types::none) shade->tiltTarget = shade->currentTiltPos = tiltPos; shade->emitState(); - DynamicJsonDocument sdoc(2048); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + shade->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"An invalid shadeId was provided\"}")); @@ -954,11 +943,12 @@ void Web::handleSetSensor(WebServer &server) { if(shade) { shade->sendSensorCommand(windy, sunny, repeat >= 0 ? (uint8_t)repeat : shade->repeats); shade->emitState(); - DynamicJsonDocument sdoc(2048); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + shade->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"An invalid shadeId was provided\"}")); @@ -969,11 +959,12 @@ void Web::handleSetSensor(WebServer &server) { if(group) { group->sendSensorCommand(windy, sunny, repeat >= 0 ? (uint8_t)repeat : group->repeats); group->emitState(); - DynamicJsonDocument sdoc(2048); - JsonObject sobj = sdoc.to(); - group->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + group->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"An invalid groupId was provided\"}")); @@ -1004,11 +995,12 @@ void Web::handleDownloadFirmware(WebServer &server) { } } if(rel) { - DynamicJsonDocument sdoc(1024); - JsonObject sobj = sdoc.to(); - rel->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + rel->toJSON(resp); + resp.endObject(); + resp.endResponse(); strcpy(git.targetRelease, rel->name); git.status = GIT_AWAITING_UPDATE; } @@ -1110,25 +1102,31 @@ void Web::begin() { GitRepo repo; repo.getReleases(); git.setCurrentRelease(repo); - DynamicJsonDocument doc(2048); - JsonObject obj = doc.to(); - repo.toJSON(obj); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + repo.toJSON(resp); + resp.endObject(); + resp.endResponse(); }); server.on("/downloadFirmware", []() { webServer.handleDownloadFirmware(server); }); server.on("/cancelFirmware", []() { webServer.sendCORSHeaders(server); if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); + // If we are currently downloading the filesystem we cannot cancel. if(!git.lockFS) { git.status = GIT_UPDATE_CANCELLING; - git.toJSON(sobj); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + git.toJSON(resp); + resp.endObject(); + resp.endResponse(); git.cancelled = true; } - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + else { + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Cannot cancel during filesystem update.\"}")); + } }); server.on("/backup", []() { webServer.handleBackup(server, true); }); server.on("/restore", HTTP_POST, []() { @@ -1160,6 +1158,7 @@ void Web::begin() { rebootDelay.rebootTime = millis() + 1000; } }, []() { + esp_task_wdt_reset(); HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { webServer.uploadSuccess = false; @@ -1170,12 +1169,15 @@ void Web::begin() { } else if (upload.status == UPLOAD_FILE_WRITE) { File fup = LittleFS.open("/shades.tmp", "a"); + //upload.buf[upload.currentSize] = 0x00; + //Serial.print((char *)upload.buf); fup.write(upload.buf, upload.currentSize); fup.close(); } else if (upload.status == UPLOAD_FILE_END) { webServer.uploadSuccess = true; } + }); server.on("/index.js", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/index.js", "text/javascript"); }); server.on("/main.css", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/main.css", "text/css"); }); @@ -1183,6 +1185,8 @@ void Web::begin() { server.on("/icons.css", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/icons.css", "text/css"); }); server.on("/favicon.png", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/favicon.png", "image/png"); }); server.on("/icon.png", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/icon.png", "image/png"); }); + server.on("/icon.svg", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/icon.svg", "image/svg+xml"); }); + server.on("/apple-icon.png", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/apple-icon.png", "image/png"); }); server.onNotFound([]() { webServer.handleNotFound(server); }); server.on("/controller", []() { webServer.handleController(server); }); server.on("/rooms", []() { webServer.handleGetRooms(server); }); @@ -1194,39 +1198,40 @@ void Web::begin() { server.on("/getNextRoom", []() { webServer.sendCORSHeaders(server); if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - StaticJsonDocument<256> doc; - uint8_t roomId = somfy.getNextRoomId(); - JsonObject obj = doc.to(); - obj["roomId"] = roomId; - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + resp.addElem("roomId", somfy.getNextRoomId()); + resp.endObject(); + resp.endResponse(); }); server.on("/getNextShade", []() { webServer.sendCORSHeaders(server); if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - StaticJsonDocument<256> doc; uint8_t shadeId = somfy.getNextShadeId(); - JsonObject obj = doc.to(); - obj["shadeId"] = shadeId; - obj["remoteAddress"] = somfy.getNextRemoteAddress(shadeId); - obj["bitLength"] = somfy.transceiver.config.type; - obj["stepSize"] = 100; - obj["proto"] = static_cast(somfy.transceiver.config.proto); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + resp.addElem("shadeId", shadeId); + resp.addElem("remoteAddress", (uint32_t)somfy.getNextRemoteAddress(shadeId)); + resp.addElem("bitLength", somfy.transceiver.config.type); + resp.addElem("stepSize", (uint8_t)100); + resp.addElem("proto", static_cast(somfy.transceiver.config.proto)); + resp.endObject(); + resp.endResponse(); }); server.on("/getNextGroup", []() { webServer.sendCORSHeaders(server); - StaticJsonDocument<256> doc; - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } uint8_t groupId = somfy.getNextGroupId(); - JsonObject obj = doc.to(); - obj["groupId"] = groupId; - obj["remoteAddress"] = somfy.getNextRemoteAddress(groupId); - obj["bitLength"] = somfy.transceiver.config.type; - obj["proto"] = static_cast(somfy.transceiver.config.proto); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + resp.addElem("groupId", groupId); + resp.addElem("remoteAddress", (uint32_t)somfy.getNextRemoteAddress(groupId)); + resp.addElem("bitLength", somfy.transceiver.config.type); + resp.addElem("proto", static_cast(somfy.transceiver.config.proto)); + resp.endObject(); + resp.endResponse(); }); server.on("/addRoom", []() { if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } @@ -1245,30 +1250,25 @@ void Web::begin() { Serial.println("Counting rooms"); if (somfy.roomCount() > SOMFY_MAX_ROOMS) { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Maximum number of rooms exceeded.\"}")); + return; } else { Serial.println("Adding room"); room = somfy.addRoom(obj); - if (room) { - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - room->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); - } - else { + if (!room) { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error adding room.\"}")); + return; } } } } if (room) { - DynamicJsonDocument doc(256); - JsonObject obj = doc.to(); - room->toJSON(obj); - serializeJson(doc, g_content); - Serial.println(g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + room->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error saving Somfy Room.\"}")); @@ -1291,31 +1291,26 @@ void Web::begin() { Serial.println("Counting shades"); if (somfy.shadeCount() > SOMFY_MAX_SHADES) { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Maximum number of shades exceeded.\"}")); + return; } else { Serial.println("Adding shade"); shade = somfy.addShade(obj); - if (shade) { - DynamicJsonDocument sdoc(1024); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); - } - else { + if (!shade) { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error adding shade.\"}")); + return; } } } } if (shade) { //Serial.println("Serializing shade"); - DynamicJsonDocument doc(1024); - JsonObject obj = doc.to(); - shade->toJSON(obj); - serializeJson(doc, g_content); - //Serial.println(g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + shade->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error saving Somfy Shade.\"}")); @@ -1338,30 +1333,25 @@ void Web::begin() { Serial.println("Counting shades"); if (somfy.groupCount() > SOMFY_MAX_GROUPS) { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Maximum number of groups exceeded.\"}")); + return; } else { Serial.println("Adding group"); group = somfy.addGroup(obj); - if (group) { - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - group->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); - } - else { + if (!group) { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error adding group.\"}")); + return; } } } } if (group) { - DynamicJsonDocument doc(256); - JsonObject obj = doc.to(); - group->toJSON(obj); - serializeJson(doc, g_content); - Serial.println(g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + group->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error saving Somfy Group.\"}")); @@ -1376,10 +1366,11 @@ void Web::begin() { int groupId = atoi(server.arg("groupId").c_str()); SomfyGroup* group = somfy.getGroupById(groupId); if (group) { - DynamicJsonDocument doc(8192); - JsonObject obj = doc.to(); - group->toJSON(obj); - JsonArray arr = obj.createNestedArray("availShades"); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + group->toJSON(resp); + resp.beginArray("availShades"); for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { SomfyShade *shade = &somfy.shades[i]; if(shade->getShadeId() != 255) { @@ -1391,13 +1382,15 @@ void Web::begin() { } } if(!isLinked) { - JsonObject s = arr.createNestedObject(); - shade->toJSONRef(s); + resp.beginObject(); + shade->toJSONRef(resp); + resp.endObject(); } } } - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + resp.endArray(); + resp.endObject(); + resp.endResponse(); } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group Id not found.\"}")); } @@ -1428,11 +1421,12 @@ void Web::begin() { if (room) { room->fromJSON(obj); room->save(); - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - room->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + room->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Room Id not found.\"}")); } @@ -1465,11 +1459,12 @@ void Web::begin() { int8_t err = shade->fromJSON(obj); if(err == 0) { shade->save(); - DynamicJsonDocument sdoc(1024); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + shade->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else { snprintf(g_content, sizeof(g_content), "{\"status\":\"DATA\",\"desc\":\"Data Error.\", \"code\":%d}", err); @@ -1505,11 +1500,12 @@ void Web::begin() { if (group) { group->fromJSON(obj); group->save(); - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - group->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + group->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group Id not found.\"}")); } @@ -1555,11 +1551,12 @@ void Web::begin() { if(shade->tiltType == tilt_types::none) tilt = -1; if(pos >= 0 && pos <= 100) shade->setMyPosition(shade->transformPosition(pos), shade->transformPosition(tilt)); - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + shade->toJSONRef(resp); + resp.endObject(); + resp.endResponse(); } else { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); @@ -1600,11 +1597,12 @@ void Web::begin() { } else { shade->setRollingCode(rollingCode); - DynamicJsonDocument doc(512); - JsonObject obj = doc.to(); - shade->toJSON(obj); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + shade->toJSON(resp); + resp.endObject(); + resp.endResponse(); } } }); @@ -1638,11 +1636,12 @@ void Web::begin() { else { shade->paired = paired; shade->save(); - DynamicJsonDocument doc(1024); - JsonObject obj = doc.to(); - shade->toJSON(obj); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + shade->toJSON(resp); + resp.endObject(); + resp.endResponse(); } }); server.on("/unpairShade", []() { @@ -1678,11 +1677,12 @@ void Web::begin() { shade->sendCommand(somfy_commands::Prog, 1); shade->paired = false; shade->save(); - DynamicJsonDocument doc(1024); - JsonObject obj = doc.to(); - shade->toJSON(obj); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + shade->toJSON(resp); + resp.endObject(); + resp.endResponse(); } } }); @@ -1713,11 +1713,12 @@ void Web::begin() { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No repeater address was supplied.\"}")); else { somfy.linkRepeater(address); - DynamicJsonDocument doc(512); - JsonArray r = doc.to(); - somfy.toJSONRepeaters(r); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginArray(); + somfy.toJSONRepeaters(resp); + resp.endArray(); + resp.endResponse(); } } }); @@ -1748,11 +1749,12 @@ void Web::begin() { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No repeater address was supplied.\"}")); else { somfy.unlinkRepeater(address); - DynamicJsonDocument doc(512); - JsonArray r = doc.to(); - somfy.toJSONRepeaters(r); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginArray(); + somfy.toJSONRepeaters(resp); + resp.endArray(); + resp.endResponse(); } } }); @@ -1781,11 +1783,12 @@ void Web::begin() { else { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Remote address not provided.\"}")); } - DynamicJsonDocument sdoc(2048); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + shade->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}")); } @@ -1821,11 +1824,12 @@ void Web::begin() { else { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Remote address not provided.\"}")); } - DynamicJsonDocument sdoc(2048); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + shade->toJSON(resp); + resp.endObject(); + resp.endResponse(); } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}")); } @@ -1871,11 +1875,12 @@ void Web::begin() { return; } group->linkShade(shadeId); - DynamicJsonDocument sdoc(2048); - JsonObject sobj = sdoc.to(); - group->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + group->toJSON(resp); + resp.endObject(); + resp.endResponse(); } } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No linking object supplied.\"}")); @@ -1926,11 +1931,12 @@ void Web::begin() { return; } group->unlinkShade(shadeId); - DynamicJsonDocument sdoc(2048); - JsonObject sobj = sdoc.to(); - group->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + group->toJSON(resp); + resp.endObject(); + resp.endResponse(); } } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No unlinking object supplied.\"}")); @@ -2079,6 +2085,7 @@ void Web::begin() { Update.printError(Serial); } } + esp_task_wdt_reset(); }); server.on("/updateShadeConfig", HTTP_POST, []() { if(git.lockFS) { @@ -2156,31 +2163,44 @@ void Web::begin() { Update.printError(Serial); } } + esp_task_wdt_reset(); }); server.on("/scanaps", []() { webServer.sendCORSHeaders(server); + esp_task_wdt_reset(); + if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - int statusCode = 200; - int n = WiFi.scanNetworks(); + esp_task_wdt_delete(NULL); + if(net.softAPOpened) WiFi.disconnect(false); + int n = WiFi.scanNetworks(false, true); + esp_task_wdt_add(NULL); + Serial.print("Scanned "); Serial.print(n); Serial.println(" networks"); // Ok we need to chunk this response as well. - server.setContentLength(CONTENT_LENGTH_UNKNOWN); - // Alright lets chunk our response to this because we cannot allocate all that memory. - snprintf(g_content, sizeof(g_content), "{\"connected\":{\"name\":\"%s\",\"passphrase\":\"%s\",\"strength\":%d,\"channel\":%d},\"accessPoints\":[", - settings.WIFI.ssid, settings.WIFI.passphrase, WiFi.RSSI(), WiFi.channel()); - server.send_P(200, _encoding_json, g_content); - bool bFirst = true; + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + resp.beginObject("connected"); + resp.addElem("name", settings.WIFI.ssid); + resp.addElem("passphrase", settings.WIFI.passphrase); + resp.addElem("strength", (int32_t)WiFi.RSSI()); + resp.addElem("channel", (int32_t)WiFi.channel()); + resp.endObject(); + resp.beginArray("accessPoints"); for(int i = 0; i < n; ++i) { if(WiFi.SSID(i).length() == 0 || WiFi.RSSI(i) < -95) continue; // Ignore hidden and weak networks that we cannot connect to anyway. - snprintf(g_content, sizeof(g_content), "%s{\"name\":\"%s\",\"channel\":%d,\"encryption\":\"%s\",\"strength\":%d,\"macAddress\":\"%s\"}", - bFirst ? "" : ",", WiFi.SSID(i).c_str(), WiFi.channel(i), settings.WIFI.mapEncryptionType(WiFi.encryptionType(i)).c_str(), WiFi.RSSI(i), WiFi.BSSIDstr(i).c_str()); - server.sendContent(g_content); - bFirst = false; + resp.beginObject(); + resp.addElem("name", WiFi.SSID(i).c_str()); + resp.addElem("channel", (int32_t)WiFi.channel(i)); + resp.addElem("strength", (int32_t)WiFi.RSSI(i)); + resp.addElem("macAddress", WiFi.BSSIDstr(i).c_str()); + resp.endObject(); } - server.sendContent("]}"); - server.sendContent("", 0); + resp.endArray(); + resp.endObject(); + resp.endResponse(); }); server.on("/reboot", []() { webServer.handleReboot(server);}); server.on("/saveSecurity", []() { @@ -2239,12 +2259,12 @@ void Web::begin() { if (method == HTTP_POST || method == HTTP_PUT) { somfy.transceiver.fromJSON(obj); somfy.transceiver.save(); - DynamicJsonDocument sdoc(1024); - JsonObject sobj = sdoc.to(); - somfy.transceiver.toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); - //server.send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully saved radio\"}"); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + somfy.transceiver.toJSON(resp); + resp.endObject(); + resp.endResponse(); } else { server.send(201, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); @@ -2253,11 +2273,12 @@ void Web::begin() { }); server.on("/getRadio", []() { webServer.sendCORSHeaders(server); - DynamicJsonDocument doc(1024); - JsonObject obj = doc.to(); - somfy.transceiver.toJSON(obj); - serializeJson(doc, g_content); - server.send(200, _encoding_json, g_content); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + somfy.transceiver.toJSON(resp); + resp.endObject(); + resp.endResponse(); }); server.on("/sendRemoteCommand", []() { webServer.sendCORSHeaders(server); @@ -2353,25 +2374,32 @@ void Web::begin() { // Parse out all the inputs. bool reboot = false; if(obj.containsKey("connType") && obj["connType"].as() != static_cast(settings.connType)) { - settings.connType = static_cast(obj["connType"].as()); + settings.connType = static_cast(obj["connType"].as()); settings.save(); reboot = true; } - if(settings.connType == conn_types::wifi) { - if(obj.containsKey("ssid") && obj["ssid"].as().compareTo(settings.WIFI.ssid) != 0) reboot = true; - if(obj.containsKey("passphrase") && obj["passphrase"].as().compareTo(settings.WIFI.passphrase) != 0) reboot = true; + if(obj.containsKey("wifi")) { + JsonObject objWifi = obj["wifi"]; + if(settings.connType == conn_types_t::wifi) { + if(objWifi.containsKey("ssid") && objWifi["ssid"].as().compareTo(settings.WIFI.ssid) != 0) { + if(WiFi.softAPgetStationNum() == 0) reboot = true; + } + if(objWifi.containsKey("passphrase") && objWifi["passphrase"].as().compareTo(settings.WIFI.passphrase) != 0) { + if(WiFi.softAPgetStationNum() == 0) reboot = true; + } + } + settings.WIFI.fromJSON(objWifi); + settings.WIFI.save(); } - else { + if(obj.containsKey("ethernet")) + { + JsonObject objEth = obj["ethernet"]; // This is an ethernet connection so if anything changes we need to reboot. - reboot = true; + if(settings.connType == conn_types_t::ethernet || settings.connType == conn_types_t::ethernetpref) + reboot = true; + settings.Ethernet.fromJSON(objEth); + settings.Ethernet.save(); } - JsonObject objWifi = obj["wifi"]; - JsonObject objEth = obj["ethernet"]; - settings.WIFI.fromJSON(objWifi); - settings.Ethernet.fromJSON(objEth); - - settings.WIFI.save(); - settings.Ethernet.save(); if (reboot) { Serial.println("Rebooting ESP for new Network settings..."); rebootDelay.reboot = true; @@ -2453,6 +2481,15 @@ void Web::begin() { }); server.on("/modulesettings", []() { webServer.sendCORSHeaders(server); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + resp.addElem("fwVersion", settings.fwVersion.name); + settings.toJSON(resp); + settings.NTP.toJSON(resp); + resp.endObject(); + resp.endResponse(); + /* DynamicJsonDocument doc(512); JsonObject obj = doc.to(); doc["fwVersion"] = settings.fwVersion.name; @@ -2462,9 +2499,28 @@ void Web::begin() { settings.NTP.toJSON(obj); serializeJson(doc, g_content); server.send(200, _encoding_json, g_content); + */ }); server.on("/networksettings", []() { webServer.sendCORSHeaders(server); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + settings.toJSON(resp); + resp.addElem("fwVersion", settings.fwVersion.name); + resp.beginObject("ethernet"); + settings.Ethernet.toJSON(resp); + resp.endObject(); + resp.beginObject("wifi"); + settings.WIFI.toJSON(resp); + resp.endObject(); + resp.beginObject("ip"); + settings.IP.toJSON(resp); + resp.endObject(); + resp.endObject(); + resp.endResponse(); + + /* DynamicJsonDocument doc(2048); JsonObject obj = doc.to(); doc["fwVersion"] = settings.fwVersion.name; @@ -2477,6 +2533,7 @@ void Web::begin() { settings.IP.toJSON(ip); serializeJson(doc, g_content); server.send(200, _encoding_json, g_content); + */ }); server.on("/connectmqtt", []() { if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } @@ -2496,12 +2553,19 @@ void Web::begin() { mqtt.disconnect(); settings.MQTT.fromJSON(obj); settings.MQTT.save(); - + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + settings.MQTT.toJSON(resp); + resp.endObject(); + resp.endResponse(); + /* DynamicJsonDocument sdoc(1024); JsonObject sobj = sdoc.to(); settings.MQTT.toJSON(sobj); serializeJson(sdoc, g_content); server.send(200, _encoding_json, g_content); + */ } else { server.send(201, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); @@ -2510,11 +2574,20 @@ void Web::begin() { }); server.on("/mqttsettings", []() { webServer.sendCORSHeaders(server); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + settings.MQTT.toJSON(resp); + resp.endObject(); + resp.endResponse(); + + /* DynamicJsonDocument doc(1024); JsonObject obj = doc.to(); settings.MQTT.toJSON(obj); serializeJson(doc, g_content); server.send(200, _encoding_json, g_content); + */ }); server.on("/roomSortOrder", []() { if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } @@ -2612,20 +2685,36 @@ void Web::begin() { server.on("/beginFrequencyScan", []() { webServer.sendCORSHeaders(server); somfy.transceiver.beginFrequencyScan(); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + somfy.transceiver.toJSON(resp); + resp.endObject(); + resp.endResponse(); + /* DynamicJsonDocument doc(1024); JsonObject obj = doc.to(); somfy.transceiver.toJSON(obj); serializeJson(doc, g_content); server.send(200, _encoding_json, g_content); + */ }); server.on("/endFrequencyScan", []() { webServer.sendCORSHeaders(server); somfy.transceiver.endFrequencyScan(); + JsonResponse resp; + resp.beginResponse(&server, g_content, sizeof(g_content)); + resp.beginObject(); + somfy.transceiver.toJSON(resp); + resp.endObject(); + resp.endResponse(); + /* DynamicJsonDocument doc(1024); JsonObject obj = doc.to(); somfy.transceiver.toJSON(obj); serializeJson(doc, g_content); server.send(200, _encoding_json, g_content); + */ }); server.on("/recoverFilesystem", [] () { if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } diff --git a/Web.h b/Web.h index b01ac23..3339295 100644 --- a/Web.h +++ b/Web.h @@ -1,4 +1,5 @@ #include +#include "Somfy.h" #ifndef webserver_h #define webserver_h class Web { @@ -41,9 +42,9 @@ class Web { bool createAPIPasswordToken(const IPAddress ipAddress, const char *username, const char *password, char *token); bool isAuthenticated(WebServer &server, bool cfg = false); - void chunkRoomsResponse(WebServer &server, const char *elem = nullptr); - void chunkShadesResponse(WebServer &server, const char *elem = nullptr); - void chunkGroupsResponse(WebServer &server, const char *elem = nullptr); + //void chunkRoomsResponse(WebServer &server, const char *elem = nullptr); + //void chunkShadesResponse(WebServer &server, const char *elem = nullptr); + //void chunkGroupsResponse(WebServer &server, const char *elem = nullptr); + //void chunkGroupResponse(WebServer &server, SomfyGroup *, const char *prefix = nullptr); }; - #endif diff --git a/data/apple-icon.png b/data/apple-icon.png new file mode 100644 index 0000000..78fc318 Binary files /dev/null and b/data/apple-icon.png differ diff --git a/data/appversion b/data/appversion index 9183195..48a6b50 100644 --- a/data/appversion +++ b/data/appversion @@ -1 +1 @@ -2.4.0 \ No newline at end of file +2.4.7 \ No newline at end of file diff --git a/data/icon.svg b/data/icon.svg new file mode 100644 index 0000000..40c864f --- /dev/null +++ b/data/icon.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/icons.css b/data/icons.css index 328063e..bf793e7 100644 --- a/data/icons.css +++ b/data/icons.css @@ -1494,4 +1494,49 @@ i.icss-bars { i.icss-bars:after { top: 0.36em; left: 0; - } \ No newline at end of file + } + +i.icss-hand { + width: .6em; + height: .5em; + border-radius: .35em .3em .5em .5em; + margin: .5em .1em 0 .21em; +} + + i.icss-hand:before { + width: .1em; + height: .55em; + background: currentColor; + left: .5em; + bottom: .3em; + border-radius: 80% / 20%; + box-shadow: -.13em -.1em 0, -.265em -.15em 0, -.4em -.11em 0; + } + + i.icss-hand:after { + width: .12em; + height: .43em; + background: currentColor; + bottom: .25em; + left: -.06em; + border-radius: .04em; + transform: rotate(-16deg); + border-radius: .04em 70% .04em .04em / .04em 70% .04em .04em; + } +i.icss-bookmark { + width: 1em; + height: 1em; + background-color: transparent; + margin: 0; +} + + i.icss-bookmark:before { + width: .5em; + height: .8em; + transform: translate(-50%, -50%); + border: .25em solid currentColor; + border-color: currentColor currentColor transparent currentColor; + border-radius: .03em; + top: 50%; + left: 50%; + } \ No newline at end of file diff --git a/data/index.html b/data/index.html index cdba8f7..fda0670 100644 --- a/data/index.html +++ b/data/index.html @@ -3,11 +3,118 @@ - - - + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -53,7 +160,7 @@
-
+
@@ -114,18 +221,32 @@
-