diff --git a/ConfigFile.cpp b/ConfigFile.cpp index 29a67cd..d5cc863 100644 --- a/ConfigFile.cpp +++ b/ConfigFile.cpp @@ -352,7 +352,13 @@ bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) { } shade->lastRollingCode = this->readUInt16(0); if(this->header.version > 7) shade->flags = this->readUInt8(0); - if(shade->getRemoteAddress() != 0) shade->lastRollingCode = max(pref.getUShort(shade->getRemotePrefId(), shade->lastRollingCode), shade->lastRollingCode); + if(shade->getRemoteAddress() != 0) { + // If the last rolling code stored on the nvs is less than the rc we currently have + // then we need to set it. + uint16_t rc = pref.getUShort(shade->getRemotePrefId(), 0); + shade->lastRollingCode = max(rc, shade->lastRollingCode); + if(rc < shade->lastRollingCode) pref.putUShort(shade->getRemotePrefId(), shade->lastRollingCode); + } if(this->header.version < 4) shade->myPos = static_cast(this->readUInt8(255)); else { @@ -391,12 +397,18 @@ bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) { this->readString(group->name, sizeof(group->name)); group->proto = static_cast(this->readUInt8(0)); group->bitLength = this->readUInt8(56); + if(group->getRemoteAddress() != 0) { + uint16_t rc = pref.getUShort(group->getRemotePrefId(), 0); + group->lastRollingCode = max(rc, group->lastRollingCode); + if(rc < group->lastRollingCode) pref.putUShort(group->getRemotePrefId(), group->lastRollingCode); + } uint8_t lsd = 0; for(uint8_t j = 0; j < SOMFY_MAX_GROUPED_SHADES; j++) { uint8_t shadeId = this->readUInt8(0); // Do this to eliminate gaps. if(shadeId > 0) group->linkedShades[lsd++] = shadeId; } + group->compressLinkedShadeIds(); } pref.end(); if(opened) { @@ -442,7 +454,7 @@ bool ShadeConfigFile::writeShadeRecord(SomfyShade *shade) { } this->writeUInt16(shade->lastRollingCode); if(shade->getShadeId() != 255) { - this->writeUInt8(shade->flags & static_cast(somfy_flags_t::SunFlag)); + this->writeUInt8(shade->flags & 0x0F); this->writeFloat(shade->myPos, 5); this->writeFloat(shade->myTiltPos, 5); this->writeFloat(shade->currentPos, 5); diff --git a/ConfigSettings.h b/ConfigSettings.h index 9bd1ca9..436064c 100644 --- a/ConfigSettings.h +++ b/ConfigSettings.h @@ -3,7 +3,7 @@ #ifndef configsettings_h #define configsettings_h -#define FW_VERSION "v2.0.3" +#define FW_VERSION "v2.1.0" enum DeviceStatus { DS_OK = 0, DS_ERROR = 1, diff --git a/MQTT.cpp b/MQTT.cpp index 70e7091..d10ffb9 100644 --- a/MQTT.cpp +++ b/MQTT.cpp @@ -28,7 +28,7 @@ void MQTTClass::reset() { bool MQTTClass::loop() { if(settings.MQTT.enabled && !mqttClient.connected()) this->connect(); - mqttClient.loop(); + if(settings.MQTT.enabled) mqttClient.loop(); return true; } void MQTTClass::receive(const char *topic, byte*payload, uint32_t length) { @@ -219,49 +219,49 @@ bool MQTTClass::subscribe(const char *topic) { } return true; } -bool MQTTClass::publish(const char *topic, const char *payload) { +bool MQTTClass::publish(const char *topic, const char *payload, bool retain) { if(mqttClient.connected()) { char top[128]; if(strlen(settings.MQTT.rootTopic) > 0) snprintf(top, sizeof(top), "%s/%s", settings.MQTT.rootTopic, topic); else strlcpy(top, topic, sizeof(top)); - mqttClient.publish(top, payload); + mqttClient.publish(top, payload, retain); return true; } return false; } -bool MQTTClass::publish(const char *topic, uint32_t val) { +bool MQTTClass::publish(const char *topic, uint32_t val, bool retain) { snprintf(g_content, sizeof(g_content), "%u", val); - return this->publish(topic, g_content); + return this->publish(topic, g_content, retain); } -bool MQTTClass::publish(const char *topic, JsonDocument &doc) { +bool MQTTClass::publish(const char *topic, JsonDocument &doc, bool retain) { serializeJson(doc, g_content, sizeof(g_content)); - return this->publish(topic, g_content); + return this->publish(topic, g_content, retain); } -bool MQTTClass::publish(const char *topic, JsonArray &arr) { +bool MQTTClass::publish(const char *topic, JsonArray &arr, bool retain) { serializeJson(arr, g_content, sizeof(g_content)); - return this->publish(topic, g_content); + return this->publish(topic, g_content, retain); } -bool MQTTClass::publish(const char *topic, JsonObject &obj) { +bool MQTTClass::publish(const char *topic, JsonObject &obj, bool retain) { serializeJson(obj, g_content, sizeof(g_content)); - return this->publish(topic, g_content); + return this->publish(topic, g_content, retain); } -bool MQTTClass::publish(const char *topic, int8_t val) { +bool MQTTClass::publish(const char *topic, int8_t val, bool retain) { snprintf(g_content, sizeof(g_content), "%d", val); - return this->publish(topic, g_content); + return this->publish(topic, g_content, retain); } -bool MQTTClass::publish(const char *topic, uint8_t val) { +bool MQTTClass::publish(const char *topic, uint8_t val, bool retain) { snprintf(g_content, sizeof(g_content), "%u", val); - return this->publish(topic, g_content); + return this->publish(topic, g_content, retain); } -bool MQTTClass::publish(const char *topic, uint16_t val) { +bool MQTTClass::publish(const char *topic, uint16_t val, bool retain) { snprintf(g_content, sizeof(g_content), "%u", val); - return this->publish(topic, g_content); + return this->publish(topic, g_content, retain); } -bool MQTTClass::publish(const char *topic, bool val) { +bool MQTTClass::publish(const char *topic, bool val, bool retain) { snprintf(g_content, sizeof(g_content), "%s", val ? "true" : "false"); - return this->publish(topic, g_content); + return this->publish(topic, g_content, retain); } bool MQTTClass::connected() { if(settings.MQTT.enabled) return mqttClient.connected(); diff --git a/MQTT.h b/MQTT.h index de90f01..943a590 100644 --- a/MQTT.h +++ b/MQTT.h @@ -14,15 +14,15 @@ class MQTTClass { bool disconnect(); bool connected(); void reset(); - bool publish(const char *topic, const char *payload); - bool publish(const char *topic, JsonDocument &doc); - bool publish(const char *topic, JsonArray &arr); - bool publish(const char *topic, JsonObject &obj); - bool publish(const char *topic, uint8_t val); - bool publish(const char *topic, int8_t val); - bool publish(const char *topic, uint32_t val); - bool publish(const char *topic, uint16_t val); - bool publish(const char *topic, bool val); + bool publish(const char *topic, const char *payload, bool retain = false); + bool publish(const char *topic, JsonDocument &doc, bool retain = false); + bool publish(const char *topic, JsonArray &arr, bool retain = false); + bool publish(const char *topic, JsonObject &obj, bool retain = false); + bool publish(const char *topic, uint8_t val, bool retain = false); + bool publish(const char *topic, int8_t val, bool retain = false); + bool publish(const char *topic, uint32_t val, bool retain = false); + bool publish(const char *topic, uint16_t val, bool retain = false); + bool publish(const char *topic, bool val, bool retain = false); bool subscribe(const char *topic); bool unsubscribe(const char *topic); static void receive(const char *topic, byte *payload, uint32_t length); diff --git a/Network.cpp b/Network.cpp index 0eeee2d..56809a9 100644 --- a/Network.cpp +++ b/Network.cpp @@ -54,6 +54,10 @@ void Network::loop() { this->emitSockets(); if(!this->connected()) return; } + if(this->connected() && millis() - this->lastMDNS > 60000) { + if(this->lastMDNS != 0) MDNS.setInstanceName(settings.hostname); + this->lastMDNS = millis(); + } sockEmit.loop(); if(settings.ssdpBroadcast) { if(!SSDP.isStarted) SSDP.begin(); diff --git a/Network.h b/Network.h index 3b8bb46..18d9f25 100644 --- a/Network.h +++ b/Network.h @@ -5,6 +5,7 @@ class Network { protected: unsigned long lastEmit = 0; + unsigned long lastMDNS = 0; int lastRSSI = 0; int lastChannel = 0; int linkSpeed = 0; diff --git a/Somfy.cpp b/Somfy.cpp index 6dd026b..ebf14db 100644 --- a/Somfy.cpp +++ b/Somfy.cpp @@ -447,7 +447,17 @@ SomfyGroup *SomfyShadeController::findGroupByRemoteAddress(uint32_t address) { } return nullptr; } - +void SomfyShadeController::updateGroupFlags() { + for(uint8_t i = 0; i < SOMFY_MAX_GROUPS; i++) { + SomfyGroup *group = &this->groups[i]; + if(group && group->getGroupId() != 255) { + uint8_t flags = group->flags; + group->updateFlags(); + if(flags != group->flags) + group->emitState(); + } + } +} bool SomfyShadeController::loadLegacy() { Serial.println("Loading Legacy shades using NVS"); pref.begin("Shades", true); @@ -729,22 +739,64 @@ bool SomfyShade::unlinkRemote(uint32_t address) { return false; } bool SomfyGroup::unlinkShade(uint8_t shadeId) { + bool removed = false; for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) { if(this->linkedShades[i] == shadeId) { this->linkedShades[i] = 0; - somfy.commit(); - return true; + removed = true; + } + } + // Compress the linked shade ids so we can stop looking on the first 0 + if(removed) { + this->compressLinkedShadeIds(); + somfy.commit(); + } + return removed; +} +void SomfyGroup::compressLinkedShadeIds() { + // [1,0,4,3,0,0,0] i:0,j:0 + // [1,0,4,3,0,0,0] i:1,j:1 + // [1,4,0,3,0,0,0] i:2,j:1 + // [1,4,3,0,0,0,0] i:3,j:2 + // [1,4,3,0,0,0,0] i:4,j:2 + + // [1,2,0,0,3,0,0] i:0,j:0 + // [1,2,0,0,3,0,0] i:1,j:1 + // [1,2,0,0,3,0,0] i:2,j:2 + // [1,2,0,0,3,0,0] i:3,j:2 + // [1,2,3,0,0,0,0] i:4,j:2 + // [1,2,3,0,0,0,0] i:5,j:3 + for(uint8_t i = 0, j = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) { + if(this->linkedShades[i] != 0) { + if(i != j) { + this->linkedShades[j] = this->linkedShades[i]; + this->linkedShades[i] = 0; + } + j++; } } - return false; } bool SomfyGroup::hasShadeId(uint8_t shadeId) { for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) { + if(this->linkedShades[i] == 0) break; if(this->linkedShades[i] == shadeId) return true; } return false; } bool SomfyShade::isAtTarget() { return this->currentPos == this->target && this->currentTiltPos == this->tiltTarget; } +bool SomfyRemote::hasSunSensor() { return (this->flags & static_cast(somfy_flags_t::SunSensor)) > 0;} +void SomfyRemote::setSunSensor(bool bHasSensor ) { bHasSensor ? this->flags |= static_cast(somfy_flags_t::SunSensor) : this->flags &= ~(static_cast(somfy_flags_t::SunSensor)); } +void SomfyGroup::updateFlags() { + this->flags = 0; + for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) { + if(this->linkedShades[i] != 0) { + SomfyShade *shade = somfy.getShadeById(this->linkedShades[i]); + if(shade) this->flags |= shade->flags; + } + else break; + } +} + bool SomfyShade::isInGroup() { if(this->getShadeId() == 255) return false; for(uint8_t i = 0; i < SOMFY_MAX_GROUPS; i++) { @@ -1138,34 +1190,64 @@ void SomfyShade::publish() { snprintf(topic, sizeof(topic), "shades/%u/tiltTarget", this->shadeId); mqtt.publish(topic, this->transformPosition(this->tiltTarget)); } - else if (this->shadeType == shade_types::awning) { - const uint8_t sunFlag = !!(this->flags & static_cast(somfy_flags_t::SunFlag)); - const uint8_t isSunny = !!(this->flags & static_cast(somfy_flags_t::Sunny)); - const uint8_t isWindy = !!(this->flags & static_cast(somfy_flags_t::Windy)); - - snprintf(topic, sizeof(topic), "shades/%u/sunFlag", this->shadeId); - mqtt.publish(topic, sunFlag); - snprintf(topic, sizeof(topic), "shades/%u/sunny", this->shadeId); - mqtt.publish(topic, isSunny); - snprintf(topic, sizeof(topic), "shades/%u/windy", this->shadeId); - mqtt.publish(topic, isWindy); - } + const uint8_t sunFlag = !!(this->flags & static_cast(somfy_flags_t::SunFlag)); + const uint8_t isSunny = !!(this->flags & static_cast(somfy_flags_t::Sunny)); + const uint8_t isWindy = !!(this->flags & static_cast(somfy_flags_t::Windy)); + snprintf(topic, sizeof(topic), "shades/%u/sunSensor", this->shadeId); + mqtt.publish(topic, this->hasSunSensor()); + snprintf(topic, sizeof(topic), "shades/%u/sunFlag", this->shadeId); + mqtt.publish(topic, sunFlag); + snprintf(topic, sizeof(topic), "shades/%u/sunny", this->shadeId); + mqtt.publish(topic, isSunny); + snprintf(topic, sizeof(topic), "shades/%u/windy", this->shadeId); + mqtt.publish(topic, isWindy); } } +void SomfyGroup::publish() { + if(mqtt.connected()) { + char topic[32]; + snprintf(topic, sizeof(topic), "groups/%u/groupId", this->groupId); + mqtt.publish(topic, this->groupId); + snprintf(topic, sizeof(topic), "groups/%u/name", this->groupId); + mqtt.publish(topic, this->name); + snprintf(topic, sizeof(topic), "groups/%u/remoteAddress", this->groupId); + mqtt.publish(topic, this->getRemoteAddress()); + snprintf(topic, sizeof(topic), "groups/%u/direction", this->groupId); + mqtt.publish(topic, this->direction); + snprintf(topic, sizeof(topic), "groups/%u/lastRollingCode", this->groupId); + mqtt.publish(topic, this->lastRollingCode); + snprintf(topic, sizeof(topic), "groups/%u/groupType", this->groupId); + mqtt.publish(topic, static_cast(this->groupType)); + snprintf(topic, sizeof(topic), "groups/%u/flags", this->groupId); + mqtt.publish(topic, this->flags); + const uint8_t sunFlag = !!(this->flags & static_cast(somfy_flags_t::SunFlag)); + const uint8_t isSunny = !!(this->flags & static_cast(somfy_flags_t::Sunny)); + const uint8_t isWindy = !!(this->flags & static_cast(somfy_flags_t::Windy)); + snprintf(topic, sizeof(topic), "groups/%u/sunSensor", this->groupId); + mqtt.publish(topic, this->hasSunSensor()); + snprintf(topic, sizeof(topic), "groups/%u/sunFlag", this->groupId); + mqtt.publish(topic, sunFlag); + snprintf(topic, sizeof(topic), "groups/%u/sunny", this->groupId); + mqtt.publish(topic, isSunny); + snprintf(topic, sizeof(topic), "groups/%u/windy", this->groupId); + mqtt.publish(topic, isWindy); + } +} + 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}", + 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}", 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->flipCommands ? "true" : "false", this->flipPosition ? "true": "false", this->flags, this->hasSunSensor() ? "true" : "false"); 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}", + 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}", 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); + static_cast(this->tiltType), this->flipCommands ? "true" : "false", this->flipPosition ? "true": "false", this->flags, this->hasSunSensor() ? "true" : "false"); if(num >= 255) sockEmit.sendToClients(evt, buf); else sockEmit.sendToClient(num, evt, buf); if(mqtt.connected()) { @@ -1186,6 +1268,9 @@ void SomfyShade::emitState(uint8_t num, const char *evt) { mqtt.publish(topic, this->transformPosition(this->myPos)); snprintf(topic, sizeof(topic), "shades/%u/tiltType", this->shadeId); mqtt.publish(topic, static_cast(this->tiltType)); + snprintf(topic, sizeof(topic), "shades/%u/sunSensor", this->shadeId); + mqtt.publish(topic, this->hasSunSensor()); + if(this->tiltType != tilt_types::none) { snprintf(topic, sizeof(topic), "shades/%u/myTiltPos", this->shadeId); mqtt.publish(topic, this->transformPosition(this->myTiltPos)); @@ -1194,18 +1279,16 @@ void SomfyShade::emitState(uint8_t num, const char *evt) { snprintf(topic, sizeof(topic), "shades/%u/tiltTarget", this->shadeId); mqtt.publish(topic, this->transformPosition(this->tiltTarget)); } - else if (this->shadeType == shade_types::awning) { - const uint8_t sunFlag = !!(this->flags & static_cast(somfy_flags_t::SunFlag)); - const uint8_t isSunny = !!(this->flags & static_cast(somfy_flags_t::Sunny)); - const uint8_t isWindy = !!(this->flags & static_cast(somfy_flags_t::Windy)); + const uint8_t sunFlag = !!(this->flags & static_cast(somfy_flags_t::SunFlag)); + const uint8_t isSunny = !!(this->flags & static_cast(somfy_flags_t::Sunny)); + const uint8_t isWindy = !!(this->flags & static_cast(somfy_flags_t::Windy)); - snprintf(topic, sizeof(topic), "shades/%u/sunFlag", this->shadeId); - mqtt.publish(topic, sunFlag); - snprintf(topic, sizeof(topic), "shades/%u/sunny", this->shadeId); - mqtt.publish(topic, isSunny); - snprintf(topic, sizeof(topic), "shades/%u/windy", this->shadeId); - mqtt.publish(topic, isWindy); - } + snprintf(topic, sizeof(topic), "shades/%u/sunFlag", this->shadeId); + mqtt.publish(topic, sunFlag); + snprintf(topic, sizeof(topic), "shades/%u/sunny", this->shadeId); + mqtt.publish(topic, isSunny); + snprintf(topic, sizeof(topic), "shades/%u/windy", this->shadeId); + mqtt.publish(topic, isWindy); } } void SomfyShade::emitCommand(somfy_commands cmd, const char *source, uint32_t sourceAddress, const char *evt) { this->emitCommand(255, cmd, source, sourceAddress, evt); } @@ -1229,34 +1312,35 @@ void SomfyGroup::emitState(const char *evt) { this->emitState(255, evt); } void SomfyGroup::emitState(uint8_t num, const char *evt) { ClientSocketEvent e(evt); char buf[30]; + uint8_t flags = 0; snprintf(buf, sizeof(buf), "{\"groupId\":%d,", this->groupId); e.appendMessage(buf); snprintf(buf, sizeof(buf), "\"remoteAddress\":%d,", this->getRemoteAddress()); e.appendMessage(buf); snprintf(buf, sizeof(buf), "\"name\":\"%s\",", this->name); e.appendMessage(buf); + snprintf(buf, sizeof(buf), "\"sunSensor\":%s,", this->hasSunSensor() ? "true" : "false"); + e.appendMessage(buf); snprintf(buf, sizeof(buf), "\"shades\":["); e.appendMessage(buf); for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) { if(this->linkedShades[i] != 255) { - snprintf(buf, sizeof(buf), "%s%d", i != 0 ? "," : "", this->linkedShades[i]); - e.appendMessage(buf); + if(this->linkedShades[i] != 0) { + SomfyShade *shade = somfy.getShadeById(this->linkedShades[i]); + if(shade) { + flags |= shade->flags; + snprintf(buf, sizeof(buf), "%s%d", i != 0 ? "," : "", this->linkedShades[i]); + e.appendMessage(buf); + } + } } } - e.appendMessage("]}"); + snprintf(buf, sizeof(buf), "],\"flags\":%d}", flags); + e.appendMessage(buf); + if(num >= 255) sockEmit.sendToClients(&e); else sockEmit.sendToClient(num, &e); - if(mqtt.connected()) { - char topic[32]; - snprintf(topic, sizeof(topic), "groups/%u/type", this->groupId); - mqtt.publish(topic, static_cast(this->groupType)); - snprintf(topic, sizeof(topic), "groups/%u/remoteAddress", this->groupId); - mqtt.publish(topic, this->getRemoteAddress()); - snprintf(topic, sizeof(topic), "groups/%u/lastRollingCode", this->groupId); - mqtt.publish(topic, this->lastRollingCode); - snprintf(topic, sizeof(topic), "groups/%u/direction", this->groupId); - mqtt.publish(topic, this->direction); - } + this->publish(); } int8_t SomfyShade::transformPosition(float fpos) { return static_cast(this->flipPosition && fpos >= 0.00f ? floor(100.0f - fpos) : floor(fpos)); } bool SomfyShade::isIdle() { return this->direction == 0 && this->tiltDirection == 0; } @@ -1390,12 +1474,10 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { const bool wasSunny = prevFlags & static_cast(somfy_flags_t::Sunny); const bool wasWindy = prevFlags & static_cast(somfy_flags_t::Windy); const uint16_t status = frame.rollingCode << 4; - if (status & static_cast(somfy_flags_t::Sunny)) this->flags |= static_cast(somfy_flags_t::Sunny); else this->flags &= ~(static_cast(somfy_flags_t::Sunny)); - if (status & static_cast(somfy_flags_t::Windy)) this->flags |= static_cast(somfy_flags_t::Windy); else @@ -1404,11 +1486,8 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { this->flags |= static_cast(somfy_flags_t::DemoMode); else this->flags &= ~(static_cast(somfy_flags_t::DemoMode)); - - const bool isSunny = this->flags & static_cast(somfy_flags_t::Sunny); const bool isWindy = this->flags & static_cast(somfy_flags_t::Windy); - if (isSunny) { this->noSunStart = 0; @@ -1419,12 +1498,10 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { this->sunStart = 0; this->sunDone = true; } - if (isWindy) { this->noWindStart = 0; this->noWindDone = true; - this->windLast = curTime; } else @@ -1432,38 +1509,32 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { this->windStart = 0; this->windDone = true; } - if (isSunny && !wasSunny) { this->sunStart = curTime; this->sunDone = false; - Serial.printf("[%u] Sun -> start\r\n", this->shadeId); } else if (!isSunny && wasSunny) { this->noSunStart = curTime; this->noSunDone = false; - Serial.printf("[%u] No Sun -> start\r\n", this->shadeId); } - if (isWindy && !wasWindy) { this->windStart = curTime; this->windDone = false; - Serial.printf("[%u] Wind -> start\r\n", this->shadeId); } else if (!isWindy && wasWindy) { this->noWindStart = curTime; this->noWindDone = false; - Serial.printf("[%u] No Wind -> start\r\n", this->shadeId); } - this->emitState(); + somfy.updateGroupFlags(); } break; case somfy_commands::Prog: @@ -1479,6 +1550,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { somfy.isDirty = true; this->emitState(); this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); + somfy.updateGroupFlags(); break; case somfy_commands::SunFlag: { @@ -1495,6 +1567,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { somfy.isDirty = true; this->emitState(); this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); + somfy.updateGroupFlags(); } break; case somfy_commands::Up: @@ -1662,6 +1735,34 @@ void SomfyShade::processInternalCommand(somfy_commands cmd, uint8_t repeat) { this->target = min(100.0f, this->currentPos + (100.0f/(static_cast(this->downTime/static_cast(this->stepSize))))); } break; + case somfy_commands::Flag: + if(this->hasSunSensor()) { + this->flags &= ~(static_cast(somfy_flags_t::SunFlag)); + somfy.isDirty = true; + this->emitState(); + } + else + Serial.printf("Shade does not have sensor %d\n", this->flags); + break; + case somfy_commands::SunFlag: + if(this->hasSunSensor()) { + const bool isWindy = this->flags & static_cast(somfy_flags_t::Windy); + this->flags |= static_cast(somfy_flags_t::SunFlag); + if (!isWindy) + { + const bool isSunny = this->flags & static_cast(somfy_flags_t::Sunny); + if (isSunny && this->sunDone) + this->target = this->myPos >= 0 ? this->myPos : 100.0f; + else if (!isSunny && this->noSunDone) + this->target = 0.0f; + } + somfy.isDirty = true; + this->emitState(); + } + else + Serial.printf("Shade does not have sensor %d\n", this->flags); + break; + default: dir = 0; break; @@ -1838,19 +1939,19 @@ void SomfyGroup::sendCommand(somfy_commands cmd, uint8_t repeat) { this->direction = 1; break; } - this->emitState(); for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) { if(this->linkedShades[i] != 0) { - SomfyShade * shade = somfy.getShadeById(this->linkedShades[i]); + SomfyShade *shade = somfy.getShadeById(this->linkedShades[i]); if(shade) { shade->processInternalCommand(cmd, repeat); shade->emitCommand(cmd, "group", this->getRemoteAddress()); } } } + this->updateFlags(); + this->emitState(); -} - +} void SomfyShade::sendTiltCommand(somfy_commands cmd) { if(cmd == somfy_commands::Up) { SomfyRemote::sendCommand(cmd, this->tiltType == tilt_types::tiltmotor ? TILT_REPEATS : 1); @@ -1963,6 +2064,7 @@ bool SomfyShade::fromJSON(JsonObject &obj) { if(obj.containsKey("hasTilt")) this->tiltType = static_cast(obj["hasTilt"]) ? tilt_types::none : tilt_types::tiltmotor; if(obj.containsKey("bitLength")) this->bitLength = obj["bitLength"]; if(obj.containsKey("proto")) this->proto = static_cast(obj["proto"].as()); + if(obj.containsKey("sunSensor")) this->setSunSensor(obj["sunSensor"]); if(obj.containsKey("shadeType")) { if(obj["shadeType"].is()) { if(strncmp(obj["shadeType"].as(), "roller", 7) == 0) @@ -1973,6 +2075,8 @@ bool SomfyShade::fromJSON(JsonObject &obj) { this->shadeType = shade_types::blind; else if(strncmp(obj["shadeType"].as(), "awning", 7) == 0) this->shadeType = shade_types::awning; + else if(strncmp(obj["shadeType"].as(), "shutter", 8) == 0) + this->shadeType = shade_types::shutter; } else { this->shadeType = static_cast(obj["shadeType"].as()); @@ -2019,6 +2123,7 @@ bool SomfyShade::toJSONRef(JsonObject &obj) { obj["bitLength"] = this->bitLength; obj["proto"] = static_cast(this->proto); obj["flags"] = this->flags; + obj["sunSensor"] = this->hasSunSensor(); SomfyRemote::toJSON(obj); return true; } @@ -2054,6 +2159,8 @@ bool SomfyShade::toJSON(JsonObject &obj) { obj["flipCommands"] = this->flipCommands; obj["flipPosition"] = this->flipPosition; obj["inGroup"] = this->isInGroup(); + obj["sunSensor"] = this->hasSunSensor(); + SomfyRemote::toJSON(obj); JsonArray arr = obj.createNestedArray("linkedRemotes"); for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { @@ -2070,6 +2177,7 @@ bool SomfyGroup::fromJSON(JsonObject &obj) { if(obj.containsKey("remoteAddress")) this->setRemoteAddress(obj["remoteAddress"]); if(obj.containsKey("bitLength")) this->bitLength = obj["bitLength"]; if(obj.containsKey("proto")) this->proto = static_cast(obj["proto"].as()); + if(obj.containsKey("sunSensor")) obj["sunSensor"] = this->hasSunSensor(); if(obj.containsKey("linkedShades")) { uint8_t linkedShades[SOMFY_MAX_GROUPED_SHADES]; memset(linkedShades, 0x00, sizeof(linkedShades)); @@ -2082,12 +2190,15 @@ bool SomfyGroup::fromJSON(JsonObject &obj) { return true; } bool SomfyGroup::toJSON(JsonObject &obj) { + this->updateFlags(); obj["groupId"] = this->getGroupId(); obj["name"] = this->name; obj["remoteAddress"] = this->m_remoteAddress; obj["lastRollingCode"] = this->lastRollingCode; obj["bitLength"] = this->bitLength; obj["proto"] = static_cast(this->proto); + obj["sunSensor"] = this->hasSunSensor(); + obj["flags"] = this->flags; SomfyRemote::toJSON(obj); JsonArray arr = obj.createNestedArray("linkedShades"); for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) { @@ -2126,15 +2237,25 @@ void SomfyShadeController::emitState(uint8_t num) { } } void SomfyShadeController::publish() { - StaticJsonDocument<128> doc; - JsonArray arr = doc.to(); + this->updateGroupFlags(); + StaticJsonDocument<128> docShades; + StaticJsonDocument<128> docGroups; + JsonArray arrShades = docShades.to(); + JsonArray arrGroups = docGroups.to(); for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { SomfyShade *shade = &this->shades[i]; if(shade->getShadeId() == 255) continue; - arr.add(shade->getShadeId()); + arrShades.add(shade->getShadeId()); shade->publish(); } - mqtt.publish("shades", arr); + mqtt.publish("shades", arrShades); + for(uint8_t i = 0; i < SOMFY_MAX_GROUPS; i++) { + SomfyGroup *group = &this->groups[i]; + if(group->getGroupId() == 255) continue; + arrGroups.add(group->getGroupId()); + group->publish(); + } + mqtt.publish("groups", arrGroups); } uint8_t SomfyShadeController::getNextShadeId() { // There is no shortcut for this since the deletion of diff --git a/Somfy.h b/Somfy.h index 782f77c..463b603 100644 --- a/Somfy.h +++ b/Somfy.h @@ -137,6 +137,7 @@ struct somfy_tx_queue_t { enum class somfy_flags_t : byte { SunFlag = 0x01, + SunSensor = 0x02, DemoMode = 0x04, Windy = 0x10, Sunny = 0x20 @@ -175,7 +176,6 @@ class SomfyRemote { public: radio_proto proto = radio_proto::RTS; bool flipCommands = false; - uint8_t flags = 0; uint8_t bitLength = 0; char *getRemotePrefId() {return m_remotePrefId;} @@ -185,6 +185,8 @@ class SomfyRemote { virtual uint16_t getNextRollingCode(); virtual uint16_t setRollingCode(uint16_t code); uint16_t lastRollingCode = 0; + bool hasSunSensor(); + void setSunSensor(bool bHasSensor); virtual void sendCommand(somfy_commands cmd, uint8_t repeat = 1); somfy_commands transformCommand(somfy_commands cmd); }; @@ -288,6 +290,9 @@ class SomfyGroup : public SomfyRemote { bool linkShade(uint8_t shadeId); bool unlinkShade(uint8_t shadeId); bool hasShadeId(uint8_t shadeId); + void compressLinkedShadeIds(); + void publish(); + void updateFlags(); void emitState(const char *evt = "groupState"); void emitState(uint8_t num, const char *evt = "groupState"); void sendCommand(somfy_commands cmd, uint8_t repeat = 1); @@ -420,6 +425,7 @@ class SomfyShadeController { bool toJSONGroups(JsonArray &arr); uint8_t shadeCount(); uint8_t groupCount(); + void updateGroupFlags(); SomfyShade * getShadeById(uint8_t shadeId); SomfyGroup * getGroupById(uint8_t groupId); SomfyShade * findShadeByRemoteAddress(uint32_t address); diff --git a/SomfyController.ino.esp32.bin b/SomfyController.ino.esp32.bin index c3d3c74..50bdbc9 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 5f9f88b..bd07bac 100644 Binary files a/SomfyController.littlefs.bin and b/SomfyController.littlefs.bin differ diff --git a/data/appversion b/data/appversion index 6acdb44..50aea0e 100644 --- a/data/appversion +++ b/data/appversion @@ -1 +1 @@ -2.0.3 \ No newline at end of file +2.1.0 \ No newline at end of file diff --git a/data/index.html b/data/index.html index 801df9a..a7c72d0 100644 --- a/data/index.html +++ b/data/index.html @@ -3,11 +3,11 @@ - - - + + + - +
@@ -368,6 +368,12 @@
+
+
+ + +
+
diff --git a/data/index.js b/data/index.js index bc138db..34019d2 100644 --- a/data/index.js +++ b/data/index.js @@ -467,6 +467,9 @@ async function initSockets() { case 'remoteFrame': somfy.procRemoteFrame(msg); break; + case 'groupState': + somfy.procGroupState(msg); + break; case 'shadeState': somfy.procShadeState(msg); break; @@ -1194,7 +1197,7 @@ var security = new Security(); class General { initialized = false; - appVersion = 'v2.0.3'; + appVersion = 'v2.1.0'; reloadApp = false; init() { if (this.initialized) return; @@ -1973,7 +1976,7 @@ class Somfy { divCtl += '
'; divCtl += `
`; - divCtl += `
`; + divCtl += `
`; divCtl += `
`; divCtl += `
my
`; divCtl += `
`; @@ -1992,6 +1995,7 @@ class Somfy { let cmd = event.currentTarget.getAttribute('data-cmd'); let shadeId = parseInt(event.currentTarget.getAttribute('data-shadeid'), 10); if (this.btnTimer) { + console.log({ timer: true, isOn: event.currentTarget.getAttribute('data-on'), cmd: cmd }); clearTimeout(this.btnTimer); this.btnTimer = null; if (new Date().getTime() - this.btnDown > 2000) event.preventDefault(); @@ -2027,6 +2031,7 @@ class Somfy { }, 2000); } } + else if (cmd === 'sunflag') return; else if (makeBool(elShade.getAttribute('data-tilt'))) { this.btnTimer = setTimeout(() => { this.sendTiltCommand(shadeId, cmd); @@ -2090,6 +2095,7 @@ class Somfy { } divCtl += '
'; divCtl += `
`; + divCtl += `
`; divCtl += `
`; divCtl += `
my
`; divCtl += `
`; @@ -2104,9 +2110,16 @@ class Somfy { btns[i].addEventListener('click', (event) => { console.log(this); console.log(event); - let cmd = event.currentTarget.getAttribute('data-cmd'); let groupId = parseInt(event.currentTarget.getAttribute('data-groupid'), 10); - this.sendGroupCommand(groupId, cmd); + let cmd = event.currentTarget.getAttribute('data-cmd'); + if (cmd === 'sunflag') { + if (makeBool(event.currentTarget.getAttribute('data-on'))) + this.sendGroupCommand(groupId, 'flag'); + else + this.sendGroupCommand(groupId, 'sunflag'); + } + else + this.sendGroupCommand(groupId, cmd); }, true); } } @@ -2246,6 +2259,14 @@ class Somfy { sel.options[sel.options.length] = new Option(`GPIO-${i > 9 ? i.toString() : '0' + i.toString()}`, i, typeof opt !== 'undefined' && opt === i); } } + procGroupState(state) { + console.log(state); + let flags = document.querySelectorAll(`.button-sunflag[data-groupid="${state.groupId}"]`); + for (let i = 0; i < flags.length; i++) { + flags[i].style.display = state.sunSensor ? '' : 'none'; + flags[i].setAttribute('data-on', state.flags & 0x01 === 0x01 ? 'true' : 'false'); + } + } procShadeState(state) { console.log(state); let icons = document.querySelectorAll(`.somfy-shade-icon[data-shadeid="${state.shadeId}"]`); @@ -2260,7 +2281,7 @@ class Somfy { } let flags = document.querySelectorAll(`.button-sunflag[data-shadeid="${state.shadeId}"]`); for (let i = 0; i < flags.length; i++) { - flags[i].style.display = state.type === 3 ? '' : 'none'; + flags[i].style.display = state.sunSensor ? '' : 'none'; flags[i].setAttribute('data-on', state.flags & 0x01 === 0x01 ? 'true' : 'false'); }