Compare commits

...

45 commits
v2.4.0 ... main

Author SHA1 Message Date
Robert Strouse
eb75868adb Update build params for C3 #458 #454 2024-08-19 10:46:00 -07:00
Robert Strouse
3d9e6c11c2 Clear the connecting flag for ETH Wifi fallback #445 2024-08-04 09:32:18 -07:00
Robert Strouse
e478d17c7f Bypass AP Scanning with Use Hidden SSID option #438 #434 2024-07-27 12:12:38 -07:00
Robert Strouse
93ebddd29d Prepare v2.4.6 Release 2024-07-25 17:14:29 -07:00
Robert Strouse
fb1f18e260 Ensure the network connection has completed before trying MQTT. 2024-07-14 09:59:14 -07:00
Robert Strouse
c3ada3b40e removed pin restrictions for S3 #426 2024-07-13 07:18:26 -07:00
Robert Strouse
75928b4ac8 Remove autoReconnect function from core #410 #407 2024-07-10 21:40:55 -07:00
Robert Strouse
0b985c0880 More aggressive WIFI scanning #410 #407 2024-06-28 14:37:41 -07:00
Robert Strouse
e04d7e3fc7 Include scan with SoftAP operation #407 #410 2024-06-25 18:04:46 -07:00
Robert Strouse
9205723125 Reduce heap emits for interface sockets 2024-06-19 11:21:51 -07:00
Robert Strouse
2b59f330a9 Add support for 1 button gates #388 2024-06-18 14:05:43 -07:00
Robert Strouse
c528fda55a Fix issue with deleting repeaters #402 2024-06-17 08:47:44 -07:00
Robert Strouse
f29cd9c089 Add additional command structure for 80-bit up/down 2024-06-15 09:22:27 -07:00
Robert Strouse
cf7a9b1fc2 Fixes for TaHoma hub #393 2024-06-07 19:06:27 -07:00
Robert Strouse
473307b320 Added additional checks for wifi dropoffs and disconnect from STA for AP scan. 2024-06-02 10:58:51 -07:00
Robert Strouse
2dc49a64e9 Bump application to v2.4.4 2024-05-26 16:36:29 -07:00
Robert Strouse
5f15a7cc93 Bump workflow actions 2024-05-26 16:05:04 -07:00
Robert Strouse
e87f42fa50 Update reconnect procedure #385 2024-05-26 15:59:18 -07:00
Robert Strouse
4f3a93b336 Prepare final release for v2.4.3 2024-05-12 11:23:23 -07:00
Robert Strouse
6077052e9b Added 80bit step functions to groups #280 2024-05-08 16:44:19 -07:00
Robert Strouse
e3e843387f Added 80bit commands for Step, Stop, and Favorite #280 2024-05-07 15:59:50 -07:00
Robert Strouse
c4e1dbe44b Bump Arduino core to 2.0.16 2024-05-04 13:36:55 -07:00
Robert Strouse
23731f793f Added group/remote processing to SimMy #360 2024-05-04 13:25:58 -07:00
Robert Strouse
d64103f259 Added 5 second wdt #349 2024-05-04 12:21:58 -07:00
Robert Strouse
2feb420551 Groups #350, #354, Ethernet #348 2024-05-03 16:39:46 -07:00
Robert Strouse
82c867d2eb Fix memory leak #273 2024-04-21 13:43:15 -07:00
Robert Strouse
f8b3bc4133 Fix byte alignments #273 2024-04-18 18:56:33 -07:00
Robert Strouse
25c8a66869 Add apple icons #326 2024-04-14 13:58:16 -07:00
Robert Strouse
652346a7c7 Fix issue where connType was not being sent. 2024-04-10 13:44:57 -07:00
Robert Strouse
ce3946c9e6 Publish TCP 80 for mDNS 2024-04-10 12:15:40 -07:00
Robert Strouse
c4d6a1008f Fix C3 casts 2024-04-09 19:08:38 -07:00
Robert Strouse
5a8c09ee9f Handle C3 ints 2024-04-09 14:26:51 -07:00
Robert Strouse
026f9315b1 Stream all responses. Fix missing ETH IP address #310 2024-04-09 13:46:51 -07:00
Robert Strouse
6ba354c7ff Chunk single group response #316 2024-04-03 07:49:26 -07:00
rstrouse
610773c6b7
Merge pull request #317 from nbarrientos/ibarrien
Fix typo in the UI
2024-04-03 07:48:16 -07:00
Nacho Barrientos
dd0d7fa2a3 Fix typo 2024-04-03 15:27:15 +02:00
Robert Strouse
d8292fdc2b Added the current ip address to the DHCP tab #310 2024-03-23 09:16:41 -07:00
Robert Strouse
49d134e6f2 Wait until reboot after firmware update. 2024-03-22 12:56:19 -07:00
Robert Strouse
b8967901de Update workflows 2024-03-22 08:34:26 -07:00
Robert Strouse
5141676724 Change workflows 2024-03-22 07:55:11 -07:00
Robert Strouse
775e82b766 Change build parameters for workflow 2024-03-21 20:23:36 -07:00
Robert Strouse
b324f59c03 Add wifi roaming #294 #270 2024-03-09 12:45:06 -08:00
Robert Strouse
cb14cd42dc Fix group output for discovery #291 2024-03-03 13:00:01 -08:00
Robert Strouse
2dd40396c2 Change the channel surfing functions for wifi #294 2024-03-03 09:25:44 -08:00
Robert Strouse
60af2bf399 Improve wifi fallback 2024-03-03 08:33:19 -08:00
35 changed files with 2935 additions and 1417 deletions

View file

@ -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:

3
.gitignore vendored
View file

@ -5,3 +5,6 @@ esp32s3.svd
debug.cfg
SomfyController.ino.XIAO_ESP32S3.bin
SomfyController.ino.esp32c3.bin
SomfyController.ino.esp32s2.bin
.vscode/settings.json

View file

