refactor: move the transports into entryPoints and outputs

The HTTP servers and MQTT command handling go to src/entryPoints/, the
socket emitter and the MQTT and socket output of shades, groups and rooms
to src/outputs/. GLOSSARY.md names what they become once split:
WebUiRoutes, ApiRoutes and MqttCommands in, MqttPublisher and
SocketEmitter out.

Only files and include paths change. esp32devdbg flash goes from
1,546,405 to 1,546,257 bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-15 19:41:49 +02:00
parent 7396786b8c
commit e624a4177d
16 changed files with 42 additions and 15 deletions

253
src/outputs/Sockets.cpp Normal file
View file

@ -0,0 +1,253 @@
#include <Arduino.h>
#include <ArduinoJson.h>
#include <esp_task_wdt.h>
#include "esp_log.h"
#include "Sockets.h"
#include "ConfigSettings.h"
#include "Somfy.h"
#include "ESPNetwork.h"
#include "GitOTA.h"
#include "Timing.h"
static const char *TAG = "Sockets";
extern ConfigSettings settings;
extern ESPNetwork net;
extern SomfyShadeController somfy;
extern SocketEmitter sockEmit;
extern GitUpdater git;
AsyncWebServer wsServer(8080);
AsyncWebSocket ws("/");
#define MAX_WS_CLIENTS 5
struct ws_slot_t {
uint32_t asyncId; // 0 when the slot is free.
// When the client connected and last pinged, so the logs show how long it
// stayed and how far apart its pings arrived.
uint32_t connectedAt;
uint32_t lastPing;
};
static ws_slot_t slots[MAX_WS_CLIENTS] = {};
static uint8_t mapClientId(uint32_t asyncId) {
for(uint8_t i = 0; i < MAX_WS_CLIENTS; i++)
if(slots[i].asyncId == asyncId) return i;
return 255;
}
static uint32_t getAsyncId(uint8_t slot) {
if(slot < MAX_WS_CLIENTS) return slots[slot].asyncId;
return 0;
}
static uint8_t addClient(uint32_t asyncId) {
for(uint8_t i = 0; i < MAX_WS_CLIENTS; i++) {
if(slots[i].asyncId == 0) { slots[i].asyncId = asyncId; return i; }
}
return 255;
}
static void removeClient(uint32_t asyncId) {
for(uint8_t i = 0; i < MAX_WS_CLIENTS; i++)
if(slots[i].asyncId == asyncId) slots[i].asyncId = 0;
}
#define MAX_SOCK_RESPONSE 2048
static char g_response[MAX_SOCK_RESPONSE];
bool room_t::isJoined(uint8_t num) {
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
if(this->clients[i] == num) return true;
}
return false;
}
bool room_t::join(uint8_t num) {
if(this->isJoined(num)) return true;
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
if(this->clients[i] == 255) {
this->clients[i] = num;
return true;
}
}
return false;
}
bool room_t::leave(uint8_t num) {
if(!this->isJoined(num)) return false;
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
if(this->clients[i] == num) this->clients[i] = 255;
}
return true;
}
void room_t::clear() {
memset(this->clients, 255, sizeof(this->clients));
}
uint8_t room_t::activeClients() {
uint8_t n = 0;
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
if(this->clients[i] != 255) n++;
}
return n;
}
/*********************************************************************
* SocketEmitter class members
********************************************************************/
void SocketEmitter::startup() {
}
void SocketEmitter::begin() {
ws.onEvent(SocketEmitter::wsEvent);
wsServer.addHandler(&ws);
wsServer.begin();
ESP_LOGI(TAG, "Socket Server Started...");
}
void SocketEmitter::loop() {
ws.cleanupClients();
this->initClients();
}
JsonSockEvent *SocketEmitter::beginEmit(const char *evt) {
this->json.beginEvent(&ws, evt, g_response, sizeof(g_response));
return &this->json;
}
void SocketEmitter::endEmit(uint8_t num) {
if(num == 255) {
this->json.endEvent(0);
} else {
uint32_t asyncId = getAsyncId(num);
this->json.endEvent(asyncId);
}
esp_task_wdt_reset();
}
void SocketEmitter::endEmitRoom(uint8_t room) {
if(room < SOCK_MAX_ROOMS) {
room_t *r = &this->rooms[room];
for(uint8_t i = 0; i < sizeof(r->clients); i++) {
if(r->clients[i] != 255) {
uint32_t asyncId = getAsyncId(r->clients[i]);
if(asyncId != 0) this->json.endEvent(asyncId);
}
}
}
}
uint8_t SocketEmitter::activeClients(uint8_t room) {
if(room < SOCK_MAX_ROOMS) return this->rooms[room].activeClients();
return 0;
}
void SocketEmitter::initClients() {
for(uint8_t i = 0; i < sizeof(this->newClients); i++) {
uint8_t slot = this->newClients[i];
if(slot != 255) {
uint32_t asyncId = getAsyncId(slot);
if(asyncId != 0 && ws.hasClient(asyncId)) {
ESP_LOGD(TAG, "Initializing Socket Client %u (asyncId=%lu)", slot, asyncId);
esp_task_wdt_reset();
ElapsedTimer total, step;
settings.emitSockets(slot);
logElapsed(TAG, "Socket init settings", step.lap());
if(!ws.hasClient(asyncId)) { this->newClients[i] = 255; continue; }
somfy.emitState(slot);
logElapsed(TAG, "Socket init somfy state", step.lap());
if(!ws.hasClient(asyncId)) { this->newClients[i] = 255; continue; }
git.emitUpdateCheck(slot);
logElapsed(TAG, "Socket init update check", step.lap());
if(!ws.hasClient(asyncId)) { this->newClients[i] = 255; continue; }
net.emitSockets(slot);
logElapsed(TAG, "Socket init network", step.lap());
logElapsed(TAG, "Socket init total", total.us());
esp_task_wdt_reset();
}
this->newClients[i] = 255;
}
}
}
void SocketEmitter::delayInit(uint8_t num) {
for(uint8_t i=0; i < sizeof(this->newClients); i++) {
if(this->newClients[i] == num) break;
else if(this->newClients[i] == 255) {
this->newClients[i] = num;
break;
}
}
}
void SocketEmitter::end() {
ws.closeAll();
wsServer.end();
for(uint8_t i = 0; i < SOCK_MAX_ROOMS; i++)
this->rooms[i].clear();
memset(slots, 0, sizeof(slots));
}
void SocketEmitter::disconnect() {
ws.closeAll();
memset(slots, 0, sizeof(slots));
}
void SocketEmitter::wsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
uint32_t asyncId = client->id();
switch(type) {
case WS_EVT_CONNECT:
{
uint8_t slot = addClient(asyncId);
if(slot == 255) {
ESP_LOGE(TAG, "Socket: No free client slots, closing %lu", asyncId);
client->close();
return;
}
IPAddress ip = client->remoteIP();
ESP_LOGI(TAG, "Socket [%lu] Connected from %d.%d.%d.%d (slot %u)", asyncId, ip[0], ip[1], ip[2], ip[3], slot);
slots[slot].connectedAt = millis();
slots[slot].lastPing = 0;
client->text("Connected");
client->setCloseClientOnQueueFull(false);
sockEmit.delayInit(slot);
}
break;
case WS_EVT_DISCONNECT:
{
uint8_t slot = mapClientId(asyncId);
if(slot != 255) {
ESP_LOGI(TAG, "Socket [%lu] Disconnected (slot %u) after %lums, last ping %lums ago", asyncId, slot,
(unsigned long)(millis() - slots[slot].connectedAt), slots[slot].lastPing ? (unsigned long)(millis() - slots[slot].lastPing) : 0UL);
for(uint8_t i = 0; i < SOCK_MAX_ROOMS; i++)
sockEmit.rooms[i].leave(slot);
}
removeClient(asyncId);
}
break;
case WS_EVT_DATA:
{
AwsFrameInfo *info = (AwsFrameInfo*)arg;
if(info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
uint8_t slot = mapClientId(asyncId);
data[len] = 0;
if(strncmp((char*)data, "join:", 5) == 0) {
uint8_t roomNum = atoi((char*)&data[5]);
ESP_LOGD(TAG, "Client %u joining room %u", slot, roomNum);
if(roomNum < SOCK_MAX_ROOMS && slot != 255) sockEmit.rooms[roomNum].join(slot);
}
else if(strncmp((char*)data, "leave:", 6) == 0) {
uint8_t roomNum = atoi((char*)&data[6]);
ESP_LOGD(TAG, "Client %u leaving room %u", slot, roomNum);
if(roomNum < SOCK_MAX_ROOMS && slot != 255) sockEmit.rooms[roomNum].leave(slot);
}
else {
ESP_LOGD(TAG, "Socket [%lu] text: %s", asyncId, data);
}
}
}
break;
case WS_EVT_ERROR:
ESP_LOGE(TAG, "Socket [%lu] Error", asyncId);
break;
case WS_EVT_PING:
{
// Home Assistant only pings after 20s without a message from us, so
// the gap depends on our own traffic and says nothing about the link.
// It is kept for the disconnect line.
uint8_t slot = mapClientId(asyncId);
if(slot != 255) {
uint32_t now = millis();
if(slots[slot].lastPing) ESP_LOGD(TAG, "Socket [%lu] ping, %lums since the last", asyncId, (unsigned long)(now - slots[slot].lastPing));
slots[slot].lastPing = now;
}
}
break;
case WS_EVT_PONG:
break;
}
}

