diff --git a/ConfigFile.cpp b/ConfigFile.cpp index e203894..2e6ffbc 100644 --- a/ConfigFile.cpp +++ b/ConfigFile.cpp @@ -573,6 +573,7 @@ bool ShadeConfigFile::readSettingsRecord() { } bool ShadeConfigFile::readGroupRecord(SomfyGroup *group) { pref.begin("ShadeCodes"); + uint32_t startPos = this->file.position(); group->setGroupId(this->readUInt8(255)); group->groupType = static_cast(this->readUInt8(0)); group->setRemoteAddress(this->readUInt32(0)); @@ -597,10 +598,15 @@ bool ShadeConfigFile::readGroupRecord(SomfyGroup *group) { if(group->getGroupId() == 255) group->clear(); else group->compressLinkedShadeIds(); pref.end(); + if(this->file.position() != startPos + this->header.groupRecordSize) { + Serial.println("Reading to end of group record"); + this->seekChar(CFG_REC_END); + } return true; } bool ShadeConfigFile::readShadeRecord(SomfyShade *shade) { pref.begin("ShadeCodes"); + uint32_t startPos = this->file.position(); shade->setShadeId(this->readUInt8(255)); shade->paired = this->readBool(false); shade->shadeType = static_cast(this->readUInt8(0)); @@ -676,6 +682,10 @@ bool ShadeConfigFile::readShadeRecord(SomfyShade *shade) { } if(shade->proto == radio_proto::GP_Remote) pinMode(shade->gpioMy, OUTPUT); + if(this->file.position() != startPos + this->header.shadeRecordSize) { + Serial.println("Reading to end of shade record"); + this->seekChar(CFG_REC_END); + } return true; } bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) { diff --git a/ConfigSettings.cpp b/ConfigSettings.cpp index a153b3d..06a28a9 100644 --- a/ConfigSettings.cpp +++ b/ConfigSettings.cpp @@ -245,21 +245,25 @@ bool MQTTSettings::begin() { } bool MQTTSettings::toJSON(JsonObject &obj) { obj["enabled"] = this->enabled; + obj["pubDisco"] = this->pubDisco; obj["protocol"] = this->protocol; obj["hostname"] = this->hostname; obj["port"] = this->port; obj["username"] = this->username; obj["password"] = this->password; obj["rootTopic"] = this->rootTopic; + obj["discoTopic"] = this->discoTopic; return true; } bool MQTTSettings::fromJSON(JsonObject &obj) { if(obj.containsKey("enabled")) this->enabled = obj["enabled"]; + if(obj.containsKey("pubDisco")) this->pubDisco = obj["pubDisco"]; this->parseValueString(obj, "protocol", this->protocol, sizeof(this->protocol)); this->parseValueString(obj, "hostname", this->hostname, sizeof(this->hostname)); this->parseValueString(obj, "username", this->username, sizeof(this->username)); this->parseValueString(obj, "password", this->password, sizeof(this->password)); this->parseValueString(obj, "rootTopic", this->rootTopic, sizeof(this->rootTopic)); + this->parseValueString(obj, "discoTopic", this->discoTopic, sizeof(this->discoTopic)); if(obj.containsKey("port")) this->port = obj["port"]; return true; } @@ -273,6 +277,8 @@ bool MQTTSettings::save() { pref.putString("password", this->password); pref.putString("rootTopic", this->rootTopic); pref.putBool("enabled", this->enabled); + pref.putBool("pubDisco", this->pubDisco); + pref.putString("discoTopic", this->discoTopic); pref.end(); return true; } @@ -285,6 +291,8 @@ bool MQTTSettings::load() { pref.getString("password", this->password, sizeof(this->password)); pref.getString("rootTopic", this->rootTopic, sizeof(this->rootTopic)); this->enabled = pref.getBool("enabled", false); + this->pubDisco = pref.getBool("pubDisco", false); + pref.getString("discoTopic", this->discoTopic, sizeof(this->discoTopic)); pref.end(); return true; } diff --git a/ConfigSettings.h b/ConfigSettings.h index d01f252..698cb14 100644 --- a/ConfigSettings.h +++ b/ConfigSettings.h @@ -131,12 +131,14 @@ class SecuritySettings: BaseSettings { class MQTTSettings: BaseSettings { public: bool enabled = false; + bool pubDisco = false; char hostname[65] = "ESPSomfyRTS"; char protocol[10] = "mqtt://"; uint16_t port = 1883; char username[33] = ""; char password[33] = ""; char rootTopic[65] = ""; + char discoTopic[65] = "homeassistant"; bool begin(); bool save(); bool load(); diff --git a/MQTT.cpp b/MQTT.cpp index cd663ce..93b9faf 100644 --- a/MQTT.cpp +++ b/MQTT.cpp @@ -309,6 +309,27 @@ bool MQTTClass::publish(const char *topic, JsonObject &obj, bool retain) { serializeJson(obj, g_content, sizeof(g_content)); return this->publish(topic, g_content, retain); } +bool MQTTClass::publishBuffer(const char *topic, uint8_t *data, uint16_t len, bool retain) { + size_t res; + uint16_t offset = 0; + uint16_t to_write = len; + uint16_t buff_len; + mqttClient.beginPublish(topic, len, retain); + do { + buff_len = to_write; + if(buff_len > 128) buff_len = 128; + res = mqttClient.write(data+offset, buff_len); + offset += buff_len; + to_write -= buff_len; + } while(res == buff_len && to_write > 0); + mqttClient.endPublish(); + return true; +} +bool MQTTClass::publishDisco(const char *topic, JsonObject &obj, bool retain) { + serializeJson(obj, g_content, sizeof(g_content)); + this->publishBuffer(topic, (uint8_t *)g_content, strlen(g_content), retain); + return true; +} 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, retain); diff --git a/MQTT.h b/MQTT.h index 831e3a1..98a86a1 100644 --- a/MQTT.h +++ b/MQTT.h @@ -24,6 +24,8 @@ class MQTTClass { 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 publishBuffer(const char *topic, uint8_t *data, uint16_t len, bool retain = false); + bool publishDisco(const char *topic, JsonObject &obj, 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/Somfy.cpp b/Somfy.cpp index e5b7c82..edf7bf0 100644 --- a/Somfy.cpp +++ b/Somfy.cpp @@ -1299,8 +1299,99 @@ void SomfyShade::publishState() { mqtt.publish(topic, isWindy); } } +void SomfyShade::publishDisco() { + if(!mqtt.connected() || !settings.MQTT.pubDisco) return; + char topic[128] = ""; + DynamicJsonDocument doc(2048); + JsonObject obj = doc.to(); + snprintf(topic, sizeof(topic), "%s/shades/%d", settings.MQTT.rootTopic, this->shadeId); + obj["~"] = topic; + JsonObject dobj = obj.createNestedObject("device"); + dobj["hw_version"] = settings.fwVersion.name; + dobj["name"] = settings.hostname; + dobj["mf"] = "rstrouse"; + JsonArray arrids = dobj.createNestedArray("identifiers"); + //snprintf(topic, sizeof(topic), "mqtt_espsomfyrts_%s_shade%d", settings.serverId, this->shadeId); + snprintf(topic, sizeof(topic), "mqtt_espsomfyrts_%s", settings.serverId); + arrids.add(topic); + //snprintf(topic, sizeof(topic), "ESPSomfy-RTS_%s", settings.serverId); + dobj["via_device"] = topic; + dobj["model"] = "ESPSomfy-RTS MQTT"; + snprintf(topic, sizeof(topic), "%s/status", settings.MQTT.rootTopic); + obj["availability_topic"] = topic; + obj["payload_available"] = "online"; + obj["payload_not_available"] = "offline"; + obj["name"] = this->name; + snprintf(topic, sizeof(topic), "mqtt_%s_shade%d", settings.serverId, this->shadeId); + obj["unique_id"] = topic; + switch(this->shadeType) { + case shade_types::blind: + obj["device_class"] = "blind"; + break; + case shade_types::ldrapery: + case shade_types::rdrapery: + case shade_types::cdrapery: + obj["device_class"] = "curtain"; + break; + case shade_types::garage1: + case shade_types::garage3: + obj["device_class"] = "garage"; + break; + case shade_types::awning: + obj["device_class"] = "awning"; + break; + case shade_types::shutter: + obj["device_class"] = "shutter"; + break; + case shade_types::drycontact: + break; + default: + obj["device_class"] = "shade"; + break; + } + if(this->shadeType != shade_types::drycontact) { + if(this->tiltType != tilt_types::tiltonly) { + obj["command_topic"] = "~/direction/set"; + obj["position_topic"] = "~/position"; + obj["set_position_topic"] = "~/target/set"; + obj["state_topic"] = "~/direction"; + obj["payload_close"] = "1"; + obj["payload_open"] = "-1"; + obj["payload_stop"] = "0"; + } + else { + obj["payload_close"] = nullptr; + obj["payload_open"] = nullptr; + obj["payload_stop"] = nullptr; + } + + if(this->tiltType != tilt_types::none) { + obj["tilt_command_topic"] = "~/tiltTarget/set"; + obj["tilt_status_topic"] = "~/tiltPosition"; + } + obj["position_open"] = 0; + obj["position_closed"] = 100; + obj["state_closing"] = "1"; + obj["state_opening"] = "-1"; + obj["state_stopped"] = "0"; + snprintf(topic, sizeof(topic), "%s/cover/%d/config", settings.MQTT.discoTopic, this->shadeId); + } + else { + obj["payload_on"] = 100; + obj["payload_off"] = 0; + obj["state_off"] = 0; + obj["state_on"] = 100; + obj["state_topic"] = "~/position"; + obj["command_topic"] = "~/target/set"; + snprintf(topic, sizeof(topic), "%s/switch/%d/config", settings.MQTT.discoTopic, this->shadeId); + } + + obj["enabled_by_default"] = true; + mqtt.publishDisco(topic, obj); +} void SomfyShade::publish() { if(mqtt.connected()) { + this->publishDisco(); char topic[32]; snprintf(topic, sizeof(topic), "shades/%u/shadeId", this->shadeId); mqtt.publish(topic, this->shadeId); @@ -2511,6 +2602,7 @@ bool SomfyShade::save() { pref.end(); } this->commit(); + this->publishDisco(); return true; } bool SomfyGroup::save() { somfy.commit(); return true; } diff --git a/Somfy.h b/Somfy.h index 7341468..727f51b 100644 --- a/Somfy.h +++ b/Somfy.h @@ -319,6 +319,7 @@ class SomfyShade : public SomfyRemote { 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); + void publishDisco(); }; class SomfyGroup : public SomfyRemote { protected: diff --git a/SomfyController.ino.esp32.bin b/SomfyController.ino.esp32.bin index 1d50520..97e55f3 100644 Binary files a/SomfyController.ino.esp32.bin and b/SomfyController.ino.esp32.bin differ diff --git a/data/index.html b/data/index.html index a3df51a..c404479 100644 --- a/data/index.html +++ b/data/index.html @@ -245,9 +245,15 @@