@ -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<conn_types>(this->readUInt8(static_cast<uint8_t>(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<conn_types_t>(this->readUInt8(static_cast<uint8_t>(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<eth_phy_type_t>(this->readUInt8(0));
settings.Ethernet.CLKMode = static_cast<eth_clock_mode_t>(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<eth_phy_type_t>(this->readUInt8(0));
settings.Ethernet.CLKMode = static_cast<eth_clock_mode_t>(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<radio_proto>(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) {

View file

@ -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();

View file

@ -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<conn_types>(pref.getChar("connType", 0x00));
this->connType = static_cast<conn_types_t>(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<uint8_t>(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<conn_types>(obj["connType"].as<uint8_t>());
if(obj.containsKey("connType")) this->connType = static_cast<conn_types_t>(obj["connType"].as<uint8_t>());
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<uint8_t>(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<uint8_t>(this->CLKMode));
json.addElem("phyType", static_cast<uint8_t>(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;

View file

@ -2,8 +2,16 @@
#include <ETH.h>
#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

View file

@ -2,12 +2,17 @@
#include <WiFiClientSecure.h>
#include <Update.h>
#include <HTTPClient.h>
#include <esp_task_wdt.h>
#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<JsonObject>();
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;
}

View file

@ -4,6 +4,7 @@
#include <ArduinoJson.h>
#include <time.h>
#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);

View file

@ -1,8 +1,9 @@
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include "MQTT.h"
#include <esp_task_wdt.h>
#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;

3
MQTT.h
View file

@ -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);

View file

@ -1,6 +1,7 @@
#include <ETH.h>
#include <WiFi.h>
#include <ESPmDNS.h>
#include <esp_task_wdt.h>
#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);
}
}
}

View file

@ -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);
};

View file

@ -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];

View file

@ -1,6 +1,7 @@
#include <Arduino.h>
#include <ArduinoJson.h>
#include <WebSocketsServer.h>
#include <esp_task_wdt.h>
#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:

View file

@ -1,4 +1,5 @@
#include <WebSocketsServer.h>
#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

717
Somfy.cpp

File diff suppressed because it is too large Load diff

58
Somfy.h
View file

@ -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();

View file

@ -1,5 +1,6 @@
#include <WiFi.h>
#include <LittleFS.h>
#include <esp_task_wdt.h>
#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();
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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));

View file

@ -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);

204
WResp.cpp Normal file
View file

@ -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;
}
}
}

74
WResp.h Normal file
View file

@ -0,0 +1,74 @@
#include <WebServer.h>
#include <WebSocketsServer.h>
#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

933
Web.cpp

File diff suppressed because it is too large Load diff

9
Web.h
View file

@ -1,4 +1,5 @@
#include <WebServer.h>
#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

BIN
data/apple-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -1 +1 @@
2.4.0
2.4.7

93
data/icon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -1494,4 +1494,49 @@ i.icss-bars {
i.icss-bars:after {
top: 0.36em;
left: 0;
}
}
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%;
}

View file

