mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2025-12-12 18:42:10 +01:00
v1.4.0 upade
* Moved shade storage from NVS. NVS storage became limited because of the wired ethernet boards. This limited the number of potential shades to around 20. * Added the ability to backup the shade configuration * Added the ability to restore the shade configuration. * Increased up. down, and tilt timing value to allow for up to 54 days of transition. The previous 16bit value did not allow for very slow shades and was limited to just over a minute. * UI cleanup and additional messages. * Transceiver tuning now applies the rx bandwidth in the proper order so no reboot is required.
This commit is contained in:
parent
21d56993d0
commit
dce0ae0c04
15 changed files with 1023 additions and 241 deletions
370
ConfigFile.cpp
Normal file
370
ConfigFile.cpp
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
#include <Arduino.h>
|
||||
#include <LittleFS.h>
|
||||
#include <Preferences.h>
|
||||
#include "ConfigFile.h"
|
||||
#include "Utils.h"
|
||||
|
||||
extern Preferences pref;
|
||||
|
||||
#define SHADE_HDR_VER 1
|
||||
#define SHADE_HDR_SIZE 16
|
||||
#define SHADE_REC_SIZE 176
|
||||
|
||||
bool ConfigFile::begin(const char* filename, bool readOnly) {
|
||||
this->file = LittleFS.open(filename, readOnly ? "r" : "w");
|
||||
this->_opened = true;
|
||||
return true;
|
||||
}
|
||||
void ConfigFile::end() {
|
||||
if(this->isOpen()) {
|
||||
if(!this->readOnly) this->file.flush();
|
||||
this->file.close();
|
||||
}
|
||||
this->_opened = false;
|
||||
}
|
||||
bool ConfigFile::isOpen() { return this->_opened; }
|
||||
bool ConfigFile::seekChar(const char val) {
|
||||
if(!this->isOpen()) return false;
|
||||
char ch;
|
||||
do {
|
||||
ch = this->readChar('\0');
|
||||
if(ch == '\0') return false;
|
||||
} while(ch != val);
|
||||
return true;
|
||||
}
|
||||
bool ConfigFile::writeSeparator() {return this->writeChar(CFG_VALUE_SEP); }
|
||||
bool ConfigFile::writeRecordEnd() { return this->writeChar(CFG_REC_END); }
|
||||
bool ConfigFile::writeHeader() { return this->writeHeader(this->header); }
|
||||
bool ConfigFile::writeHeader(const config_header_t &hdr) {
|
||||
if(!this->isOpen()) return false;
|
||||
this->writeUInt8(hdr.version);
|
||||
this->writeUInt8(hdr.length);
|
||||
this->writeUInt8(hdr.recordSize);
|
||||
this->writeUInt8(hdr.records, CFG_REC_END);
|
||||
return true;
|
||||
}
|
||||
bool ConfigFile::readHeader() {
|
||||
if(!this->isOpen()) return false;
|
||||
//if(this->file.position() != 0) this->file.seek(0, SeekSet);
|
||||
Serial.printf("Reading header at %u\n", this->file.position());
|
||||
this->header.version = this->readUInt8(this->header.version);
|
||||
this->header.length = this->readUInt8(0);
|
||||
this->header.recordSize = this->readUInt8(this->header.recordSize);
|
||||
this->header.records = this->readUInt8(this->header.records);
|
||||
Serial.printf("version:%u len:%u size:%u recs:%u pos:%d\n", this->header.version, this->header.length, this->header.recordSize, this->header.records, this->file.position());
|
||||
return true;
|
||||
}
|
||||
bool ConfigFile::seekRecordByIndex(uint16_t ndx) {
|
||||
if(!this->file) {
|
||||
return false;
|
||||
}
|
||||
if(((this->header.recordSize * ndx) + this->header.length) > this->file.size()) return false;
|
||||
}
|
||||
bool ConfigFile::readString(char *buff, size_t len) {
|
||||
if(!this->file) return false;
|
||||
memset(buff, 0x00, len);
|
||||
uint16_t i = 0;
|
||||
while(i < len) {
|
||||
uint8_t val;
|
||||
if(this->file.read(&val, 1) == 1) {
|
||||
switch(val) {
|
||||
case CFG_REC_END:
|
||||
case CFG_VALUE_SEP:
|
||||
_rtrim(buff);
|
||||
return true;
|
||||
}
|
||||
buff[i++] = val;
|
||||
if(i == len) {
|
||||
_rtrim(buff);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
_rtrim(buff);
|
||||
return true;
|
||||
}
|
||||
bool ConfigFile::writeString(const char *val, size_t len, const char tok) {
|
||||
if(!this->isOpen()) return false;
|
||||
int slen = strlen(val);
|
||||
if(slen > 0)
|
||||
if(this->file.write((uint8_t *)val, slen) != slen) return false;
|
||||
// Now we need to pad the end of the string so that it is of a fixed length.
|
||||
while(slen < len - 1) {
|
||||
this->file.write(' ');
|
||||
slen++;
|
||||
}
|
||||
// 255 = len = 4 slen = 3
|
||||
if(tok != CFG_TOK_NONE)
|
||||
return this->writeChar(tok);
|
||||
return true;
|
||||
}
|
||||
bool ConfigFile::writeChar(const char val) {
|
||||
if(!this->isOpen()) return false;
|
||||
if(this->file.write(static_cast<uint8_t>(val)) == 1) return true;
|
||||
return false;
|
||||
}
|
||||
bool ConfigFile::writeUInt8(const uint8_t val, const char tok) {
|
||||
char buff[4];
|
||||
snprintf(buff, sizeof(buff), "%3u", val);
|
||||
return this->writeString(buff, sizeof(buff), tok);
|
||||
}
|
||||
bool ConfigFile::writeUInt16(const uint16_t val, const char tok) {
|
||||
char buff[6];
|
||||
snprintf(buff, sizeof(buff), "%5u", val);
|
||||
return this->writeString(buff, sizeof(buff), tok);
|
||||
}
|
||||
bool ConfigFile::writeUInt32(const uint32_t val, const char tok) {
|
||||
char buff[11];
|
||||
snprintf(buff, sizeof(buff), "%10u", val);
|
||||
return this->writeString(buff, sizeof(buff), tok);
|
||||
}
|
||||
bool ConfigFile::writeFloat(const float val, const uint8_t prec, const char tok) {
|
||||
char buff[20];
|
||||
snprintf(buff, sizeof(buff), "%*.*f", 7 + prec, prec, val);
|
||||
return this->writeString(buff, 8 + prec, tok);
|
||||
}
|
||||
bool ConfigFile::writeBool(const bool val, const char tok) {
|
||||
return this->writeString(val ? "true" : "false", 6, tok);
|
||||
}
|
||||
|
||||
char ConfigFile::readChar(const char defVal) {
|
||||
uint8_t ch;
|
||||
if(this->file.read(&ch, 1) == 1) return (char)ch;
|
||||
return defVal;
|
||||
}
|
||||
uint8_t ConfigFile::readUInt8(const uint8_t defVal) {
|
||||
char buff[4];
|
||||
if(this->readString(buff, sizeof(buff)))
|
||||
return static_cast<uint8_t>(atoi(buff));
|
||||
return defVal;
|
||||
}
|
||||
uint16_t ConfigFile::readUInt16(const uint16_t defVal) {
|
||||
char buff[6];
|
||||
if(this->readString(buff, sizeof(buff)))
|
||||
return static_cast<uint16_t>(atoi(buff));
|
||||
return defVal;
|
||||
}
|
||||
uint32_t ConfigFile::readUInt32(const uint32_t defVal) {
|
||||
char buff[11];
|
||||
if(this->readString(buff, sizeof(buff)))
|
||||
return static_cast<uint32_t>(atoi(buff));
|
||||
return defVal;
|
||||
}
|
||||
float ConfigFile::readFloat(const float defVal) {
|
||||
char buff[25];
|
||||
if(this->readString(buff, sizeof(buff)))
|
||||
return atof(buff);
|
||||
return defVal;
|
||||
}
|
||||
bool ConfigFile::readBool(const bool defVal) {
|
||||
char buff[6];
|
||||
if(this->readString(buff, sizeof(buff))) {
|
||||
switch(buff[0]) {
|
||||
case 't':
|
||||
case 'T':
|
||||
case '1':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return defVal;
|
||||
}
|
||||
|
||||
bool ShadeConfigFile::seekRecordById(uint8_t id) {
|
||||
if(this->isOpen()) return false;
|
||||
this->file.seek(this->header.length, SeekSet); // Start at the beginning of the file after the header.
|
||||
uint8_t i = 0;
|
||||
while(i < SOMFY_MAX_SHADES) {
|
||||
uint32_t pos = this->file.position();
|
||||
uint8_t len = this->readUInt8(this->header.recordSize);
|
||||
uint8_t cid = this->readUInt8(255);
|
||||
if(cid == id) {
|
||||
this->file.seek(pos, SeekSet);
|
||||
return true;
|
||||
}
|
||||
pos += len;
|
||||
this->file.seek(pos, SeekSet);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool ShadeConfigFile::begin(bool readOnly) { return this->begin("/shades.cfg", readOnly); }
|
||||
bool ShadeConfigFile::begin(const char *filename, bool readOnly) { return ConfigFile::begin(filename, readOnly); }
|
||||
void ShadeConfigFile::end() { ConfigFile::end(); }
|
||||
bool ShadeConfigFile::save(SomfyShadeController *s) {
|
||||
this->header.version = SHADE_HDR_VER;
|
||||
this->header.recordSize = SHADE_REC_SIZE;
|
||||
this->header.length = SHADE_HDR_SIZE;
|
||||
this->header.records = SOMFY_MAX_SHADES;
|
||||
this->writeHeader();
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
SomfyShade *shade = &s->shades[i];
|
||||
this->writeShadeRecord(shade);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool ShadeConfigFile::validate() {
|
||||
this->readHeader();
|
||||
if(this->header.version < 1) {
|
||||
Serial.print("Invalid Header Version:");
|
||||
Serial.println(this->header.version);
|
||||
return false;
|
||||
}
|
||||
if(this->header.recordSize < 100) {
|
||||
Serial.print("Invalid Record Size:");
|
||||
Serial.println(this->header.recordSize);
|
||||
return false;
|
||||
}
|
||||
if(this->header.records != 32) {
|
||||
Serial.print("Invalid Record Count:");
|
||||
Serial.println(this->header.records);
|
||||
return false;
|
||||
}
|
||||
if(this->file.position() != this->header.length) {
|
||||
Serial.printf("File not positioned at %u end of header: %d\n", this->header.length, this->file.position());
|
||||
return false;
|
||||
}
|
||||
// We should know the file size based upon the record information in the header
|
||||
if(this->file.size() != this->header.length + (this->header.recordSize * this->header.records)) {
|
||||
Serial.printf("File size is not correct should be %d and got %d", this->header.length + (this->header.recordSize * this->header.records), this->file.size());
|
||||
}
|
||||
// Next check to see if the records match the header length.
|
||||
uint8_t recs = 0;
|
||||
uint32_t startPos = this->file.position();
|
||||
while(recs < this->header.records) {
|
||||
uint32_t pos = this->file.position();
|
||||
if(!this->seekChar(CFG_REC_END)) {
|
||||
Serial.printf("Failed to find the record end %d\n", recs);
|
||||
return false;
|
||||
}
|
||||
if(this->file.position() - pos != this->header.recordSize) {
|
||||
Serial.printf("Record length is %d and should be %d\n", this->file.position() - pos, this->header.recordSize);
|
||||
return false;
|
||||
}
|
||||
recs++;
|
||||
}
|
||||
this->file.seek(startPos, SeekSet);
|
||||
return true;
|
||||
}
|
||||
bool ShadeConfigFile::load(SomfyShadeController *s, const char *filename) {
|
||||
ShadeConfigFile file;
|
||||
if(file.begin(filename, true)) {
|
||||
bool success = file.loadFile(s, filename);
|
||||
file.end();
|
||||
return success;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) {
|
||||
bool opened = false;
|
||||
if(!this->isOpen()) {
|
||||
Serial.println("Opening shade config file");
|
||||
this->begin(filename, true);
|
||||
opened = true;
|
||||
}
|
||||
else {
|
||||
//this->file.seek(0, SeekSet);
|
||||
}
|
||||
if(!this->validate()) {
|
||||
Serial.println("Shade config file invalid!");
|
||||
if(opened) this->end();
|
||||
return false;
|
||||
}
|
||||
// We should be valid so start reading.
|
||||
pref.begin("ShadeCodes");
|
||||
for(uint8_t i = 0; i < this->header.records; i++) {
|
||||
SomfyShade *shade = &s->shades[i];
|
||||
shade->setShadeId(this->readUInt8(255));
|
||||
shade->paired = this->readBool(false);
|
||||
shade->shadeType = static_cast<shade_types>(this->readUInt8(0));
|
||||
shade->setRemoteAddress(this->readUInt32(0));
|
||||
this->readString(shade->name, sizeof(shade->name));
|
||||
shade->hasTilt = this->readBool(false);
|
||||
shade->upTime = this->readUInt32(shade->upTime);
|
||||
shade->downTime = this->readUInt32(shade->downTime);
|
||||
shade->tiltTime = this->readUInt32(shade->tiltTime);
|
||||
for(uint8_t j = 0; j < SOMFY_MAX_LINKED_REMOTES; j++) {
|
||||
SomfyLinkedRemote *rem = &shade->linkedRemotes[j];
|
||||
rem->setRemoteAddress(this->readUInt32(0));
|
||||
if(rem->getRemoteAddress() != 0) rem->lastRollingCode = pref.getUShort(rem->getRemotePrefId(), 0);
|
||||
}
|
||||
shade->lastRollingCode = this->readUInt16(0);
|
||||
if(shade->getRemoteAddress() != 0) shade->lastRollingCode = max(pref.getUShort(shade->getRemotePrefId(), shade->lastRollingCode), shade->lastRollingCode);
|
||||
shade->myPos = this->readUInt8(255);
|
||||
shade->currentPos = this->readFloat(0);
|
||||
shade->currentTiltPos = this->readFloat(0);
|
||||
shade->tiltPosition = (uint8_t)floor(shade->currentTiltPos * 100);
|
||||
shade->position = (uint8_t)floor(shade->currentPos * 100);
|
||||
shade->target = shade->position;
|
||||
shade->tiltTarget = shade->tiltPosition;
|
||||
}
|
||||
pref.end();
|
||||
if(opened) {
|
||||
Serial.println("Closing shade config file");
|
||||
this->end();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool ShadeConfigFile::writeShadeRecord(SomfyShade *shade) {
|
||||
this->writeUInt8(shade->getShadeId());
|
||||
this->writeBool(shade->paired);
|
||||
this->writeUInt8(static_cast<uint8_t>(shade->shadeType));
|
||||
this->writeUInt32(shade->getRemoteAddress());
|
||||
this->writeString(shade->name, sizeof(shade->name));
|
||||
this->writeBool(shade->hasTilt);
|
||||
this->writeUInt32(shade->upTime);
|
||||
this->writeUInt32(shade->downTime);
|
||||
this->writeUInt32(shade->tiltTime);
|
||||
for(uint8_t j = 0; j < SOMFY_MAX_LINKED_REMOTES; j++) {
|
||||
SomfyLinkedRemote *rem = &shade->linkedRemotes[j];
|
||||
this->writeUInt32(rem->getRemoteAddress());
|
||||
}
|
||||
this->writeUInt16(shade->lastRollingCode);
|
||||
this->writeUInt8(shade->myPos);
|
||||
this->writeFloat(shade->currentPos, 5);
|
||||
this->writeFloat(shade->currentTiltPos, 5, CFG_REC_END);
|
||||
return true;
|
||||
}
|
||||
bool ShadeConfigFile::exists() { return LittleFS.exists("/shades.cfg"); }
|
||||
bool ShadeConfigFile::getAppVersion(appver_t &ver) {
|
||||
char app[15];
|
||||
if(!LittleFS.exists("/appversion")) return false;
|
||||
File f = LittleFS.open("/appversion", "r");
|
||||
size_t fsize = f.size();
|
||||
memset(app, 0x00, sizeof(app));
|
||||
f.read((uint8_t *)app, sizeof(app) - 1);
|
||||
f.close();
|
||||
// Now lets parse this pig.
|
||||
memset(&ver, 0x00, sizeof(appver_t));
|
||||
char num[3];
|
||||
uint8_t i = 0;
|
||||
for(uint8_t j = 0; j < 3 && i < strlen(app); j++) {
|
||||
char ch = app[i++];
|
||||
if(ch != '.')
|
||||
num[j] = ch;
|
||||
else
|
||||
break;
|
||||
}
|
||||
ver.major = static_cast<uint8_t>(atoi(num) & 0xFF);
|
||||
memset(num, 0x00, sizeof(num));
|
||||
for(uint8_t j = 0; j < 3 && i < strlen(app); j++) {
|
||||
char ch = app[i++];
|
||||
if(ch != '.')
|
||||
num[j] = ch;
|
||||
else
|
||||
break;
|
||||
}
|
||||
ver.minor = static_cast<uint8_t>(atoi(num) & 0xFF);
|
||||
memset(num, 0x00, sizeof(num));
|
||||
for(uint8_t j = 0; j < 3 && i < strlen(app); j++) {
|
||||
char ch = app[i++];
|
||||
if(ch != '.')
|
||||
num[j] = ch;
|
||||
else
|
||||
break;
|
||||
}
|
||||
ver.build = static_cast<uint8_t>(atoi(num) & 0xFF);
|
||||
return true;
|
||||
}
|
||||
66
ConfigFile.h
Normal file
66
ConfigFile.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#include <ArduinoJson.h>
|
||||
#include <LittleFS.h>
|
||||
#include "Somfy.h"
|
||||
#ifndef configfile_h
|
||||
#define configfile_h
|
||||
|
||||
#define CFG_VALUE_SEP ','
|
||||
#define CFG_REC_END '\n'
|
||||
#define CFG_TOK_NONE 0x00
|
||||
|
||||
typedef struct config_header_t {
|
||||
uint8_t version = 1;
|
||||
uint16_t recordSize = 0;
|
||||
uint16_t records = 0;
|
||||
uint8_t length = 0;
|
||||
};
|
||||
class ConfigFile {
|
||||
protected:
|
||||
File file;
|
||||
bool readOnly = false;
|
||||
bool begin(const char *filename, bool readOnly = false);
|
||||
uint32_t startRecPos = 0;
|
||||
bool _opened = false;
|
||||
public:
|
||||
config_header_t header;
|
||||
bool save();
|
||||
void end();
|
||||
bool isOpen();
|
||||
bool seekRecordByIndex(uint16_t ndx);
|
||||
bool readHeader();
|
||||
bool seekChar(const char val);
|
||||
bool writeHeader(const config_header_t &header);
|
||||
bool writeHeader();
|
||||
bool writeSeparator();
|
||||
bool writeRecordEnd();
|
||||
bool writeChar(const char val);
|
||||
bool writeUInt8(const uint8_t val, const char tok = CFG_VALUE_SEP);
|
||||
bool writeUInt16(const uint16_t val, const char tok = CFG_VALUE_SEP);
|
||||
bool writeUInt32(const uint32_t val, const char tok = CFG_VALUE_SEP);
|
||||
bool writeBool(const bool val, const char tok = CFG_VALUE_SEP);
|
||||
bool writeFloat(const float val, const uint8_t prec, const char tok = CFG_VALUE_SEP);
|
||||
bool readString(char *buff, size_t len);
|
||||
bool writeString(const char *val, size_t len, const char tok = CFG_VALUE_SEP);
|
||||
char readChar(const char defVal = '\0');
|
||||
uint8_t readUInt8(const uint8_t defVal = 0);
|
||||
uint16_t readUInt16(const uint16_t defVal = 0);
|
||||
uint32_t readUInt32(const uint32_t defVal = 0);
|
||||
bool readBool(const bool defVal = false);
|
||||
float readFloat(const float defVal = 0.00);
|
||||
};
|
||||
class ShadeConfigFile : public ConfigFile {
|
||||
protected:
|
||||
bool writeShadeRecord(SomfyShade *shade);
|
||||
public:
|
||||
static bool getAppVersion(appver_t &ver);
|
||||
static bool exists();
|
||||
static bool load(SomfyShadeController *somfy, const char *filename = "/shades.cfg");
|
||||
bool begin(const char *filename, bool readOnly = false);
|
||||
bool begin(bool readOnly = false);
|
||||
bool save(SomfyShadeController *sofmy);
|
||||
bool loadFile(SomfyShadeController *somfy, const char *filename = "/shades.cfg");
|
||||
void end();
|
||||
bool seekRecordById(uint8_t id);
|
||||
bool validate();
|
||||
};
|
||||
#endif;
|
||||
|
|
@ -47,8 +47,6 @@ bool BaseSettings::parseIPAddress(JsonObject &obj, const char *prop, IPAddress *
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int BaseSettings::parseValueInt(JsonObject &obj, const char *prop, int defVal) {
|
||||
if(obj.containsKey(prop)) return obj[prop];
|
||||
return defVal;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#ifndef configsettings_h
|
||||
#define configsettings_h
|
||||
|
||||
#define FW_VERSION "v1.3.2"
|
||||
#define FW_VERSION "v1.4.0"
|
||||
enum DeviceStatus {
|
||||
DS_OK = 0,
|
||||
DS_ERROR = 1,
|
||||
|
|
|
|||
|
|
@ -123,10 +123,10 @@ void Network::setConnected(conn_types connType) {
|
|||
Serial.print(" ");
|
||||
Serial.print(ETH.linkSpeed());
|
||||
Serial.println("Mbps");
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "{\"connected\":true,\"speed\":%d,\"fullduplex\":%s}", ETH.linkSpeed(), ETH.fullDuplex() ? "true" : "false");
|
||||
sockEmit.sendToClients("ethernet", buf);
|
||||
}
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "{\"connected\":true,\"speed\":%d,\"fullduplex\":%s}", ETH.linkSpeed(), ETH.fullDuplex() ? "true" : "false");
|
||||
sockEmit.sendToClients("ethernet", buf);
|
||||
}
|
||||
else {
|
||||
Serial.println();
|
||||
|
|
|
|||
510
Somfy.cpp
510
Somfy.cpp
|
|
@ -7,12 +7,14 @@
|
|||
#include "Somfy.h"
|
||||
#include "Sockets.h"
|
||||
#include "MQTT.h"
|
||||
#include "ConfigFile.h"
|
||||
|
||||
extern Preferences pref;
|
||||
extern SomfyShadeController somfy;
|
||||
extern SocketEmitter sockEmit;
|
||||
extern MQTTClass mqtt;
|
||||
|
||||
|
||||
uint8_t rxmode = 0; // Indicates whether the radio is in receive mode. Just to ensure there isn't more than one interrupt hooked.
|
||||
#define SYMBOL 640
|
||||
#if defined(ESP8266)
|
||||
|
|
@ -245,13 +247,16 @@ SomfyShade *SomfyShadeController::findShadeByRemoteAddress(uint32_t address) {
|
|||
}
|
||||
return nullptr;
|
||||
}
|
||||
bool SomfyShadeController::begin() {
|
||||
// Load up all the configuration data.
|
||||
//Serial.printf("sizeof(SomfyShade) = %d\n", sizeof(SomfyShade));
|
||||
pref.begin("Shades");
|
||||
bool SomfyShadeController::loadLegacy() {
|
||||
Serial.println("Loading Legacy shades using NVS");
|
||||
pref.begin("Shades", true);
|
||||
pref.getBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.end();
|
||||
//this->transceiver.begin();
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
if(i != 0) DEBUG_SOMFY.print(",");
|
||||
DEBUG_SOMFY.print(this->m_shadeIds[i]);
|
||||
}
|
||||
DEBUG_SOMFY.println();
|
||||
sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
#ifdef DEBUG_SOMFY
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
|
|
@ -281,12 +286,63 @@ bool SomfyShadeController::begin() {
|
|||
}
|
||||
Serial.println();
|
||||
#endif
|
||||
pref.begin("Shades");
|
||||
pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.end();
|
||||
if(!this->useNVS()) {
|
||||
pref.begin("Shades");
|
||||
pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.end();
|
||||
}
|
||||
this->commit();
|
||||
return true;
|
||||
}
|
||||
bool SomfyShadeController::begin() {
|
||||
// Load up all the configuration data.
|
||||
ShadeConfigFile::getAppVersion(this->appVersion);
|
||||
Serial.printf("App Version:%u.%u.%u\n", this->appVersion.major, this->appVersion.minor, this->appVersion.build);
|
||||
if(!this->useNVS()) { // At 1.4 we started using the configuration file. If the file doesn't exist then booh.
|
||||
// We need to remove all the extraeneous data from NVS for the shades. From here on out we
|
||||
// will rely on the shade configuration.
|
||||
Serial.println("No longer using NVS");
|
||||
if(ShadeConfigFile::exists()) {
|
||||
ShadeConfigFile::load(this);
|
||||
}
|
||||
else {
|
||||
this->loadLegacy();
|
||||
}
|
||||
pref.begin("Shades");
|
||||
if(pref.isKey("shadeIds")) {
|
||||
pref.getBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.clear(); // Delete all the keys.
|
||||
}
|
||||
pref.end();
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
// Start deleting the keys for the shades.
|
||||
if(this->m_shadeIds[i] == 255) continue;
|
||||
char shadeKey[15];
|
||||
sprintf(shadeKey, "SomfyShade%u", this->m_shadeIds[i]);
|
||||
pref.begin(shadeKey);
|
||||
pref.clear();
|
||||
pref.end();
|
||||
}
|
||||
}
|
||||
else if(ShadeConfigFile::exists()) {
|
||||
Serial.println("shades.cfg exists so we are using that");
|
||||
ShadeConfigFile::load(this);
|
||||
}
|
||||
else {
|
||||
Serial.println("Starting clean");
|
||||
this->loadLegacy();
|
||||
}
|
||||
this->transceiver.begin();
|
||||
return true;
|
||||
}
|
||||
void SomfyShadeController::commit() {
|
||||
ShadeConfigFile file;
|
||||
file.begin();
|
||||
file.save(this);
|
||||
file.end();
|
||||
this->isDirty = false;
|
||||
this->lastCommit = millis();
|
||||
}
|
||||
SomfyShade * SomfyShadeController::getShadeById(uint8_t shadeId) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
if(this->shades[i].getShadeId() == shadeId) return &this->shades[i];
|
||||
|
|
@ -304,41 +360,85 @@ bool SomfyShade::linkRemote(uint32_t address, uint16_t rollingCode) {
|
|||
}
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
|
||||
if(this->linkedRemotes[i].getRemoteAddress() == 0) {
|
||||
char shadeKey[15];
|
||||
this->linkedRemotes[i].setRemoteAddress(address);
|
||||
this->linkedRemotes[i].setRollingCode(rollingCode);
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId());
|
||||
pref.begin(shadeKey);
|
||||
uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES];
|
||||
memset(linkedAddresses, 0x00, sizeof(linkedAddresses));
|
||||
uint8_t j = 0;
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
|
||||
SomfyLinkedRemote lremote = this->linkedRemotes[i];
|
||||
if(lremote.getRemoteAddress() != 0) linkedAddresses[j++] = lremote.getRemoteAddress();
|
||||
if(somfy.useNVS()) {
|
||||
uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES];
|
||||
memset(linkedAddresses, 0x00, sizeof(linkedAddresses));
|
||||
uint8_t j = 0;
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
|
||||
SomfyLinkedRemote lremote = this->linkedRemotes[i];
|
||||
if(lremote.getRemoteAddress() != 0) linkedAddresses[j++] = lremote.getRemoteAddress();
|
||||
}
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId());
|
||||
pref.begin(shadeKey);
|
||||
pref.putBytes("linkedAddr", linkedAddresses, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES);
|
||||
pref.end();
|
||||
}
|
||||
pref.putBytes("linkedAddr", linkedAddresses, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES);
|
||||
pref.end();
|
||||
this->commit();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void SomfyShade::commit() { somfy.commit(); }
|
||||
void SomfyShade::commitShadePosition() {
|
||||
somfy.isDirty = true;
|
||||
char shadeKey[15];
|
||||
if(somfy.useNVS()) {
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
|
||||
Serial.print("Writing current shade position: ");
|
||||
Serial.println(this->currentPos, 4);
|
||||
pref.begin(shadeKey);
|
||||
pref.putFloat("currentPos", this->currentPos);
|
||||
pref.end();
|
||||
}
|
||||
}
|
||||
void SomfyShade::commitMyPosition() {
|
||||
somfy.isDirty = true;
|
||||
if(somfy.useNVS()) {
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
|
||||
Serial.print("Writing my shade position:");
|
||||
Serial.print(this->myPos);
|
||||
Serial.println("%");
|
||||
pref.begin(shadeKey);
|
||||
pref.putUShort("myPos", this->myPos);
|
||||
pref.end();
|
||||
}
|
||||
}
|
||||
void SomfyShade::commitTiltPosition() {
|
||||
somfy.isDirty = true;
|
||||
if(somfy.useNVS()) {
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
|
||||
Serial.print("Writing current shade tilt position: ");
|
||||
Serial.println(this->currentTiltPos, 4);
|
||||
pref.begin(shadeKey);
|
||||
pref.putFloat("currentTiltPos", this->currentTiltPos);
|
||||
pref.end();
|
||||
}
|
||||
}
|
||||
bool SomfyShade::unlinkRemote(uint32_t address) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
|
||||
if(this->linkedRemotes[i].getRemoteAddress() == address) {
|
||||
char shadeKey[15];
|
||||
this->linkedRemotes[i].setRemoteAddress(0);
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId());
|
||||
pref.begin(shadeKey);
|
||||
uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES];
|
||||
memset(linkedAddresses, 0x00, sizeof(linkedAddresses));
|
||||
uint8_t j = 0;
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
|
||||
SomfyLinkedRemote lremote = this->linkedRemotes[i];
|
||||
if(lremote.getRemoteAddress() != 0) linkedAddresses[j++] = lremote.getRemoteAddress();
|
||||
if(somfy.useNVS()) {
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId());
|
||||
uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES];
|
||||
memset(linkedAddresses, 0x00, sizeof(linkedAddresses));
|
||||
uint8_t j = 0;
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
|
||||
SomfyLinkedRemote lremote = this->linkedRemotes[i];
|
||||
if(lremote.getRemoteAddress() != 0) linkedAddresses[j++] = lremote.getRemoteAddress();
|
||||
}
|
||||
pref.begin(shadeKey);
|
||||
pref.putBytes("linkedAddr", linkedAddresses, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES);
|
||||
pref.end();
|
||||
}
|
||||
pref.putBytes("linkedAddr", linkedAddresses, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES);
|
||||
pref.end();
|
||||
this->commit();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -505,13 +605,7 @@ void SomfyShade::checkMovement() {
|
|||
}
|
||||
}
|
||||
if(currDir != this->direction && this->direction == 0) {
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
|
||||
Serial.print("Writing current shade position: ");
|
||||
Serial.println(this->currentPos, 4);
|
||||
pref.begin(shadeKey);
|
||||
pref.putFloat("currentPos", this->currentPos);
|
||||
pref.end();
|
||||
this->commitShadePosition();
|
||||
if(this->settingMyPos) {
|
||||
delay(200);
|
||||
// Set this position before sending the command. If you don't the processFrame function
|
||||
|
|
@ -521,23 +615,11 @@ void SomfyShade::checkMovement() {
|
|||
SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS);
|
||||
this->settingMyPos = false;
|
||||
this->seekingMyPos = false;
|
||||
Serial.print("Committing My Position: ");
|
||||
Serial.print(this->myPos);
|
||||
Serial.println("%");
|
||||
|
||||
pref.begin(shadeKey);
|
||||
pref.putUShort("myPos", this->myPos);
|
||||
pref.end();
|
||||
this->commitMyPosition();
|
||||
}
|
||||
}
|
||||
if(currTiltDir != this->tiltDirection && this->tiltDirection == 0) {
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
|
||||
Serial.print("Writing current shade tilt position: ");
|
||||
Serial.println(this->currentTiltPos, 4);
|
||||
pref.begin(shadeKey);
|
||||
pref.putFloat("currentTiltPos", this->currentTiltPos);
|
||||
pref.end();
|
||||
this->commitTiltPosition();
|
||||
}
|
||||
if(currDir != this->direction || currPos != this->position || currTiltDir != this->tiltDirection || currTiltPos != this->tiltPosition) {
|
||||
// We need to emit on the socket that our state has changed.
|
||||
|
|
@ -555,19 +637,36 @@ void SomfyShade::load() {
|
|||
// Now load up each of the shades into memory.
|
||||
//Serial.print("key:");
|
||||
//Serial.println(shadeKey);
|
||||
pref.begin(shadeKey);
|
||||
|
||||
pref.begin(shadeKey, !somfy.useNVS());
|
||||
pref.getString("name", this->name, sizeof(this->name));
|
||||
this->paired = pref.getBool("paired", false);
|
||||
this->upTime = pref.getUShort("upTime", 10000);
|
||||
this->downTime = pref.getUShort("downTime", 10000);
|
||||
this->setRemoteAddress(pref.getULong("remoteAddress", 0));
|
||||
if(pref.isKey("upTime") && pref.getType("upTime") != PreferenceType::PT_U32) {
|
||||
// We need to convert these to 32 bits because earlier versions did not support this.
|
||||
this->upTime = static_cast<uint32_t>(pref.getUShort("upTime", 1000));
|
||||
this->downTime = static_cast<uint32_t>(pref.getUShort("downTime", 1000));
|
||||
this->tiltTime = static_cast<uint32_t>(pref.getUShort("tiltTime", 7000));
|
||||
if(somfy.useNVS()) {
|
||||
pref.remove("upTime");
|
||||
pref.putUInt("upTime", this->upTime);
|
||||
pref.remove("downTime");
|
||||
pref.putUInt("downTime", this->downTime);
|
||||
pref.remove("tiltTime");
|
||||
pref.putUInt("tiltTime", this->tiltTime);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this->upTime = pref.getUInt("upTime", this->upTime);
|
||||
this->downTime = pref.getUInt("downTime", this->downTime);
|
||||
this->tiltTime = pref.getUInt("tiltTime", this->tiltTime);
|
||||
}
|
||||
this->setRemoteAddress(pref.getUInt("remoteAddress", 0));
|
||||
this->currentPos = pref.getFloat("currentPos", 0);
|
||||
this->position = (uint8_t)floor(this->currentPos * 100);
|
||||
this->target = this->position;
|
||||
this->myPos = pref.getUShort("myPos", this->myPos);
|
||||
this->hasTilt = pref.getBool("hasTilt", false);
|
||||
this->shadeType = static_cast<shade_types>(pref.getChar("shadeType", static_cast<uint8_t>(this->shadeType)));
|
||||
this->tiltTime = pref.getUShort("tiltTime", 3000);
|
||||
this->currentTiltPos = pref.getFloat("currentTiltPos", 0);
|
||||
this->tiltPosition = (uint8_t)floor(this->currentTiltPos * 100);
|
||||
this->tiltTarget = this->tiltPosition;
|
||||
|
|
@ -591,7 +690,6 @@ void SomfyShade::load() {
|
|||
lremote.lastRollingCode = pref.getUShort(lremote.getRemotePrefId(), 0);
|
||||
}
|
||||
pref.end();
|
||||
|
||||
}
|
||||
void SomfyShade::publish() {
|
||||
if(mqtt.connected()) {
|
||||
|
|
@ -698,10 +796,7 @@ void SomfyShade::processWaitingFrame() {
|
|||
this->myPos = 255;
|
||||
else
|
||||
this->myPos = this->position;
|
||||
Serial.print(this->name);
|
||||
Serial.print(" MY POSITION SET TO:");
|
||||
Serial.print(this->myPos);
|
||||
Serial.println("%");
|
||||
this->commitMyPosition();
|
||||
this->lastFrame.processed = true;
|
||||
this->emitState();
|
||||
}
|
||||
|
|
@ -755,6 +850,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
|
|||
// This is an internal tilt command.
|
||||
Serial.println("Processing Tilt UP...");
|
||||
this->setTiltMovement(-1);
|
||||
this->lastFrame.processed = true;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
|
|
@ -777,6 +873,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
|
|||
// This is an internal tilt command.
|
||||
Serial.println("Processing Tilt DOWN...");
|
||||
this->setTiltMovement(1);
|
||||
this->lastFrame.processed = true;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
|
|
@ -833,13 +930,7 @@ void SomfyShade::setTiltMovement(int8_t dir) {
|
|||
this->tiltStart = 0;
|
||||
this->tiltDirection = dir;
|
||||
if(currDir != dir) {
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
|
||||
Serial.print("Writing current shade position:");
|
||||
Serial.println(this->currentTiltPos, 4);
|
||||
pref.begin(shadeKey);
|
||||
pref.putFloat("currentTiltPos", this->currentTiltPos);
|
||||
pref.end();
|
||||
this->commitTiltPosition();
|
||||
}
|
||||
}
|
||||
else if(this->direction != dir) {
|
||||
|
|
@ -852,7 +943,6 @@ void SomfyShade::setTiltMovement(int8_t dir) {
|
|||
this->emitState();
|
||||
}
|
||||
}
|
||||
|
||||
void SomfyShade::setMovement(int8_t dir) {
|
||||
int8_t currDir = this->direction;
|
||||
if(dir == 0) {
|
||||
|
|
@ -861,13 +951,7 @@ void SomfyShade::setMovement(int8_t dir) {
|
|||
this->moveStart = 0;
|
||||
this->direction = dir;
|
||||
if(currDir != dir) {
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
|
||||
Serial.print("Writing current shade position:");
|
||||
Serial.println(this->currentPos, 4);
|
||||
pref.begin(shadeKey);
|
||||
pref.putFloat("currentPos", this->currentPos);
|
||||
pref.end();
|
||||
this->commitShadePosition();
|
||||
}
|
||||
}
|
||||
else if(this->direction != dir) {
|
||||
|
|
@ -895,18 +979,11 @@ void SomfyShade::setMyPosition(uint8_t target) {
|
|||
}
|
||||
else {
|
||||
this->sendCommand(somfy_commands::My, SETMY_REPEATS);
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
|
||||
if(target == this->myPos)
|
||||
this->myPos = 255;
|
||||
else
|
||||
this->myPos = target;
|
||||
Serial.print("Writing my shade position:");
|
||||
Serial.print(this->myPos);
|
||||
Serial.println("%");
|
||||
pref.begin(shadeKey);
|
||||
pref.putUShort("myPos", this->myPos);
|
||||
pref.end();
|
||||
this->commitMyPosition();
|
||||
this->emitState();
|
||||
}
|
||||
}
|
||||
|
|
@ -1005,29 +1082,34 @@ void SomfyShade::moveToTarget(uint8_t target) {
|
|||
SomfyRemote::sendCommand(cmd);
|
||||
}
|
||||
bool SomfyShade::save() {
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId());
|
||||
pref.begin(shadeKey);
|
||||
pref.putString("name", this->name);
|
||||
pref.putBool("hasTilt", this->hasTilt);
|
||||
pref.putBool("paired", this->paired);
|
||||
pref.putUShort("upTime", this->upTime);
|
||||
pref.putUShort("downTime", this->downTime);
|
||||
pref.putUShort("tiltTime", this->tiltTime);
|
||||
pref.putULong("remoteAddress", this->getRemoteAddress());
|
||||
pref.putFloat("currentPos", this->currentPos);
|
||||
pref.putFloat("currentTiltPos", this->currentTiltPos);
|
||||
pref.putUShort("myPos", this->myPos);
|
||||
pref.putChar("shadeType", static_cast<uint8_t>(this->shadeType));
|
||||
uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES];
|
||||
memset(linkedAddresses, 0x00, sizeof(linkedAddresses));
|
||||
uint8_t j = 0;
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
|
||||
SomfyLinkedRemote lremote = this->linkedRemotes[i];
|
||||
if(lremote.getRemoteAddress() != 0) linkedAddresses[j++] = lremote.getRemoteAddress();
|
||||
if(somfy.useNVS()) {
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId());
|
||||
pref.begin(shadeKey);
|
||||
pref.clear();
|
||||
pref.putChar("shadeType", static_cast<uint8_t>(this->shadeType));
|
||||
pref.putUInt("remoteAddress", this->getRemoteAddress());
|
||||
pref.putString("name", this->name);
|
||||
pref.putBool("hasTilt", this->hasTilt);
|
||||
pref.putBool("paired", this->paired);
|
||||
pref.putUInt("upTime", this->upTime);
|
||||
pref.putUInt("downTime", this->downTime);
|
||||
pref.putUInt("tiltTime", this->tiltTime);
|
||||
pref.putFloat("currentPos", this->currentPos);
|
||||
pref.putFloat("currentTiltPos", this->currentTiltPos);
|
||||
pref.putUShort("myPos", this->myPos);
|
||||
uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES];
|
||||
memset(linkedAddresses, 0x00, sizeof(linkedAddresses));
|
||||
uint8_t j = 0;
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
|
||||
SomfyLinkedRemote lremote = this->linkedRemotes[i];
|
||||
if(lremote.getRemoteAddress() != 0) linkedAddresses[j++] = lremote.getRemoteAddress();
|
||||
}
|
||||
pref.remove("linkedAddr");
|
||||
pref.putBytes("linkedAddr", linkedAddresses, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES);
|
||||
pref.end();
|
||||
}
|
||||
pref.putBytes("linkedAddr", linkedAddresses, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES);
|
||||
pref.end();
|
||||
this->commit();
|
||||
return true;
|
||||
}
|
||||
bool SomfyShade::fromJSON(JsonObject &obj) {
|
||||
|
|
@ -1152,7 +1234,6 @@ uint8_t SomfyShadeController::getNextShadeId() {
|
|||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 255;
|
||||
}
|
||||
uint8_t SomfyShadeController::shadeCount() {
|
||||
|
|
@ -1188,40 +1269,61 @@ SomfyShade *SomfyShadeController::addShade(JsonObject &obj) {
|
|||
}
|
||||
SomfyShade *SomfyShadeController::addShade() {
|
||||
uint8_t shadeId = this->getNextShadeId();
|
||||
SomfyShade *shade = nullptr;
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
if(this->shades[i].getShadeId() == 255) {
|
||||
shade = &this->shades[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
// So the next shade id will be the first one we run into with an id of 255 so
|
||||
// if it gets deleted in the middle then it will get the first slot that is empty.
|
||||
// There is no apparent way around this. In the future we might actually add an indexer
|
||||
// to it for sorting later.
|
||||
if(shadeId == 255) return nullptr;
|
||||
SomfyShade *shade = &this->shades[shadeId - 1];
|
||||
if(shade) {
|
||||
shade->setShadeId(shadeId);
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
this->m_shadeIds[i] = this->shades[i].getShadeId();
|
||||
}
|
||||
sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
uint8_t id = 0;
|
||||
// This little diddy is about a bug I had previously that left duplicates in the
|
||||
// sorted array. So we will walk the sorted array until we hit a duplicate where the previous
|
||||
// value == the current value. Set it to 255 then sort the array again.
|
||||
// 1,1,2,2,3,3,255...
|
||||
bool hadDups = false;
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
if(this->m_shadeIds[i] == 255) break;
|
||||
if(id == this->m_shadeIds[i]) {
|
||||
id = this->m_shadeIds[i];
|
||||
this->m_shadeIds[i] = 255;
|
||||
hadDups = true;
|
||||
this->isDirty = true;
|
||||
if(this->useNVS()) {
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
this->m_shadeIds[i] = this->shades[i].getShadeId();
|
||||
}
|
||||
else {
|
||||
id = this->m_shadeIds[i];
|
||||
sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
uint8_t id = 0;
|
||||
// This little diddy is about a bug I had previously that left duplicates in the
|
||||
// sorted array. So we will walk the sorted array until we hit a duplicate where the previous
|
||||
// value == the current value. Set it to 255 then sort the array again.
|
||||
// 1,1,2,2,3,3,255...
|
||||
bool hadDups = false;
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
if(this->m_shadeIds[i] == 255) break;
|
||||
if(id == this->m_shadeIds[i]) {
|
||||
id = this->m_shadeIds[i];
|
||||
this->m_shadeIds[i] = 255;
|
||||
hadDups = true;
|
||||
}
|
||||
else {
|
||||
id = this->m_shadeIds[i];
|
||||
}
|
||||
}
|
||||
if(hadDups) sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.begin("Shades");
|
||||
pref.remove("shadeIds");
|
||||
int x = pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
Serial.printf("WROTE %d bytes to shadeIds\n", x);
|
||||
pref.end();
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
if(i != 0) Serial.print(",");
|
||||
else Serial.print("Shade Ids: ");
|
||||
Serial.print(this->m_shadeIds[i]);
|
||||
}
|
||||
Serial.println();
|
||||
pref.begin("Shades");
|
||||
pref.getBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
Serial.print("LENGTH:");
|
||||
Serial.println(pref.getBytesLength("shadeIds"));
|
||||
pref.end();
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
if(i != 0) Serial.print(",");
|
||||
else Serial.print("Shade Ids: ");
|
||||
Serial.print(this->m_shadeIds[i]);
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
if(hadDups) sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.begin("Shades");
|
||||
pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.end();
|
||||
}
|
||||
return shade;
|
||||
}
|
||||
|
|
@ -1231,6 +1333,7 @@ void SomfyRemote::sendCommand(somfy_commands cmd, uint8_t repeat) {
|
|||
frame.remoteAddress = this->getRemoteAddress();
|
||||
frame.cmd = cmd;
|
||||
frame.repeats = repeat;
|
||||
this->lastRollingCode = frame.rollingCode;
|
||||
somfy.sendFrame(frame, repeat);
|
||||
somfy.processFrame(frame, true);
|
||||
}
|
||||
|
|
@ -1261,18 +1364,24 @@ bool SomfyShadeController::deleteShade(uint8_t shadeId) {
|
|||
this->shades[i].setShadeId(255);
|
||||
}
|
||||
}
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds) - 1; i++) {
|
||||
if(this->m_shadeIds[i] == shadeId) {
|
||||
this->m_shadeIds[i] = 255;
|
||||
if(this->useNVS()) {
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds) - 1; i++) {
|
||||
if(this->m_shadeIds[i] == shadeId) {
|
||||
this->m_shadeIds[i] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
//qsort(this->m_shadeIds, sizeof(this->m_shadeIds)/sizeof(this->m_shadeIds[0]), sizeof(this->m_shadeIds[0]), sort_asc);
|
||||
sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
|
||||
pref.begin("Shades");
|
||||
pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.end();
|
||||
}
|
||||
//qsort(this->m_shadeIds, sizeof(this->m_shadeIds)/sizeof(this->m_shadeIds[0]), sizeof(this->m_shadeIds[0]), sort_asc);
|
||||
sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.begin("Shades");
|
||||
pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.end();
|
||||
this->commit();
|
||||
return true;
|
||||
}
|
||||
bool SomfyShadeController::loadShadesFile(const char *filename) { return ShadeConfigFile::load(this, filename); }
|
||||
uint16_t SomfyRemote::getNextRollingCode() {
|
||||
pref.begin("ShadeCodes");
|
||||
uint16_t code = pref.getUShort(this->m_remotePrefId, 0);
|
||||
|
|
@ -1315,15 +1424,6 @@ bool SomfyShadeController::toJSON(JsonObject &obj) {
|
|||
this->transceiver.toJSON(oradio);
|
||||
JsonArray arr = obj.createNestedArray("shades");
|
||||
this->toJSON(arr);
|
||||
/*
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
SomfyShade &shade = this->shades[i];
|
||||
if(shade.getShadeId() != 255) {
|
||||
JsonObject oshade = arr.createNestedObject();
|
||||
shade.toJSON(oshade);
|
||||
}
|
||||
}
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
bool SomfyShadeController::toJSON(JsonArray &arr) {
|
||||
|
|
@ -1341,6 +1441,10 @@ void SomfyShadeController::loop() {
|
|||
for(uint8_t i; i < SOMFY_MAX_SHADES; i++) {
|
||||
if(this->shades[i].getShadeId() != 255) this->shades[i].checkMovement();
|
||||
}
|
||||
// Only commit the file once per second.
|
||||
if(this->isDirty && millis() - this->lastCommit > 1000) {
|
||||
this->commit();
|
||||
}
|
||||
}
|
||||
SomfyLinkedRemote::SomfyLinkedRemote() {}
|
||||
|
||||
|
|
@ -1552,9 +1656,6 @@ bool Transceiver::fromJSON(JsonObject& obj) {
|
|||
bool Transceiver::save() {
|
||||
this->config.save();
|
||||
this->config.apply();
|
||||
//ELECHOUSE_cc1101.setRxBW(this->config.rxBandwidth); // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz.
|
||||
//ELECHOUSE_cc1101.setPA(this->config.txPower); // Set TxPower. The following settings are possible depending on the frequency band. (-30 -20 -15 -10 -6 0 5 7 10 11 12) Default is max!
|
||||
//ELECHOUSE_cc1101.setDeviation(this->config.deviation);
|
||||
return true;
|
||||
}
|
||||
bool Transceiver::end() {
|
||||
|
|
@ -1570,15 +1671,18 @@ void transceiver_config_t::fromJSON(JsonObject& obj) {
|
|||
if(obj.containsKey("RXPin")) this->RXPin = obj["RXPin"];
|
||||
if(obj.containsKey("SCKPin")) this->SCKPin = obj["SCKPin"];
|
||||
if(obj.containsKey("TXPin")) this->TXPin = obj["TXPin"];
|
||||
if(obj.containsKey("rxBandwidth")) this->rxBandwidth = obj["rxBandwidth"]; // float
|
||||
if(obj.containsKey("frequency")) this->frequency = obj["frequency"]; // float
|
||||
if(obj.containsKey("deviation")) this->deviation = obj["deviation"]; // float
|
||||
if(obj.containsKey("enabled")) this->enabled = obj["enabled"];
|
||||
if(obj.containsKey("txPower")) this->txPower = obj["txPower"];
|
||||
|
||||
/*
|
||||
if (obj.containsKey("internalCCMode")) this->internalCCMode = obj["internalCCMode"];
|
||||
if (obj.containsKey("modulationMode")) this->modulationMode = obj["modulationMode"];
|
||||
if (obj.containsKey("frequency")) this->frequency = obj["frequency"]; // float
|
||||
if (obj.containsKey("deviation")) this->deviation = obj["deviation"]; // float
|
||||
if (obj.containsKey("channel")) this->channel = obj["channel"];
|
||||
if (obj.containsKey("channelSpacing")) this->channelSpacing = obj["channelSpacing"]; // float
|
||||
if (obj.containsKey("rxBandwidth")) this->rxBandwidth = obj["rxBandwidth"]; // float
|
||||
if (obj.containsKey("dataRate")) this->dataRate = obj["dataRate"]; // float
|
||||
if (obj.containsKey("txPower")) this->txPower = obj["txPower"];
|
||||
if (obj.containsKey("syncMode")) this->syncMode = obj["syncMode"];
|
||||
if (obj.containsKey("syncWordHigh")) this->syncWordHigh = obj["syncWordHigh"];
|
||||
if (obj.containsKey("syncWordLow")) this->syncWordLow = obj["syncWordLow"];
|
||||
|
|
@ -1597,7 +1701,7 @@ void transceiver_config_t::fromJSON(JsonObject& obj) {
|
|||
if (obj.containsKey("pqtThreshold")) this->pqtThreshold = obj["pqtThreshold"];
|
||||
if (obj.containsKey("appendStatus")) this->appendStatus = obj["appendStatus"];
|
||||
if (obj.containsKey("printBuffer")) this->printBuffer = obj["printBuffer"];
|
||||
if(obj.containsKey("enabled")) this->enabled = obj["enabled"];
|
||||
*/
|
||||
Serial.printf("SCK:%u MISO:%u MOSI:%u CSN:%u RX:%u TX:%u\n", this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin, this->RXPin, this->TXPin);
|
||||
}
|
||||
void transceiver_config_t::toJSON(JsonObject& obj) {
|
||||
|
|
@ -1608,15 +1712,16 @@ void transceiver_config_t::toJSON(JsonObject& obj) {
|
|||
obj["MOSIPin"] = this->MOSIPin;
|
||||
obj["MISOPin"] = this->MISOPin;
|
||||
obj["CSNPin"] = this->CSNPin;
|
||||
obj["internalCCMode"] = this->internalCCMode;
|
||||
obj["modulationMode"] = this->modulationMode;
|
||||
obj["rxBandwidth"] = this->rxBandwidth; // float
|
||||
obj["frequency"] = this->frequency; // float
|
||||
obj["deviation"] = this->deviation; // float
|
||||
obj["txPower"] = this->txPower;
|
||||
/*
|
||||
obj["internalCCMode"] = this->internalCCMode;
|
||||
obj["modulationMode"] = this->modulationMode;
|
||||
obj["channel"] = this->channel;
|
||||
obj["channelSpacing"] = this->channelSpacing; // float
|
||||
obj["rxBandwidth"] = this->rxBandwidth; // float
|
||||
obj["dataRate"] = this->dataRate; // float
|
||||
obj["txPower"] = this->txPower;
|
||||
obj["syncMode"] = this->syncMode;
|
||||
obj["syncWordHigh"] = this->syncWordHigh;
|
||||
obj["syncWordLow"] = this->syncWordLow;
|
||||
|
|
@ -1635,6 +1740,7 @@ void transceiver_config_t::toJSON(JsonObject& obj) {
|
|||
obj["pqtThreshold"] = this->pqtThreshold;
|
||||
obj["appendStatus"] = this->appendStatus;
|
||||
obj["printBuffer"] = somfy.transceiver.printBuffer;
|
||||
*/
|
||||
obj["enabled"] = this->enabled;
|
||||
obj["radioInit"] = this->radioInit;
|
||||
Serial.print("Serialize Radio JSON ");
|
||||
|
|
@ -1642,6 +1748,7 @@ void transceiver_config_t::toJSON(JsonObject& obj) {
|
|||
}
|
||||
void transceiver_config_t::save() {
|
||||
pref.begin("CC1101");
|
||||
pref.clear();
|
||||
pref.putUChar("type", this->type);
|
||||
pref.putUChar("TXPin", this->TXPin);
|
||||
pref.putUChar("RXPin", this->RXPin);
|
||||
|
|
@ -1649,10 +1756,15 @@ void transceiver_config_t::save() {
|
|||
pref.putUChar("MOSIPin", this->MOSIPin);
|
||||
pref.putUChar("MISOPin", this->MISOPin);
|
||||
pref.putUChar("CSNPin", this->CSNPin);
|
||||
pref.putBool("internalCCMode", this->internalCCMode);
|
||||
pref.putUChar("modulationMode", this->modulationMode);
|
||||
pref.putFloat("frequency", this->frequency); // float
|
||||
pref.putFloat("deviation", this->deviation); // float
|
||||
pref.putFloat("rxBandwidth", this->rxBandwidth); // float
|
||||
pref.putBool("enabled", this->enabled);
|
||||
pref.putBool("radioInit", true);
|
||||
|
||||
/*
|
||||
pref.putBool("internalCCMode", this->internalCCMode);
|
||||
pref.putUChar("modulationMode", this->modulationMode);
|
||||
pref.putUChar("channel", this->channel);
|
||||
pref.putFloat("channelSpacing", this->channelSpacing); // float
|
||||
pref.putFloat("rxBandwidth", this->rxBandwidth); // float
|
||||
|
|
@ -1675,13 +1787,18 @@ void transceiver_config_t::save() {
|
|||
pref.putUChar("minPreambleBytes", this->minPreambleBytes);
|
||||
pref.putUChar("pqtThreshold", this->pqtThreshold);
|
||||
pref.putBool("appendStatus", this->appendStatus);
|
||||
pref.putBool("enabled", this->enabled);
|
||||
pref.putBool("radioInit", true);
|
||||
*/
|
||||
pref.end();
|
||||
|
||||
Serial.print("Save Radio Settings ");
|
||||
Serial.printf("SCK:%u MISO:%u MOSI:%u CSN:%u RX:%u TX:%u\n", this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin, this->RXPin, this->TXPin);
|
||||
}
|
||||
void transceiver_config_t::removeNVSKey(const char *key) {
|
||||
if(pref.isKey(key)) {
|
||||
Serial.printf("Removing NVS Key: CC1101.%s\n", key);
|
||||
pref.remove(key);
|
||||
}
|
||||
}
|
||||
void transceiver_config_t::load() {
|
||||
pref.begin("CC1101");
|
||||
this->type = pref.getUChar("type", 56);
|
||||
|
|
@ -1691,35 +1808,37 @@ void transceiver_config_t::load() {
|
|||
this->MOSIPin = pref.getUChar("MOSIPin", this->MOSIPin);
|
||||
this->MISOPin = pref.getUChar("MISOPin", this->MISOPin);
|
||||
this->CSNPin = pref.getUChar("CSNPin", this->CSNPin);
|
||||
this->internalCCMode = pref.getBool("internalCCMode", this->internalCCMode);
|
||||
this->modulationMode = pref.getUChar("modulationMode", this->modulationMode);
|
||||
this->frequency = pref.getFloat("frequency", this->frequency); // float
|
||||
this->deviation = pref.getFloat("deviation", this->deviation); // float
|
||||
this->channel = pref.getUChar("channel", this->channel);
|
||||
this->channelSpacing = pref.getFloat("channelSpacing", this->channelSpacing); // float
|
||||
this->rxBandwidth = pref.getFloat("rxBandwidth", this->rxBandwidth); // float
|
||||
this->dataRate = pref.getFloat("dataRate", this->dataRate); // float
|
||||
this->txPower = pref.getChar("txPower", this->txPower);
|
||||
this->syncMode = pref.getUChar("syncMode", this->syncMode);
|
||||
this->syncWordHigh = pref.getUShort("syncWordHigh", this->syncWordHigh);
|
||||
this->syncWordLow = pref.getUShort("syncWordLow", this->syncWordLow);
|
||||
this->addrCheckMode = pref.getUChar("addrCheckMode", this->addrCheckMode);
|
||||
this->checkAddr = pref.getUChar("checkAddr", this->checkAddr);
|
||||
this->dataWhitening = pref.getBool("dataWhitening", this->dataWhitening);
|
||||
this->pktFormat = pref.getUChar("pktFormat", this->pktFormat);
|
||||
this->pktLengthMode = pref.getUChar("pktLengthMode", this->pktLengthMode);
|
||||
this->pktLength = pref.getUChar("pktLength", this->pktLength);
|
||||
this->useCRC = pref.getBool("useCRC", this->useCRC);
|
||||
this->autoFlushCRC = pref.getBool("autoFlushCRC", this->autoFlushCRC);
|
||||
this->disableDCFilter = pref.getBool("disableDCFilter", this->disableDCFilter);
|
||||
this->enableManchester = pref.getBool("enableManchester", this->enableManchester);
|
||||
this->enableFEC = pref.getBool("enableFEC", this->enableFEC);
|
||||
this->minPreambleBytes = pref.getUChar("minPreambleBytes", this->minPreambleBytes);
|
||||
this->pqtThreshold = pref.getUChar("pqtThreshold", this->pqtThreshold);
|
||||
this->appendStatus = pref.getBool("appendStatus", this->appendStatus);
|
||||
this->enabled = pref.getBool("enabled", this->enabled);
|
||||
this->txPower = pref.getChar("txPower", this->txPower);
|
||||
this->rxBandwidth = pref.getFloat("rxBandwidth", this->rxBandwidth);
|
||||
|
||||
|
||||
this->removeNVSKey("internalCCMode");
|
||||
this->removeNVSKey("modulationMode");
|
||||
this->removeNVSKey("channel");
|
||||
this->removeNVSKey("channelSpacing");
|
||||
this->removeNVSKey("dataRate");
|
||||
this->removeNVSKey("syncMode");
|
||||
this->removeNVSKey("syncWordHigh");
|
||||
this->removeNVSKey("syncWordLow");
|
||||
this->removeNVSKey("addrCheckMode");
|
||||
this->removeNVSKey("checkAddr");
|
||||
this->removeNVSKey("dataWhitening");
|
||||
this->removeNVSKey("pktFormat");
|
||||
this->removeNVSKey("pktLengthMode");
|
||||
this->removeNVSKey("pktLength");
|
||||
this->removeNVSKey("useCRC");
|
||||
this->removeNVSKey("autoFlushCRC");
|
||||
this->removeNVSKey("disableDCFilter");
|
||||
this->removeNVSKey("enableManchester");
|
||||
this->removeNVSKey("enableFEC");
|
||||
this->removeNVSKey("minPreambleBytes");
|
||||
this->removeNVSKey("pqtThreshold");
|
||||
this->removeNVSKey("appendStatus");
|
||||
pref.end();
|
||||
this->printBuffer = somfy.transceiver.printBuffer;
|
||||
//this->printBuffer = somfy.transceiver.printBuffer;
|
||||
}
|
||||
void transceiver_config_t::apply() {
|
||||
somfy.transceiver.disableReceive();
|
||||
|
|
@ -1733,7 +1852,7 @@ void transceiver_config_t::apply() {
|
|||
this->radioInit = false;
|
||||
pref.end();
|
||||
if(!radioInit) return;
|
||||
Serial.print("Applying Initializing radio settings ");
|
||||
Serial.print("Applying radio settings ");
|
||||
Serial.printf("Setting Data Pins RX:%u TX:%u\n", this->RXPin, this->TXPin);
|
||||
ELECHOUSE_cc1101.setGDO(this->TXPin, this->RXPin);
|
||||
Serial.printf("Setting SPI Pins SCK:%u MISO:%u MOSI:%u CSN:%u\n", this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin);
|
||||
|
|
@ -1742,6 +1861,7 @@ void transceiver_config_t::apply() {
|
|||
ELECHOUSE_cc1101.Init();
|
||||
ELECHOUSE_cc1101.setMHZ(this->frequency); // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet.
|
||||
ELECHOUSE_cc1101.setRxBW(this->rxBandwidth); // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz.
|
||||
ELECHOUSE_cc1101.setDeviation(this->deviation);
|
||||
ELECHOUSE_cc1101.setPA(this->txPower); // Set TxPower. The following settings are possible depending on the frequency band. (-30 -20 -15 -10 -6 0 5 7 10 11 12) Default is max!
|
||||
//ELECHOUSE_cc1101.setCCMode(this->internalCCMode); // set config for internal transmission mode.
|
||||
//ELECHOUSE_cc1101.setModulation(this->modulationMode); // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
|
||||
|
|
|
|||
34
Somfy.h
34
Somfy.h
|
|
@ -4,6 +4,12 @@
|
|||
#define SOMFY_MAX_SHADES 32
|
||||
#define SOMFY_MAX_LINKED_REMOTES 5
|
||||
|
||||
typedef struct appver_t {
|
||||
uint8_t major;
|
||||
uint8_t minor;
|
||||
uint8_t build;
|
||||
};
|
||||
|
||||
enum class somfy_commands : byte {
|
||||
My = 0x1,
|
||||
Up = 0x2,
|
||||
|
|
@ -99,9 +105,9 @@ class SomfyShade : public SomfyRemote {
|
|||
char name[21] = "";
|
||||
void setShadeId(uint8_t id) { shadeId = id; }
|
||||
uint8_t getShadeId() { return shadeId; }
|
||||
uint16_t upTime = 10000;
|
||||
uint16_t downTime = 10000;
|
||||
uint16_t tiltTime = 5000;
|
||||
uint32_t upTime = 10000;
|
||||
uint32_t downTime = 10000;
|
||||
uint32_t tiltTime = 7000;
|
||||
bool save();
|
||||
bool isIdle();
|
||||
void checkMovement();
|
||||
|
|
@ -121,6 +127,10 @@ class SomfyShade : public SomfyRemote {
|
|||
void moveToMyPosition();
|
||||
void processWaitingFrame();
|
||||
void publish();
|
||||
void commit();
|
||||
void commitShadePosition();
|
||||
void commitTiltPosition();
|
||||
void commitMyPosition();
|
||||
};
|
||||
|
||||
typedef struct transceiver_config_t {
|
||||
|
|
@ -134,15 +144,16 @@ typedef struct transceiver_config_t {
|
|||
uint8_t MISOPin = 19;
|
||||
uint8_t CSNPin = 5;
|
||||
bool radioInit = false;
|
||||
bool internalCCMode = false; // Use internal transmission mode FIFO buffers.
|
||||
byte modulationMode = 2; // Modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
|
||||
float frequency = 433.42; // Basic frequency
|
||||
float deviation = 47.60; // Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz.
|
||||
float rxBandwidth = 812.5; // Receive bandwidth in kHz. Value from 58.03 to 812.50. Default is 99.97kHz.
|
||||
int8_t txPower = 10; // Transmission power {-30, -20, -15, -10, -6, 0, 5, 7, 10, 11, 12}. Default is 12.
|
||||
/*
|
||||
bool internalCCMode = false; // Use internal transmission mode FIFO buffers.
|
||||
byte modulationMode = 2; // Modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
|
||||
uint8_t channel = 0; // The channel number from 0 to 255
|
||||
float channelSpacing = 199.95; // Channel spacing in multiplied by the channel number and added to the base frequency in kHz. 25.39 to 405.45. Default 199.95
|
||||
float rxBandwidth = 812.5; // Receive bandwidth in kHz. Value from 58.03 to 812.50. Default is 99.97kHz.
|
||||
float dataRate = 99.97; // The data rate in kBaud. 0.02 to 1621.83 Default is 99.97.
|
||||
int8_t txPower = 10; // Transmission power {-30, -20, -15, -10, -6, 0, 5, 7, 10, 11, 12}. Default is 12.
|
||||
uint8_t syncMode = 0; // 0=No preamble/sync,
|
||||
// 1=16 sync word bits detected,
|
||||
// 2=16/16 sync words bits detected.
|
||||
|
|
@ -186,11 +197,13 @@ typedef struct transceiver_config_t {
|
|||
// decreases the bounter by 8 each time a bit is received that is the same as the lats bit. A threshold of 4 PQT for this counter is used to gate sync word detection.
|
||||
// When PQT = 0 a sync word is always accepted.
|
||||
bool appendStatus = false; // Appends the RSSI and LQI values to the TX packed as well as the CRC.
|
||||
*/
|
||||
void fromJSON(JsonObject& obj);
|
||||
void toJSON(JsonObject& obj);
|
||||
void save();
|
||||
void load();
|
||||
void apply();
|
||||
void removeNVSKey(const char *key);
|
||||
};
|
||||
class Transceiver {
|
||||
private:
|
||||
|
|
@ -218,7 +231,11 @@ class Transceiver {
|
|||
class SomfyShadeController {
|
||||
protected:
|
||||
uint8_t m_shadeIds[SOMFY_MAX_SHADES];
|
||||
uint32_t lastCommit = 0;
|
||||
public:
|
||||
appver_t appVersion;
|
||||
bool useNVS() { return !(this->appVersion.major > 1 || this->appVersion.minor >= 4); }
|
||||
bool isDirty = false;
|
||||
uint32_t startingAddress;
|
||||
uint8_t getNextShadeId();
|
||||
uint32_t getNextRemoteAddress(uint8_t shadeId);
|
||||
|
|
@ -242,6 +259,9 @@ class SomfyShadeController {
|
|||
void emitState(uint8_t num = 255);
|
||||
void publish();
|
||||
void processWaitingFrame();
|
||||
void commit();
|
||||
bool loadShadesFile(const char *filename);
|
||||
bool loadLegacy();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
148
Web.cpp
148
Web.cpp
|
|
@ -271,6 +271,86 @@ void Web::begin() {
|
|||
server.streamFile(file, _encoding_html);
|
||||
file.close();
|
||||
});
|
||||
server.on("/shades.cfg", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
// Load the index html page from the data directory.
|
||||
Serial.println("Loading file shades.cfg");
|
||||
File file = LittleFS.open("/shades.cfg", "r");
|
||||
if (!file) {
|
||||
Serial.println("Error opening shades.cfg");
|
||||
server.send(500, _encoding_text, "shades.cfg");
|
||||
}
|
||||
server.streamFile(file, _encoding_text);
|
||||
file.close();
|
||||
|
||||
});
|
||||
server.on("/shades.tmp", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
// Load the index html page from the data directory.
|
||||
Serial.println("Loading file shades.cfg");
|
||||
File file = LittleFS.open("/shades.tmp", "r");
|
||||
if (!file) {
|
||||
Serial.println("Error opening shades.tmp");
|
||||
server.send(500, _encoding_text, "shades.tmp");
|
||||
}
|
||||
server.streamFile(file, _encoding_text);
|
||||
file.close();
|
||||
});
|
||||
|
||||
server.on("/backup", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
char filename[120];
|
||||
Timestamp ts;
|
||||
char * iso = ts.getISOTime();
|
||||
// Replace the invalid characters as quickly as we can.
|
||||
for(uint8_t i = 0; i < strlen(iso); i++) {
|
||||
switch(iso[i]) {
|
||||
case '.':
|
||||
// Just trim off the ms.
|
||||
iso[i] = '\0';
|
||||
break;
|
||||
case ':':
|
||||
iso[i] = '_';
|
||||
break;
|
||||
}
|
||||
}
|
||||
snprintf(filename, sizeof(filename), "attachment; filename=\"ESPSomfyRTS %s.backup\"", iso);
|
||||
Serial.println(filename);
|
||||
server.sendHeader(F("Content-Disposition"), filename);
|
||||
Serial.println("Saving current shade information");
|
||||
somfy.commit();
|
||||
File file = LittleFS.open("/shades.cfg", "r");
|
||||
if (!file) {
|
||||
Serial.println("Error opening shades.cfg");
|
||||
server.send(500, _encoding_text, "shades.cfg");
|
||||
}
|
||||
server.streamFile(file, _encoding_text);
|
||||
file.close();
|
||||
});
|
||||
server.on("/restore", HTTP_POST, []() {
|
||||
webServer.sendCORSHeaders();
|
||||
server.sendHeader("Connection", "close");
|
||||
server.send(200, _encoding_json, "{\"status\":\"Success\",\"desc\":\"Restoring Shade settings\"}");
|
||||
}, []() {
|
||||
HTTPUpload& upload = server.upload();
|
||||
if (upload.status == UPLOAD_FILE_START) {
|
||||
Serial.printf("Restore: %s\n", upload.filename.c_str());
|
||||
// Begin by opening a new temporary file.
|
||||
File fup = LittleFS.open("/shades.tmp", "w");
|
||||
fup.close();
|
||||
}
|
||||
else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
File fup = LittleFS.open("/shades.tmp", "a");
|
||||
fup.write(upload.buf, upload.currentSize);
|
||||
fup.close();
|
||||
}
|
||||
else if (upload.status == UPLOAD_FILE_END) {
|
||||
// TODO: Do some validation of the file.
|
||||
Serial.println("Validating restore");
|
||||
// Go through the uploaded file to determine if it is valid.
|
||||
somfy.loadShadesFile("/shades.tmp");
|
||||
}
|
||||
});
|
||||
server.on("/index.js", []() {
|
||||
webServer.sendCacheHeaders(604800);
|
||||
webServer.sendCORSHeaders();
|
||||
|
|
@ -324,6 +404,20 @@ void Web::begin() {
|
|||
server.streamFile(file, "image/png");
|
||||
file.close();
|
||||
});
|
||||
server.on("/icon.png", []() {
|
||||
webServer.sendCacheHeaders(604800);
|
||||
webServer.sendCORSHeaders();
|
||||
|
||||
// Load the index html page from the data directory.
|
||||
Serial.println("Loading file favicon.png");
|
||||
File file = LittleFS.open("/icon.png", "r");
|
||||
if (!file) {
|
||||
Serial.println("Error opening data/favicon.png");
|
||||
server.send(500, _encoding_text, "Unable to open data/icons.css");
|
||||
}
|
||||
server.streamFile(file, "image/png");
|
||||
file.close();
|
||||
});
|
||||
server.onNotFound([]() {
|
||||
Serial.print("Request 404:");
|
||||
HTTPMethod method = server.method();
|
||||
|
|
@ -385,7 +479,7 @@ void Web::begin() {
|
|||
SomfyShade* shade;
|
||||
if (method == HTTP_POST || method == HTTP_PUT) {
|
||||
Serial.println("Adding a shade");
|
||||
DynamicJsonDocument doc(256);
|
||||
DynamicJsonDocument doc(512);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
|
|
@ -410,7 +504,7 @@ void Web::begin() {
|
|||
Serial.println("Adding shade");
|
||||
shade = somfy.addShade(obj);
|
||||
if (shade) {
|
||||
DynamicJsonDocument sdoc(256);
|
||||
DynamicJsonDocument sdoc(512);
|
||||
JsonObject sobj = sdoc.to<JsonObject>();
|
||||
shade->toJSON(sobj);
|
||||
serializeJson(sdoc, g_content);
|
||||
|
|
@ -592,7 +686,7 @@ void Web::begin() {
|
|||
shade->moveToTiltTarget(target);
|
||||
else
|
||||
shade->sendTiltCommand(command);
|
||||
DynamicJsonDocument sdoc(256);
|
||||
DynamicJsonDocument sdoc(512);
|
||||
JsonObject sobj = sdoc.to<JsonObject>();
|
||||
shade->toJSON(sobj);
|
||||
serializeJson(sdoc, g_content);
|
||||
|
|
@ -656,7 +750,7 @@ void Web::begin() {
|
|||
shade->moveToTarget(target);
|
||||
else
|
||||
shade->sendCommand(command);
|
||||
DynamicJsonDocument sdoc(256);
|
||||
DynamicJsonDocument sdoc(512);
|
||||
JsonObject sobj = sdoc.to<JsonObject>();
|
||||
shade->toJSON(sobj);
|
||||
serializeJson(sdoc, g_content);
|
||||
|
|
@ -711,7 +805,7 @@ void Web::begin() {
|
|||
if(target == 255) target = shade->myPos;
|
||||
if(target >= 0 && target <= 100)
|
||||
shade->setMyPosition(target);
|
||||
DynamicJsonDocument sdoc(256);
|
||||
DynamicJsonDocument sdoc(512);
|
||||
JsonObject sobj = sdoc.to<JsonObject>();
|
||||
shade->toJSON(sobj);
|
||||
serializeJson(sdoc, g_content);
|
||||
|
|
@ -761,7 +855,7 @@ void Web::begin() {
|
|||
}
|
||||
else {
|
||||
shade->setRollingCode(rollingCode);
|
||||
StaticJsonDocument<256> doc;
|
||||
DynamicJsonDocument doc(512);
|
||||
JsonObject obj = doc.to<JsonObject>();
|
||||
shade->toJSON(obj);
|
||||
serializeJson(doc, g_content);
|
||||
|
|
@ -776,7 +870,7 @@ void Web::begin() {
|
|||
uint8_t shadeId = 255;
|
||||
if (server.hasArg("plain")) {
|
||||
// Its coming in the body.
|
||||
StaticJsonDocument<129> doc;
|
||||
DynamicJsonDocument doc(512);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
|
|
@ -804,10 +898,10 @@ void Web::begin() {
|
|||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to pair\"}"));
|
||||
}
|
||||
else {
|
||||
shade->sendCommand(somfy_commands::Prog, 4);
|
||||
shade->sendCommand(somfy_commands::Prog, 7);
|
||||
shade->paired = true;
|
||||
shade->save();
|
||||
StaticJsonDocument<256> doc;
|
||||
DynamicJsonDocument doc(512);
|
||||
JsonObject obj = doc.to<JsonObject>();
|
||||
shade->toJSON(obj);
|
||||
serializeJson(doc, g_content);
|
||||
|
|
@ -822,7 +916,7 @@ void Web::begin() {
|
|||
uint8_t shadeId = 255;
|
||||
if (server.hasArg("plain")) {
|
||||
// Its coming in the body.
|
||||
StaticJsonDocument<129> doc;
|
||||
DynamicJsonDocument doc(512);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
|
|
@ -850,10 +944,10 @@ void Web::begin() {
|
|||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to unpair\"}"));
|
||||
}
|
||||
else {
|
||||
shade->sendCommand(somfy_commands::Prog, 4);
|
||||
shade->sendCommand(somfy_commands::Prog, 7);
|
||||
shade->paired = false;
|
||||
shade->save();
|
||||
StaticJsonDocument<256> doc;
|
||||
DynamicJsonDocument doc(512);
|
||||
JsonObject obj = doc.to<JsonObject>();
|
||||
shade->toJSON(obj);
|
||||
serializeJson(doc, g_content);
|
||||
|
|
@ -867,7 +961,7 @@ void Web::begin() {
|
|||
if (method == HTTP_PUT || method == HTTP_POST) {
|
||||
// We are updating an existing shade by adding a linked remote.
|
||||
if (server.hasArg("plain")) {
|
||||
DynamicJsonDocument doc(256);
|
||||
DynamicJsonDocument doc(512);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
|
|
@ -914,7 +1008,7 @@ void Web::begin() {
|
|||
// We are updating an existing shade by adding a linked remote.
|
||||
if (server.hasArg("plain")) {
|
||||
Serial.println("Linking a remote");
|
||||
DynamicJsonDocument doc(256);
|
||||
DynamicJsonDocument doc(512);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
|
|
@ -1026,6 +1120,29 @@ void Web::begin() {
|
|||
}
|
||||
}
|
||||
});
|
||||
server.on("/updateShadeConfig", HTTP_POST, []() {
|
||||
webServer.sendCORSHeaders();
|
||||
server.sendHeader("Connection", "close");
|
||||
server.send(200, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Updating Shade Config: \"}");
|
||||
}, []() {
|
||||
HTTPUpload& upload = server.upload();
|
||||
if (upload.status == UPLOAD_FILE_START) {
|
||||
Serial.printf("Update: shades.cfg\n");
|
||||
File fup = LittleFS.open("/shades.tmp", "w");
|
||||
fup.close();
|
||||
}
|
||||
else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
/* flashing littlefs to ESP*/
|
||||
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
|
||||
File fup = LittleFS.open("/shades.tmp", "a");
|
||||
fup.write(upload.buf, upload.currentSize);
|
||||
fup.close();
|
||||
}
|
||||
}
|
||||
else if (upload.status == UPLOAD_FILE_END) {
|
||||
somfy.loadShadesFile("/shades.tmp");
|
||||
}
|
||||
});
|
||||
server.on("/updateApplication", HTTP_POST, []() {
|
||||
webServer.sendCORSHeaders();
|
||||
server.sendHeader("Connection", "close");
|
||||
|
|
@ -1041,7 +1158,7 @@ void Web::begin() {
|
|||
}
|
||||
}
|
||||
else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
/* flashing firmware to ESP*/
|
||||
/* flashing littlefs to ESP*/
|
||||
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
|
|
@ -1049,6 +1166,7 @@ void Web::begin() {
|
|||
else if (upload.status == UPLOAD_FILE_END) {
|
||||
if (Update.end(true)) { //true to set the size to the current progress
|
||||
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
|
||||
somfy.commit();
|
||||
}
|
||||
else {
|
||||
Update.printError(Serial);
|
||||
|
|
|
|||
1
data/appversion
Normal file
1
data/appversion
Normal file
|
|
@ -0,0 +1 @@
|
|||
1.4.0
|
||||
BIN
data/icon.png
Normal file
BIN
data/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
|
|
@ -3,15 +3,15 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="main.css?v=1.3.2" type="text/css" />
|
||||
<link rel="stylesheet" href="icons.css?v=1.3.2" type="text/css" />
|
||||
<link rel="stylesheet" href="main.css?v=1.4.1" type="text/css" />
|
||||
<link rel="stylesheet" href="icons.css?v=1.4.0" type="text/css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<script type="text/javascript" src="index.js?v=1.3.2"></script>
|
||||
<script type="text/javascript" src="index.js?v=1.4.0"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="divContainer" class="container" style="user-select:none;position:relative;">
|
||||
<div id="divContainer" class="container" style="user-select:none;position:relative;border-radius:27px;">
|
||||
<div id="divRadioError" style="text-align:center;background:gainsboro;color:gray;margin-bottom:7px;text-transform:uppercase;font-weight:bold;padding:4px;border-radius:5px;">Radio Not Initialized</div>
|
||||
<h1 style="text-align: center;"><span>ESPSomfy RTS</span><span class="button-outline" onclick="general.toggleConfig();" style="float:right;font-size:1.25rem;display:inline-block;vertical-align:middle;width:38px;height:38px;position:relative;padding-top:4px;"><span style="vertical-align:middle;clear:both;text-align:center;display:inline-block;"><i id="icoConfig" class="icss-gear" style=""></i></span></span></h1>
|
||||
<h1 style="text-align: center;"><img src="icon.png" style="width:50px;float:left;margin-left:1px;margin-top:-10px;" /><span>ESPSomfy RTS</span><span class="button-outline" onclick="general.toggleConfig();" style="float:right;font-size:1.25rem;display:inline-block;vertical-align:middle;width:38px;height:38px;position:relative;padding-top:4px;"><span style="vertical-align:middle;clear:both;text-align:center;display:inline-block;"><i id="icoConfig" class="icss-gear" style=""></i></span></span></h1>
|
||||
<div id="divConfigPnl" style="display:none;">
|
||||
<div id="divWiFiStrength" style="margin-top:-10px;text-align:center;font-size:12px;">
|
||||
<div style="display:inline-block;vertical-align:middle;">
|
||||
|
|
@ -234,9 +234,9 @@
|
|||
</div>
|
||||
<div id="divSomfyButtons" style="float:right;margin-top:10px;position:relative">
|
||||
<div style="display:inline-block;margin-right:7px;position:relative;font-size:48px;"><i id="icoShade" class="somfy-shade-icon icss-window-shade" data-shadeid="0" style="--shade-position:0%;vertical-align:middle;"></i><i class="icss-window-tilt" data-tiltposition="0" style="display:none;"></i></div>
|
||||
<div class="button-outline" onclick="somfy.sendCommand(parseInt(document.getElementById('spanShadeId').innerText, 10), 'up');" style="display:inline-block;padding:7px;cursor:pointer;vertical-align:middle;margin:0px;"><i class="icss-somfy-up"></i></div>
|
||||
<div class="button-outline" onclick="somfy.sendCommand(parseInt(document.getElementById('spanShadeId').innerText, 10), 'my');" style="display: inline-block; font-size: 2em; padding: 10px; vertical-align: middle; cursor: pointer;margin:0px;"><span>my</span></div>
|
||||
<div class="button-outline" onclick="somfy.sendCommand(parseInt(document.getElementById('spanShadeId').innerText, 10), 'down');" style="display: inline-block; padding: 7px; cursor: pointer; vertical-align: middle;margin:0px;"><i class="icss-somfy-down" style="margin-top:-4px;"></i></div>
|
||||
<div class="button-outline" onclick="somfy.sendCommand(parseInt(document.getElementById('spanShadeId').innerText, 10), 'up');"><i class="icss-somfy-up"></i></div>
|
||||
<div class="button-outline" onclick="somfy.sendCommand(parseInt(document.getElementById('spanShadeId').innerText, 10), 'my');" style="font-size: 2em; padding: 10px;"><span>my</span></div>
|
||||
<div class="button-outline" onclick="somfy.sendCommand(parseInt(document.getElementById('spanShadeId').innerText, 10), 'down');"><i class="icss-somfy-down" style="margin-top:-4px;"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
|
|
@ -395,7 +395,14 @@
|
|||
<button id="btnReloadApplication" type="button" onclick="general.reload();">
|
||||
Refresh Cache
|
||||
</button>
|
||||
|
||||
<div class="button-container" style="text-align:center;">
|
||||
<button id="btnBackup" style="width:47%;display:inline-block;" type="button" onclick="firmware.backup();">
|
||||
Backup
|
||||
</button>
|
||||
<button id="btnRestore" style="width:47%;display:inline-block;" type="button" onclick="firmware.restore();">
|
||||
Restore
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</fieldset>
|
||||
|
|
|
|||
|
|
@ -193,6 +193,20 @@ function getJSON(url, cb) {
|
|||
}
|
||||
xhr.send();
|
||||
}
|
||||
function getText(url, cb) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
console.log({ get: url });
|
||||
xhr.open('GET', url, true);
|
||||
xhr.responseType = 'text';
|
||||
xhr.onload = () => {
|
||||
let status = xhr.status;
|
||||
cb(status === 200 ? null : status, xhr.responseText);
|
||||
}
|
||||
xhr.onerror = (evt) => {
|
||||
cb(xhr.status || 500, xhr.statusText);
|
||||
}
|
||||
xhr.send();
|
||||
}
|
||||
function putJSON(url, data, cb) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
console.log({ put: url, data: data });
|
||||
|
|
@ -355,7 +369,7 @@ async function reopenSocket() {
|
|||
await initSockets();
|
||||
}
|
||||
class General {
|
||||
appVersion = 'v1.3.2';
|
||||
appVersion = 'v1.4.0';
|
||||
reloadApp = false;
|
||||
async init() {
|
||||
this.setAppVersion();
|
||||
|
|
@ -1419,12 +1433,12 @@ class Somfy {
|
|||
errorMessage(document.getElementById('fsSomfySettings'), 'You must provide a name for the shade between 1 and 20 characters.');
|
||||
valid = false;
|
||||
}
|
||||
if (valid && (isNaN(obj.upTime) || obj.upTime < 1 || obj.upTime > 65355)) {
|
||||
errorMessage(document.getElementById('fsSomfySettings'), 'Up Time must be a value between 0 and 65,355 milliseconds. This is the travel time to go from full closed to full open.');
|
||||
if (valid && (isNaN(obj.upTime) || obj.upTime < 1 || obj.upTime > 4294967295)) {
|
||||
errorMessage(document.getElementById('fsSomfySettings'), 'Up Time must be a value between 0 and 4,294,967,295 milliseconds. This is the travel time to go from full closed to full open.');
|
||||
valid = false;
|
||||
}
|
||||
if (valid && (isNaN(obj.downTime) || obj.downTime < 1 || obj.downTime > 65355)) {
|
||||
errorMessage(document.getElementById('fsSomfySettings'), 'Down Time must be a value between 0 and 65,355 milliseconds. This is the travel time to go from full open to full closed.');
|
||||
if (valid && (isNaN(obj.downTime) || obj.downTime < 1 || obj.downTime > 4294967295)) {
|
||||
errorMessage(document.getElementById('fsSomfySettings'), 'Down Time must be a value between 0 and 4,294,967,295 milliseconds. This is the travel time to go from full open to full closed.');
|
||||
valid = false;
|
||||
}
|
||||
if (valid) {
|
||||
|
|
@ -1814,6 +1828,20 @@ class MQTT {
|
|||
var mqtt = new MQTT();
|
||||
class Firmware {
|
||||
async init() { }
|
||||
backup() {
|
||||
var link = document.createElement('a');
|
||||
link.href = '/backup';
|
||||
link.setAttribute('download', 'backup');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
};
|
||||
restore() {
|
||||
let div = this.createFileUploader('/restore');
|
||||
let inst = div.querySelector('div[id=divInstText]');
|
||||
inst.innerHTML = '<div style="font-size:14px;margin-bottom:20px;">Select a backup file that you would like to restore then press the Upload File button.</div>';
|
||||
document.getElementById('fsUpdates').appendChild(div);
|
||||
};
|
||||
createFileUploader(service) {
|
||||
let div = document.createElement('div');
|
||||
div.setAttribute('id', 'divUploadFile');
|
||||
|
|
@ -1823,7 +1851,7 @@ class Firmware {
|
|||
html += `<div id="divInstText"></div>`;
|
||||
html += `<input id="fileName" type="file" name="updateFS" style="display:none;" onchange="document.getElementById('span-selected-file').innerText = this.files[0].name;"/>`;
|
||||
html += `<label for="fileName">`;
|
||||
html += `<span id="span-selected-file" style="display:inline-block;min-width:257px;border-bottom:solid 2px white;font-size:17px"></span>`;
|
||||
html += `<span id="span-selected-file" style="display:inline-block;min-width:257px;border-bottom:solid 2px white;font-size:14px;white-space:nowrap;overflow:hidden;max-width:320px;text-overflow:ellipsis;"></span>`;
|
||||
html += `<div id="btn-select-file" class="button-outline" style="font-size:.8em;padding:10px;"><i class="icss-upload" style="margin:0px;"></i></div>`;
|
||||
html += `</label>`;
|
||||
html += `<div class="progress-bar" id="progFileUpload" style="--progress:0%;margin-top:10px;display:none;"></div>`
|
||||
|
|
@ -1837,14 +1865,15 @@ class Firmware {
|
|||
updateFirmware() {
|
||||
let div = this.createFileUploader('/updateFirmware');
|
||||
let inst = div.querySelector('div[id=divInstText]');
|
||||
inst.innerHTML = '<div style="font-size:14px;margin-bottom:20px;">Select a binary file containing the device firmware then press the Upload File button.</div>';
|
||||
inst.innerHTML = '<div style="font-size:14px;margin-bottom:20px;">Select a binary file [SomfyController.ino.esp32.bin] containing the device firmware then press the Upload File button.</div>';
|
||||
document.getElementById('fsUpdates').appendChild(div);
|
||||
};
|
||||
updateApplication() {
|
||||
let div = this.createFileUploader('/updateApplication');
|
||||
general.reloadApp = true;
|
||||
let inst = div.querySelector('div[id=divInstText]');
|
||||
inst.innerHTML = '<div style="font-size:14px;margin-bottom:20px;">Select a binary file containing the littlefs data for the application then press the Upload File button.</div>';
|
||||
inst.innerHTML = '<div style="font-size:14px;">Select a binary file [SomfyController.littlefs.bin] containing the littlefs data for the application then press the Upload File button.</div>';
|
||||
inst.innerHTML += '<hr/><div style="font-size:14px;margin-bottom:10px;">A backup file for your configuration will be downloaded to your browser. If the application update process fails please restore this file using the restore button</div>';
|
||||
document.getElementById('fsUpdates').appendChild(div);
|
||||
};
|
||||
async uploadFile(service, el) {
|
||||
|
|
@ -1857,6 +1886,28 @@ class Firmware {
|
|||
errorMessage(el, 'This file is not a valid littleFS file system.');
|
||||
return;
|
||||
}
|
||||
// The first thing we need to do is backup the configuration. So lets do this
|
||||
// in a promise.
|
||||
await new Promise((resolve, reject) => {
|
||||
firmware.backup();
|
||||
try {
|
||||
// Next we need to download the current configuration data.
|
||||
getText('/shades.cfg', (err, cfg) => {
|
||||
if (err)
|
||||
reject(err);
|
||||
else {
|
||||
resolve();
|
||||
console.log(cfg);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
serviceError(el, err);
|
||||
return;
|
||||
}
|
||||
}).catch((err) => {
|
||||
serviceError(el, err);
|
||||
});
|
||||
break;
|
||||
case '/updateFirmware':
|
||||
if (filename.indexOf('.ino.') === -1 || !filename.endsWith('.bin')) {
|
||||
|
|
@ -1864,6 +1915,12 @@ class Firmware {
|
|||
return;
|
||||
}
|
||||
break;
|
||||
case '/restore':
|
||||
if (!filename.endsWith('.backup') || filename.indexOf('ESPSomfyRTS') === -1) {
|
||||
errorMessage(el, 'This file is not a valid backup file')
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
let formData = new FormData();
|
||||
let btnUpload = el.querySelector('button[id="btnUploadFile"]');
|
||||
|
|
@ -1884,9 +1941,23 @@ class Firmware {
|
|||
console.log(evt);
|
||||
|
||||
};
|
||||
xhr.onerror = function (err) {
|
||||
console.log(err);
|
||||
};
|
||||
xhr.onload = function () {
|
||||
console.log('File upload load called');
|
||||
btnCancel.innerText = 'Close';
|
||||
switch (service) {
|
||||
case '/restore':
|
||||
(async () => {
|
||||
await somfy.init();
|
||||
if (document.getElementById('divUploadFile')) document.getElementById('divUploadFile').remove();
|
||||
})();
|
||||
break;
|
||||
case '/updateApplication':
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
xhr.send(formData);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -311,7 +311,7 @@ div.wifiSignal {
|
|||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
div.wifiSignal:hover {
|
||||
#divAps div.wifiSignal:hover {
|
||||
background: #00bcd4;
|
||||
color: white;
|
||||
}
|
||||
|
|
@ -588,6 +588,7 @@ div.waitoverlay > .lds-roller {
|
|||
font-size: 12px;
|
||||
}
|
||||
.somfyShadeCtl .shadectl-buttons {
|
||||
margin-top:3px;
|
||||
float:right;
|
||||
white-space:nowrap;
|
||||
}
|
||||
|
|
@ -596,6 +597,16 @@ div.waitoverlay > .lds-roller {
|
|||
padding: 7px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#divSomfyButtons div.button-outline {
|
||||
margin-top: -10px;
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
margin-bottom: 0px;
|
||||
display: inline-block;
|
||||
padding: 7px;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.shade-positioner {
|
||||
position: absolute;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue