mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2025-12-13 11:02:12 +01:00
Compare commits
55 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb75868adb | ||
|
|
3d9e6c11c2 | ||
|
|
e478d17c7f | ||
|
|
93ebddd29d | ||
|
|
fb1f18e260 | ||
|
|
c3ada3b40e | ||
|
|
75928b4ac8 | ||
|
|
0b985c0880 | ||
|
|
e04d7e3fc7 | ||
|
|
9205723125 | ||
|
|
2b59f330a9 | ||
|
|
c528fda55a | ||
|
|
f29cd9c089 | ||
|
|
cf7a9b1fc2 | ||
|
|
473307b320 | ||
|
|
2dc49a64e9 | ||
|
|
5f15a7cc93 | ||
|
|
e87f42fa50 | ||
|
|
4f3a93b336 | ||
|
|
6077052e9b | ||
|
|
e3e843387f | ||
|
|
c4e1dbe44b | ||
|
|
23731f793f | ||
|
|
d64103f259 | ||
|
|
2feb420551 | ||
|
|
82c867d2eb | ||
|
|
f8b3bc4133 | ||
|
|
25c8a66869 | ||
|
|
652346a7c7 | ||
|
|
ce3946c9e6 | ||
|
|
c4d6a1008f | ||
|
|
5a8c09ee9f | ||
|
|
026f9315b1 | ||
|
|
6ba354c7ff | ||
|
|
610773c6b7 | ||
|
|
dd0d7fa2a3 | ||
|
|
d8292fdc2b | ||
|
|
49d134e6f2 | ||
|
|
b8967901de | ||
|
|
5141676724 | ||
|
|
775e82b766 | ||
|
|
b324f59c03 | ||
|
|
cb14cd42dc | ||
|
|
2dd40396c2 | ||
|
|
60af2bf399 | ||
|
|
ea5614c700 | ||
|
|
632dd3900b | ||
|
|
2a3d7aa7e7 | ||
|
|
48ff18030e | ||
|
|
bbefe7fe6d | ||
|
|
bf8da394d5 | ||
|
|
4e0d89e7db | ||
|
|
b458435ddf | ||
|
|
153778c3d2 | ||
|
|
ad6e604f17 |
37 changed files with 3495 additions and 1466 deletions
2
.github/ISSUE_TEMPLATE/BUG-REPORT.yaml
vendored
2
.github/ISSUE_TEMPLATE/BUG-REPORT.yaml
vendored
|
|
@ -54,7 +54,7 @@ body:
|
|||
- type: textarea
|
||||
id: reprod
|
||||
attributes:
|
||||
label: "How to reproduce it (setp by step)"
|
||||
label: "How to reproduce it (step by step)"
|
||||
description: Please enter an explicit description of your issue
|
||||
value: |
|
||||
1. Go to...
|
||||
|
|
|
|||
38
.github/workflows/release.yaml
vendored
38
.github/workflows/release.yaml
vendored
|
|
@ -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
3
.gitignore
vendored
|
|
@ -5,3 +5,6 @@ esp32s3.svd
|
|||
debug.cfg
|
||||
|
||||
SomfyController.ino.XIAO_ESP32S3.bin
|
||||
SomfyController.ino.esp32c3.bin
|
||||
SomfyController.ino.esp32s2.bin
|
||||
.vscode/settings.json
|
||||
|
|
|
|||
213
ConfigFile.cpp
213
ConfigFile.cpp
|
|
@ -7,12 +7,13 @@
|
|||
|
||||
extern Preferences pref;
|
||||
|
||||
#define SHADE_HDR_VER 20
|
||||
#define SHADE_HDR_SIZE 66
|
||||
#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
|
||||
|
||||
extern ConfigSettings settings;
|
||||
|
||||
|
|
@ -51,7 +52,8 @@ bool ConfigFile::writeHeader(const config_header_t &hdr) {
|
|||
this->writeUInt8(hdr.shadeRecords);
|
||||
this->writeUInt16(hdr.groupRecordSize);
|
||||
this->writeUInt8(hdr.groupRecords);
|
||||
|
||||
this->writeUInt16(hdr.repeaterRecordSize);
|
||||
this->writeUInt8(hdr.repeaterRecords);
|
||||
this->writeUInt16(hdr.settingsRecordSize);
|
||||
this->writeUInt16(hdr.netRecordSize);
|
||||
this->writeUInt16(hdr.transRecordSize);
|
||||
|
|
@ -76,6 +78,10 @@ bool ConfigFile::readHeader() {
|
|||
else this->header.groupRecordSize = this->readUInt8(this->header.groupRecordSize);
|
||||
this->header.groupRecords = this->readUInt8(this->header.groupRecords);
|
||||
}
|
||||
if(this->header.version >= 21) {
|
||||
this->header.repeaterRecordSize = this->readUInt16(this->header.repeaterRecordSize);
|
||||
this->header.repeaterRecords = this->readUInt8(this->header.repeaterRecords);
|
||||
}
|
||||
if(this->header.version > 13) {
|
||||
this->header.settingsRecordSize = this->readUInt16(this->header.settingsRecordSize);
|
||||
this->header.netRecordSize = this->readUInt16(this->header.netRecordSize);
|
||||
|
|
@ -119,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);
|
||||
|
|
@ -292,6 +321,8 @@ bool ShadeConfigFile::save(SomfyShadeController *s) {
|
|||
this->header.shadeRecords = s->shadeCount();
|
||||
this->header.groupRecordSize = GROUP_REC_SIZE;
|
||||
this->header.groupRecords = s->groupCount();
|
||||
this->header.repeaterRecords = 1;
|
||||
this->header.repeaterRecordSize = REPEATER_REC_SIZE;
|
||||
this->header.settingsRecordSize = 0;
|
||||
this->header.netRecordSize = 0;
|
||||
this->header.transRecordSize = 0;
|
||||
|
|
@ -311,6 +342,7 @@ bool ShadeConfigFile::save(SomfyShadeController *s) {
|
|||
if(group->getGroupId() != 255)
|
||||
this->writeGroupRecord(group);
|
||||
}
|
||||
this->writeRepeaterRecord(s);
|
||||
return true;
|
||||
}
|
||||
bool ShadeConfigFile::backup(SomfyShadeController *s) {
|
||||
|
|
@ -322,6 +354,8 @@ bool ShadeConfigFile::backup(SomfyShadeController *s) {
|
|||
this->header.shadeRecords = s->shadeCount();
|
||||
this->header.groupRecordSize = GROUP_REC_SIZE;
|
||||
this->header.groupRecords = s->groupCount();
|
||||
this->header.repeaterRecords = 1;
|
||||
this->header.repeaterRecordSize = REPEATER_REC_SIZE;
|
||||
this->header.settingsRecordSize = settings.calcSettingsRecSize();
|
||||
this->header.netRecordSize = settings.calcNetRecSize();
|
||||
this->header.transRecordSize = TRANS_REC_SIZE;
|
||||
|
|
@ -341,6 +375,7 @@ bool ShadeConfigFile::backup(SomfyShadeController *s) {
|
|||
if(group->getGroupId() != 255)
|
||||
this->writeGroupRecord(group);
|
||||
}
|
||||
this->writeRepeaterRecord(s);
|
||||
this->writeSettingsRecord();
|
||||
this->writeNetRecord();
|
||||
this->writeTransRecord(s->transceiver.config);
|
||||
|
|
@ -394,6 +429,9 @@ bool ShadeConfigFile::validate() {
|
|||
fsize += (this->header.netRecordSize);
|
||||
fsize += (this->header.transRecordSize);
|
||||
}
|
||||
if(this->header.version >= 21) {
|
||||
fsize += (this->header.repeaterRecordSize * this->header.repeaterRecords);
|
||||
}
|
||||
if(this->file.size() != fsize) {
|
||||
Serial.printf("File size is not correct should be %d and got %d\n", fsize, this->file.size());
|
||||
}
|
||||
|
|
@ -442,6 +480,17 @@ bool ShadeConfigFile::validate() {
|
|||
}
|
||||
}
|
||||
}
|
||||
if(this->header.version >= 21) {
|
||||
recs = 0;
|
||||
while(recs < this->header.repeaterRecords) {
|
||||
//uint32_t pos = this->file.position();
|
||||
if(!this->seekChar(CFG_REC_END)) {
|
||||
Serial.printf("Failed to find the repeater record end %d\n", recs);
|
||||
}
|
||||
recs++;
|
||||
|
||||
}
|
||||
}
|
||||
this->file.seek(startPos, SeekSet);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -519,6 +568,18 @@ bool ShadeConfigFile::restoreFile(SomfyShadeController *s, const char *filename,
|
|||
+ (this->header.shadeRecords * this->header.shadeRecordSize)
|
||||
+ (this->header.groupRecords * this->header.groupRecordSize), SeekSet); // Start at the beginning of the file after the header.
|
||||
}
|
||||
if(opts.repeaters) {
|
||||
Serial.println("Restoring Repeaters...");
|
||||
if(this->header.repeaterRecords > 0) {
|
||||
memset(s->repeaters, 0x00, sizeof(uint32_t) * SOMFY_MAX_REPEATERS);
|
||||
for(uint8_t i = 0; i < this->header.repeaterRecords; i++) {
|
||||
this->readRepeaterRecord(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
this->file.seek(this->file.position() + this->header.repeaterRecordSize, SeekSet);
|
||||
}
|
||||
if(opts.settings) {
|
||||
// First read out the data.
|
||||
this->readSettingsRecord();
|
||||
|
|
@ -526,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);
|
||||
|
|
@ -545,38 +606,71 @@ 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) {
|
||||
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);
|
||||
// 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) {
|
||||
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);
|
||||
uint32_t startPos = this->file.position();
|
||||
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 {
|
||||
// We are not going to get the network adapter settings.
|
||||
Serial.println("Skipping Ethernet adapter settings (Chip ids do not match)...");
|
||||
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) {
|
||||
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(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");
|
||||
this->seekChar(CFG_REC_END);
|
||||
}
|
||||
}
|
||||
|
|
@ -584,6 +678,7 @@ bool ShadeConfigFile::readNetRecord() {
|
|||
}
|
||||
bool ShadeConfigFile::readTransRecord(transceiver_config_t &cfg) {
|
||||
if(this->header.transRecordSize > 0) {
|
||||
uint32_t startPos = this->file.position();
|
||||
Serial.println("Reading Transceiver settings from file...");
|
||||
cfg.enabled = this->readBool(false);
|
||||
cfg.proto = static_cast<radio_proto>(this->readUInt8(0));
|
||||
|
|
@ -598,6 +693,11 @@ bool ShadeConfigFile::readTransRecord(transceiver_config_t &cfg) {
|
|||
cfg.rxBandwidth = this->readFloat(cfg.rxBandwidth);
|
||||
cfg.deviation = this->readFloat(cfg.deviation);
|
||||
cfg.txPower = this->readInt8(cfg.txPower);
|
||||
if(this->file.position() != startPos + this->header.transRecordSize) {
|
||||
Serial.println("Reading to end of transceiver record");
|
||||
this->seekChar(CFG_REC_END);
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -628,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.
|
||||
|
|
@ -647,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");
|
||||
|
|
@ -654,6 +758,18 @@ bool ShadeConfigFile::readGroupRecord(SomfyGroup *group) {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
bool ShadeConfigFile::readRepeaterRecord(SomfyShadeController *s) {
|
||||
uint32_t startPos = this->file.position();
|
||||
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) {
|
||||
s->linkRepeater(this->readUInt32(0));
|
||||
}
|
||||
if(this->file.position() != startPos + this->header.repeaterRecordSize) {
|
||||
Serial.println("Reading to end of repeater record");
|
||||
this->seekChar(CFG_REC_END);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool ShadeConfigFile::readRoomRecord(SomfyRoom *room) {
|
||||
uint32_t startPos = this->file.position();
|
||||
room->roomId = this->readUInt8(0);
|
||||
|
|
@ -795,6 +911,11 @@ bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) {
|
|||
((SomfyGroup *)&s->groups[ndx++])->clear();
|
||||
}
|
||||
}
|
||||
if(this->header.repeaterRecords > 0) {
|
||||
memset(s->repeaters, 0x00, sizeof(uint32_t) * SOMFY_MAX_REPEATERS);
|
||||
for(uint8_t i = 0; i < this->header.repeaterRecords; i++)
|
||||
this->readRepeaterRecord(s);
|
||||
}
|
||||
if(opened) {
|
||||
Serial.println("Closing shade config file");
|
||||
this->end();
|
||||
|
|
@ -814,7 +935,14 @@ 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) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) {
|
||||
this->writeUInt32(s->repeaters[i], i == SOMFY_MAX_REPEATERS - 1 ? CFG_REC_END : CFG_VALUE_SEP);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool ShadeConfigFile::writeRoomRecord(SomfyRoom *room) {
|
||||
|
|
@ -823,7 +951,6 @@ bool ShadeConfigFile::writeRoomRecord(SomfyRoom *room) {
|
|||
this->writeUInt8(room->sortOrder, CFG_REC_END);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShadeConfigFile::writeShadeRecord(SomfyShade *shade) {
|
||||
if(shade->tiltType == tilt_types::none || shade->shadeType != shade_types::blind) {
|
||||
shade->myTiltPos = -1;
|
||||
|
|
@ -890,6 +1017,12 @@ bool ShadeConfigFile::writeNetRecord() {
|
|||
this->writeVarString(settings.IP.subnet.toString().c_str());
|
||||
this->writeVarString(settings.IP.dns1.toString().c_str());
|
||||
this->writeVarString(settings.IP.dns2.toString().c_str());
|
||||
this->writeVarString(settings.MQTT.protocol);
|
||||
this->writeVarString(settings.MQTT.hostname);
|
||||
this->writeUInt16(settings.MQTT.port);
|
||||
this->writeBool(settings.MQTT.pubDisco);
|
||||
this->writeVarString(settings.MQTT.rootTopic);
|
||||
this->writeVarString(settings.MQTT.discoTopic);
|
||||
this->writeUInt8(settings.Ethernet.boardType);
|
||||
this->writeUInt8(static_cast<uint8_t>(settings.Ethernet.phyType));
|
||||
this->writeUInt8(static_cast<uint8_t>(settings.Ethernet.CLKMode));
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
struct config_header_t {
|
||||
uint8_t version = 1;
|
||||
uint8_t repeaterRecords = 0;
|
||||
uint8_t repeaterRecordSize = 0;
|
||||
uint8_t roomRecords = 0;
|
||||
uint16_t roomRecordSize = 0;
|
||||
uint16_t shadeRecordSize = 0;
|
||||
|
|
@ -53,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');
|
||||
|
|
@ -65,17 +68,19 @@ class ConfigFile {
|
|||
};
|
||||
class ShadeConfigFile : public ConfigFile {
|
||||
protected:
|
||||
bool writeRepeaterRecord(SomfyShadeController *s);
|
||||
bool writeRoomRecord(SomfyRoom *room);
|
||||
bool writeShadeRecord(SomfyShade *shade);
|
||||
bool writeGroupRecord(SomfyGroup *group);
|
||||
bool writeSettingsRecord();
|
||||
bool writeNetRecord();
|
||||
bool writeTransRecord(transceiver_config_t &cfg);
|
||||
bool readRepeaterRecord(SomfyShadeController *s);
|
||||
bool readRoomRecord(SomfyRoom *room);
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ void restore_options_t::fromJSON(JsonObject &obj) {
|
|||
if(obj.containsKey("settings")) this->settings = obj["settings"];
|
||||
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;
|
||||
|
|
@ -83,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) {
|
||||
|
|
@ -189,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);
|
||||
|
|
@ -233,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;
|
||||
}
|
||||
|
|
@ -245,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) {}
|
||||
|
|
@ -266,6 +290,12 @@ uint16_t ConfigSettings::calcNetRecSize() {
|
|||
+ this->IP.subnet.toString().length() + 3
|
||||
+ this->IP.dns1.toString().length() + 3
|
||||
+ this->IP.dns2.toString().length() + 3
|
||||
+ strlen(this->MQTT.protocol) + 3
|
||||
+ strlen(this->MQTT.hostname) + 3
|
||||
+ 6 // MQTT Port
|
||||
+ 6 // PubDisco
|
||||
+ strlen(this->MQTT.rootTopic) + 3
|
||||
+ strlen(this->MQTT.discoTopic) + 3
|
||||
+ 4 // ETH.boardType
|
||||
+ 4 // ETH.phyType
|
||||
+ 4 // ETH.clkMode
|
||||
|
|
@ -278,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;
|
||||
|
|
@ -376,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;
|
||||
|
|
@ -412,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();
|
||||
|
|
@ -472,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();
|
||||
|
|
@ -514,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;
|
||||
}
|
||||
|
|
@ -535,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;
|
||||
}
|
||||
|
|
@ -564,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...");
|
||||
|
|
@ -583,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);
|
||||
|
|
@ -616,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;
|
||||
|
|
|
|||
|
|
@ -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.3.2"
|
||||
enum DeviceStatus {
|
||||
DS_OK = 0,
|
||||
DS_ERROR = 1,
|
||||
|
|
@ -14,6 +22,8 @@ struct restore_options_t {
|
|||
bool shades = false;
|
||||
bool network = false;
|
||||
bool transceiver = false;
|
||||
bool repeaters = false;
|
||||
bool mqtt = false;
|
||||
void fromJSON(JsonObject &obj);
|
||||
};
|
||||
struct appver_t {
|
||||
|
|
@ -24,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);
|
||||
};
|
||||
|
|
@ -34,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);
|
||||
|
|
@ -48,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();
|
||||
|
|
@ -57,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();
|
||||
|
|
@ -85,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();
|
||||
|
|
@ -102,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();
|
||||
|
|
@ -126,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 {
|
||||
|
|
@ -143,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;
|
||||
|
|
@ -172,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();
|
||||
|
|
@ -183,5 +199,4 @@ class ConfigSettings: BaseSettings {
|
|||
uint16_t calcNetRecSize();
|
||||
bool getAppVersion();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
379
GitOTA.cpp
379
GitOTA.cpp
|
|
@ -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,60 +310,93 @@ 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;
|
||||
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);
|
||||
this->inetAvailable = true;
|
||||
}
|
||||
else {
|
||||
err = httpCode;
|
||||
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();
|
||||
}
|
||||
|
|
@ -427,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;
|
||||
}
|
||||
|
|
|
|||
9
GitOTA.h
9
GitOTA.h
|
|
@ -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:
|
||||
|
|
@ -43,6 +43,7 @@ class GitUpdater {
|
|||
uint8_t status = 0;
|
||||
uint32_t lastCheck = 0;
|
||||
bool updateAvailable = false;
|
||||
bool inetAvailable = false;
|
||||
appver_t latest;
|
||||
bool cancelled = false;
|
||||
int16_t error = 0;
|
||||
|
|
@ -57,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);
|
||||
|
|
|
|||
33
MQTT.cpp
33
MQTT.cpp
|
|
@ -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
3
MQTT.h
|
|
@ -17,9 +17,6 @@ class MQTTClass {
|
|||
void reset();
|
||||
bool unpublish(const char *topic);
|
||||
bool publish(const char *topic, const char *payload, bool retain = false);
|
||||
bool publish(const char *topic, JsonDocument &doc, bool retain = false);
|
||||
bool publish(const char *topic, JsonArray &arr, bool retain = false);
|
||||
bool publish(const char *topic, JsonObject &obj, bool retain = false);
|
||||
bool publish(const char *topic, uint8_t val, bool retain = false);
|
||||
bool publish(const char *topic, int8_t val, bool retain = false);
|
||||
bool publish(const char *topic, uint32_t val, bool retain = false);
|
||||
|
|
|
|||
759
Network.cpp
759
Network.cpp
|
|
@ -1,7 +1,7 @@
|
|||
#include <Arduino.h>
|
||||
#include <ETH.h>
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include "ConfigSettings.h"
|
||||
#include "Network.h"
|
||||
#include "Web.h"
|
||||
|
|
@ -16,106 +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) {
|
||||
while(!this->connect()) {
|
||||
// If we lost our connenction
|
||||
connectRetries++;
|
||||
if(connectRetries > 100) {
|
||||
this->openSoftAP();
|
||||
break;
|
||||
}
|
||||
sockEmit.loop();
|
||||
// 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();
|
||||
}
|
||||
connectRetries = 0;
|
||||
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();
|
||||
this->emitSockets();
|
||||
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(WiFi.status() == WL_CONNECTED) {
|
||||
if(abs(abs(WiFi.RSSI()) - abs(this->lastRSSI)) > 1 || WiFi.channel() != this->lastChannel) {
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "{\"ssid\":\"%s\",\"strength\":%d,\"channel\":%d}", WiFi.SSID().c_str(), WiFi.RSSI(), WiFi.channel());
|
||||
sockEmit.sendToClients("wifiStrength", buf);
|
||||
this->lastRSSI = WiFi.RSSI();
|
||||
this->lastChannel = WiFi.channel();
|
||||
sockEmit.loop();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(this->connType == conn_types::ethernet && this->lastRSSI != -100 && this->lastChannel != -1) {
|
||||
sockEmit.sendToClients("wifiStrength", "{\"ssid\":\"\", \"strength\":-100,\"channel\":-1}");
|
||||
this->lastRSSI = -100;
|
||||
this->lastChannel = -1;
|
||||
sockEmit.loop();
|
||||
}
|
||||
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(WiFi.status() == WL_CONNECTED) {
|
||||
snprintf(buf, sizeof(buf), "{\"ssid\":\"%s\",\"strength\":%d,\"channel\":%d}", WiFi.SSID().c_str(), WiFi.RSSI(), WiFi.channel());
|
||||
sockEmit.sendToClient(num, "wifiStrength", buf);
|
||||
this->lastRSSI = WiFi.RSSI();
|
||||
this->lastChannel = WiFi.channel();
|
||||
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(this->connType == conn_types::ethernet && this->lastRSSI != -100 && this->lastChannel != -1)
|
||||
sockEmit.sendToClient(num, "wifiStrength", "{\"ssid\":\"\", \"strength\":-100,\"channel\":-1}");
|
||||
this->lastRSSI = -100;
|
||||
this->lastChannel = -1;
|
||||
if(WiFi.status() == WL_CONNECTED) {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if(this->connType == conn_types::ethernet) {
|
||||
snprintf(buf, sizeof(buf), "{\"connected\":true,\"speed\":%d,\"fullduplex\":%s}", ETH.linkSpeed(), ETH.fullDuplex() ? "true" : "false");
|
||||
sockEmit.sendToClient(num, "ethernet", buf);
|
||||
}
|
||||
else
|
||||
sockEmit.sendToClient(num, "ethernet", "{\"connected\":false, \"speed\":0,\"fullduplex\":false}");
|
||||
|
||||
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;
|
||||
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_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(" (");
|
||||
|
|
@ -145,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 {
|
||||
|
|
@ -155,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);
|
||||
|
|
@ -190,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");
|
||||
|
|
@ -198,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");
|
||||
|
||||
|
|
@ -210,69 +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) {
|
||||
this->disconnected = 0;
|
||||
if(ETH.linkUp()) {
|
||||
// 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);
|
||||
// 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();
|
||||
}
|
||||
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))
|
||||
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);
|
||||
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;
|
||||
}
|
||||
else {
|
||||
uint32_t wait = millis();
|
||||
while(millis() - wait < 7000) {
|
||||
if(this->connected()) return true;
|
||||
delay(500);
|
||||
}
|
||||
if(settings.connType == conn_types::ethernetpref) {
|
||||
this->wifiFallback = true;
|
||||
return connectWiFi();
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
|
@ -287,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);
|
||||
|
|
@ -303,89 +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::wifi && settings.connType != conn_types::unset && !this->wifiFallback)
|
||||
return this->connectWired();
|
||||
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);
|
||||
else if((ctype == conn_types_t::wifi && this->connType != conn_types_t::wifi && settings.WIFI.hidden)) {
|
||||
this->connectWiFi();
|
||||
}
|
||||
return -100;
|
||||
|
||||
return true;
|
||||
}
|
||||
uint32_t Network::getChipId() {
|
||||
uint32_t chipId = 0;
|
||||
|
|
@ -395,142 +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 ((WiFi.status() != WL_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 && 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 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_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_START:
|
||||
Serial.print("(evt) WiFi SoftAP Started IP:");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
net.openingSoftAP = false;
|
||||
net.softAPOpened = true;
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_AP_STOP:
|
||||
Serial.println("WiFi AP Stopped");
|
||||
if(!net.openingSoftAP) Serial.println("(evt) WiFi SoftAP Stopped");
|
||||
net.softAPOpened = false;
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_AP_START:
|
||||
Serial.println("WiFi AP Started");
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_START:
|
||||
if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname);
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
36
Network.h
36
Network.h
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
#ifndef Network_h
|
||||
#define Network_h
|
||||
|
||||
//enum class conn_types_t : byte;
|
||||
|
||||
#define CONNECT_TIMEOUT 20000
|
||||
#define SSID_SCAN_INTERVAL 60000
|
||||
class Network {
|
||||
protected:
|
||||
unsigned long lastEmit = 0;
|
||||
|
|
@ -9,32 +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;
|
||||
conn_types connType = conn_types::unset;
|
||||
bool softAPOpened = false;
|
||||
bool openingSoftAP = false;
|
||||
bool needsBroadcast = true;
|
||||
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);
|
||||
};
|
||||
|
|
|
|||
36
SSDP.cpp
36
SSDP.cpp
|
|
@ -32,7 +32,7 @@ static const char _ssdp_bye_template[] PROGMEM =
|
|||
"NTS: ssdp:byebye\r\n"
|
||||
"NT: %s\r\n"
|
||||
"USN: %s\r\n"
|
||||
"BOOTID.UPNP.ORG: %ul\r\n"
|
||||
"BOOTID.UPNP.ORG: %lu\r\n"
|
||||
"CONFIGID.UPNP.ORG: %d\r\n"
|
||||
"\r\n";
|
||||
static const char _ssdp_packet_template[] PROGMEM =
|
||||
|
|
@ -42,7 +42,7 @@ static const char _ssdp_packet_template[] PROGMEM =
|
|||
"USN: %s\r\n" // _uuid
|
||||
"%s: %s\r\n" // "NT" or "ST", _deviceType
|
||||
"LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL
|
||||
"BOOTID.UPNP.ORG: %ul\r\n"
|
||||
"BOOTID.UPNP.ORG: %lu\r\n"
|
||||
"CONFIGID.UPNP.ORG: %d\r\n"
|
||||
"\r\n";
|
||||
static const char _ssdp_device_schema_template[] PROGMEM =
|
||||
|
|
@ -160,8 +160,8 @@ void UPNPDeviceType::setChipId(uint32_t chipId) {
|
|||
(uint16_t)((chipId >> 8) & 0xff),
|
||||
(uint16_t)chipId & 0xff);
|
||||
}
|
||||
SSDPClass::SSDPClass():sendQueue{false, INADDR_NONE, 0, nullptr, false, 0, ""} {}
|
||||
SSDPClass::~SSDPClass() { end(); }
|
||||
SSDPClass::SSDPClass():sendQueue{false, INADDR_NONE, 0, nullptr, false, 0, "", response_types_t::root} {}
|
||||
SSDPClass::~SSDPClass() { end(); this->isStarted = false; }
|
||||
bool SSDPClass::begin() {
|
||||
for(int i = 0; i < SSDP_QUEUE_SIZE; i++) {
|
||||
this->sendQueue[i].waiting = false;
|
||||
|
|
@ -209,6 +209,7 @@ void SSDPClass::end() {
|
|||
if(this->_server.connected()) {
|
||||
this->_sendByeBye();
|
||||
this->_server.close();
|
||||
Serial.println("Disconnected from SSDP...");
|
||||
}
|
||||
this->isStarted = false;
|
||||
// Clear out the last notified so if the user starts us up again it will notify
|
||||
|
|
@ -216,8 +217,6 @@ void SSDPClass::end() {
|
|||
for(uint8_t i = 0; i < this->m_cdeviceTypes; i++) {
|
||||
this->deviceTypes[i].lastNotified = 0;
|
||||
}
|
||||
|
||||
Serial.println("Disconnected from SSDP...");
|
||||
}
|
||||
UPNPDeviceType* SSDPClass::getDeviceType(uint8_t ndx) { if(ndx < this->m_cdeviceTypes) return &this->deviceTypes[ndx]; return nullptr; }
|
||||
UPNPDeviceType* SSDPClass::findDeviceByType(char *devType) {
|
||||
|
|
@ -407,7 +406,7 @@ void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, UPNPDeviceType *d,
|
|||
strcpy_P(pbuff, _ssdp_response_template);
|
||||
|
||||
// Don't use ip.toString as this fragments the heap like no tomorrow.
|
||||
int len = snprintf_P(buffer, sizeof(buffer)-1,
|
||||
snprintf_P(buffer, sizeof(buffer)-1,
|
||||
_ssdp_packet_template,
|
||||
pbuff,
|
||||
this->_interval,
|
||||
|
|
@ -418,28 +417,6 @@ void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, UPNPDeviceType *d,
|
|||
buffer[sizeof(buffer) - 1] = '\0';
|
||||
this->_sendResponse(addr, port, buffer);
|
||||
free(pbuff);
|
||||
/*
|
||||
static const char _ssdp_packet_template[] PROGMEM =
|
||||
"%s" // _ssdp_response_template / _ssdp_notify_template
|
||||
"CACHE-CONTROL: max-age=%u\r\n" // _interval
|
||||
"SERVER: Arduino/1.0 UPNP/1.1 %s/%s\r\n" // _modelName, _modelNumber
|
||||
"USN: %s\r\n" // _uuid
|
||||
"%s: %s\r\n" // "NT" or "ST", _deviceType
|
||||
"LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL
|
||||
"\r\n";
|
||||
|
||||
|
||||
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.print("Sending Response to ");
|
||||
DEBUG_SSDP.print(IPAddress(addr));
|
||||
DEBUG_SSDP.print(":");
|
||||
DEBUG_SSDP.println(port);
|
||||
DEBUG_SSDP.println(buffer);
|
||||
#endif
|
||||
|
||||
_server.writeTo((const uint8_t *)buffer, len, addr, port);
|
||||
*/
|
||||
}
|
||||
void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, const char *buff) {
|
||||
#ifdef DEBUG_SSDP
|
||||
|
|
@ -545,6 +522,7 @@ void SSDPClass::_sendNotify(UPNPDeviceType *d, bool root) {
|
|||
ip[0], ip[1], ip[2], ip[3], _port, d->schemaURL, this->bootId, this->configId);
|
||||
this->_sendNotify(buffer);
|
||||
d->lastNotified = millis();
|
||||
free(pbuff);
|
||||
}
|
||||
void SSDPClass::setActive(uint8_t ndx, bool isActive) {
|
||||
UPNPDeviceType *d = &this->deviceTypes[ndx];
|
||||
|
|
|
|||
134
Sockets.cpp
134
Sockets.cpp
|
|
@ -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,10 +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");
|
||||
settings.emitSockets(num);
|
||||
somfy.emitState(num);
|
||||
net.emitSockets(num);
|
||||
git.emitUpdateCheck(num);
|
||||
//sockServer.loop();
|
||||
sockEmit.delayInit(num);
|
||||
}
|
||||
break;
|
||||
case WStype_TEXT:
|
||||
|
|
|
|||
38
Sockets.h
38
Sockets.h
|
|
@ -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
|
||||
|
|
|
|||
97
Somfy.h
97
Somfy.h
|
|
@ -1,12 +1,14 @@
|
|||
#ifndef SOMFY_H
|
||||
#define SOMFY_H
|
||||
#include "ConfigSettings.h"
|
||||
#include "WResp.h"
|
||||
|
||||
#define SOMFY_MAX_SHADES 32
|
||||
#define SOMFY_MAX_GROUPS 16
|
||||
#define SOMFY_MAX_LINKED_REMOTES 7
|
||||
#define SOMFY_MAX_GROUPED_SHADES 32
|
||||
#define SOMFY_MAX_ROOMS 16
|
||||
#define SOMFY_MAX_REPEATERS 7
|
||||
|
||||
#define SECS_TO_MILLIS(x) ((x) * 1000)
|
||||
#define MINS_TO_MILLIS(x) SECS_TO_MILLIS((x) * 60)
|
||||
|
|
@ -45,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
|
||||
|
|
@ -63,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,
|
||||
|
|
@ -77,7 +84,7 @@ somfy_commands translateSomfyCommand(const String& string);
|
|||
|
||||
#define MAX_TIMINGS 300
|
||||
#define MAX_RX_BUFFER 3
|
||||
#define MAX_TX_BUFFER 3
|
||||
#define MAX_TX_BUFFER 5
|
||||
|
||||
typedef enum {
|
||||
waiting_synchro = 0,
|
||||
|
|
@ -115,34 +122,35 @@ struct somfy_rx_queue_t {
|
|||
uint8_t length = 0;
|
||||
uint8_t index[MAX_RX_BUFFER];
|
||||
somfy_rx_t items[MAX_RX_BUFFER];
|
||||
//void push(somfy_rx_t *rx);
|
||||
void push(somfy_rx_t *rx);
|
||||
bool pop(somfy_rx_t *rx);
|
||||
};
|
||||
struct somfy_tx_t {
|
||||
void clear() {
|
||||
this->await = 0;
|
||||
this->cmd = somfy_commands::Unknown0;
|
||||
this->repeats = 0;
|
||||
this->hwsync = 0;
|
||||
this->bit_length = 0;
|
||||
memset(this->payload, 0x00, sizeof(this->payload));
|
||||
}
|
||||
uint32_t await = 0;
|
||||
somfy_commands cmd;
|
||||
uint8_t repeats;
|
||||
uint8_t hwsync = 0;
|
||||
uint8_t bit_length = 0;
|
||||
uint8_t payload[10] = {};
|
||||
};
|
||||
struct somfy_tx_queue_t {
|
||||
somfy_tx_queue_t() {
|
||||
this->clear();
|
||||
}
|
||||
somfy_tx_queue_t() { this->clear(); }
|
||||
void clear() {
|
||||
for (uint8_t i = 0; i < MAX_TX_BUFFER; i++) {
|
||||
this->index[i] = 255;
|
||||
this->items[i].clear();
|
||||
}
|
||||
this->length = 0;
|
||||
}
|
||||
unsigned long delay_time = 0;
|
||||
uint8_t length = 0;
|
||||
uint8_t index[MAX_TX_BUFFER];
|
||||
uint8_t index[MAX_TX_BUFFER] = {255};
|
||||
somfy_tx_t items[MAX_TX_BUFFER];
|
||||
bool pop(somfy_tx_t *tx);
|
||||
bool push(uint32_t await, somfy_commands cmd, uint8_t repeats);
|
||||
void push(somfy_rx_t *rx); // Used for repeats
|
||||
void push(uint8_t hwsync, byte *payload, uint8_t bit_length);
|
||||
};
|
||||
|
||||
enum class somfy_flags_t : byte {
|
||||
|
|
@ -152,14 +160,21 @@ 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
|
||||
};
|
||||
struct somfy_relay_t {
|
||||
uint32_t remoteAddress = 0;
|
||||
uint8_t sync = 0;
|
||||
byte frame[10] = {0};
|
||||
};
|
||||
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;
|
||||
|
|
@ -173,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);
|
||||
};
|
||||
|
||||
|
|
@ -189,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();
|
||||
|
|
@ -219,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);
|
||||
|
|
@ -270,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
|
||||
|
|
@ -284,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; }
|
||||
|
|
@ -304,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");
|
||||
|
|
@ -370,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);
|
||||
|
|
@ -384,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);
|
||||
|
|
@ -459,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();
|
||||
|
|
@ -473,13 +499,14 @@ 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();
|
||||
void loop();
|
||||
bool end();
|
||||
bool receive();
|
||||
bool receive(somfy_rx_t *rx);
|
||||
void clearReceived();
|
||||
void enableReceive();
|
||||
void disableReceive();
|
||||
|
|
@ -523,14 +550,18 @@ class SomfyShadeController {
|
|||
bool begin();
|
||||
void loop();
|
||||
void end();
|
||||
void compressRepeaters();
|
||||
uint32_t repeaters[SOMFY_MAX_REPEATERS] = {0};
|
||||
SomfyRoom rooms[SOMFY_MAX_ROOMS];
|
||||
SomfyShade shades[SOMFY_MAX_SHADES];
|
||||
SomfyGroup groups[SOMFY_MAX_GROUPS];
|
||||
bool toJSON(DynamicJsonDocument &doc);
|
||||
bool toJSON(JsonObject &obj);
|
||||
bool toJSONRooms(JsonArray &arr);
|
||||
bool toJSONShades(JsonArray &arr);
|
||||
bool toJSONGroups(JsonArray &arr);
|
||||
bool linkRepeater(uint32_t address);
|
||||
bool unlinkRepeater(uint32_t address);
|
||||
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();
|
||||
uint8_t groupCount();
|
||||
|
|
|
|||
|
|
@ -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.
13
Utils.cpp
13
Utils.cpp
|
|
@ -9,13 +9,13 @@
|
|||
unsigned long Timestamp::epoch() {
|
||||
struct tm tmNow;
|
||||
time_t now;
|
||||
if(!getLocalTime(&tmNow)) return 0;
|
||||
if(!getLocalTime(&tmNow,50)) return 0;
|
||||
time(&now);
|
||||
return now;
|
||||
}
|
||||
time_t Timestamp::now() {
|
||||
struct tm tmNow;
|
||||
getLocalTime(&tmNow);
|
||||
getLocalTime(&tmNow,50);
|
||||
return mktime(&tmNow);
|
||||
}
|
||||
time_t Timestamp::getUTC() {
|
||||
|
|
@ -36,7 +36,14 @@ time_t Timestamp::mkUTCTime(struct tm *dt) {
|
|||
return tsBadLocal + tsLocalOffset;
|
||||
}
|
||||
time_t Timestamp::parseUTCTime(const char *buff) {
|
||||
struct tm dt = {0};
|
||||
struct tm dt;
|
||||
dt.tm_hour = 0;
|
||||
dt.tm_mday = 0;
|
||||
dt.tm_mon = 0;
|
||||
dt.tm_year = 0;
|
||||
dt.tm_wday = 0;
|
||||
dt.tm_yday = 0;
|
||||
dt.tm_isdst = false;
|
||||
char num[5];
|
||||
uint8_t i = 0;
|
||||
memset(num, 0x00, sizeof(num));
|
||||
|
|
|
|||
3
Utils.h
3
Utils.h
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
|
||||
[[maybe_unused]] static void SETCHARPROP(char *prop, const char *value, size_t size) {strncpy(prop, value, size); prop[size - 1] = '\0';}
|
||||
/*
|
||||
namespace util {
|
||||
// Createa a custom to_string function. C++ can be annoying
|
||||
// with all the trailing 0s on number formats.
|
||||
|
|
@ -23,6 +24,8 @@ namespace util {
|
|||
return str;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
static void _ltrim(char *str) {
|
||||
int s = 0, j, k = 0;
|
||||
int e = strlen(str);
|
||||
|
|
|
|||
204
WResp.cpp
Normal file
204
WResp.cpp
Normal 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
74
WResp.h
Normal 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
|
||||
10
Web.h
10
Web.h
|
|
@ -1,4 +1,5 @@
|
|||
#include <WebServer.h>
|
||||
#include "Somfy.h"
|
||||
#ifndef webserver_h
|
||||
#define webserver_h
|
||||
class Web {
|
||||
|
|
@ -12,6 +13,7 @@ class Web {
|
|||
void handleStreamFile(WebServer &server, const char *filename, const char *encoding);
|
||||
void handleController(WebServer &server);
|
||||
void handleLoginContext(WebServer &server);
|
||||
void handleGetRepeaters(WebServer &server);
|
||||
void handleGetRooms(WebServer &server);
|
||||
void handleGetShades(WebServer &server);
|
||||
void handleGetGroups(WebServer &server);
|
||||
|
|
@ -40,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
BIN
data/apple-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
|
|
@ -1 +1 @@
|
|||
2.3.2
|
||||
2.4.7
|
||||
93
data/icon.svg
Normal file
93
data/icon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -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%;
|
||||
}
|
||||
274
data/index.html
274
data/index.html
|
|
@ -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.3.2c" type="text/css" />
|
||||
<link rel="stylesheet" href="widgets.css?v=2.3.2c" type="text/css" />
|
||||
<link rel="stylesheet" href="icons.css?v=2.3.2c" 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.3.2c"></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,14 +275,25 @@
|
|||
<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;">
|
||||
<input id="cbFallbackWireless" name="fallbackwireless" data-bind="wirelessFallback" type="checkbox" style="display:inline-block;" />
|
||||
<input id="cbFallbackWireless" name="fallbackwireless" data-bind="ethernet.wirelessFallback" type="checkbox" style="display:inline-block;" />
|
||||
<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>
|
||||
|
|
@ -295,7 +428,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="divSomfySettings" style="display:none;">
|
||||
<div class="subtab-container"><span class="selected" data-grpid="divSomfyRooms">Rooms</span><span class="" data-grpid="divSomfyMotors">Shades</span><span data-grpid="divSomfyGroups">Groups</span><span data-grpid="divVirtualRemote">Virtual Remote</span></div>
|
||||
<div class="subtab-container"><span class="selected" data-grpid="divSomfyRooms">Rooms</span><span class="" data-grpid="divSomfyMotors">Shades</span><span data-grpid="divSomfyGroups">Groups</span><span data-grpid="divRepeater">Repeater</span><span class="tabname-virtual-remote" data-grpid="divVirtualRemote"></span></div>
|
||||
<div id="divSomfyRooms" class="subtab-content" style="padding-top:10px;">
|
||||
<div id="divRoomListContainer">
|
||||
<div style="font-size:.8em;">Drag each item to set the order in which they appear in the list.</div>
|
||||
|
|
@ -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>
|
||||
|
|
@ -462,7 +599,7 @@
|
|||
<div id="divSunSensor" style="margin-top:-10px;">
|
||||
<div class="field-group">
|
||||
<input id="cbHasSunsensor" name="hasSunSensor" data-bind="sunSensor" type="checkbox" style="" />
|
||||
<label for="cbHasSunSensor" style="display:block;font-size:1em;margin-top:0px;margin-left:7px;display:inline-block;">Has Sun Sensor</label>
|
||||
<label for="cbHasSunsensor" style="display:block;font-size:1em;margin-top:0px;margin-left:7px;display:inline-block;">Has Sun Sensor</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="divLightSwitch" style="margin-top:-10px;">
|
||||
|
|
@ -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;">
|
||||
|
|
@ -631,11 +775,22 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
<div id="divVirtualRemote" style="display:none;" class="subtab-content">
|
||||
<div id="divRepeater" style="display:none;padding-top:10px;" class="subtab-content">
|
||||
<div id="divRepeaterListContainer">
|
||||
<div style="font-size:.8em;">All repeated remote addresses are listed below.</div>
|
||||
<div id="divRepeatList" class="edit-repeaterlist"></div>
|
||||
<div class="button-container">
|
||||
<button id="btnLinkRepeater" type="button" onclick="somfy.linkRepeatRemote();">
|
||||
Scan Remote
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
|
|
@ -645,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>
|
||||
|
|
@ -675,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>
|
||||
|
|
@ -694,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">
|
||||
|
|
@ -926,24 +1090,24 @@
|
|||
mouseDown = false;
|
||||
});
|
||||
(async () => { await init(); })();
|
||||
/*
|
||||
security.init();
|
||||
//(async () => { await general.init(); })();
|
||||
general.init();
|
||||
wifi.init();
|
||||
somfy.init();
|
||||
mqtt.init();
|
||||
firmware.init();
|
||||
*/
|
||||
/*
|
||||
security.init();
|
||||
//(async () => { await general.init(); })();
|
||||
general.init();
|
||||
wifi.init();
|
||||
somfy.init();
|
||||
mqtt.init();
|
||||
firmware.init();
|
||||
*/
|
||||
/*
|
||||
let o = {
|
||||
general: { hostname: 'hostname', ntpServer: 'pool.ntp.org', posixZone: '<-10>10', ssdpBroadcast: true }
|
||||
|
||||
}
|
||||
*/
|
||||
//ui.toElement(document.getElementById('divContainer'), o);
|
||||
//console.log(ui.fromElement(document.getElementById('divContainer')));
|
||||
//(async () => { await initSockets(); })();
|
||||
//ui.toElement(document.getElementById('divContainer'), o);
|
||||
//console.log(ui.fromElement(document.getElementById('divContainer')));
|
||||
//(async () => { await initSockets(); })();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
259
data/index.js
259
data/index.js
|
|
@ -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();
|
||||
|
|
@ -571,7 +577,7 @@ async function initSockets() {
|
|||
};
|
||||
socket.onclose = (evt) => {
|
||||
wifi.procWifiStrength({ ssid: '', channel: -1, strength: -100 });
|
||||
wifi.procEthernet({ connected: '', speed: 0, fullduplex: false });
|
||||
wifi.procEthernet({ connected: false, speed: 0, fullduplex: false });
|
||||
if (document.getElementsByClassName('socket-wait').length === 0)
|
||||
ui.waitMessage(document.getElementById('divContainer')).classList.add('socket-wait');
|
||||
if (evt.wasClean) {
|
||||
|
|
@ -1264,7 +1270,7 @@ var security = new Security();
|
|||
|
||||
class General {
|
||||
initialized = false;
|
||||
appVersion = 'v2.3.2';
|
||||
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;
|
||||
|
|
@ -1807,7 +1828,7 @@ class Wifi {
|
|||
saveNetwork() {
|
||||
let pnl = document.getElementById('divNetAdapter');
|
||||
let obj = ui.fromElement(pnl);
|
||||
obj.connType = obj.ethernet.hardwired ? obj.ethernet.wirelessFallback ? 3 : 2 : 1;
|
||||
obj.connType = obj.ethernet.hardwired ? (obj.ethernet.wirelessFallback ? 3 : 2) : 1;
|
||||
console.log(obj);
|
||||
if (obj.connType >= 2) {
|
||||
let boardType = this.ethBoardTypes.find(elem => obj.ethernet.boardType === elem.val);
|
||||
|
|
@ -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;
|
||||
|
|
@ -1957,6 +1982,7 @@ class Somfy {
|
|||
this.setRoomsList(somfy.rooms);
|
||||
this.setShadesList(somfy.shades);
|
||||
this.setGroupsList(somfy.groups);
|
||||
this.setRepeaterList(somfy.repeaters);
|
||||
if (typeof somfy.version !== 'undefined') firmware.procFwStatus(somfy.version);
|
||||
}
|
||||
});
|
||||
|
|
@ -2203,6 +2229,18 @@ class Somfy {
|
|||
});
|
||||
});
|
||||
}
|
||||
setRepeaterList(addresses) {
|
||||
let divCfg = '';
|
||||
if (typeof addresses !== 'undefined') {
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
divCfg += `<div class="somfyRepeater" data-address="${addresses[i]}"><div class="repeater-name">${addresses[i]}</div>`;
|
||||
divCfg += `<div class="button-outline" onclick="somfy.unlinkRepeater(${addresses[i]});"><i class="icss-trash"></i></div>`;
|
||||
divCfg += '</div>';
|
||||
}
|
||||
}
|
||||
document.getElementById('divRepeatList').innerHTML = divCfg;
|
||||
|
||||
}
|
||||
setShadesList(shades) {
|
||||
let divCfg = '';
|
||||
let divCtl = '';
|
||||
|
|
@ -2277,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;
|
||||
|
|
@ -2565,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">`;
|
||||
|
|
@ -2584,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;
|
||||
|
|
@ -2748,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, 3, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 39, 40, 41, 42, 43, 44], outputs: [0, 3, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 39, 40, 41, 42, 43, 44] },
|
||||
{ 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] }
|
||||
];
|
||||
|
||||
|
|
@ -2832,6 +2880,16 @@ class Somfy {
|
|||
this.setLinkedRemotesList(shade);
|
||||
});
|
||||
}
|
||||
else {
|
||||
lnk = document.getElementById('divLinkRepeater');
|
||||
if (lnk) {
|
||||
putJSONSync(`/linkRepeater`, {address:frame.address}, (err, repeaters) => {
|
||||
lnk.remove();
|
||||
if (err) ui.serviceError(err);
|
||||
else this.setRepeaterList(repeaters);
|
||||
});
|
||||
}
|
||||
}
|
||||
let frames = document.getElementById('divFrames');
|
||||
let row = document.createElement('div');
|
||||
row.classList.add('frame-row');
|
||||
|
|
@ -2854,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]}`;
|
||||
|
|
@ -3092,6 +3150,8 @@ class Somfy {
|
|||
showEditRoom(bShow) {
|
||||
let el = document.getElementById('divLinking');
|
||||
if (el) el.remove();
|
||||
el = document.getElementById('divLinkRepeater');
|
||||
if (el) el.remove();
|
||||
el = document.getElementById('divPairing');
|
||||
if (el) el.remove();
|
||||
el = document.getElementById('divRollingCode');
|
||||
|
|
@ -3108,6 +3168,8 @@ class Somfy {
|
|||
showEditShade(bShow) {
|
||||
let el = document.getElementById('divLinking');
|
||||
if (el) el.remove();
|
||||
el = document.getElementById('divLinkRepeater');
|
||||
if (el) el.remove();
|
||||
el = document.getElementById('divPairing');
|
||||
if (el) el.remove();
|
||||
el = document.getElementById('divRollingCode');
|
||||
|
|
@ -3124,6 +3186,8 @@ class Somfy {
|
|||
showEditGroup(bShow) {
|
||||
let el = document.getElementById('divLinking');
|
||||
if (el) el.remove();
|
||||
el = document.getElementById('divLinkRepeater');
|
||||
if (el) el.remove();
|
||||
el = document.getElementById('divPairing');
|
||||
if (el) el.remove();
|
||||
el = document.getElementById('divRollingCode');
|
||||
|
|
@ -3196,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.');
|
||||
|
|
@ -3332,6 +3399,15 @@ class Somfy {
|
|||
}
|
||||
});
|
||||
}
|
||||
updateRepeatList() {
|
||||
getJSONSync('/repeaters', (err, repeaters) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
ui.serviceError(err);
|
||||
}
|
||||
else this.setRepeaterList(repeaters);
|
||||
});
|
||||
}
|
||||
deleteRoom(roomId) {
|
||||
let valid = true;
|
||||
if (isNaN(roomId) || roomId >= 255 || roomId <= 0) {
|
||||
|
|
@ -3626,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 };
|
||||
|
|
@ -3669,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);
|
||||
|
|
@ -3692,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) => {
|
||||
|
|
@ -3740,6 +3864,22 @@ class Somfy {
|
|||
document.getElementById('somfyShade').appendChild(div);
|
||||
return div;
|
||||
}
|
||||
linkRepeatRemote() {
|
||||
let div = document.createElement('div');
|
||||
let html = `<div id="divLinkRepeater" class="instructions" data-type="link-repeatremote" style="border-radius:27px;">`;
|
||||
html += '<div>Press any button on the remote to repeat its signals.</div>';
|
||||
html += '<div class="sub-message">When assigned, ESPSomfy RTS will act as a repeater and repeat any frames for the identified remotes.</div>'
|
||||
html += '<div class="sub-message" style="font-size:14px;">Only assign a repeater when ESPSomfy RTS reliably hears a physical remote but the motor does not. Repeating unnecessary radio signals will degrade radio performance and never assign the same repeater to more than one ESPSomfy RTS device. You will have created an insidious echo chamber.</div>'
|
||||
|
||||
html += '<div class="sub-message">Once a signal is detected from the remote this window will close and the remote signals will be repeated.</div>'
|
||||
html += '<hr></hr>';
|
||||
html += `<div><div class="button-container"><button id="btnStopLinking" type="button" style="padding-left:20px;padding-right:20px;" onclick="document.getElementById('divLinkRepeater').remove();">Cancel</button></div>`;
|
||||
html += '</div>';
|
||||
div.innerHTML = html;
|
||||
document.getElementById('divConfigPnl').appendChild(div);
|
||||
return div;
|
||||
}
|
||||
|
||||
linkGroupShade(groupId) {
|
||||
let div = document.createElement('div');
|
||||
let html = `<div id="divLinkGroup" class="inst-overlay wizard" data-type="link-shade" data-groupid="${groupId}" data-stepid="1">`;
|
||||
|
|
@ -3956,6 +4096,17 @@ class Somfy {
|
|||
});
|
||||
return div;
|
||||
}
|
||||
unlinkRepeater(address) {
|
||||
let prompt = ui.promptMessage('Are you sure you want to stop repeating frames from this address?', () => {
|
||||
putJSONSync('/unlinkRepeater', { address: address }, (err, repeaters) => {
|
||||
if (err) ui.serviceError(err);
|
||||
else this.setRepeaterList(repeaters);
|
||||
prompt.remove();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
unlinkRemote(shadeId, remoteAddress) {
|
||||
let prompt = ui.promptMessage('Are you sure you want to unlink this remote from the shade?', () => {
|
||||
let obj = {
|
||||
|
|
@ -4030,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;
|
||||
|
|
@ -4121,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'));
|
||||
|
|
@ -4189,11 +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;
|
||||
|
|
@ -4221,12 +4377,24 @@ 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');
|
||||
if (rel.updateAvailable && rel.status === 0) {
|
||||
div.style.color = 'red';
|
||||
div.innerHTML = `Firmware ${rel.latest.name} Available`;
|
||||
if (rel.available && rel.status === 0 && rel.checkForUpdate !== false) {
|
||||
div.style.color = 'black';
|
||||
div.innerHTML = `<span>Firmware ${rel.fwVersion.name} Installed<span><span style="color:red"> ${rel.latest.name} Available</span>`;
|
||||
}
|
||||
else {
|
||||
switch (rel.status) {
|
||||
|
|
@ -4236,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';
|
||||
|
|
@ -4559,11 +4734,15 @@ class Firmware {
|
|||
ui.errorMessage('You must select a valid backup file to proceed.');
|
||||
return;
|
||||
}
|
||||
else if (!filename.endsWith('.backup') || filename.indexOf('ESPSomfyRTS') === -1) {
|
||||
else if (field.files[0].size > 20480) {
|
||||
ui.errorMessage(el, `This file is ${field.files[0].size.fmt("#,##0")} bytes in length. This file is too large to be a valid backup file.`);
|
||||
return;
|
||||
}
|
||||
else if (!filename.endsWith('.backup')) {
|
||||
ui.errorMessage(el, 'This file is not a valid backup file');
|
||||
return;
|
||||
}
|
||||
if (!data.shades && !data.settings && !data.network && !data.transceiver) {
|
||||
if (!data.shades && !data.settings && !data.network && !data.transceiver && !data.repeaters && !data.mqtt) {
|
||||
ui.errorMessage(el, 'No restore options have been selected');
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="main.css?v=2.0.0" type="text/css" />
|
||||
<link rel="stylesheet" href="widgets.css?v=2.0.0" type="text/css" />
|
||||
<link rel="stylesheet" href="icons.css?v=2.0.0" type="text/css" />
|
||||
<link rel="stylesheet" href="main.css?v=2.4.0" type="text/css" />
|
||||
<link rel="stylesheet" href="widgets.css?v=2.4.0" type="text/css" />
|
||||
<link rel="stylesheet" href="icons.css?v=2.4.0" type="text/css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<script type="text/javascript" src="index.js?v=2.0.0"></script>
|
||||
<script type="text/javascript" src="index.js?v=2.4.0"></script>
|
||||
</head>
|
||||
<body onload="general.loadLogin();">
|
||||
<div id="divContainer" class="container" data-securitytype="0">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@
|
|||
left: 0px;
|
||||
color: white;
|
||||
}
|
||||
#divLinkRepeat {
|
||||
border-radius:24px;
|
||||
}
|
||||
.edit-repeaterlist,
|
||||
.edit-roomlist,
|
||||
.edit-grouplist,
|
||||
.edit-motorlist {
|
||||
|
|
@ -61,6 +65,7 @@
|
|||
max-height:calc(100% - 77px);
|
||||
overflow:auto;
|
||||
}
|
||||
.instructions .sub-message,
|
||||
.prompt-message .sub-message {
|
||||
font-size: 17px;
|
||||
padding-left: 10px;
|
||||
|
|
@ -82,6 +87,7 @@
|
|||
.wizard[data-stepid="6"] .wizard-step:not([data-stepid="6"]) { display: none; }
|
||||
.wizard[data-stepid="7"] .wizard-step:not([data-stepid="7"]) { display: none; }
|
||||
|
||||
.somfyRepeater,
|
||||
.somfyRoom,
|
||||
.somfyGroup,
|
||||
.somfyShade {
|
||||
|
|
@ -93,6 +99,8 @@
|
|||
}
|
||||
.linked-shade > div,
|
||||
.linked-shade > span,
|
||||
.somfyRepeater > div,
|
||||
.somfyRepeater > span,
|
||||
.somfyRoom > div,
|
||||
.somfyRoom > span,
|
||||
.somfyGroup > div,
|
||||
|
|
@ -103,12 +111,16 @@
|
|||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.repeater-name,
|
||||
.linked-shade {
|
||||
width: 100%;
|
||||
padding-bottom: 4px;
|
||||
display: inline-table;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.repeater-name {
|
||||
text-align:left;
|
||||
}
|
||||
.linked-shade > .linkedshade-name { width: 100%; }
|
||||
.pin-digit {
|
||||
|
|
@ -138,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"],
|
||||
|
|
@ -147,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;
|
||||
}
|
||||
|
|
@ -162,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,
|
||||
|
|
@ -227,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 {
|
||||
|
|
@ -303,6 +342,10 @@
|
|||
.room-selector-list .room-row:hover {
|
||||
color: var(--shade-color, gray);
|
||||
}
|
||||
span.tabname-virtual-remote:after {
|
||||
content: "Virtual Remote";
|
||||
}
|
||||
|
||||
@media only screen and (max-device-width: 480px) {
|
||||
body {
|
||||
margin-top: 0px;
|
||||
|
|
@ -312,4 +355,7 @@
|
|||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
span.tabname-virtual-remote:after {
|
||||
content: "Remote";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue