mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 13:52:15 +01:00
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:
parent
7396786b8c
commit
e624a4177d
16 changed files with 42 additions and 15 deletions
253
src/outputs/Sockets.cpp
Normal file
253
src/outputs/Sockets.cpp
Normal 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
37
src/outputs/Sockets.h
Normal 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
|
||||
99
src/outputs/SomfyEvents.cpp
Normal file
99
src/outputs/SomfyEvents.cpp
Normal 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
412
src/outputs/SomfyMqtt.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue