Skip to content

Commit

Permalink
v1.4.0 upade
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
rstrouse committed Mar 11, 2023
1 parent 21d5699 commit dce0ae0
Show file tree
Hide file tree
Showing 15 changed files with 1,023 additions and 241 deletions.
370 changes: 370 additions & 0 deletions ConfigFile.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit dce0ae0

Please sign in to comment.