37
src/outputs/Sockets.h Normal file
View file

@ -0,0 +1,37 @@
#include <ESPAsyncWebServer.h>
#include "WResp.h"
#ifndef sockets_h
#define sockets_h
#define SOCK_MAX_ROOMS 1
#define ROOM_EMIT_FRAME 0
struct room_t {
uint8_t clients[5] = {255, 255, 255, 255, 255};
uint8_t activeClients();
bool isJoined(uint8_t num);
bool join(uint8_t num);
bool leave(uint8_t num);
void clear();
};
class SocketEmitter {
protected:
uint8_t newclients = 0;
uint8_t newClients[5] = {255,255,255,255,255};
void delayInit(uint8_t num);
public:
JsonSockEvent json;
room_t rooms[SOCK_MAX_ROOMS];
uint8_t activeClients(uint8_t room);
void initClients();
void startup();
void begin();
void loop();
void end();
void disconnect();
JsonSockEvent * beginEmit(const char *evt);
void endEmit(uint8_t num = 255);
void endEmitRoom(uint8_t num);
static void wsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
};
#endif

View file

@ -0,0 +1,99 @@
#include <esp_task_wdt.h>
#include "Somfy.h"
#include "Sockets.h"
#include "entryPoints/MQTT.h"
extern SomfyShadeController somfy;
extern SocketEmitter sockEmit;
extern MQTTClass mqtt;
void SomfyShade::emitState(const char *evt) { this->emitState(255, evt); }
void SomfyShade::emitState(uint8_t num, const char *evt) {
JsonSockEvent *json = sockEmit.beginEmit(evt);
json->beginObject();
json->addElem("shadeId", this->shadeId);
json->addElem("type", static_cast<uint8_t>(this->shadeType));
json->addElem("remoteAddress", (uint32_t)this->getRemoteAddress());
json->addElem("name", this->name);
json->addElem("direction", this->direction);
json->addElem("position", this->transformPosition(this->currentPos));
json->addElem("target", this->transformPosition(this->target));
json->addElem("myPos", this->transformPosition(this->myPos));
json->addElem("tiltType", static_cast<uint8_t>(this->tiltType));
json->addElem("flipCommands", this->flipCommands);
json->addElem("flipPosition", this->flipPosition);
json->addElem("flags", this->flags);
json->addElem("sunSensor", this->hasSunSensor());
json->addElem("light", this->hasLight());
json->addElem("sortOrder", this->sortOrder);
if(this->tiltType != tilt_types::none) {
json->addElem("tiltDirection", this->tiltDirection);
json->addElem("tiltTarget", this->transformPosition(this->tiltTarget));
json->addElem("tiltPosition", this->transformPosition(this->currentTiltPos));
json->addElem("myTiltPos", this->transformPosition(this->myTiltPos));
}
json->endObject();
sockEmit.endEmit(num);
}
void SomfyShade::emitCommand(somfy_commands cmd, const char *source, uint32_t sourceAddress, const char *evt) { this->emitCommand(255, cmd, source, sourceAddress, evt); }
void SomfyShade::emitCommand(uint8_t num, somfy_commands cmd, const char *source, uint32_t sourceAddress, const char *evt) {
JsonSockEvent *json = sockEmit.beginEmit(evt);
json->beginObject();
json->addElem("shadeId", this->shadeId);
json->addElem("remoteAddress", (uint32_t)this->getRemoteAddress());
json->addElem("cmd", translateSomfyCommand(cmd).c_str());
json->addElem("source", source);
json->addElem("rcode", (uint32_t)this->lastRollingCode);
json->addElem("sourceAddress", (uint32_t)sourceAddress);
json->endObject();
sockEmit.endEmit(num);
if(mqtt.connected()) {
this->publish("cmdSource", source);
this->publish("cmdAddress", sourceAddress);
this->publish("cmd", translateSomfyCommand(cmd).c_str());
}
}
void SomfyRoom::emitState(const char *evt) { this->emitState(255, evt); }
void SomfyRoom::emitState(uint8_t num, const char *evt) {
JsonSockEvent *json = sockEmit.beginEmit(evt);
json->beginObject();
json->addElem("roomId", this->roomId);
json->addElem("name", this->name);
json->addElem("sortOrder", this->sortOrder);
json->endObject();
sockEmit.endEmit(num);
this->publish();
}
void SomfyGroup::emitState(const char *evt) { this->emitState(255, evt); }
void SomfyGroup::emitState(uint8_t num, const char *evt) {
uint8_t flags = 0;
JsonSockEvent *json = sockEmit.beginEmit(evt);
json->beginObject();
json->addElem("groupId", this->groupId);
json->addElem("remoteAddress", (uint32_t)this->getRemoteAddress());
json->addElem("name", this->name);
json->addElem("sunSensor", this->hasSunSensor());
json->beginArray("shades");
for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) {
if(this->linkedShades[i] != 255 && this->linkedShades[i] != 0) {
SomfyShade *shade = somfy.getShadeById(this->linkedShades[i]);
if(shade) {
json->addElem(this->linkedShades[i]);
flags |= shade->flags;
}
}
}
json->endArray();
json->addElem("flags", flags);
json->endObject();
sockEmit.endEmit(num);
this->publish();
}
void SomfyShadeController::emitState(uint8_t num) {
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
SomfyShade *shade = &this->shades[i];
if(shade->getShadeId() == 255) continue;
esp_task_wdt_reset();
shade->emitState(num);
}
}

