diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ce288ac..fe6096d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,7 +7,7 @@ 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.16" + ARDUINO_ESP32_VERSION: "2.0.17" ARDUINO_JSON_VERSION: "6.21.5" ESPTOOL_VERSION: "4.7" LITTLEFS_VERSION: "v2.5.1" @@ -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 }} @@ -91,7 +91,7 @@ jobs: - board: esp32c3 addr_bootloader: 0x0 chip: ESP32-C3 - fqbn: esp32:esp32:esp32c3:JTAGAdapter=default,CDCOnBoot=default,PartitionScheme=default,CPUFreq=160,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none,EraseFlash=none + 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 @@ -99,7 +99,7 @@ jobs: - board: esp32s2 addr_bootloader: 0x1000 chip: ESP32-S2 - fqbn: 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 + 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 @@ -120,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 }} @@ -175,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 }} @@ -187,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 8eec9f2..be96788 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ 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 f1bcd30..e515bed 100644 --- a/ConfigFile.cpp +++ b/ConfigFile.cpp @@ -614,7 +614,7 @@ bool ShadeConfigFile::readNetRecord(restore_options_t &opts) { uint32_t startPos = this->file.position(); if(opts.network) { Serial.println("Reading network settings from file..."); - settings.connType = static_cast(this->readUInt8(static_cast(conn_types::unset))); + 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)); diff --git a/ConfigSettings.cpp b/ConfigSettings.cpp index a6bf603..5deec5e 100644 --- a/ConfigSettings.cpp +++ b/ConfigSettings.cpp @@ -205,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); @@ -261,7 +261,7 @@ bool ConfigSettings::requiresAuth() { return this->Security.type != security_typ 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; } @@ -269,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) {} @@ -580,18 +580,21 @@ 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() { @@ -600,6 +603,7 @@ bool WifiSettings::save() { pref.putString("ssid", this->ssid); pref.putString("passphrase", this->passphrase); pref.putBool("roaming", this->roaming); + pref.putBool("hidden", this->hidden); pref.end(); return true; } @@ -610,6 +614,7 @@ bool WifiSettings::load() { 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; } diff --git a/ConfigSettings.h b/ConfigSettings.h index d5af040..350db96 100644 --- a/ConfigSettings.h +++ b/ConfigSettings.h @@ -3,7 +3,15 @@ #ifndef configsettings_h #define configsettings_h #include "WResp.h" -#define FW_VERSION "v2.4.3" +#define FW_VERSION "v2.4.7" +enum class conn_types_t : byte { + unset = 0x00, + wifi = 0x01, + ethernet = 0x02, + ethernetpref = 0x03, + ap = 0x04 +}; + enum DeviceStatus { DS_OK = 0, DS_ERROR = 1, @@ -64,6 +72,7 @@ class WifiSettings: BaseSettings { public: WifiSettings(); bool roaming = true; + bool hidden = false; char ssid[65] = ""; char passphrase[65] = ""; //bool ssdpBroadcast = true; @@ -157,20 +166,13 @@ class MQTTSettings: BaseSettings { void toJSON(JsonResponse &json); bool fromJSON(JsonObject &obj); }; -enum class conn_types : byte { - unset = 0x00, - wifi = 0x01, - ethernet = 0x02, - ethernetpref = 0x03, - ap = 0x04 -}; 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; @@ -197,5 +199,4 @@ class ConfigSettings: BaseSettings { uint16_t calcNetRecSize(); bool getAppVersion(); }; - #endif diff --git a/GitOTA.cpp b/GitOTA.cpp index 763d387..49ea3c5 100644 --- a/GitOTA.cpp +++ b/GitOTA.cpp @@ -3,13 +3,15 @@ #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" + @@ -18,6 +20,7 @@ extern SocketEmitter sockEmit; extern SomfyShadeController somfy; extern rebootDelay_t rebootDelay; extern Web webServer; +extern Network net; @@ -249,9 +252,10 @@ void GitRepo::toJSON(JsonResponse &json) { #define ERR_DOWNLOAD_CONNECTION -42 void GitUpdater::loop() { + if(!net.connected()) return; if(this->status == GIT_STATUS_READY) { if(settings.checkForUpdate && - (millis() > 60000) && // Wait a minute before checking after boot. + (millis() > net.connectTime + 60000) && // Wait a minute before checking after connection. (this->lastCheck + 86400000 < millis() || this->lastCheck == 0) && !rebootDelay.reboot) { // 1 day this->checkForUpdate(); } @@ -356,7 +360,9 @@ int GitUpdater::checkInternet() { 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); diff --git a/MQTT.cpp b/MQTT.cpp index 6ade1da..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; } @@ -306,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; } @@ -320,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/Network.cpp b/Network.cpp index fcc9698..ad8f2e1 100644 --- a/Network.cpp +++ b/Network.cpp @@ -18,6 +18,7 @@ extern rebootDelay_t rebootDelay; extern Network net; extern SomfyShadeController somfy; +static unsigned long _lastHeapEmit = 0; static bool _apScanning = false; static uint32_t _lastMaxHeap = 0; @@ -33,125 +34,150 @@ bool Network::setup() { WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); WiFi.persistent(false); + WiFi.setAutoReconnect(false); WiFi.onEvent(this->networkEvent); + this->disconnectTime = millis(); if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true, true); - if(settings.connType == conn_types::wifi || settings.connType == conn_types::unset) { + 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(); } - //if(!this->connect()) this->openSoftAP(); + sockEmit.begin(); 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() { - this->connect(); - if(!this->connected() || this->connecting()) return; - if(millis() - this->lastEmit > 1500) { - this->lastEmit = millis(); - if(!this->softAPOpened) { - if(this->connected()) { - this->emitSockets(); - this->lastEmit = millis(); - } - } - } - sockEmit.loop(); - if(this->connected() && millis() - this->lastMDNS > 60000) { - // Every 60 seconds we are going to look at wifi connectivity - // to get around the roaming issues with ESP32. We will try to do this in an async manner. If - // there is a channel that is better we will stop the wifi radio and reconnect - if(this->connType == conn_types::wifi && settings.WIFI.roaming && !this->softAPOpened) { - // If we are not already scanning then we need to start a passive scan - // and only respond if there is a better connection. - // 1. If there is currently a waiting scan don't do anything - if(!_apScanning && WiFi.scanNetworks(true, false, true, 300, 0, settings.WIFI.ssid) == -1) { - _apScanning = true; - } - } - this->lastMDNS = millis(); - } + // 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.roaming || this->connType != conn_types::wifi || this->softAPOpened) _apScanning = false; + 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(); + } else { - uint16_t n = WiFi.scanComplete(); - if( n > 0) { + 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(memcmp(bssid, WiFi.BSSID(), sizeof(bssid)) != 0) { - Serial.printf("Found stronger AP %d %02X:%02X:%02X:%02X:%02X:%02X\n", channel, bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); - this->changeAP(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(settings.ssdpBroadcast) { + 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()) { + this->emitSockets(); + this->lastEmit = millis(); + } + esp_task_wdt_reset(); // Make sure we do not reboot here. + } + + 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(); + //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); - uint8_t retries = 0; - while(retries < 100) { - esp_task_wdt_reset(); // Make sure we do not reboot here. - wl_status_t stat = WiFi.status(); - if(stat == WL_CONNECTED) { - Serial.println("WiFi module connected"); - this->ssid = WiFi.SSID(); - this->mac = WiFi.BSSIDstr(); - this->strength = WiFi.RSSI(); - this->channel = WiFi.channel(); - return true; - } - else if(stat == WL_CONNECT_FAILED) { - Serial.println("WiFi Module connection failed"); - return false; - } - else if(stat == WL_NO_SSID_AVAIL) { - Serial.println(" Connection failed the SSID "); - return false; - } - else if(stat == WL_NO_SHIELD) { - Serial.println("Connection failed - WiFi module not found"); - return false; - } - else if(stat == WL_IDLE_STATUS) { - Serial.print("*"); - } - else if(stat == WL_DISCONNECTED) { - Serial.print("-"); - } - else { - Serial.printf("Unknown status %d\n", stat); - } - delay(300); - } + this->connectStart = millis(); return false; } void Network::emitSockets() { this->emitHeap(); if(this->needsBroadcast || - (this->connType == conn_types::wifi && (abs(abs(WiFi.RSSI()) - abs(this->lastRSSI)) > 1 || WiFi.channel() != this->lastChannel))) { + (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) { + if(this->connType == conn_types_t::ethernet) { JsonSockEvent *json = sockEmit.beginEmit("ethernet"); json->beginObject(); json->addElem("connected", this->connected()); @@ -159,13 +185,6 @@ void Network::emitSockets(uint8_t num) { json->addElem("fullduplex", ETH.fullDuplex()); json->endObject(); sockEmit.endEmit(num); - /* - 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); - */ } else { if(WiFi.status() == WL_CONNECTED) { @@ -176,13 +195,6 @@ void Network::emitSockets(uint8_t num) { json->addElem("channel", (int32_t)this->channel); json->endObject(); sockEmit.endEmit(num); - /* - snprintf(buf, sizeof(buf), "{\"ssid\":\"%s\",\"strength\":%d,\"channel\":%d}", WiFi.SSID().c_str(), WiFi.RSSI(), this->channel); - if(num == 255) - sockEmit.sendToClients("wifiStrength", buf); - else - sockEmit.sendToClient(num, "wifiStrength", buf); - */ this->lastRSSI = WiFi.RSSI(); this->lastChannel = WiFi.channel(); } @@ -202,29 +214,20 @@ void Network::emitSockets(uint8_t num) { json->addElem("fullduplex", false); json->endObject(); sockEmit.endEmit(num); - /* - - 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}"); - } - */ 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); } @@ -233,20 +236,25 @@ void Network::setConnected(conn_types connType) { 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; } - sockEmit.begin(); + // 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(" ("); @@ -276,6 +284,7 @@ void Network::setConnected(conn_types connType) { settings.IP.dns1 = ETH.dnsIP(0); settings.IP.dns2 = ETH.dnsIP(1); } + esp_task_wdt_reset(); JsonSockEvent *json = sockEmit.beginEmit("ethernet"); json->beginObject(); json->addElem("connected", this->connected()); @@ -283,11 +292,7 @@ void Network::setConnected(conn_types connType) { json->addElem("fullduplex", ETH.fullDuplex()); json->endObject(); sockEmit.endEmit(); - /* - 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(); } } else { @@ -295,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); @@ -338,6 +343,7 @@ 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); @@ -350,9 +356,11 @@ 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; @@ -361,16 +369,16 @@ bool Network::connectWired() { if(ETH.linkUp()) { // If the ethernet link is re-established then we need to shut down wifi. if(WiFi.status() == WL_CONNECTED) { - sockEmit.end(); + //sockEmit.end(); WiFi.disconnect(true); WiFi.mode(WIFI_OFF); } - if(this->connType != conn_types::ethernet) this->setConnected(conn_types::ethernet); - this->wifiFallback = false; + if(this->connType != conn_types_t::ethernet) this->setConnected(conn_types_t::ethernet); return true; } else if(this->ethStarted) { - if(settings.connType == conn_types::ethernetpref && settings.WIFI.ssid[0] != '\0') + // 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) { @@ -379,47 +387,45 @@ bool Network::connectWired() { } else Serial.println("Connecting to Wired Ethernet"); - - this->connectAttempts++; this->_connecting = true; - this->connTarget = conn_types::ethernet; - this->connType = conn_types::unset; + this->connTarget = conn_types_t::ethernet; + this->connType = conn_types_t::unset; if(!this->ethStarted) { - 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::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); - } + 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); } + else + ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); + } } 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); @@ -434,13 +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) { - // If we are connected to the target SSID then just return. +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); @@ -451,62 +493,49 @@ bool Network::connectWiFi() { Serial.println("dbm) "); } else Serial.println("Connecting to AP"); - this->connectAttempts++; - this->_connecting = true; - this->connTarget = conn_types::wifi; - this->connType = conn_types::unset; - - WiFi.setSleep(false); - WiFi.mode(WIFI_MODE_NULL); - 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.setScanMethod(WIFI_ALL_CHANNEL_SCAN); WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); - uint8_t bssid[6]; - int32_t channel = 0; - if(this->getStrongestAP(settings.WIFI.ssid, bssid, &channel)) { - Serial.printf("Found strongest AP %d %02X:%02X:%02X:%02X:%02X:%02X\n", channel, bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); - WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase, channel, bssid); + 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); } 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(); } + this->connectStart = millis(); return true; } -bool Network::connect() { - if(this->connecting()) { - if(this->connType == conn_types::unset) { - // If we reached our timeout for the connection then we need to open the soft ap. - if(millis() > this->connectStart + CONNECT_TIMEOUT) { - if(this->connTarget == conn_types::ethernet && settings.connType == conn_types::ethernetpref && settings.WIFI.ssid[0] != '\0') - this->connectWiFi(); - else { - Serial.println("Fell into timeout"); - this->openSoftAP(); - - } - } - } - else - this->setConnected(this->connTarget); - } - else if(settings.connType == conn_types::ethernet || settings.connType == conn_types::ethernetpref) +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(settings.connType == conn_types::wifi && strlen(settings.WIFI.ssid) > 0) + } + 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(); + } + } + else if((ctype == conn_types_t::wifi && this->connType != conn_types_t::wifi && settings.WIFI.hidden)) { this->connectWiFi(); - else - this->openSoftAP(); + } + return true; } uint32_t Network::getChipId() { @@ -517,16 +546,16 @@ uint32_t Network::getChipId() { } return chipId; } - 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); - uint8_t n = this->connected() ? WiFi.scanComplete() : WiFi.scanNetworks(false, false, false, 300, 0, ssid); + 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(uint8_t i = 0; i < n; i++) { + for(int16_t i = 0; i < n; i++) { if(WiFi.SSID(i).compareTo(ssid) == 0) { if(WiFi.RSSI(i) > strength) { strength = WiFi.RSSI(i); @@ -539,98 +568,56 @@ bool Network::getStrongestAP(const char *ssid, uint8_t *bssid, int32_t *channel) 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); - this->_connecting = false; - 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; - sockEmit.begin(); - while (!this->connected()) - { - int clients = WiFi.softAPgetStationNum(); - somfy.loop(); - 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 && this->connType != conn_types::unset) { - Serial.println(); - Serial.println("Connection Established Stopping AP Mode"); - this->setConnected(this->connType); - return false; - } - else 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; - } - esp_task_wdt_reset(); - 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->connecting()) return false; - else 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; + 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() { - return this->_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_WIFI_READY: Serial.println("WiFi interface ready"); break; - case ARDUINO_EVENT_WIFI_SCAN_DONE: Serial.println("Completed scan for access points"); break; + 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("WiFi clients stopped"); break; - case ARDUINO_EVENT_WIFI_STA_CONNECTED: Serial.println("Connected to access point"); break; - case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: Serial.println("Disconnected from WiFi access point"); break; - case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: Serial.println("Authentication mode of access point has changed"); 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("Got WiFi IP: "); + Serial.print("(evt) Got WiFi STA IP: "); Serial.println(WiFi.localIP()); - net.connType = conn_types::wifi; + 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: @@ -638,52 +625,87 @@ void Network::networkEvent(WiFiEvent_t event) { if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true); Serial.print("Got Ethernet IP "); Serial.println(ETH.localIP()); - net.connType = 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_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"); - 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("Ethernet Started"); + 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_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_AP_STOP: - Serial.println("WiFi AP Stopped"); + 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) { - if(num != 255 || this->needsBroadcast || (ESP.getMaxAllocHeap() != _lastMaxHeap || ESP.getFreeHeap() != _lastHeap)) { - _lastMaxHeap = ESP.getMaxAllocHeap(); - _lastHeap = ESP.getFreeHeap(); + 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", _lastMaxHeap); - json->addElem("free", _lastHeap); - json->addElem("min", ESP.getMinFreeHeap()); + json->addElem("max", maxHeap); + json->addElem("free", freeHeap); + json->addElem("min", minHeap); json->addElem("total", ESP.getHeapSize()); json->endObject(); - sockEmit.endEmit(num); + 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 d05d664..864966c 100644 --- a/Network.h +++ b/Network.h @@ -3,7 +3,10 @@ #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; @@ -13,27 +16,32 @@ class Network { int linkSpeed = 0; 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 connTarget = 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; + 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); + 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); diff --git a/SSDP.cpp b/SSDP.cpp index 6f76a1f..f866865 100644 --- a/SSDP.cpp +++ b/SSDP.cpp @@ -161,7 +161,7 @@ void UPNPDeviceType::setChipId(uint32_t chipId) { (uint16_t)chipId & 0xff); } SSDPClass::SSDPClass():sendQueue{false, INADDR_NONE, 0, nullptr, false, 0, "", response_types_t::root} {} -SSDPClass::~SSDPClass() { end(); } +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) { diff --git a/Sockets.cpp b/Sockets.cpp index 97c26e0..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" @@ -42,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++) { @@ -76,7 +80,7 @@ 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(); @@ -105,10 +109,12 @@ void SocketEmitter::initClients() { 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; } @@ -123,7 +129,11 @@ void SocketEmitter::delayInit(uint8_t num) { } } } -void SocketEmitter::end() { sockServer.close(); } +void SocketEmitter::end() { + sockServer.close(); + for(uint8_t i = 0; i < SOCK_MAX_ROOMS; i++) + this->rooms[i].clear(); +} void SocketEmitter::disconnect() { sockServer.disconnect(); } void SocketEmitter::wsEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) { switch(type) { diff --git a/Sockets.h b/Sockets.h index f6acf46..d76ab93 100644 --- a/Sockets.h +++ b/Sockets.h @@ -12,6 +12,7 @@ struct room_t { bool isJoined(uint8_t num); bool join(uint8_t num); bool leave(uint8_t num); + void clear(); }; class SocketEmitter { protected: diff --git a/Somfy.cpp b/Somfy.cpp index f28aeb4..bffebd2 100644 --- a/Somfy.cpp +++ b/Somfy.cpp @@ -257,6 +257,10 @@ 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. @@ -291,28 +295,44 @@ void somfy_frame_t::encode80BitFrame(byte *frame, uint8_t repeat) { frame[9] |= this->calc80Checksum(frame[7], frame[8], frame[9]); break; case somfy_commands::Toggle: - if(repeat == 0) { - frame[0] = 164; - frame[1] |= 0xF0; - } - frame[7] = 196; + 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::Prog: - if(repeat > 0) frame[7] = 196 + (repeat * 4); - else frame[7] = 132; - frame[8] = 0; - frame[9] = ((repeat + 1) & 0x0F) << 4; + 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) @@ -400,7 +420,7 @@ void somfy_frame_t::encodeFrame(byte *frame) { } else { - if(this->bitLength == 80) this->encode80BitFrame(&frame[0], 0); + if(this->bitLength == 80) this->encode80BitFrame(&frame[0], this->repeats); } byte checksum = 0; @@ -432,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)) { @@ -440,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; @@ -970,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); @@ -979,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); @@ -989,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); } @@ -998,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); @@ -1006,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); @@ -1014,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); @@ -1482,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: @@ -2370,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); @@ -2398,6 +2423,7 @@ 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); @@ -2406,6 +2432,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { } } else { + if(this->lastFrame.processed) return; this->lastFrame.processed = true; if(!internal) { if(this->tiltType != tilt_types::tiltonly) this->p_target(this->currentPos); @@ -2511,8 +2538,10 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { 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(); @@ -2899,7 +2928,7 @@ void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSiz } } 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()) { @@ -2916,7 +2945,7 @@ void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSiz 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) { + else if(this->isToggle() && cmd == somfy_commands::Prog) { SomfyRemote::sendCommand(somfy_commands::Toggle, repeat, stepSize); } else { @@ -2998,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(); @@ -3081,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) { @@ -3138,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) { @@ -3147,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))) @@ -3895,6 +3938,7 @@ void SomfyRemote::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSi 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; @@ -3953,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); @@ -4149,7 +4197,7 @@ void SomfyShadeController::toJSONGroups(JsonResponse &json) { } void SomfyShadeController::toJSONRepeaters(JsonResponse &json) { for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) { - if(somfy.repeaters[i] != 0) json.addElem((uint8_t)somfy.repeaters[i]); + if(somfy.repeaters[i] != 0) json.addElem((uint32_t)somfy.repeaters[i]); } } void SomfyShadeController::loop() { @@ -4613,6 +4661,7 @@ 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) { @@ -4623,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); } diff --git a/Somfy.h b/Somfy.h index 26d6bb4..bc5c99b 100644 --- a/Somfy.h +++ b/Somfy.h @@ -67,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, @@ -171,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; @@ -188,10 +192,12 @@ struct somfy_frame_t { 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); }; @@ -319,6 +325,7 @@ 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); diff --git a/SomfyController.ino b/SomfyController.ino index 3702b5d..464d179 100644 --- a/SomfyController.ino +++ b/SomfyController.ino @@ -49,23 +49,33 @@ void loop() { 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) { @@ -73,14 +83,4 @@ void loop() { ESP.restart(); } esp_task_wdt_reset(); - - /* - if(heap < oldheap) { - Serial.print("Heap: "); - Serial.print(oldheap); - Serial.print(" -> "); - Serial.println(heap); - } - oldheap = heap; - */ } diff --git a/SomfyController.ino.esp32.bin b/SomfyController.ino.esp32.bin index c2fc5d9..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 1e8b11b..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 fb06c91..98d9f83 100644 Binary files a/SomfyController.littlefs.bin and b/SomfyController.littlefs.bin differ 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 index 3b158ca..e44e3ba 100644 --- a/WResp.cpp +++ b/WResp.cpp @@ -21,7 +21,7 @@ void JsonSockEvent::endEvent(uint8_t num) { else this->server->sendTXT(num, this->buff); } void JsonSockEvent::_safecat(const char *val, bool escape) { - size_t len = strlen(val) + strlen(this->buff); + 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); @@ -29,7 +29,8 @@ void JsonSockEvent::_safecat(const char *val, bool escape) { return; } if(escape) strcat(this->buff, "\""); - strcat(this->buff, val); + 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) { @@ -52,13 +53,14 @@ void JsonResponse::send() { this->_headersSent = true; } void JsonResponse::_safecat(const char *val, bool escape) { - size_t len = strlen(val) + strlen(this->buff); + 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, "\""); - strcat(this->buff, val); + if(escape) this->escapeString(val, &this->buff[strlen(this->buff)]); + else strcat(this->buff, val); if(escape) strcat(this->buff, "\""); } @@ -133,13 +135,70 @@ void JsonFormatter::addElem(const char *name, uint64_t lval) { sprintf(this->_nu 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 = strlen(val) + strlen(this->buff); + 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, "\""); - strcat(this->buff, val); + 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 index f1a7f2c..4bda5d5 100644 --- a/WResp.h +++ b/WResp.h @@ -16,6 +16,8 @@ class JsonFormatter { 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); diff --git a/Web.cpp b/Web.cpp index efa2efb..425d1b2 100644 --- a/Web.cpp +++ b/Web.cpp @@ -215,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); @@ -225,9 +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_reset(); + esp_task_wdt_delete(NULL); server.streamFile(file, encoding); file.close(); + esp_task_wdt_add(NULL); + esp_task_wdt_reset(); } void Web::handleController(WebServer &server) { webServer.sendCORSHeaders(server); @@ -789,8 +791,8 @@ void Web::handleDiscovery(WebServer &server) { if (method == HTTP_POST || method == HTTP_GET) { Serial.println("Discovery Requested"); char connType[10] = "Unknown"; - if(net.connType == conn_types::ethernet) strcpy(connType, "Ethernet"); - else if(net.connType == conn_types::wifi) strcpy(connType, "Wifi"); + 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)); @@ -2169,7 +2171,8 @@ void Web::begin() { if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } esp_task_wdt_delete(NULL); - int n = WiFi.scanNetworks(); + if(net.softAPOpened) WiFi.disconnect(false); + int n = WiFi.scanNetworks(false, true); esp_task_wdt_add(NULL); Serial.print("Scanned "); @@ -2371,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; diff --git a/data/appversion b/data/appversion index 6550da6..48a6b50 100644 --- a/data/appversion +++ b/data/appversion @@ -1 +1 @@ -2.4.3 \ No newline at end of file +2.4.7 \ No newline at end of file diff --git a/data/index.html b/data/index.html index 405fa44..fda0670 100644 --- a/data/index.html +++ b/data/index.html @@ -8,9 +8,9 @@ - - - + + + @@ -114,7 +114,7 @@ rel="apple-touch-startup-image"> - +
@@ -160,7 +160,7 @@
-
+
@@ -275,18 +275,25 @@