diff --git a/ConfigFile.cpp b/ConfigFile.cpp index 2359e15..6746c42 100644 --- a/ConfigFile.cpp +++ b/ConfigFile.cpp @@ -6,10 +6,10 @@ extern Preferences pref; -#define SHADE_HDR_VER 12 -#define SHADE_HDR_SIZE 24 -#define SHADE_REC_SIZE 252 -#define GROUP_REC_SIZE 180 +#define SHADE_HDR_VER 13 +#define SHADE_HDR_SIZE 28 +#define SHADE_REC_SIZE 256 +#define GROUP_REC_SIZE 184 bool ConfigFile::begin(const char* filename, bool readOnly) { this->file = LittleFS.open(filename, readOnly ? "r" : "w"); @@ -40,9 +40,9 @@ bool ConfigFile::writeHeader(const config_header_t &hdr) { if(!this->isOpen()) return false; this->writeUInt8(hdr.version); this->writeUInt8(hdr.length); - this->writeUInt8(hdr.shadeRecordSize); + this->writeUInt16(hdr.shadeRecordSize); this->writeUInt8(hdr.shadeRecords); - this->writeUInt8(hdr.groupRecordSize); + this->writeUInt16(hdr.groupRecordSize); this->writeUInt8(hdr.groupRecords, CFG_REC_END); return true; } @@ -52,10 +52,12 @@ bool ConfigFile::readHeader() { 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.shadeRecordSize = this->readUInt8(this->header.shadeRecordSize); + if(this->header.version >= 13) this->header.shadeRecordSize = this->readUInt16(this->header.shadeRecordSize); + else this->header.shadeRecordSize = this->readUInt8((uint8_t)this->header.shadeRecordSize); this->header.shadeRecords = this->readUInt8(this->header.shadeRecords); if(this->header.version > 10) { - this->header.groupRecordSize = this->readUInt8(this->header.groupRecordSize); + if(this->header.version >= 13) this->header.groupRecordSize = this->readUInt16(this->header.groupRecordSize); + else this->header.groupRecordSize = this->readUInt8(this->header.groupRecordSize); this->header.groupRecords = this->readUInt8(this->header.groupRecords); } Serial.printf("version:%u len:%u shadeSize:%u shadeRecs:%u groupSize:%u groupRecs: %u pos:%d\n", this->header.version, this->header.length, this->header.shadeRecordSize, this->header.shadeRecords, this->header.groupRecordSize, this->header.groupRecords, this->file.position()); @@ -332,18 +334,12 @@ bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) { shade->tiltType = this->readBool(false) ? tilt_types::none : tilt_types::tiltmotor; else shade->tiltType = static_cast(this->readUInt8(0)); - if(this->header.version > 6) { - shade->proto = static_cast(this->readUInt8(0)); - } - if(this->header.version > 1) { - shade->bitLength = this->readUInt8(56); - } + if(this->header.version > 6) shade->proto = static_cast(this->readUInt8(0)); + if(this->header.version > 1) shade->bitLength = this->readUInt8(56); shade->upTime = this->readUInt32(shade->upTime); shade->downTime = this->readUInt32(shade->downTime); shade->tiltTime = this->readUInt32(shade->tiltTime); - if(this->header.version > 5) { - shade->stepSize = this->readUInt16(100); - } + if(this->header.version > 5) shade->stepSize = this->readUInt16(100); for(uint8_t j = 0; j < SOMFY_MAX_LINKED_REMOTES; j++) { SomfyLinkedRemote *rem = &shade->linkedRemotes[j]; rem->setRemoteAddress(this->readUInt32(0)); @@ -381,15 +377,14 @@ bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) { } shade->target = floor(shade->currentPos); shade->tiltTarget = floor(shade->currentTiltPos); - if(this->header.version >= 9) { - shade->flipCommands = this->readBool(false); - } - if(this->header.version >= 10) { - shade->flipPosition = this->readBool(false); - } + if(this->header.version >= 9) shade->flipCommands = this->readBool(false); + if(this->header.version >= 10) shade->flipPosition = this->readBool(false); if(this->header.version >= 12) shade->repeats = this->readUInt8(1); + if(this->header.version >= 13) shade->sortOrder = this->readUInt8(shade->getShadeId() - 1); + else shade->sortOrder = shade->getShadeId() - 1; if(shade->getShadeId() == 255) shade->clear(); } + for(uint8_t i = 0; i < this->header.groupRecords; i++) { SomfyGroup *group = &s->groups[i]; group->setGroupId(this->readUInt8(255)); @@ -410,6 +405,9 @@ bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) { if(shadeId > 0) group->linkedShades[lsd++] = shadeId; } if(this->header.version >= 12) group->repeats = this->readUInt8(1); + if(this->header.version >= 13) group->sortOrder = this->readUInt8(group->getGroupId() - 1); + else group->sortOrder = group->getGroupId() - 1; + if(group->getGroupId() == 255) group->clear(); else group->compressLinkedShadeIds(); } @@ -430,7 +428,8 @@ bool ShadeConfigFile::writeGroupRecord(SomfyGroup *group) { for(uint8_t j = 0; j < SOMFY_MAX_GROUPED_SHADES; j++) { this->writeUInt8(group->linkedShades[j]); } - this->writeUInt8(group->repeats, CFG_REC_END); + this->writeUInt8(group->repeats); + this->writeUInt8(group->sortOrder, CFG_REC_END); return true; } bool ShadeConfigFile::writeShadeRecord(SomfyShade *shade) { @@ -473,7 +472,8 @@ bool ShadeConfigFile::writeShadeRecord(SomfyShade *shade) { } this->writeBool(shade->flipCommands); this->writeBool(shade->flipPosition); - this->writeUInt8(shade->repeats, CFG_REC_END); + this->writeUInt8(shade->repeats); + this->writeUInt8(shade->sortOrder, CFG_REC_END); return true; } bool ShadeConfigFile::exists() { return LittleFS.exists("/shades.cfg"); } diff --git a/ConfigFile.h b/ConfigFile.h index 3c24869..a1708c4 100644 --- a/ConfigFile.h +++ b/ConfigFile.h @@ -10,11 +10,11 @@ struct config_header_t { uint8_t version = 1; - uint8_t shadeRecordSize = 0; + uint16_t shadeRecordSize = 0; uint8_t shadeRecords = 0; - uint8_t groupRecordSize = 0; + uint16_t groupRecordSize = 0; uint8_t groupRecords = 0; - uint8_t length = 0; + int8_t length = 0; }; class ConfigFile { protected: diff --git a/ConfigSettings.h b/ConfigSettings.h index fb8fedb..d90c72d 100644 --- a/ConfigSettings.h +++ b/ConfigSettings.h @@ -3,7 +3,7 @@ #ifndef configsettings_h #define configsettings_h -#define FW_VERSION "v2.1.4" +#define FW_VERSION "v2.1.5" enum DeviceStatus { DS_OK = 0, DS_ERROR = 1, diff --git a/MQTT.cpp b/MQTT.cpp index 778af84..398443a 100644 --- a/MQTT.cpp +++ b/MQTT.cpp @@ -14,19 +14,21 @@ static char g_content[MQTT_MAX_RESPONSE]; extern ConfigSettings settings; extern SomfyShadeController somfy; bool MQTTClass::begin() { + this->suspended = false; return true; } bool MQTTClass::end() { + this->suspended = true; this->disconnect(); - this->lastConnect = 0; - this->connect(); return true; } void MQTTClass::reset() { this->disconnect(); + this->lastConnect = 0; + this->connect(); } bool MQTTClass::loop() { - if(settings.MQTT.enabled && !mqttClient.connected()) + if(settings.MQTT.enabled && !this->suspended && !mqttClient.connected()) this->connect(); if(settings.MQTT.enabled) mqttClient.loop(); return true; @@ -149,12 +151,12 @@ void MQTTClass::receive(const char *topic, byte*payload, uint32_t length) { } bool MQTTClass::connect() { if(mqttClient.connected()) { - if(!settings.MQTT.enabled) + if(!settings.MQTT.enabled || this->suspended) return this->disconnect(); else return true; } - if(settings.MQTT.enabled) { + if(settings.MQTT.enabled && !this->suspended) { if(this->lastConnect + 10000 > millis()) return false; uint64_t mac = ESP.getEfuseMac(); snprintf(this->clientId, sizeof(this->clientId), "client-%08x%08x", (uint32_t)((mac >> 32) & 0xFFFFFFFF), (uint32_t)(mac & 0xFFFFFFFF)); diff --git a/MQTT.h b/MQTT.h index 943a590..831e3a1 100644 --- a/MQTT.h +++ b/MQTT.h @@ -6,6 +6,7 @@ class MQTTClass { public: uint64_t lastConnect = 0; + bool suspended = false; char clientId[32] = {'\0'}; bool begin(); bool loop(); @@ -26,7 +27,5 @@ class MQTTClass { bool subscribe(const char *topic); bool unsubscribe(const char *topic); static void receive(const char *topic, byte *payload, uint32_t length); - - }; #endif diff --git a/Somfy.cpp b/Somfy.cpp index e72b320..0afb4d8 100644 --- a/Somfy.cpp +++ b/Somfy.cpp @@ -599,6 +599,7 @@ void SomfyShade::clear() { this->tiltTime = 7000; this->stepSize = 100; this->repeats = 1; + this->sortOrder = 255; } void SomfyGroup::clear() { this->setGroupId(255); @@ -1219,16 +1220,16 @@ void SomfyShade::emitState(const char *evt) { this->emitState(255, evt); } void SomfyShade::emitState(uint8_t num, const char *evt) { char buf[420]; if(this->tiltType != tilt_types::none) - snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"type\":%u,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d,\"mypos\":%d,\"myTiltPos\":%d,\"tiltType\":%u,\"tiltDirection\":%d,\"tiltTarget\":%d,\"tiltPosition\":%d,\"flipCommands\":%s,\"flipPosition\":%s,\"flags\":%d,\"sunSensor\":%s,\"light\":%s}", + snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"type\":%u,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d,\"mypos\":%d,\"myTiltPos\":%d,\"tiltType\":%u,\"tiltDirection\":%d,\"tiltTarget\":%d,\"tiltPosition\":%d,\"flipCommands\":%s,\"flipPosition\":%s,\"flags\":%d,\"sunSensor\":%s,\"light\":%s,\"sortOrder\":%d}", this->shadeId, static_cast(this->shadeType), this->getRemoteAddress(), this->name, this->direction, this->transformPosition(this->currentPos), this->transformPosition(this->target), this->transformPosition(this->myPos), this->transformPosition(this->myTiltPos), static_cast(this->tiltType), this->tiltDirection, this->transformPosition(this->tiltTarget), this->transformPosition(this->currentTiltPos), - this->flipCommands ? "true" : "false", this->flipPosition ? "true": "false", this->flags, this->hasSunSensor() ? "true" : "false", this->hasLight() ? "true" : "false"); + this->flipCommands ? "true" : "false", this->flipPosition ? "true": "false", this->flags, this->hasSunSensor() ? "true" : "false", this->hasLight() ? "true" : "false", this->sortOrder); else - snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"type\":%u,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d,\"mypos\":%d,\"tiltType\":%u,\"flipCommands\":%s,\"flipPosition\":%s,\"flags\":%d,\"sunSensor\":%s,\"light\":%s}", + snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"type\":%u,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d,\"mypos\":%d,\"tiltType\":%u,\"flipCommands\":%s,\"flipPosition\":%s,\"flags\":%d,\"sunSensor\":%s,\"light\":%s,\"sortOrder\":%d}", this->shadeId, static_cast(this->shadeType), this->getRemoteAddress(), this->name, this->direction, this->transformPosition(this->currentPos), this->transformPosition(this->target), this->transformPosition(this->myPos), - static_cast(this->tiltType), this->flipCommands ? "true" : "false", this->flipPosition ? "true": "false", this->flags, this->hasSunSensor() ? "true" : "false", this->hasLight() ? "true" : "false"); + static_cast(this->tiltType), this->flipCommands ? "true" : "false", this->flipPosition ? "true": "false", this->flags, this->hasSunSensor() ? "true" : "false", this->hasLight() ? "true" : "false", this->sortOrder); if(num >= 255) sockEmit.sendToClients(evt, buf); else sockEmit.sendToClient(num, evt, buf); if(mqtt.connected()) { @@ -2232,7 +2233,7 @@ bool SomfyShade::toJSON(JsonObject &obj) { obj["sunSensor"] = this->hasSunSensor(); obj["light"] = this->hasLight(); obj["repeats"] = this->repeats; - + obj["sortOrder"] = this->sortOrder; SomfyRemote::toJSON(obj); JsonArray arr = obj.createNestedArray("linkedRemotes"); for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { @@ -2273,6 +2274,7 @@ bool SomfyGroup::toJSON(JsonObject &obj) { obj["sunSensor"] = this->hasSunSensor(); obj["flags"] = this->flags; obj["repeats"] = this->repeats; + obj["sortOrder"] = this->sortOrder; SomfyRemote::toJSON(obj); JsonArray arr = obj.createNestedArray("linkedShades"); for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) { @@ -2351,6 +2353,25 @@ uint8_t SomfyShadeController::getNextShadeId() { } return 255; } +int8_t SomfyShadeController::getMaxShadeOrder() { + int8_t order = -1; + for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { + SomfyShade *shade = &this->shades[i]; + if(shade->getShadeId() == 255) continue; + if(order < shade->sortOrder) order = shade->sortOrder; + } + return order; +} +int8_t SomfyShadeController::getMaxGroupOrder() { + int8_t order = -1; + for(uint8_t i = 0; i < SOMFY_MAX_GROUPS; i++) { + SomfyGroup *group = &this->groups[i]; + if(group->getGroupId() == 255) continue; + if(order < group->sortOrder) order = group->sortOrder; + } + return order; +} + uint8_t SomfyShadeController::getNextGroupId() { // There is no shortcut for this since the deletion of // a group in the middle makes all of this very difficult. @@ -2415,11 +2436,13 @@ SomfyShade *SomfyShadeController::addShade() { // 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. + // to it for sorting later. The time has come so the sort order is set below. if(shadeId == 255) return nullptr; SomfyShade *shade = &this->shades[shadeId - 1]; if(shade) { shade->setShadeId(shadeId); + shade->sortOrder = this->getMaxShadeOrder() + 1; + Serial.printf("Sort order set to %d\n", shade->sortOrder); this->isDirty = true; if(this->useNVS()) { for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) { @@ -2489,6 +2512,7 @@ SomfyGroup *SomfyShadeController::addGroup() { SomfyGroup *group = &this->groups[groupId - 1]; if(group) { group->setGroupId(groupId); + group->sortOrder = this->getMaxGroupOrder() + 1; this->isDirty = true; } return group; diff --git a/Somfy.h b/Somfy.h index c435d43..5e87954 100644 --- a/Somfy.h +++ b/Somfy.h @@ -227,6 +227,7 @@ class SomfyShade : public SomfyRemote { bool settingTiltPos = false; uint32_t awaitMy = 0; public: + int8_t sortOrder = 0; bool flipPosition = false; shade_types shadeType = shade_types::roller; tilt_types tiltType = tilt_types::none; @@ -290,6 +291,7 @@ class SomfyGroup : public SomfyRemote { protected: uint8_t groupId = 255; public: + int8_t sortOrder = 0; group_types groupType = group_types::channel; int8_t direction = 0; // 0 = stopped, 1=down, -1=up. char name[21] = ""; @@ -419,6 +421,8 @@ class SomfyShadeController { uint32_t startingAddress; uint8_t getNextShadeId(); uint8_t getNextGroupId(); + int8_t getMaxShadeOrder(); + int8_t getMaxGroupOrder(); uint32_t getNextRemoteAddress(uint8_t shadeId); SomfyShadeController(); Transceiver transceiver; diff --git a/SomfyController.ino.esp32.bin b/SomfyController.ino.esp32.bin index fee28e8..4558cb1 100644 Binary files a/SomfyController.ino.esp32.bin and b/SomfyController.ino.esp32.bin differ diff --git a/SomfyController.littlefs.bin b/SomfyController.littlefs.bin index f9a1dd0..7eee504 100644 Binary files a/SomfyController.littlefs.bin and b/SomfyController.littlefs.bin differ diff --git a/Web.cpp b/Web.cpp index 82672a3..6d2c148 100644 --- a/Web.cpp +++ b/Web.cpp @@ -310,27 +310,29 @@ void Web::handleShadeCommand(WebServer& server) { } } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); + SomfyShade* shade = somfy.getShadeById(shadeId); + if (shade) { + Serial.print("Received:"); + Serial.println(server.arg("plain")); + // Send the command to the shade. + if (target <= 100) + shade->moveToTarget(shade->transformPosition(target)); + else if (repeat > 0) + shade->sendCommand(command, repeat); + else + shade->sendCommand(command); + DynamicJsonDocument sdoc(512); + JsonObject sobj = sdoc.to(); + shade->toJSON(sobj); + serializeJson(sdoc, g_content); + server.send(200, _encoding_json, g_content); + } + else { + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); + } } - SomfyShade* shade = somfy.getShadeById(shadeId); - if (shade) { - Serial.print("Received:"); - Serial.println(server.arg("plain")); - // Send the command to the shade. - if (target <= 100) - shade->moveToTarget(shade->transformPosition(target)); - else if (repeat > 0) - shade->sendCommand(command, repeat); - else - shade->sendCommand(command); - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); - } + else + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); } void Web::handleRepeatCommand(WebServer& server) { webServer.sendCORSHeaders(server); @@ -415,40 +417,161 @@ void Web::handleRepeatCommand(WebServer& server) { server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); } } -void Web::begin() { - Serial.println("Creating Web MicroServices..."); - server.enableCORS(true); - const char *keys[1] = {"apikey"}; - server.collectHeaders(keys, 1); - apiServer.collectHeaders(keys, 1); - apiServer.enableCORS(true); - apiServer.on("/discovery", []() { - HTTPMethod method = apiServer.method(); - if (method == HTTP_POST || method == HTTP_GET) { - Serial.println("Discovery Requested"); - DynamicJsonDocument doc(16384); - JsonObject obj = doc.to(); - obj["serverId"] = settings.serverId; - obj["version"] = settings.fwVersion; - obj["model"] = "ESPSomfyRTS"; - obj["hostname"] = settings.hostname; - obj["authType"] = static_cast(settings.Security.type); - obj["permissions"] = settings.Security.permissions; - JsonArray arrShades = obj.createNestedArray("shades"); - somfy.toJSONShades(arrShades); - JsonArray arrGroups = obj.createNestedArray("groups"); - somfy.toJSONGroups(arrGroups); - serializeJson(doc, g_content); - apiServer.send(200, _encoding_json, g_content); +void Web::handleGroupCommand(WebServer &server) { + webServer.sendCORSHeaders(server); + if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } + HTTPMethod method = server.method(); + uint8_t groupId = 255; + int8_t repeat = -1; + somfy_commands command = somfy_commands::My; + if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { + if (server.hasArg("groupId")) { + groupId = atoi(server.arg("groupId").c_str()); + if (server.hasArg("command")) command = translateSomfyCommand(server.arg("command")); + if(server.hasArg("repeat")) repeat = atoi(server.arg("repeat").c_str()); } - apiServer.send(500, _encoding_text, "Invalid http method"); - }); - apiServer.on("/shades", []() { webServer.handleGetShades(apiServer); }); - apiServer.on("/groups", []() { webServer.handleGetGroups(apiServer); }); - apiServer.on("/login", []() { webServer.handleLogin(apiServer); }); - apiServer.onNotFound([]() { - Serial.print("Request 404:"); - HTTPMethod method = apiServer.method(); + else if (server.hasArg("plain")) { + Serial.println("Sending Group Command"); + DynamicJsonDocument doc(256); + DeserializationError err = deserializeJson(doc, server.arg("plain")); + if (err) { + switch (err.code()) { + case DeserializationError::InvalidInput: + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}")); + break; + case DeserializationError::NoMemory: + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}")); + break; + default: + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}")); + break; + } + return; + } + else { + JsonObject obj = doc.as(); + if (obj.containsKey("groupId")) groupId = obj["groupId"]; + else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group id was supplied.\"}")); + if (obj.containsKey("command")) { + String scmd = obj["command"]; + command = translateSomfyCommand(scmd); + } + if(obj.containsKey("repeat")) repeat = obj["repeat"].as(); + } + } + else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group object supplied.\"}")); + SomfyGroup * group = somfy.getGroupById(groupId); + if (group) { + Serial.print("Received:"); + Serial.println(server.arg("plain")); + // Send the command to the group. + if(repeat > 0) group->sendCommand(command, repeat); + else group->sendCommand(command); + DynamicJsonDocument sdoc(512); + JsonObject sobj = sdoc.to(); + group->toJSON(sobj); + serializeJson(sdoc, g_content); + server.send(200, _encoding_json, g_content); + } + else { + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group with the specified id not found.\"}")); + } + } + else + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); +} +void Web::handleTiltCommand(WebServer &server) { + webServer.sendCORSHeaders(server); + if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } + HTTPMethod method = server.method(); + uint8_t shadeId = 255; + uint8_t target = 255; + somfy_commands command = somfy_commands::My; + if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { + if (server.hasArg("shadeId")) { + shadeId = atoi(server.arg("shadeId").c_str()); + if (server.hasArg("command")) command = translateSomfyCommand(server.arg("command")); + else if(server.hasArg("target")) target = atoi(server.arg("target").c_str()); + } + else if (server.hasArg("plain")) { + Serial.println("Sending Shade Tilt Command"); + DynamicJsonDocument doc(256); + DeserializationError err = deserializeJson(doc, server.arg("plain")); + if (err) { + switch (err.code()) { + case DeserializationError::InvalidInput: + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}")); + break; + case DeserializationError::NoMemory: + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}")); + break; + default: + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}")); + break; + } + return; + } + else { + JsonObject obj = doc.as(); + if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; + else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); + if (obj.containsKey("command")) { + String scmd = obj["command"]; + command = translateSomfyCommand(scmd); + } + else if(obj.containsKey("target")) { + target = obj["target"].as(); + } + } + } + else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); + SomfyShade* shade = somfy.getShadeById(shadeId); + if (shade) { + Serial.print("Received:"); + Serial.println(server.arg("plain")); + // Send the command to the shade. + if(target <= 100) + shade->moveToTiltTarget(shade->transformPosition(target)); + else + shade->sendTiltCommand(command); + DynamicJsonDocument sdoc(512); + JsonObject sobj = sdoc.to(); + shade->toJSON(sobj); + serializeJson(sdoc, g_content); + server.send(200, _encoding_json, g_content); + } + else { + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); + } + } + else + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); +} +void Web::handleDiscovery(WebServer &server) { + HTTPMethod method = apiServer.method(); + if (method == HTTP_POST || method == HTTP_GET) { + Serial.println("Discovery Requested"); + DynamicJsonDocument doc(16384); + JsonObject obj = doc.to(); + obj["serverId"] = settings.serverId; + obj["version"] = settings.fwVersion; + obj["model"] = "ESPSomfyRTS"; + obj["hostname"] = settings.hostname; + obj["authType"] = static_cast(settings.Security.type); + obj["permissions"] = settings.Security.permissions; + JsonArray arrShades = obj.createNestedArray("shades"); + somfy.toJSONShades(arrShades); + JsonArray arrGroups = obj.createNestedArray("groups"); + somfy.toJSONGroups(arrGroups); + serializeJson(doc, g_content); + server.send(200, _encoding_json, g_content); + } + else + server.send(500, _encoding_text, "Invalid http method"); +} +void Web::handleNotFound(WebServer &server) { + HTTPMethod method = server.method(); + Serial.printf("Request %s 404-%d ", server.uri().c_str(), method); switch (method) { case HTTP_POST: Serial.print("POST "); @@ -460,8 +583,8 @@ void Web::begin() { Serial.print("PUT "); break; case HTTP_OPTIONS: - Serial.print("OPTIONS "); - apiServer.send(200, "OK"); + Serial.println("OPTIONS "); + server.send(200, "OK"); return; default: Serial.print("["); @@ -470,210 +593,35 @@ void Web::begin() { break; } - snprintf(g_content, sizeof(g_content), "404 Service Not Found: %s", apiServer.uri().c_str()); - apiServer.send(404, _encoding_text, g_content); - }); + snprintf(g_content, sizeof(g_content), "404 Service Not Found: %s", server.uri().c_str()); + server.send(404, _encoding_text, g_content); +} +void Web::begin() { + Serial.println("Creating Web MicroServices..."); + server.enableCORS(true); + const char *keys[1] = {"apikey"}; + server.collectHeaders(keys, 1); + // API Server Handlers + apiServer.collectHeaders(keys, 1); + apiServer.enableCORS(true); + apiServer.on("/discovery", []() { webServer.handleDiscovery(apiServer); }); + apiServer.on("/shades", []() { webServer.handleGetShades(apiServer); }); + apiServer.on("/groups", []() { webServer.handleGetGroups(apiServer); }); + apiServer.on("/login", []() { webServer.handleLogin(apiServer); }); + apiServer.onNotFound([]() { webServer.handleNotFound(apiServer); }); apiServer.on("/controller", []() { webServer.handleController(apiServer); }); - apiServer.on("/shadeCommand", []() { - webServer.sendCORSHeaders(apiServer); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = apiServer.method(); - uint8_t shadeId = 255; - uint8_t target = 255; - int8_t repeat = -1; - somfy_commands command = somfy_commands::My; - if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { - if (apiServer.hasArg("shadeId")) { - shadeId = atoi(apiServer.arg("shadeId").c_str()); - if (apiServer.hasArg("command")) command = translateSomfyCommand(apiServer.arg("command")); - else if(apiServer.hasArg("target")) target = atoi(apiServer.arg("target").c_str()); - if(apiServer.hasArg("repeat")) repeat = atoi(apiServer.arg("repeat").c_str()); - } - else if (apiServer.hasArg("plain")) { - Serial.println("Sending Shade Command"); - DynamicJsonDocument doc(256); - DeserializationError err = deserializeJson(doc, apiServer.arg("plain")); - if (err) { - switch (err.code()) { - case DeserializationError::InvalidInput: - apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}")); - break; - case DeserializationError::NoMemory: - apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}")); - break; - default: - apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}")); - break; - } - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; - else apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); - if (obj.containsKey("command")) { - String scmd = obj["command"]; - command = translateSomfyCommand(scmd); - } - else if(obj.containsKey("target")) { - target = obj["target"].as(); - } - if(obj.containsKey("repeat")) repeat = obj["repeat"].as(); - } - } - else apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); - } - SomfyShade* shade = somfy.getShadeById(shadeId); - if (shade) { - Serial.print("Received:"); - Serial.println(apiServer.arg("plain")); - // Send the command to the shade. - if(target <= 100) - shade->moveToTarget(shade->transformPosition(target)); - else if(repeat > 0) - shade->sendCommand(command, repeat); - else - shade->sendCommand(command); - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - apiServer.send(200, _encoding_json, g_content); - } - else { - apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); - } - }); - apiServer.on("/groupCommand", []() { - webServer.sendCORSHeaders(apiServer); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = apiServer.method(); - uint8_t groupId = 255; - int8_t repeat = -1; - somfy_commands command = somfy_commands::My; - if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { - if (apiServer.hasArg("groupId")) { - groupId = atoi(apiServer.arg("groupId").c_str()); - if (apiServer.hasArg("command")) command = translateSomfyCommand(apiServer.arg("command")); - if(apiServer.hasArg("repeat")) repeat = atoi(apiServer.arg("repeat").c_str()); - } - else if (apiServer.hasArg("plain")) { - Serial.println("Sending Group Command"); - DynamicJsonDocument doc(256); - DeserializationError err = deserializeJson(doc, apiServer.arg("plain")); - if (err) { - switch (err.code()) { - case DeserializationError::InvalidInput: - apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}")); - break; - case DeserializationError::NoMemory: - apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}")); - break; - default: - apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}")); - break; - } - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("groupId")) groupId = obj["groupId"]; - else apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group id was supplied.\"}")); - if (obj.containsKey("command")) { - String scmd = obj["command"]; - command = translateSomfyCommand(scmd); - } - if(obj.containsKey("repeat")) repeat = obj["repeat"].as(); - } - } - else apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group object supplied.\"}")); - } - SomfyGroup * group = somfy.getGroupById(groupId); - if (group) { - Serial.print("Received:"); - Serial.println(apiServer.arg("plain")); - // Send the command to the group. - if(repeat != -1) group->sendCommand(command, repeat); - else group->sendCommand(command); - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - group->toJSON(sobj); - serializeJson(sdoc, g_content); - apiServer.send(200, _encoding_json, g_content); - } - else { - apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group with the specified id not found.\"}")); - } - }); - apiServer.on("/tiltCommand", []() { - webServer.sendCORSHeaders(apiServer); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = apiServer.method(); - uint8_t shadeId = 255; - uint8_t target = 255; - somfy_commands command = somfy_commands::My; - if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { - if (apiServer.hasArg("shadeId")) { - shadeId = atoi(apiServer.arg("shadeId").c_str()); - if (apiServer.hasArg("command")) command = translateSomfyCommand(apiServer.arg("command")); - else if(apiServer.hasArg("target")) target = atoi(apiServer.arg("target").c_str()); - } - else if (apiServer.hasArg("plain")) { - Serial.println("Sending Shade Tilt Command"); - DynamicJsonDocument doc(256); - DeserializationError err = deserializeJson(doc, apiServer.arg("plain")); - if (err) { - switch (err.code()) { - case DeserializationError::InvalidInput: - apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}")); - break; - case DeserializationError::NoMemory: - apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}")); - break; - default: - apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}")); - break; - } - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; - else apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); - if (obj.containsKey("command")) { - String scmd = obj["command"]; - command = translateSomfyCommand(scmd); - } - else if(obj.containsKey("target")) { - target = obj["target"].as(); - } - } - } - else apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); - } - SomfyShade* shade = somfy.getShadeById(shadeId); - if (shade) { - Serial.print("Received:"); - Serial.println(apiServer.arg("plain")); - // Send the command to the shade. - if(target <= 100) - shade->moveToTiltTarget(shade->transformPosition(target)); - else - shade->sendTiltCommand(command); - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - apiServer.send(200, _encoding_json, g_content); - } - else { - apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); - } - }); + apiServer.on("/shadeCommand", []() { webServer.handleShadeCommand(apiServer); }); + apiServer.on("/groupCommand", []() { webServer.handleGroupCommand(apiServer); }); + apiServer.on("/tiltCommand", []() { webServer.handleTiltCommand(apiServer); }); + apiServer.on("/repeatCommand", []() { webServer.handleRepeatCommand(apiServer); }); - server.on("/upnp.xml", []() { - SSDP.schema(server.client()); - }); + // Web Interface + server.on("/tiltCommand", []() { webServer.handleTiltCommand(server); }); + server.on("/repeatCommand", []() { webServer.handleRepeatCommand(server); }); + server.on("/shadeCommand", []() { webServer.handleShadeCommand(server); }); + server.on("/groupCommand", []() { webServer.handleGroupCommand(server); }); + + server.on("/upnp.xml", []() { SSDP.schema(server.client()); }); server.on("/", []() { webServer.handleStreamFile(server, "/index.html", _encoding_html); }); server.on("/login", []() { webServer.handleLogin(server); }); server.on("/loginContext", []() { webServer.handleLoginContext(server); }); @@ -739,33 +687,7 @@ void Web::begin() { server.on("/icons.css", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/icons.css", "text/css"); }); server.on("/favicon.png", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/favicon.png", "image/png"); }); server.on("/icon.png", []() { webServer.sendCacheHeaders(604800); webServer.handleStreamFile(server, "/icon.png", "image/png"); }); - server.onNotFound([]() { - HTTPMethod method = server.method(); - Serial.printf("Request %s 404-%d ", server.uri().c_str(), method); - switch (method) { - case HTTP_POST: - Serial.print("POST "); - break; - case HTTP_GET: - Serial.print("GET "); - break; - case HTTP_PUT: - Serial.print("PUT "); - break; - case HTTP_OPTIONS: - Serial.println("OPTIONS "); - server.send(200, "OK"); - return; - default: - Serial.print("["); - Serial.print(method); - Serial.print("]"); - break; - - } - snprintf(g_content, sizeof(g_content), "404 Service Not Found: %s", server.uri().c_str()); - server.send(404, _encoding_text, g_content); - }); + server.onNotFound([]() { webServer.handleNotFound(server); }); server.on("/controller", []() { webServer.handleController(server); }); server.on("/shades", []() { webServer.handleGetShades(server); }); server.on("/groups", []() { webServer.handleGetGroups(server); }); @@ -845,7 +767,7 @@ void Web::begin() { JsonObject obj = doc.to(); shade->toJSON(obj); serializeJson(doc, g_content); - Serial.println(g_content); + //Serial.println(g_content); server.send(200, _encoding_json, g_content); } else { @@ -1157,134 +1079,6 @@ void Web::begin() { else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group object supplied.\"}")); } }); - server.on("/tiltCommand", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - uint8_t shadeId = 255; - uint8_t target = 255; - somfy_commands command = somfy_commands::My; - if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { - if (server.hasArg("shadeId")) { - shadeId = atoi(server.arg("shadeId").c_str()); - if (server.hasArg("command")) command = translateSomfyCommand(server.arg("command")); - else if(server.hasArg("target")) target = atoi(server.arg("target").c_str()); - } - else if (server.hasArg("plain")) { - Serial.println("Sending Shade Tilt Command"); - DynamicJsonDocument doc(256); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - switch (err.code()) { - case DeserializationError::InvalidInput: - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}")); - break; - case DeserializationError::NoMemory: - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}")); - break; - default: - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}")); - break; - } - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); - if (obj.containsKey("command")) { - String scmd = obj["command"]; - command = translateSomfyCommand(scmd); - } - else if(obj.containsKey("target")) { - target = obj["target"].as(); - } - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); - } - SomfyShade* shade = somfy.getShadeById(shadeId); - if (shade) { - Serial.print("Received:"); - Serial.println(server.arg("plain")); - // Send the command to the shade. - if(target <= 100) - shade->moveToTiltTarget(shade->transformPosition(target)); - else - shade->sendTiltCommand(command); - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); - } - }); - server.on("/repeatCommand", []() { webServer.handleRepeatCommand(server); }); - server.on("/shadeCommand", []() { webServer.handleShadeCommand(server); }); - server.on("/groupCommand", []() { - webServer.sendCORSHeaders(server); - if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } - HTTPMethod method = server.method(); - uint8_t groupId = 255; - int8_t repeat = -1; - somfy_commands command = somfy_commands::My; - if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { - if (server.hasArg("groupId")) { - groupId = atoi(server.arg("groupId").c_str()); - if (server.hasArg("command")) command = translateSomfyCommand(server.arg("command")); - if(server.hasArg("repeat")) repeat = atoi(server.arg("repeat").c_str()); - } - else if (server.hasArg("plain")) { - Serial.println("Sending Group Command"); - DynamicJsonDocument doc(256); - DeserializationError err = deserializeJson(doc, server.arg("plain")); - if (err) { - switch (err.code()) { - case DeserializationError::InvalidInput: - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}")); - break; - case DeserializationError::NoMemory: - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}")); - break; - default: - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}")); - break; - } - return; - } - else { - JsonObject obj = doc.as(); - if (obj.containsKey("groupId")) groupId = obj["groupId"]; - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group id was supplied.\"}")); - if (obj.containsKey("command")) { - String scmd = obj["command"]; - command = translateSomfyCommand(scmd); - } - if(obj.containsKey("repeat")) repeat = obj["repeat"].as(); - } - } - else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No group object supplied.\"}")); - } - SomfyGroup * group = somfy.getGroupById(groupId); - if (group) { - Serial.print("Received:"); - Serial.println(server.arg("plain")); - // Send the command to the group. - if(repeat > 0) group->sendCommand(command, repeat); - else group->sendCommand(command); - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - group->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Group with the specified id not found.\"}")); - } - }); server.on("/setMyPosition", []() { webServer.sendCORSHeaders(server); if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } @@ -1324,23 +1118,25 @@ void Web::begin() { } } else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); + SomfyShade* shade = somfy.getShadeById(shadeId); + if (shade) { + // Send the command to the shade. + if(tilt < 0) tilt = shade->myPos; + if(shade->tiltType == tilt_types::none) tilt = -1; + if(pos >= 0 && pos <= 100) + shade->setMyPosition(shade->transformPosition(pos), shade->transformPosition(tilt)); + DynamicJsonDocument sdoc(512); + JsonObject sobj = sdoc.to(); + shade->toJSON(sobj); + serializeJson(sdoc, g_content); + server.send(200, _encoding_json, g_content); + } + else { + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); + } } - SomfyShade* shade = somfy.getShadeById(shadeId); - if (shade) { - // Send the command to the shade. - if(tilt < 0) tilt = shade->myPos; - if(shade->tiltType == tilt_types::none) tilt = -1; - if(pos >= 0 && pos <= 100) - shade->setMyPosition(shade->transformPosition(pos), shade->transformPosition(tilt)); - DynamicJsonDocument sdoc(512); - JsonObject sobj = sdoc.to(); - shade->toJSON(sobj); - serializeJson(sdoc, g_content); - server.send(200, _encoding_json, g_content); - } - else { - server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}")); - } + else + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid Http method\"}")); }); server.on("/setRollingCode", []() { webServer.sendCORSHeaders(server); @@ -1780,7 +1576,6 @@ void Web::begin() { }); server.on("/updateFirmware", HTTP_POST, []() { webServer.sendCORSHeaders(server); - somfy.transceiver.end(); // Shut down the radio so we do not get any interrupts during this process. if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } if (Update.hasError()) server.send(500, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Error updating firmware: \"}"); @@ -1795,6 +1590,10 @@ void Web::begin() { if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size Update.printError(Serial); } + else { + somfy.transceiver.end(); // Shut down the radio so we do not get any interrupts during this process. + mqtt.end(); + } } else if (upload.status == UPLOAD_FILE_WRITE) { /* flashing firmware to ESP*/ @@ -1837,7 +1636,6 @@ void Web::begin() { }); server.on("/updateApplication", HTTP_POST, []() { webServer.sendCORSHeaders(server); - somfy.transceiver.end(); // Shut down the radio so we do not get any interrupts during this process. if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } server.sendHeader("Connection", "close"); server.send(200, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Updating Application: \"}"); @@ -1850,6 +1648,10 @@ void Web::begin() { if (!Update.begin(UPDATE_SIZE_UNKNOWN, U_SPIFFS)) { //start with max available size and tell it we are updating the file system. Update.printError(Serial); } + else { + somfy.transceiver.end(); // Shut down the radio so we do not get any interrupts during this process. + mqtt.end(); + } } else if (upload.status == UPLOAD_FILE_WRITE) { /* flashing littlefs to ESP*/ @@ -2265,6 +2067,72 @@ void Web::begin() { serializeJson(doc, g_content); server.send(200, _encoding_json, g_content); }); + server.on("/shadeSortOrder", []() { + if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } + DynamicJsonDocument doc(512); + Serial.print("Plain: "); + Serial.print(server.method()); + Serial.println(server.arg("plain")); + DeserializationError err = deserializeJson(doc, server.arg("plain")); + if (err) { + Serial.print("Error parsing JSON "); + Serial.println(err.c_str()); + String msg = err.c_str(); + server.send(400, _encoding_html, "Error parsing JSON body
" + msg); + } + else { + JsonArray arr = doc.as(); + HTTPMethod method = server.method(); + if (method == HTTP_POST || method == HTTP_PUT) { + // Parse out all the inputs. + uint8_t order = 0; + for(JsonVariant v : arr) { + uint8_t shadeId = v.as(); + if (shadeId != 255) { + SomfyShade *shade = somfy.getShadeById(shadeId); + if(shade) shade->sortOrder = order++; + } + } + server.send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully set shade order\"}"); + } + else { + server.send(201, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); + } + } + }); + server.on("/groupSortOrder", []() { + if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } + DynamicJsonDocument doc(512); + Serial.print("Plain: "); + Serial.print(server.method()); + Serial.println(server.arg("plain")); + DeserializationError err = deserializeJson(doc, server.arg("plain")); + if (err) { + Serial.print("Error parsing JSON "); + Serial.println(err.c_str()); + String msg = err.c_str(); + server.send(400, _encoding_html, "Error parsing JSON body
" + msg); + } + else { + JsonArray arr = doc.as(); + HTTPMethod method = server.method(); + if (method == HTTP_POST || method == HTTP_PUT) { + // Parse out all the inputs. + uint8_t order = 0; + for(JsonVariant v : arr) { + uint8_t groupId = v.as(); + if (groupId != 255) { + SomfyGroup *group = somfy.getGroupById(groupId); + if(group) group->sortOrder = order++; + } + } + server.send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Successfully set group order\"}"); + } + else { + server.send(201, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Invalid HTTP Method: \"}"); + } + } + }); server.begin(); apiServer.begin(); } diff --git a/Web.h b/Web.h index 460dbcc..6438885 100644 --- a/Web.h +++ b/Web.h @@ -15,6 +15,10 @@ class Web { void handleGetGroups(WebServer &server); void handleShadeCommand(WebServer &server); void handleRepeatCommand(WebServer &server); + void handleGroupCommand(WebServer &server); + void handleTiltCommand(WebServer &server); + void handleDiscovery(WebServer &server); + void handleNotFound(WebServer &server); void begin(); void loop(); void end(); diff --git a/data/appversion b/data/appversion index c346e7a..c5864dc 100644 --- a/data/appversion +++ b/data/appversion @@ -1 +1 @@ -2.1.4 \ No newline at end of file +2.1.5 \ No newline at end of file diff --git a/data/index.html b/data/index.html index 62e3a16..622ccfe 100644 --- a/data/index.html +++ b/data/index.html @@ -3,11 +3,11 @@ - - - + + + - +
@@ -276,6 +276,7 @@