412
src/outputs/SomfyMqtt.cpp Normal file
View file

@ -0,0 +1,412 @@
#include "Utils.h"
#include "ConfigSettings.h"
#include "Somfy.h"
#include "Sockets.h"
#include "entryPoints/MQTT.h"
extern SomfyShadeController somfy;
extern SocketEmitter sockEmit;
extern ConfigSettings settings;
extern MQTTClass mqtt;
void SomfyRoom::publish() {
if(mqtt.connected()) {
char topic[64];
sprintf(topic, "rooms/%d/roomId", this->roomId);
mqtt.publish(topic, this->roomId, true);
sprintf(topic, "rooms/%d/name", this->roomId);
mqtt.publish(topic, this->name, true);
sprintf(topic, "rooms/%d/sortOrder", this->roomId);
mqtt.publish(topic, this->sortOrder, true);
}
}
void SomfyRoom::unpublish() {
if(mqtt.connected()) {
char topic[64];
sprintf(topic, "rooms/%d/roomId", this->roomId);
mqtt.unpublish(topic);
sprintf(topic, "rooms/%d/name", this->roomId);
mqtt.unpublish(topic);
sprintf(topic, "rooms/%d/sortOrder", this->roomId);
mqtt.unpublish(topic);
}
}
void SomfyShade::publishState() {
if(mqtt.connected()) {
this->publish("position", (uint8_t)50, true);
this->publish("direction", (int8_t)0, true);
this->publish("target", (uint8_t)50, true);
this->publish("lastRollingCode", this->lastRollingCode);
this->publish("mypos", this->transformPosition(this->myPos), true);
this->publish("myTiltPos", this->transformPosition(this->myTiltPos), true);
if(this->tiltType != tilt_types::none) {
this->publish("tiltDirection", this->tiltDirection, true);
this->publish("tiltPosition", this->transformPosition(this->currentTiltPos), true);
this->publish("tiltTarget", this->transformPosition(this->tiltTarget), true);
}
const uint8_t sunFlag = !!(this->flags & static_cast<uint8_t>(somfy_flags_t::SunFlag));
const uint8_t isSunny = !!(this->flags & static_cast<uint8_t>(somfy_flags_t::Sunny));
const uint8_t isWindy = !!(this->flags & static_cast<uint8_t>(somfy_flags_t::Windy));
if(this->hasSunSensor()) {
this->publish("sunFlag", sunFlag);
this->publish("sunny", isSunny);
}
this->publish("windy", isWindy);
}
}
void SomfyShade::publishDisco() {
if(!mqtt.connected() || !settings.MQTT.pubDisco) return;
char topic[128] = "";
JsonDocument doc;
JsonObject obj = doc.to<JsonObject>();
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["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";
obj["payload_close"] = this->flipPosition ? "-1" : "1";
obj["payload_open"] = this->flipPosition ? "1" : "-1";
obj["position_open"] = this->flipPosition ? 100 : 0;
obj["position_closed"] = this->flipPosition ? 0 : 100;
obj["state_closing"] = this->flipPosition ? "-1" : "1";
obj["state_opening"] = this->flipPosition ? "1" : "-1";
break;
case shade_types::lgate:
case shade_types::cgate:
case shade_types::rgate:
case shade_types::lgate1:
case shade_types::cgate1:
case shade_types::rgate1:
case shade_types::ldrapery:
case shade_types::rdrapery:
case shade_types::cdrapery:
obj["device_class"] = "curtain";
obj["payload_close"] = this->flipPosition ? "-1" : "1";
obj["payload_open"] = this->flipPosition ? "1" : "-1";
obj["position_open"] = this->flipPosition ? 100 : 0;
obj["position_closed"] = this->flipPosition ? 0 : 100;
obj["state_closing"] = this->flipPosition ? "-1" : "1";
obj["state_opening"] = this->flipPosition ? "1" : "-1";
break;
case shade_types::garage1:
case shade_types::garage3:
obj["device_class"] = "garage";
obj["payload_close"] = this->flipPosition ? "-1" : "1";
obj["payload_open"] = this->flipPosition ? "1" : "-1";
obj["position_open"] = this->flipPosition ? 100 : 0;
obj["position_closed"] = this->flipPosition ? 0 : 100;
obj["state_closing"] = this->flipPosition ? "-1" : "1";
obj["state_opening"] = this->flipPosition ? "1" : "-1";
break;
case shade_types::awning:
obj["device_class"] = "awning";
obj["payload_close"] = this->flipPosition ? "1" : "-1";
obj["payload_open"] = this->flipPosition ? "-1" : "1";
obj["position_open"] = this->flipPosition ? 0 : 100;
obj["position_closed"] = this->flipPosition ? 100 : 0;
obj["state_closing"] = this->flipPosition ? "1" : "-1";
obj["state_opening"] = this->flipPosition ? "-1" : "1";
break;
case shade_types::shutter:
obj["device_class"] = "shutter";
obj["payload_close"] = this->flipPosition ? "-1" : "1";
obj["payload_open"] = this->flipPosition ? "1" : "-1";
obj["position_open"] = this->flipPosition ? 100 : 0;
obj["position_closed"] = this->flipPosition ? 0 : 100;
obj["state_closing"] = this->flipPosition ? "-1" : "1";
obj["state_opening"] = this->flipPosition ? "1" : "-1";
break;
case shade_types::drycontact2:
case shade_types::drycontact:
break;
default:
obj["device_class"] = "shade";
obj["payload_close"] = this->flipPosition ? "-1" : "1";
obj["payload_open"] = this->flipPosition ? "1" : "-1";
obj["position_open"] = this->flipPosition ? 100 : 0;
obj["position_closed"] = this->flipPosition ? 0 : 100;
obj["state_closing"] = this->flipPosition ? "-1" : "1";
obj["state_opening"] = this->flipPosition ? "1" : "-1";
break;
}
if(this->shadeType != shade_types::drycontact && this->shadeType != shade_types::drycontact2) {
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_stop"] = "0";
obj["state_stopped"] = "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";
}
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, true);
}
void SomfyShade::unpublishDisco() {
if(!mqtt.connected() || !settings.MQTT.pubDisco) return;
char topic[128] = "";
if(this->shadeType != shade_types::drycontact && this->shadeType != shade_types::drycontact2) {
snprintf(topic, sizeof(topic), "%s/cover/%d/config", settings.MQTT.discoTopic, this->shadeId);
}
else
snprintf(topic, sizeof(topic), "%s/switch/%d/config", settings.MQTT.discoTopic, this->shadeId);
mqtt.unpublish(topic);
}
void SomfyShade::publish() {
if(mqtt.connected()) {
this->publish("shadeId", this->shadeId, true);
this->publish("name", this->name, true);
this->publish("remoteAddress", this->getRemoteAddress(), true);
this->publish("shadeType", static_cast<uint8_t>(this->shadeType), true);
this->publish("tiltType", static_cast<uint8_t>(this->tiltType), true);
this->publish("flags", this->flags, true);
this->publish("flipCommands", this->flipCommands, true);
this->publish("flipPosition", this->flipPosition, true);
this->publishState();
this->publishDisco();
sockEmit.loop(); // Keep our socket alive.
}
}
void SomfyGroup::publishState() {
if(mqtt.connected()) {
this->publish("direction", this->direction, true);
this->publish("lastRollingCode", this->lastRollingCode, true);
this->publish("flipCommands", this->flipCommands, true);
const uint8_t sunFlag = !!(this->flags & static_cast<uint8_t>(somfy_flags_t::SunFlag));
const uint8_t isSunny = !!(this->flags & static_cast<uint8_t>(somfy_flags_t::Sunny));
const uint8_t isWindy = !!(this->flags & static_cast<uint8_t>(somfy_flags_t::Windy));
this->publish("sunFlag", sunFlag);
this->publish("sunny", isSunny);
this->publish("windy", isWindy);
}
}
void SomfyGroup::publish() {
if(mqtt.connected()) {
this->publish("groupId", this->groupId, true);
this->publish("name", this->name, true);
this->publish("remoteAddress", this->getRemoteAddress(), true);
this->publish("groupType", static_cast<uint8_t>(this->groupType), true);
this->publish("flags", this->flags, true);
this->publish("sunSensor", this->hasSunSensor(), true);
this->publishState();
}
}
char mqttTopicBuffer[55];
void SomfyGroup::unpublish() { SomfyGroup::unpublish(this->groupId); }
void SomfyShade::unpublish() { SomfyShade::unpublish(this->shadeId); }
void SomfyShade::unpublish(uint8_t id) {
if(mqtt.connected()) {
SomfyShade::unpublish(id, "shadeId");
SomfyShade::unpublish(id, "name");
SomfyShade::unpublish(id, "remoteAddress");
SomfyShade::unpublish(id, "shadeType");
SomfyShade::unpublish(id, "tiltType");
SomfyShade::unpublish(id, "flags");
SomfyShade::unpublish(id, "flipCommands");
SomfyShade::unpublish(id, "flipPosition");
SomfyShade::unpublish(id, "position");
SomfyShade::unpublish(id, "direction");
SomfyShade::unpublish(id, "target");
SomfyShade::unpublish(id, "lastRollingCode");
SomfyShade::unpublish(id, "mypos");
SomfyShade::unpublish(id, "myTiltPos");
SomfyShade::unpublish(id, "tiltDirection");
SomfyShade::unpublish(id, "tiltPosition");
SomfyShade::unpublish(id, "tiltTarget");
SomfyShade::unpublish(id, "windy");
SomfyShade::unpublish(id, "sunny");
if(settings.MQTT.pubDisco) {
char topic[128] = "";
snprintf(topic, sizeof(topic), "%s/cover/%d/config", settings.MQTT.discoTopic, id);
mqtt.unpublish(topic);
snprintf(topic, sizeof(topic), "%s/switch/%d/config", settings.MQTT.discoTopic, id);
mqtt.unpublish(topic);
}
}
}
void SomfyGroup::unpublish(uint8_t id) {
if(mqtt.connected()) {
SomfyGroup::unpublish(id, "groupId");
SomfyGroup::unpublish(id, "name");
SomfyGroup::unpublish(id, "remoteAddress");
SomfyGroup::unpublish(id, "groupType");
SomfyGroup::unpublish(id, "direction");
SomfyGroup::unpublish(id, "lastRollingCode");
SomfyGroup::unpublish(id, "flags");
SomfyGroup::unpublish(id, "SunSensor");
SomfyGroup::unpublish(id, "flipCommands");
}
}
void SomfyGroup::unpublish(uint8_t id, const char *topic) {
if(mqtt.connected()) {
snprintf(mqttTopicBuffer, sizeof(mqttTopicBuffer), "groups/%u/%s", id, topic);
mqtt.unpublish(mqttTopicBuffer);
}
}
void SomfyShade::unpublish(uint8_t id, const char *topic) {
if(mqtt.connected()) {
snprintf(mqttTopicBuffer, sizeof(mqttTopicBuffer), "shades/%u/%s", id, topic);
mqtt.unpublish(mqttTopicBuffer);
}
}
bool SomfyShade::publish(const char *topic, int8_t val, bool retain) {
if(mqtt.connected()) {
snprintf(mqttTopicBuffer, sizeof(mqttTopicBuffer), "shades/%u/%s", this->shadeId, topic);
mqtt.publish(mqttTopicBuffer, val, retain);
return true;
}
return false;
}
bool SomfyShade::publish(const char *topic, const char *val, bool retain) {
if(mqtt.connected()) {
snprintf(mqttTopicBuffer, sizeof(mqttTopicBuffer), "shades/%u/%s", this->shadeId, topic);
mqtt.publish(mqttTopicBuffer, val, retain);
return true;
}
return false;
}
bool SomfyShade::publish(const char *topic, uint8_t val, bool retain) {
if(mqtt.connected()) {
snprintf(mqttTopicBuffer, sizeof(mqttTopicBuffer), "shades/%u/%s", this->shadeId, topic);
mqtt.publish(mqttTopicBuffer, val, retain);
return true;
}
return false;
}
bool SomfyShade::publish(const char *topic, uint32_t val, bool retain) {
if(mqtt.connected()) {
snprintf(mqttTopicBuffer, sizeof(mqttTopicBuffer), "shades/%u/%s", this->shadeId, topic);
mqtt.publish(mqttTopicBuffer, val, retain);
return true;
}
return false;
}
bool SomfyShade::publish(const char *topic, uint16_t val, bool retain) {
if(mqtt.connected()) {
snprintf(mqttTopicBuffer, sizeof(mqttTopicBuffer), "shades/%u/%s", this->shadeId, topic);
mqtt.publish(mqttTopicBuffer, val, retain);
return true;
}
return false;
}
bool SomfyShade::publish(const char *topic, bool val, bool retain) {
if(mqtt.connected()) {
snprintf(mqttTopicBuffer, sizeof(mqttTopicBuffer), "shades/%u/%s", this->shadeId, topic);
mqtt.publish(mqttTopicBuffer, val, retain);
return true;
}
return false;
}
bool SomfyGroup::publish(const char *topic, int8_t val, bool retain) {
if(mqtt.connected()) {
snprintf(mqttTopicBuffer, sizeof(mqttTopicBuffer), "groups/%u/%s", this->groupId, topic);
mqtt.publish(mqttTopicBuffer, val, retain);
return true;
}
return false;
}
bool SomfyGroup::publish(const char *topic, uint8_t val, bool retain) {
if(mqtt.connected()) {
snprintf(mqttTopicBuffer, sizeof(mqttTopicBuffer), "groups/%u/%s", this->groupId, topic);
mqtt.publish(mqttTopicBuffer, val, retain);
return true;
}
return false;
}
bool SomfyGroup::publish(const char *topic, uint32_t val, bool retain) {
if(mqtt.connected()) {
snprintf(mqttTopicBuffer, sizeof(mqttTopicBuffer), "groups/%u/%s", this->groupId, topic);
mqtt.publish(mqttTopicBuffer, val, retain);
return true;
}
return false;
}
bool SomfyGroup::publish(const char *topic, uint16_t val, bool retain) {
if(mqtt.connected()) {
snprintf(mqttTopicBuffer, sizeof(mqttTopicBuffer), "groups/%u/%s", this->groupId, topic);
mqtt.publish(mqttTopicBuffer, val, retain);
return true;
}
return false;
}
bool SomfyGroup::publish(const char *topic, bool val, bool retain) {
if(mqtt.connected()) {
snprintf(mqttTopicBuffer, sizeof(mqttTopicBuffer), "groups/%u/%s", this->groupId, topic);
mqtt.publish(mqttTopicBuffer, val, retain);
return true;
}
return false;
}
void SomfyShadeController::publish() {
this->updateGroupFlags();
char arrIds[128] = "[";
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
SomfyShade *shade = &this->shades[i];
if(shade->getShadeId() == 255) continue;
if(strlen(arrIds) > 1) strcat(arrIds, ",");
itoa(shade->getShadeId(), &arrIds[strlen(arrIds)], 10);
shade->publish();
}
strcat(arrIds, "]");
mqtt.publish("shades", arrIds, true);
for(uint8_t i = 1; i <= SOMFY_MAX_SHADES; i++) {
SomfyShade *shade = this->getShadeById(i);
if(shade) continue;
else {
SomfyShade::unpublish(i);
}
}
strcpy(arrIds, "[");
for(uint8_t i = 0; i < SOMFY_MAX_GROUPS; i++) {
SomfyGroup *group = &this->groups[i];
if(group->getGroupId() == 255) continue;
if(strlen(arrIds) > 1) strcat(arrIds, ",");
itoa(group->getGroupId(), &arrIds[strlen(arrIds)], 10);
group->publish();
}
strcat(arrIds, "]");
mqtt.publish("groups", arrIds, true);
for(uint8_t i = 1; i <= SOMFY_MAX_GROUPS; i++) {
SomfyGroup *group = this->getGroupById(i);
if(group) continue;
else SomfyGroup::unpublish(i);
}
}