@ -3,11 +3,118 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<link rel="stylesheet" href="main.css?v=2.4.0c" type="text/css" />
<link rel="stylesheet" href="widgets.css?v=2.4.0c" type="text/css" />
<link rel="stylesheet" href="icons.css?v=2.4.0c" type="text/css" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="ESPSomfy RTS App">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="stylesheet" href="main.css?v=2.4.7c" type="text/css" />
<link rel="stylesheet" href="widgets.css?v=2.4.7c" type="text/css" />
<link rel="stylesheet" href="icons.css?v=2.4.7c" type="text/css" />
<link rel="icon" type="image/png" href="favicon.png" />
<script type="text/javascript" src="index.js?v=2.4.0c"></script>
<!-- iPad retina icon -->
<link href="apple-icon.png"
sizes="152x152"
rel="apple-touch-icon-precomposed">
<!-- iPad retina icon (iOS < 7) -->
<link href="apple-icon.png"
sizes="144x144"
rel="apple-touch-icon-precomposed">
<!-- iPad non-retina icon -->
<link href="apple-icon.png"
sizes="76x76"
rel="apple-touch-icon-precomposed">
<!-- iPad non-retina icon (iOS < 7) -->
<link href="apple-icon.png"
sizes="72x72"
rel="apple-touch-icon-precomposed">
<!-- iPhone 6 Plus icon -->
<link href="apple-icon.png"
sizes="120x120"
rel="apple-touch-icon-precomposed">
<!-- iPhone retina icon (iOS < 7) -->
<link href="apple-icon.png"
sizes="114x114"
rel="apple-touch-icon-precomposed">
<!-- iPhone non-retina icon (iOS < 7) -->
<link href="apple-icon.png"
sizes="57x57"
rel="apple-touch-icon-precomposed">
<link href="apple-icon.png"
media="(device-width: 768px) and (device-height: 1024px)
and (-webkit-device-pixel-ratio: 2)
and (orientation: portrait)"
rel="apple-touch-startup-image">
<!-- iPad retina landscape startup image -->
<link href="apple-icon.png"
media="(device-width: 768px) and (device-height: 1024px)
and (-webkit-device-pixel-ratio: 2)
and (orientation: landscape)"
rel="apple-touch-startup-image">
<!-- iPad non-retina portrait startup image -->
<link href="apple-icon.png"
media="(device-width: 768px) and (device-height: 1024px)
and (-webkit-device-pixel-ratio: 1)
and (orientation: portrait)"
rel="apple-touch-startup-image">
<!-- iPad non-retina landscape startup image -->
<link href="apple-icon.png"
media="(device-width: 768px) and (device-height: 1024px)
and (-webkit-device-pixel-ratio: 1)
and (orientation: landscape)"
rel="apple-touch-startup-image">
<!-- iPhone 6 Plus portrait startup image -->
<link href="apple-icon.png"
media="(device-width: 414px) and (device-height: 736px)
and (-webkit-device-pixel-ratio: 3)
and (orientation: portrait)"
rel="apple-touch-startup-image">
<!-- iPhone 6 Plus landscape startup image -->
<link href="apple-icon.png"
media="(device-width: 414px) and (device-height: 736px)
and (-webkit-device-pixel-ratio: 3)
and (orientation: landscape)"
rel="apple-touch-startup-image">
<!-- iPhone 6 startup image -->
<link href="apple-icon.png"
media="(device-width: 375px) and (device-height: 667px)
and (-webkit-device-pixel-ratio: 2)"
rel="apple-touch-startup-image">
<!-- iPhone 5 startup image -->
<link href="apple-icon.png"
media="(device-width: 320px) and (device-height: 568px)
and (-webkit-device-pixel-ratio: 2)"
rel="apple-touch-startup-image">
<!-- iPhone < 5 retina startup image -->
<link href="apple-icon.png"
media="(device-width: 320px) and (device-height: 480px)
and (-webkit-device-pixel-ratio: 2)"
rel="apple-touch-startup-image">
<!-- iPhone < 5 non-retina startup image -->
<link href="apple-icon.png"
media="(device-width: 320px) and (device-height: 480px)
and (-webkit-device-pixel-ratio: 1)"
rel="apple-touch-startup-image">
<script type="text/javascript" src="index.js?v=2.4.7c"></script>
</head>
<body>
<div id="divContainer" class="container main" data-auth="false">
@ -53,7 +160,7 @@
<input id="cbSsdpBroadcast" name="ssdpBroadcast" type="checkbox" data-bind="general.ssdpBroadcast" style="display:inline-block;" />
<label for="cbSsdpBroadcast" style="display:inline-block;cursor:pointer;">Broadcast uPnP over SSDP</label>
</div>
<div class="field-group">
<div class="field-group" style="margin-top:-12px;">
<input id="cbCheckForUpdate" type="checkbox" data-bind="general.checkForUpdate" style="display:inline-block;" />
<label for="cbCheckForUpdate" style="display:inline-block;cursor:pointer;">Auto Check for Updates</label>
</div>
@ -114,18 +221,32 @@
</button>
</div>
</div>
<div id="divFirmware" class="subtab-content" style="display:none;">
<div style="font-size:17px;">
<span style="text-align:right;display:inline-block;color:#00bcd4;width:127px;margin-top:-27px;">Hardware: </span>
<span style="padding-left: 4px; display: inline-block;">ESP32<span id="spanHwVersion" style="text-transform:uppercase; text-align:left;width:120px;"></span></span>
<div id="divFirmware" class="subtab-content" style="display:none;padding-top:10px;">
<div style="display:inline-block">
<div style="font-size:17px;">
<span style="text-align:right;display:inline-block;color:#00bcd4;width:127px;margin-top:-27px;">Hardware: </span>
<span style="padding-left: 4px; display: inline-block;">ESP32<span id="spanHwVersion" style="text-transform:uppercase; text-align:left;width:120px;"></span></span>
</div>
<div style="font-size:17px;">
<span style="text-align:right;display:inline-block;color:#00bcd4;width:127px;margin-top:-27px;">Firmware:</span>
<span id="spanFwVersion" style="padding-left:4px;display:inline-block;text-align:left;width:120px;">v-.--</span>
</div>
<div style="font-size:17px;">
<span style="text-align:right;display:inline-block;color:#00bcd4;width:127px;">Application:</span>
<span id="spanAppVersion" style="padding-left:4px;display:inline-block;text-align:left;width:120px;">v-.--</span>
</div>
</div>
<div style="font-size:17px;">
<span style="text-align:right;display:inline-block;color:#00bcd4;width:127px;margin-top:-27px;">Firmware:</span>
<span id="spanFwVersion" style="padding-left:4px;display:inline-block;text-align:left;width:120px;">v-.--</span>
</div>
<div style="font-size:17px;">
<span style="text-align:right;display:inline-block;color:#00bcd4;width:127px;">Application:</span>
<span id="spanAppVersion" style="padding-left:4px;display:inline-block;text-align:left;width:120px;">v-.--</span>
<div style="display:inline-block;font-size:12px;padding-top:10px;padding-left:37px;">
<div style="color: #00bcd4;">Memory (bytes)</div>
<div style="font-size:12px;display:inline-block;">
<span style="text-align:right;display:inline-block;color:#00bcd4;">Free: </span>
<span id="spanFreeMemory" style="text-align:right;width:120px;"></span>
<span style="text-align:right;display:inline-block;color:#00bcd4;">Max: </span>
<span id="spanMaxMemory" style="text-align:right;width:120px;"></span>
<span style="text-align:right;display:inline-block;color:#00bcd4;">Min: </span>
<span id="spanMinMemory" style="text-align:right;width:120px;"></span>
</div>
</div>
<div class="button-container">
<button id="btnUpdateGithub" type="button" onclick="firmware.updateGithub();">
@ -154,7 +275,7 @@
<div id="divNetworkSettings" style="display:none;">
<div class="subtab-container"><span class="selected" data-grpid="divNetAdapter">Adapter</span><span data-grpid="divDHCP">DHCP/Static IP</span><span data-grpid="divMQTT">MQTT</span></div>
<div id="divNetAdapter" class="subtab-content">
<div class="field-group" style="vertical-align:middle;color:#00bcd4;margin-top:-24px;margin-bottom:18px;">
<div class="field-group" style="vertical-align:middle;color:#00bcd4;margin-top:-24px;">
<input id="cbHardwired" name="hardwired" data-bind="ethernet.hardwired" type="checkbox" style="display:inline-block;" onclick="wifi.useEthernetClicked();" />
<label for="cbHardwired" style="display:inline-block;cursor:pointer;">Use Ethernet</label>
<div id="divFallbackWireless" style="display:inline-block;padding-left:7px;">
@ -162,6 +283,17 @@
<label for="cbFallbackWireless" style="display:inline-block;cursor:pointer;">Fallback to Wireless</label>
</div>
</div>
<div class="field-group" style="vertical-align:middle;color:#00bcd4;margin-top:-12px;margin-bottom:18px;">
<div id="divHiddenSSID" style="display:inline-block;">
<input id="cbHiddenSSID" data-bind="wifi.hidden" type="checkbox" style="display:inline-block;" onclick="wifi.hiddenSSIDClicked();" />
<label for="cbHiddenSSID" style="display:inline-block;cursor:pointer;">Use Hidden SSID</label>
</div>
<div id="divRoaming" style="display:inline-block;padding-left:7px;">
<input id="cbRoaming" name="roaming" data-bind="wifi.roaming" type="checkbox" style="display:inline-block;" />
<label for="cbRoaming" style="display:inline-block;cursor:pointer;">Enable Roaming</label>
</div>
</div>
<div id="divWiFiMode">
<form method="post" action="/scan">
<div id="divAps" data-lastloaded="0" style="border-radius:5px;border:solid 1px #00bcd4;margin-bottom:-10px;"></div>
@ -217,6 +349,7 @@
</div>
</div>
<div id="divDHCP" class="subtab-content" style="display:none;">
<div style="margin-top:-14px;"><span style="color: #00bcd4;">IP Address:</span><span id="spanCurrentIP"></span></div>
<div class="field-group">
<input id="cbUseDHCP" name="dhcp" type="checkbox" data-bind="ip.dhcp" style="display:inline-block;" onclick="wifi.onDHCPClicked(this);" checked="checked" />
<label for="cbUseDHCP" style="display:inline-block;cursor:pointer;">Acquire IP Address automatically (DHCP)</label>
@ -391,6 +524,10 @@
<option value="11">Gate (left)</option>
<option value="12">Gate (center)</option>
<option value="13">Gate (right)</option>
<option value="14">Gate (1-button left)</option>
<option value="15">Gate (1-button center)</option>
<option value="16">Gate (1-button right)</option>
</select>
<label for="selShadeType">Type</label>
</div>
@ -483,6 +620,13 @@
<label for="cbFlipPosition" style="display:block;font-size:1em;margin-top:0px;margin-left:7px;display:inline-block;">Invert Position (expressed in % of open)</label>
</div>
</div>
<div style="margin-top:-10px;" id="divSimMy">
<div class="field-group">
<input id="cbSimMy" name="simMy" data-bind="simMy" type="checkbox" style="" />
<label for="cbSimMy" style="display:block;font-size:1em;margin-top:0px;margin-left:7px;display:inline-block;">Simulate Favorite Position</label>
</div>
</div>
<div classs="field-group" style="display:inline-block;">
<label for="selRepeatCommnds" style="cursor:pointer;color:#00bcd4;margin-right:4px;">Repeat Commands</label>
<select id="selRepeatCommands" data-bind="repeats" data-datatype="int" style="width:127px;">
@ -642,11 +786,11 @@
</div>
</div>
</div>
<div id="divVirtualRemote" style="display:none;" class="subtab-content">
<div id="divVirtualRemote" style="display:none;" class="subtab-content" data-bitlength="56">
<div class="field-group" style="margin-top:-18px;display:inline-block;width:100%;">
<select id="selVRMotor" style="width:100%;">
<select id="selVRMotor" style="width:100%;" onchange="document.getElementById('divVirtualRemote').setAttribute('data-bitlength', this.options[this.selectedIndex].getAttribute('data-bitlength'));">
</select>
<label for="selVRMotor">Select a Motor</label>
<label for="selVRMotor">Select a Motor or Group</label>
</div>
<div class="vr-button vr-updownmy">
<span>Remote Buttons</span>
@ -656,12 +800,38 @@
<div class="button-outline" data-cmd="down" onmousedown="somfy.sendVRCommand(this);" ontouchstart="somfy.sendVRCommand(this);"><i class="icss-somfy-down" style="margin-top:-4px;"></i></div>
</div>
</div>
<div class="vr-button vr-stop vr-80bit">
<span>Stop</span>
<div>
<div class="button-outline toggle-button" style="width:127px;text-align:center;border-radius:33%;font-size:2em;padding:10px;" data-cmd="stop" onmousedown="somfy.sendVRCommand(this);" ontouchstart="somfy.sendVRCommand(this);"><i class="icss-hand" style=""></i></div>
</div>
</div>
<div class="vr-button vr-updownmy">
<span>Toggle Button</span>
<div>
<div class="button-outline toggle-button" style="width:127px;text-align:center;border-radius:33%;font-size:2em;padding:10px;" data-cmd="toggle" onmousedown="somfy.sendVRCommand(this);" ontouchstart="somfy.sendVRCommand(this);"><i class="icss-somfy-toggle" style="margin-top:-4px;"></i></div>
</div>
</div>
<div class="vr-button vr-updown vr-80bit">
<span>Step</span>
<div style="margin-right:7px;width:470px;font-size:.8em;">
<input id="vrslidStepSize" name="stepSize" type="range" min="1" max="127" step="1" style="width:100%;" data-bind="stepSize" value="1" oninput="document.getElementById('vrspanStepSize').innerText = this.value;" />
<div>
<label style="color:#00bcd4;">Step Size:</label><span id="vrspanStepSize">1</span>
</div>
</div>
<div>
<div class="button-outline" style="width:59px;text-align:center;border-radius:33%;font-size:2em;padding:10px;" data-cmd="StepUp" onmousedown="somfy.sendVRCommand(this);" ontouchstart="somfy.sendVRCommand(this);"><span></span><i class="icss-somfy-up" style="margin-top:4px;"></i></div>
<div class="button-outline" style="width:59px;text-align:center;border-radius:33%;font-size:2em;padding:10px;" data-cmd="StepDown" onmousedown="somfy.sendVRCommand(this);" ontouchstart="somfy.sendVRCommand(this);"><span></span><i class="icss-somfy-down" style="margin-top:-4px;"></i></div>
</div>
</div>
<div class="vr-button vr-favorite vr-80bit">
<span>Favorite</span>
<div>
<div class="button-outline toggle-button" style="width:127px;text-align:center;border-radius:33%;font-size:2em;padding:10px;" data-cmd="favorite" onmousedown="somfy.sendVRCommand(this);" ontouchstart="somfy.sendVRCommand(this);"><i class="icss-bookmark" style=""></i></div>
</div>
</div>
<div class="vr-button vr-updown">
<span>Up + Down</span>
<div>
@ -686,18 +856,6 @@
<div class="button-outline" style="width:127px;text-align:center;border-radius:33%;font-size:2em;padding:10px;" data-cmd="MyDown" onmousedown="somfy.sendVRCommand(this);" ontouchstart="somfy.sendVRCommand(this);"><span>my</span><span> + </span><i class="icss-somfy-down"></i></div>
</div>
</div>
<div class="vr-button vr-updown">
<span>Step Up</span>
<div>
<div class="button-outline" style="width:127px;text-align:center;border-radius:33%;font-size:2em;padding:10px;" data-cmd="StepUp" onmousedown="somfy.sendVRCommand(this);" ontouchstart="somfy.sendVRCommand(this);"><span>step </span><i class="icss-somfy-up"></i></div>
</div>
</div>
<div class="vr-button vr-updown">
<span>Step Down</span>
<div>
<div class="button-outline" style="width:127px;text-align:center;border-radius:33%;font-size:2em;padding:10px;" data-cmd="StepDown" onmousedown="somfy.sendVRCommand(this);" ontouchstart="somfy.sendVRCommand(this);"><span>step </span><i class="icss-somfy-down"></i></div>
</div>
</div>
<div class="vr-button vr-updown">
<span>Prog</span>
<div>
@ -705,15 +863,10 @@
</div>
</div>
<div class="vr-button vr-updown">
<span>Sun Flag On</span>
<span>Sun Flag</span>
<div>
<div class="button-outline" style="width:127px;text-align:center;border-radius:33%;font-size:2em;padding:10px;" data-cmd="SunFlag" onmousedown="somfy.sendVRCommand(this);" ontouchstart="somfy.sendVRCommand(this);"><div class="button-sunflag" data-on="true" style="margin:0px;"><i class="icss-sun-c" style="background:white;"></i><i class="icss-sun-o" style="left:0px;color:white;"></i></div><span>on</span></div>
</div>
</div>
<div class="vr-button vr-updown">
<span>Sun Flag Off</span>
<div>
<div class="button-outline" style="width:127px;text-align:center;border-radius:33%;font-size:2em;padding:10px;" data-cmd="Flag" onmousedown="somfy.sendVRCommand(this);" ontouchstart="somfy.sendVRCommand(this);"><div class="button-sunflag" data-on="false" style="margin:0px;"><i class="icss-sun-c" style="background:transparent;"></i><i class="icss-sun-o" style="left:0px;color:white;"></i></div><span>off</span></div>
<div class="button-outline" style="width: 59px; text-align: center; border-radius: 33%; font-size: 2em; padding: 10px; height: 52px;" data-cmd="SunFlag" onmousedown="somfy.sendVRCommand(this);" ontouchstart="somfy.sendVRCommand(this);"><div class="button-sunflag" data-on="true" style="margin:0px;margin-left:4px;"><i class="icss-sun-c" style="background:white;"></i><i class="icss-sun-o" style="left:0px;color:white;"></i></div></div>
<div class="button-outline" style="width: 59px; text-align: center; border-radius: 33%; font-size: 2em; padding: 10px; height: 52px;" data-cmd="Flag" onmousedown="somfy.sendVRCommand(this);" ontouchstart="somfy.sendVRCommand(this);"><div class="button-sunflag" data-on="false" style="margin:0px;margin-left:4px;"><i class="icss-sun-c" style="background:transparent;"></i><i class="icss-sun-o" style="left:0px;color:white;"></i></div></div>
</div>
</div>
<div class="vr-button">

