diff --git a/include/net/mqttHass.h b/include/net/mqttHass.h index a6da4cb..c3f354b 100644 --- a/include/net/mqttHass.h +++ b/include/net/mqttHass.h @@ -28,6 +28,7 @@ class MqttHass : public Mqtt { MqttHass(WiFiClient&, const char*, const uint16_t, const char*, const char*); bool isDiscoveryDone(); void setDiscoveryDone(bool); + void cleanupOldDiscovery(const Device*); bool publishDevice(const Device*); void subscribeDevice(const Device*); void notifyAvailability(const Device*, const char*); @@ -35,6 +36,7 @@ class MqttHass : public Mqtt { void notifyOffline(const Device*); void notifyPower(const Device*); void notifyPower(const Device*, DeviceStatus); + void notifyCover(const Device*); void notifyBrightness(const Device* device); }; diff --git a/src/RF/device.cpp b/src/RF/device.cpp index 2d2c4a7..71f5afd 100644 --- a/src/RF/device.cpp +++ b/src/RF/device.cpp @@ -387,8 +387,7 @@ int Device::loadFromLittleFS(Device** devices, const unsigned int size) { File f = LittleFS.open(LITTLEFS_CONFIG_FILENAME, "r"); if (!f) { - LOG.print(LITTLEFS_CONFIG_FILENAME); - LOG.println(" - File open failed"); + // Normal on first boot — no devices saved yet return 0; } diff --git a/src/commands/callbacks.cpp b/src/commands/callbacks.cpp index b9a037e..94ae060 100644 --- a/src/commands/callbacks.cpp +++ b/src/commands/callbacks.cpp @@ -455,6 +455,11 @@ bool reloadConfig(const char*) { } g_nb_devices = Device::loadFromLittleFS(g_devices, MAX_YOKIS_DEVICES_NUM); + // Force MQTT re-discovery so new/removed devices are published to Home Assistant + #if MQTT_ENABLED + if (g_mqtt != NULL) g_mqtt->setDiscoveryDone(false); + #endif + // Reattach tickers to devices for (uint8_t i = 0; i < g_nb_devices; i++) { if (g_devices[i] != NULL) { diff --git a/src/main.cpp b/src/main.cpp index 5b36b77..6b98ed0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -243,12 +243,19 @@ void pollForStatus(Device* d) { // Update device status - even if unchanged // Hence, in case of hass restart, status are updated - d->setStatus(ds); if (d->getMode() == DIMMER) { + d->setStatus(ds); if (ds == ON && d->getBrightness() == 0) d->setBrightness(BRIGHTNESS_MAX); g_mqtt->notifyBrightness(d); + } else if (d->getMode() == SHUTTER || d->getMode() == SHUTTER_BUS) { + // For shutters, don't override status from polling. + // The RF status response doesn't reliably distinguish open/closed + // when the shutter is at rest. Keep the status set by the last + // command (OPEN/CLOSE/STOP) and just re-publish it for HA. + g_mqtt->notifyCover(d); } else { + d->setStatus(ds); g_mqtt->notifyPower(d); } } else { @@ -328,9 +335,21 @@ void mqttCallback(char* topic, uint8_t* payload, unsigned int length) { IrqManager::irqType = E2BP; g_bp->setDevice(d); switch (d->getMode()) { - case ON_OFF: case SHUTTER: case SHUTTER_BUS: + if (strcmp(mPayload, "OPEN") == 0) { + g_bp->on(); + d->setStatus(ON); + } else if (strcmp(mPayload, "CLOSE") == 0) { + g_bp->off(); + d->setStatus(OFF); + } else if (strcmp(mPayload, "STOP") == 0) { + g_bp->pauseShutter(); + d->setStatus(PAUSE_SHUTTER); + } + g_mqtt->notifyCover(d); + break; + case ON_OFF: case NO_RCPT: if (strcmp(mPayload, "ON") == 0) { g_bp->on(); diff --git a/src/net/mqttHass.cpp b/src/net/mqttHass.cpp index b85bf4e..951af0c 100644 --- a/src/net/mqttHass.cpp +++ b/src/net/mqttHass.cpp @@ -61,6 +61,34 @@ char* MqttHass::newMessageJson(const Device* device, char* buf) { "}", device->getName(), device->getName(), device->getName(), device->getName()); + } else if (device->getMode() == SHUTTER || device->getMode() == SHUTTER_BUS) { + sprintf(buf, + "{" + "\"name\":\"Shutter\"," + "\"device_class\":\"shutter\"," + "\"cmd_t\":\"~cmnd/COVER\"," + "\"pl_open\":\"OPEN\"," + "\"pl_cls\":\"CLOSE\"," + "\"pl_stop\":\"STOP\"," + "\"state_topic\":\"~tele/STATE\"," + "\"state_open\":\"open\"," + "\"state_closed\":\"closed\"," + "\"state_stopped\":\"stopped\"," + "\"val_tpl\":\"{{value_json.STATE}}\"," + "\"avty_t\":\"~tele/LWT\"," + "\"pl_avail\":\"Online\"," + "\"pl_not_avail\":\"Offline\"," + "\"uniq_id\":\"esp-%s\"," + "\"device\":{" + "\"name\":\"%s\"," + "\"identifiers\":[\"yokis-%s\"]," + "\"model\":\"MVR500ERX\"," + "\"mf\":\"Yokis\"" + "}," + "\"~\":\"%s/\"" + "}", + device->getName(), device->getName(), + device->getName(), device->getName()); } else { sprintf(buf, "{" @@ -92,16 +120,50 @@ char* MqttHass::newMessageJson(const Device* device, char* buf) { } char* MqttHass::newPublishTopic(const Device* device, char* buf) { - sprintf(buf, "%s/light/%s/config", HASS_PREFIX, device->getName()); + const char* component; + if (device->getMode() == DIMMER) { + component = "light"; + } else if (device->getMode() == SHUTTER || device->getMode() == SHUTTER_BUS) { + component = "cover"; + } else { + component = "switch"; + } + sprintf(buf, "%s/%s/%s/config", HASS_PREFIX, component, device->getName()); return buf; } +// Remove stale discovery entries from old component types +// e.g. a switch that was previously published as a light +void MqttHass::cleanupOldDiscovery(const Device* device) { + char topic[128]; + const char* components[] = {"light", "switch", "cover"}; + const char* current; + + if (device->getMode() == DIMMER) { + current = "light"; + } else if (device->getMode() == SHUTTER || device->getMode() == SHUTTER_BUS) { + current = "cover"; + } else { + current = "switch"; + } + + for (uint8_t i = 0; i < 3; i++) { + if (strcmp(components[i], current) != 0) { + sprintf(topic, "%s/%s/%s/config", HASS_PREFIX, components[i], device->getName()); + this->publish(topic, "", true); + } + } +} + // Publish device to MQTT for HASS discovery bool MqttHass::publishDevice(const Device* device) { bool ret; char topic[128]; char payload[MQTT_MAX_PACKET_SIZE]; + // Clean up old retained entries under wrong component types + cleanupOldDiscovery(device); + newPublishTopic(device, topic); newMessageJson(device, payload); @@ -150,6 +212,19 @@ void MqttHass::notifyPower(const Device* device, DeviceStatus ds) { publish(buf, bufPayload, false); } +void MqttHass::notifyCover(const Device* device) { + char buf[64]; + char bufPayload[64]; + + sprintf(buf, "%s/tele/STATE", device->getName()); + const char* state; + if (device->getStatus() == ON) state = "open"; + else if (device->getStatus() == PAUSE_SHUTTER) state = "stopped"; + else state = "closed"; + sprintf(bufPayload, "{\"STATE\":\"%s\"}", state); + publish(buf, bufPayload, false); +} + void MqttHass::notifyBrightness(const Device* device) { char buf[64]; char bufPayload[64]; @@ -165,8 +240,8 @@ void MqttHass::notifyBrightness(const Device* device) { void MqttHass::subscribeDevice(const Device* device) { char buf[64]; - if (device->getMode() == ON_OFF || device->getMode() == NO_RCPT) { - sprintf(buf, "%s/cmnd/POWER", device->getName()); + if (device->getMode() == SHUTTER || device->getMode() == SHUTTER_BUS) { + sprintf(buf, "%s/cmnd/COVER", device->getName()); this->subscribe(buf); } else if (device->getMode() == DIMMER) { sprintf(buf, "%s/cmnd/POWER", device->getName()); @@ -175,6 +250,9 @@ void MqttHass::subscribeDevice(const Device* device) { this->subscribe(buf); sprintf(buf, "%s/cmnd/FX", device->getName()); this->subscribe(buf); + } else { + sprintf(buf, "%s/cmnd/POWER", device->getName()); + this->subscribe(buf); } } #endif