View file

@ -1,5 +1,6 @@
//var hst = '192.168.1.208';
var hst = '192.168.1.152';
//var hst = '192.168.1.159';
var _rooms = [{ roomId: 0, name: 'Home' }];
var errors = [
@ -490,6 +491,9 @@ async function initSockets() {
return value;
});
switch (eventName) {
case 'memStatus':
firmware.procMemoryStatus(msg);
break;
case 'updateProgress':
firmware.procUpdateProgress(msg);
break;
@ -562,6 +566,8 @@ async function initSockets() {
await wifi.loadNetwork();
await somfy.loadSomfy();
await mqtt.loadMQTT();
if (ui.isConfigOpen()) socket.send('join:0');
//await general.init();
//await somfy.init();
//await mqtt.init();
@ -1264,7 +1270,7 @@ var security = new Security();
class General {
initialized = false;
appVersion = 'v2.4.0';
appVersion = 'v2.4.7';
reloadApp = false;
init() {
if (this.initialized) return;
@ -1597,12 +1603,13 @@ var general = new General();
class Wifi {
initialized = false;
ethBoardTypes = [{ val: 0, label: 'Custom Config' },
{ val: 1, label: 'WT32-ETH01', clk: 0, ct: 0, addr: 1, pwr: 16, mdc: 23, mdio: 18 },
{ val: 2, label: 'Olimex ESP32-POE', clk: 3, ct: 0, addr: 0, pwr: 12, mdc: 23, mdio: 18 },
{ val: 3, label: 'Olimex ESP32-EVB', clk: 0, ct: 0, addr: 0, pwr: -1, mdc: 23, mdio: 18 },
{ val: 4, label: 'LILYGO T-Internet POE', clk: 3, ct: 0, addr: 0, pwr: 16, mdc: 23, mdio: 18 },
{ val: 5, label: 'wESP32 v7+', clk: 0, ct: 2, addr: 0, pwr: -1, mdc: 16, mdio: 17 },
{ val: 6, label: 'wESP32 < v7', clk: 0, ct: 0, addr: 0, pwr: -1, mdc: 16, mdio: 17 }
{ val: 7, label: 'EST-PoE-32 - Everything Smart', clk: 3, ct: 0, addr: 0, pwr: 12, mdc: 23, mdio: 18 },
{ val: 3, label: 'ESP32-EVB - Olimex', clk: 0, ct: 0, addr: 0, pwr: -1, mdc: 23, mdio: 18 },
{ val: 2, label: 'ESP32-POE - Olimex', clk: 3, ct: 0, addr: 0, pwr: 12, mdc: 23, mdio: 18 },
{ val: 4, label: 'T-Internet POE - LILYGO', clk: 3, ct: 0, addr: 0, pwr: 16, mdc: 23, mdio: 18 },
{ val: 5, label: 'wESP32 v7+ - Silicognition', clk: 0, ct: 2, addr: 0, pwr: -1, mdc: 16, mdio: 17 },
{ val: 6, label: 'wESP32 < v7 - Silicognition', clk: 0, ct: 0, addr: 0, pwr: -1, mdc: 16, mdio: 17 },
{ val: 1, label: 'WT32-ETH01 - Wireless Tag', clk: 0, ct: 0, addr: 1, pwr: 16, mdc: 23, mdio: 18 }
];
ethClockModes = [{ val: 0, label: 'GPIO0 IN' }, { val: 1, label: 'GPIO0 OUT' }, { val: 2, label: 'GPIO16 OUT' }, { val: 3, label: 'GPIO17 OUT' }];
ethPhyTypes = [{ val: 0, label: 'LAN8720' }, { val: 1, label: 'TLK110' }, { val: 2, label: 'RTL8201' }, { val: 3, label: 'DP83848' }, { val: 4, label: 'DM9051' }, { val: 5, label: 'KZ8081' }];
@ -1669,19 +1676,26 @@ class Wifi {
document.getElementById('cbHardwired').checked = settings.connType >= 2;
document.getElementById('cbFallbackWireless').checked = settings.connType === 3;
ui.toElement(pnl, settings);
/*
if (settings.connType >= 2) {
document.getElementById('divWiFiMode').style.display = 'none';
document.getElementById('divEthernetMode').style.display = '';
document.getElementById('divRoaming').style.display = 'none';
document.getElementById('divFallbackWireless').style.display = 'inline-block';
}
else {
document.getElementById('divWiFiMode').style.display = '';
document.getElementById('divEthernetMode').style.display = 'none';
document.getElementById('divFallbackWireless').style.display = 'none';
document.getElementById('divRoaming').style.display = 'inline-block';
}
*/
ui.toElement(document.getElementById('divDHCP'), settings);
document.getElementById('divETHSettings').style.display = settings.ethernet.boardType === 0 ? '' : 'none';
document.getElementById('divStaticIP').style.display = settings.ip.dhcp ? 'none' : '';
ui.toElement(document.getElementById('divDHCP'), settings);
document.getElementById('spanCurrentIP').innerHTML = settings.ip.ip;
this.useEthernetClicked();
this.hiddenSSIDClicked();
}
});
@ -1691,6 +1705,13 @@ class Wifi {
document.getElementById('divWiFiMode').style.display = useEthernet ? 'none' : '';
document.getElementById('divEthernetMode').style.display = useEthernet ? '' : 'none';
document.getElementById('divFallbackWireless').style.display = useEthernet ? 'inline-block' : 'none';
document.getElementById('divRoaming').style.display = useEthernet ? 'none' : 'inline-block';
document.getElementById('divHiddenSSID').style.display = useEthernet ? 'none' : 'inline-block';
}
hiddenSSIDClicked() {
let hidden = document.getElementById('cbHiddenSSID').checked;
if (hidden) document.getElementById('cbRoaming').checked = false;
document.getElementById('cbRoaming').disabled = hidden;
}
async loadAPs() {
if (document.getElementById('btnScanAPs').classList.contains('disabled')) return;
@ -1877,6 +1898,7 @@ class Wifi {
});
}
procWifiStrength(strength) {
//console.log(strength);
let ssid = strength.ssid || strength.name;
document.getElementById('spanNetworkSSID').innerHTML = !ssid || ssid === '' ? '-------------' : ssid;
document.getElementById('spanNetworkChannel').innerHTML = isNaN(strength.channel) || strength.channel < 0 ? '--' : strength.channel;
@ -1915,6 +1937,9 @@ class Somfy {
{ type: 11, name: 'Gate (left)', ico: 'icss-lgate', lift: true, fcmd: true, fpos: true },
{ type: 12, name: 'Gate (center)', ico: 'icss-cgate', lift: true, fcmd: true, fpos: true },
{ type: 13, name: 'Gate (right)', ico: 'icss-rgate', lift: true, fcmd: true, fpos: true },
{ type: 14, name: 'Gate (1-button left)', ico: 'icss-lgate', lift: true, fcmd: true, fpos: true },
{ type: 15, name: 'Gate (1-button center)', ico: 'icss-cgate', lift: true, fcmd: true, fpos: true },
{ type: 16, name: 'Gate (1-button right)', ico: 'icss-rgate', lift: true, fcmd: true, fpos: true },
];
init() {
if (this.initialized) return;
@ -2290,8 +2315,11 @@ class Somfy {
opt.setAttribute('data-type', 'shade');
opt.setAttribute('data-shadetype', shade.shadeType);
opt.setAttribute('data-shadeid', shade.shadeId);
opt.setAttribute('data-bitlength', shade.bitLength);
optGroup.appendChild(opt);
}
let sopt = vrList.options[vrList.selectedIndex];
document.getElementById('divVirtualRemote').setAttribute('data-bitlength', sopt ? sopt.getAttribute('data-bitlength') : 'none');
document.getElementById('divShadeList').innerHTML = divCfg;
let shadeControls = document.getElementById('divShadeControls');
shadeControls.innerHTML = divCtl;
@ -2578,12 +2606,15 @@ class Somfy {
divCtl += `<span class="groupctl-name">${group.name}</span>`;
divCtl += `<div class="groupctl-shades">`;
if (typeof group.linkedShades !== 'undefined') {
divCtl += `<label>Members:</label><span>${group.linkedShades.length}`;
/*
for (let j = 0; j < group.linkedShades.length; j++) {
divCtl += '<span>';
if (j !== 0) divCtl += ', ';
divCtl += group.linkedShades[j].name;
divCtl += '</span>';
}
*/
}
divCtl += '</div></div>';
divCtl += `<div class="groupctl-buttons">`;
@ -2597,9 +2628,13 @@ class Somfy {
opt.setAttribute('data-address', group.remoteAddress);
opt.setAttribute('data-type', 'group');
opt.setAttribute('data-groupid', group.groupId);
opt.setAttribute('data-bitlength', group.bitLength);
optGroup.appendChild(opt);
}
}
let sopt = vrList.options[vrList.selectedIndex];
document.getElementById('divVirtualRemote').setAttribute('data-bitlength', sopt ? sopt.getAttribute('data-bitlength') : 'none');
document.getElementById('divGroupList').innerHTML = divCfg;
let groupControls = document.getElementById('divGroupControls');
groupControls.innerHTML = divCtl;
@ -2761,8 +2796,8 @@ class Somfy {
}
pinMaps = [
{ name: '', maxPins: 39, inputs: [0, 1, 6, 7, 8, 9, 10, 11, 37, 38], outputs: [3, 6, 7, 8, 9, 10, 11, 34, 35, 36, 37, 38, 39] },
{ name: 's2', maxPins: 46, inputs: [0, 15, 16, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 45], outputs: [0, 15, 16, 19, 20, 26, 27, 28, 29, 30, 31, 32, 45, 46]},
{ name: 's3', maxPins: 48, inputs: [0, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 39, 40, 41, 42, 43], outputs: [0, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 39, 40, 41, 42, 43] },
{ name: 's2', maxPins: 46, inputs: [0, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 45], outputs: [0, 19, 20, 26, 27, 28, 29, 30, 31, 32, 45, 46]},
{ name: 's3', maxPins: 48, inputs: [19, 20, 22, 23, 24, 25, 27, 28, 29, 30, 31, 32], outputs: [19, 20, 22, 23, 24, 25, 27, 28, 29, 30, 31, 32] },
{ name: 'c3', maxPins: 21, inputs: [11, 12, 13, 14, 15, 16, 17, 18, 19, 20], outputs: [11, 12, 13, 14, 15, 16, 17, 21] }
];
@ -2877,7 +2912,7 @@ class Somfy {
proto = '-V';
break;
}
let html = `<span>${frame.encKey}</span><span>${frame.address}</span><span>${frame.command}</span><span>${frame.rcode}</span><span>${frame.rssi}dBm</span><span>${frame.bits}${proto}</span><span>${fnFmtTime(frame.time)}</span><div class="frame-pulses">`;
let html = `<span>${frame.encKey}</span><span>${frame.address}</span><span>${frame.command}<sup>${frame.stepSize ? frame.stepSize : ''}</sup></span><span>${frame.rcode}</span><span>${frame.rssi}dBm</span><span>${frame.bits}${proto}</span><span>${fnFmtTime(frame.time)}</span><div class="frame-pulses">`;
for (let i = 0; i < frame.pulses.length; i++) {
if (i !== 0) html += ',';
html += `${frame.pulses[i]}`;
@ -3225,6 +3260,9 @@ class Somfy {
if (obj.proto === 8 || obj.proto === 9) {
switch (obj.shadeType) {
case 5: // Garage 1-button
case 14: // Gate left 1-button
case 15: // Gate center 1-button
case 16: // Gate right 1-button
case 10: // Two button dry contact
if (obj.proto !== 9 && obj.gpioUp === obj.gpioDown) {
ui.errorMessage(document.getElementById('divSomfySettings'), 'For GPIO controlled motors the up and down GPIO selections must be unique.');
@ -3664,31 +3702,80 @@ class Somfy {
html += '<li>If the shade does not jog, press the prog button again until the shade jogs.</li>';
html += '</ul>';
html += `<div class="button-container">`;
html += `<button id="btnSendUnpairing" type="button" style="padding-left:20px;padding-right:20px;display:inline-block;" onclick="somfy.sendCommand(${shadeId}, 'prog', 1);">Prog</button>`;
html += `<button id="btnSendUnpairing" type="button" style="padding-left:20px;padding-right:20px;display:inline-block;">Prog</button>`;
html += `<button id="btnMarkPaired" type="button" style="padding-left:20px;padding-right:20px;display:inline-block;" onclick="somfy.setPaired(${shadeId}, false);">Shade Unpaired</button>`;
html += `<button id="btnStopUnpairing" type="button" style="padding-left:20px;padding-right:20px;display:inline-block" onclick="document.getElementById('divPairing').remove();">Close</button>`;
html += `</div>`;
div.innerHTML = html;
let fnRepeatProg = (err, shade) => {
if (this.btnTimer) {
clearTimeout(this.btnTimer);
this.btnTimer = null;
}
if (err) return;
if (mouseDown) {
somfy.sendCommandRepeat(shadeId, 'prog', null, fnRepeatProg);
}
}
document.getElementById('somfyShade').appendChild(div);
let btn = document.getElementById('btnSendUnpairing');
btn.addEventListener('mousedown', (event) => {
console.log(this);
console.log(event);
console.log('mousedown');
somfy.sendCommand(shadeId, 'prog', null, (err, shade) => { fnRepeatProg(err, shade); });
}, true);
btn.addEventListener('touchstart', (event) => {
console.log(this);
console.log(event);
console.log('touchstart');
somfy.sendCommand(shadeId, 'prog', null, (err, shade) => { fnRepeatProg(err, shade); });
}, true);
return div;
}
sendCommand(shadeId, command, repeat, cb) {
console.log(`Sending Shade command ${shadeId}-${command}`);
let obj = { shadeId: shadeId };
if (isNaN(parseInt(command, 10))) obj.command = command;
else obj.target = parseInt(command, 10);
if (typeof repeat === 'number') obj.repeat = parseInt(repeat);
let obj = {};
if (typeof shadeId.shadeId !== 'undefined') {
obj = shadeId;
cb = command;
shadeId = obj.shadeId;
repeat = obj.repeat;
command = obj.command;
}
else {
obj = { shadeId: shadeId };
if (isNaN(parseInt(command, 10))) obj.command = command;
else obj.target = parseInt(command, 10);
if (typeof repeat === 'number') obj.repeat = parseInt(repeat);
}
putJSON('/shadeCommand', obj, (err, shade) => {
if (typeof cb === 'function') cb(err, shade);
});
}
sendCommandRepeat(shadeId, command, repeat, cb) {
//console.log(`Sending Shade command ${shadeId}-${command}`);
let obj = { shadeId: shadeId, command: command };
if (typeof repeat === 'number') obj.repeat = parseInt(repeat);
let obj = {};
if (typeof shadeId.shadeId !== 'undefined') {
obj = shadeId;
cb = command;
shadeId = obj.shadeId;
repeat = obj.repeat;
command = obj.command;
}
else {
obj = { shadeId: shadeId, command: command };
if (typeof repeat === 'number') obj.repeat = parseInt(repeat);
}
putJSON('/repeatCommand', obj, (err, shade) => {
if (typeof cb === 'function') cb(err, shade);
});
/*
putJSON(`/repeatCommand?shadeId=${shadeId}&command=${command}`, null, (err, shade) => {
if(typeof cb === 'function') cb(err, shade);
});
*/
}
sendGroupRepeat(groupId, command, repeat, cb) {
let obj = { groupId: groupId, command: command };
@ -3707,7 +3794,6 @@ class Somfy {
cmd: el.getAttribute('data-cmd')
};
ui.fromElement(el.parentElement.parentElement, o);
console.log(o);
switch (o.type) {
case 'shade':
o.shadeId = parseInt(opt.getAttribute('data-shadeId'), 10);
@ -3730,17 +3816,17 @@ class Somfy {
else if (o.type === 'group')
somfy.sendGroupRepeat(o.groupId, o.cmd, null, fnRepeatCommand);
else
somfy.sendCommandRepeat(o.shadeId, o.cmd, null, fnRepeatCommand);
somfy.sendCommandRepeat(o, fnRepeatCommand);
}
}
o.command = o.cmd;
if (o.cmd === 'Sensor') {
somfy.sendSetSensor(o);
}
else if (o.type === 'group')
somfy.sendGroupCommand(o.groupId, o.cmd, null, (err, group) => { fnRepeatCommand(err, group); });
else
somfy.sendCommand(o.shadeId, o.cmd, null, (err, shade) => { fnRepeatCommand(err, shade); });
somfy.sendCommand(o, (err, shade) => { fnRepeatCommand(err, shade); });
}
sendSetSensor(obj, cb) {
putJSON('/setSensor', obj, (err, device) => {
@ -4095,6 +4181,9 @@ class Somfy {
case 5:
case 9:
case 10:
case 14:
case 15:
case 16:
return;
}
let tiltType = parseInt(shade.getAttribute('data-tilt'), 10) || 0;
@ -4186,7 +4275,7 @@ class Firmware {
init() { this.initialized = true; }
isMobile() {
let agt = navigator.userAgent.toLowerCase();
return /Android|iPhone|iPod|BlackBerry|BB|PlayBook|IEMobile|Windows Phone|Kindle|Silk|Opera Mini/i.test(navigator.userAgent);
return /Android|iPhone|iPad|iPod|BlackBerry|BB|PlayBook|IEMobile|Windows Phone|Kindle|Silk|Opera Mini/i.test(navigator.userAgent);
}
async backup() {
let overlay = ui.waitMessage(document.getElementById('divContainer'));
@ -4254,12 +4343,13 @@ class Firmware {
let div = this.createFileUploader('/restore');
let inst = div.querySelector('div[id=divInstText]');
let html = '<div style="font-size:14px;">Select a backup file that you would like to restore and the options you would like to restore then press the Upload File button.</div><hr />';
html += `<div style="font-size:14px;">Restoring network settings from a different board than the original will ignore Ethernet chip settings. Security, MQTT and WiFi will also not be restored since backup files do not contain passwords.</div><hr/>`;
html += `<div style="font-size:14px;">Restoring network settings from a different board than the original will ignore Ethernet chip settings. Security, MQTT and WiFi connection information will also not be restored since backup files do not contain passwords.</div><hr/>`;
html += '<div style="font-size:14px;margin-bottom:27px;text-align:left;margin-left:70px;">';
html += `<div class="field-group" style="vertical-align:middle;width:auto;"><input id="cbRestoreShades" type="checkbox" data-bind="shades" style="display:inline-block;" checked="true" /><label for="cbRestoreShades" style="display:inline-block;cursor:pointer;color:white;">Restore Shades and Groups</label></div>`;
html += `<div class="field-group" style="vertical-align:middle;width:auto;"><input id="cbRestoreRepeaters" type="checkbox" data-bind="repeaters" style="display:inline-block;" /><label for="cbRestoreRepeaters" style="display:inline-block;cursor:pointer;color:white;">Restore Repeaters</label></div>`;
html += `<div class="field-group" style="vertical-align:middle;width:auto;"><input id="cbRestoreSystem" type="checkbox" data-bind="settings" style="display:inline-block;" /><label for="cbRestoreSystem" style="display:inline-block;cursor:pointer;color:white;">Restore System Settings</label></div>`;
html += `<div class="field-group" style="vertical-align:middle;width:auto;"><input id="cbRestoreNetwork" type="checkbox" data-bind="network" style="display:inline-block;" /><label for="cbRestoreNetwork" style="display:inline-block;cursor:pointer;color:white;">Restore Network Settings</label></div>`
html += `<div class="field-group" style="vertical-align:middle;width:auto;"><input id="cbRestoreMQTT" type="checkbox" data-bind="mqtt" style="display:inline-block;" /><label for="cbRestoreMQTT" style="display:inline-block;cursor:pointer;color:white;">Restore MQTT Settings</label></div>`
html += `<div class="field-group" style="vertical-align:middle;width:auto;"><input id="cbRestoreTransceiver" type="checkbox" data-bind="transceiver" style="display:inline-block;" /><label for="cbRestoreTransceiver" style="display:inline-block;cursor:pointer;color:white;">Restore Radio Settings</label></div>`;
html += '</div>';
inst.innerHTML = html;
@ -4287,6 +4377,18 @@ class Firmware {
div.innerHTML = html;
return div;
}
procMemoryStatus(mem) {
console.log(mem);
let sp = document.getElementById('spanFreeMemory');
if (sp) sp.innerHTML = mem.free.fmt("#,##0");
sp = document.getElementById('spanMaxMemory');
if (sp) sp.innerHTML = mem.max.fmt('#,##0');
sp = document.getElementById('spanMinMemory');
if (sp) sp.innerHTML = mem.min.fmt('#,##0');
}
procFwStatus(rel) {
console.log(rel);
let div = document.getElementById('divFirmwareUpdate');
@ -4302,15 +4404,22 @@ class Firmware {
break;
case 3: // Updating -- this will be set by the update progress.
break;
case 4:
div.style.color = 'red';
let e = errors.find(x => x.code === rel.error) || { code: err.code, desc: 'Unspecified error' };
let inst = document.getElementById('divGitInstall');
if (inst) {
inst.remove();
ui.errorMessage(e.desc);
case 4: // Complete
if (rel.error !== 0) {
div.style.color = 'red';
let e = errors.find(x => x.code === rel.error) || { code: rel.error, desc: 'Unspecified error' };
let inst = document.getElementById('divGitInstall');
if (inst) {
inst.remove();
ui.errorMessage(e.desc);
}
div.innerHTML = e.desc;
}
else {
div.innerHTML = `Firmware update complete`;
// Throw up a wait message this will be cleared on the reload.
ui.waitMessage(document.getElementById('divContainer'));
}
div.innerHTML = e.desc;
break;
case 5:
div.style.color = 'red';
@ -4633,7 +4742,7 @@ class Firmware {
ui.errorMessage(el, 'This file is not a valid backup file');
return;
}
if (!data.shades && !data.settings && !data.network && !data.transceiver && !data.repeaters) {
if (!data.shades && !data.settings && !data.network && !data.transceiver && !data.repeaters && !data.mqtt) {
ui.errorMessage(el, 'No restore options have been selected');
return;
}

View file

@ -651,7 +651,10 @@ div.wait-overlay > .lds-roller {
}
.somfyGroupCtl .groupctl-shades {
font-size:12px;
margin-top:-3px;
margin-top:0px;
}
.somfyGroupCtl .groupctl-shades > label {
margin-right:3px;
}
.somfyGroupCtl .groupctl-name,
.somfyShadeCtl .shadectl-name {
@ -699,6 +702,12 @@ div.wait-overlay > .lds-roller {
cursor: pointer;
}
#divVirtualRemote[data-bitlength="56"] div.vr-button.vr-80bit {
display:none;
}
#divVirtualRemote[data-bitlength="none"] div.vr-button {
display:none;
}
.shade-positioner {
position: absolute;
width: 100%;
@ -801,6 +810,7 @@ div.frame-header > span {
}
div.frame-row > span:nth-child(3),
div.frame-header > span:nth-child(3) {
white-space:nowrap;
width: 80px;
text-align:center;
}

View file

@ -150,6 +150,9 @@
.shadectl-buttons div.button-outline {
display: inline-block;
}
.shadectl-buttons[data-shadetype="14"] > .cmd-button[data-cmd="sunflag"],
.shadectl-buttons[data-shadetype="15"] > .cmd-button[data-cmd="sunflag"],
.shadectl-buttons[data-shadetype="16"] > .cmd-button[data-cmd="sunflag"],
.shadectl-buttons[data-shadetype="6"] > .cmd-button[data-cmd="sunflag"],
.shadectl-buttons[data-shadetype="5"] > .cmd-button[data-cmd="sunflag"],
.shadectl-buttons[data-shadetype="9"] > .cmd-button[data-cmd="sunflag"],
@ -159,6 +162,15 @@
.shadectl-buttons[data-shadetype="5"] > .button-outline[data-cmd="my"],
.shadectl-buttons[data-shadetype="5"] > .button-outline[data-cmd="up"],
.shadectl-buttons[data-shadetype="5"] > .button-outline[data-cmd="down"],
.shadectl-buttons[data-shadetype="14"] > .button-outline[data-cmd="my"],
.shadectl-buttons[data-shadetype="14"] > .button-outline[data-cmd="up"],
.shadectl-buttons[data-shadetype="14"] > .button-outline[data-cmd="down"],
.shadectl-buttons[data-shadetype="15"] > .button-outline[data-cmd="my"],
.shadectl-buttons[data-shadetype="15"] > .button-outline[data-cmd="up"],
.shadectl-buttons[data-shadetype="15"] > .button-outline[data-cmd="down"],
.shadectl-buttons[data-shadetype="16"] > .button-outline[data-cmd="my"],
.shadectl-buttons[data-shadetype="16"] > .button-outline[data-cmd="up"],
.shadectl-buttons[data-shadetype="16"] > .button-outline[data-cmd="down"],
.shadectl-buttons[data-shadetype="10"] > .button-outline[data-cmd="my"] {
display: none;
}
@ -174,10 +186,13 @@
.shadectl-buttons[data-shadetype="10"] > .button-outline[data-cmd="down"] i {
top:.3em;
}
.shadectl-buttons:not([data-shadetype="5"]):not([data-shadetype="9"]) > .button-outline[data-cmd="toggle"] {
.shadectl-buttons:not([data-shadetype="5"]):not([data-shadetype="9"]):not([data-shadetype="14"]):not([data-shadetype="15"]):not([data-shadetype="16"]) > .button-outline[data-cmd="toggle"] {
display: none;
}
.somfyShadeCtl[data-shadetype="5"] .shadectl-mypos,
.somfyShadeCtl[data-shadetype="14"] .shadectl-mypos,
.somfyShadeCtl[data-shadetype="15"] .shadectl-mypos,
.somfyShadeCtl[data-shadetype="16"] .shadectl-mypos,
.somfyShadeCtl[data-shadetype="9"] .shadectl-mypos,
.somfyShadeCtl[data-tilt="3"] .shadectl-mypos .my-pos,
.somfyShadeCtl:not([data-shadetype="1"]) .shadectl-mypos .my-pos-tilt,
@ -239,12 +254,24 @@
#somfyShade[data-proto="8"] #divGPIOMy,
#somfyShade[data-bitlength="56"] #divStepSettings,
#somfyShade[data-shadetype="5"] #divStepSettings,
#somfyShade[data-shadetype="14"] #divStepSettings,
#somfyShade[data-shadetype="15"] #divStepSettings,
#somfyShade[data-shadetype="16"] #divStepSettings,
#somfyShade[data-shadetype="6"] #divStepSettings {
display: none;
}
#somfyShade[data-proto="9"][data-shadetype="5"] #divGPIOUp,
#somfyShade[data-proto="9"][data-shadetype="5"] #divGPIOMy,
#somfyShade[data-proto="8"][data-shadetype="5"] #divGPIOMy,
#somfyShade[data-proto="9"][data-shadetype="14"] #divGPIOUp,
#somfyShade[data-proto="9"][data-shadetype="14"] #divGPIOMy,
#somfyShade[data-proto="8"][data-shadetype="14"] #divGPIOMy,
#somfyShade[data-proto="9"][data-shadetype="15"] #divGPIOUp,
#somfyShade[data-proto="9"][data-shadetype="15"] #divGPIOMy,
#somfyShade[data-proto="8"][data-shadetype="15"] #divGPIOMy,
#somfyShade[data-proto="9"][data-shadetype="16"] #divGPIOUp,
#somfyShade[data-proto="9"][data-shadetype="16"] #divGPIOMy,
#somfyShade[data-proto="8"][data-shadetype="16"] #divGPIOMy,
#somfyShade[data-proto="9"][data-shadetype="9"] #divGPIOUp,
#somfyShade[data-proto="9"][data-shadetype="9"] #divGPIOMy,
#somfyShade[data-proto="8"][data-shadetype="9"] #divGPIOUp {