ESPSomfy-RTS/src/entryPoints/MqttCommands.cpp
cjkas fdf1a47ff8 refactor: split the transports into the modules GLOSSARY.md names
Code moves unchanged; only files and names change.

- entryPoints/WebUiRoutes.cpp: registerWebUiRoutes(), the port 80 routes
  and the UI's files, with the helpers only they use.
- entryPoints/ApiRoutes.cpp: registerApiRoutes(), the port 8081 routes.
- entryPoints/Web.cpp keeps the servers' lifecycle, auth and the handlers
  both route sets call; entryPoints/WebRoutes.h is what the three share.
- entryPoints/MQTT.* -> MqttConnection.*, class MQTTClass -> MqttConnection.
- outputs/SomfyMqtt.cpp -> outputs/MqttPublisher.cpp.
- outputs/Sockets.* -> outputs/SocketEmitter.*.

The subscribe and unsubscribe lists (the latter repeated
groups/+/direction/set) become MqttCommands' MQTT_COMMAND_TOPICS, and a
native test checks the parser takes every topic in it. GLOSSARY.md gains
MqttConnection and Change fan-out.

328 native tests. Flash: esp32devdbg +3,112 bytes (the routes are a second
ArduinoJson file), esp32dev -2,336 bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 14:40:22 +02:00

143 lines
4.2 KiB
C++

#include <stdlib.h>
#include <string.h>
#include "MqttCommands.h"
const char *const MQTT_COMMAND_TOPICS[MQTT_COMMAND_TOPIC_COUNT] = {
"shades/+/target/set",
"shades/+/tiltTarget/set",
"shades/+/direction/set",
"shades/+/mypos/set",
"shades/+/myTiltPos/set",
"shades/+/sunFlag/set",
"shades/+/sunny/set",
"shades/+/windy/set",
"shades/+/position/set",
"shades/+/tiltPosition/set",
"groups/+/direction/set",
"groups/+/sunFlag/set",
"groups/+/sunny/set",
"groups/+/windy/set"
};
namespace {
struct segment_t {
const char *s = "";
size_t len = 0;
bool is(const char *word) const { return this->len == strlen(word) && strncmp(this->s, word, this->len) == 0; }
};
bool inRange(int val) { return val >= 0 && val <= 100; }
somfy_commands directionCommand(int val) {
if(val < 0) return somfy_commands::Up;
if(val > 0) return somfy_commands::Down;
return somfy_commands::My;
}
}
bool parseMqttCommand(const char *topic, const uint8_t *payload, size_t length, shade_request_t &req) {
// Only the last four segments count, so the root topic may hold slashes or
// be empty: type, id, command and "set".
segment_t seg[4];
size_t end = strlen(topic);
for(int i = 3; i >= 0; i--) {
size_t start = end;
while(start > 0 && topic[start - 1] != '/') start--;
seg[i].s = topic + start;
seg[i].len = end - start;
if(i > 0) {
if(start == 0) return false;
end = start - 1;
}
}
if(!seg[3].is("set")) return false;
if(seg[1].len == 0 || seg[1].len > 3) return false;
int id = 0;
for(size_t i = 0; i < seg[1].len; i++) {
if(seg[1].s[i] < '0' || seg[1].s[i] > '9') return false;
id = id * 10 + (seg[1].s[i] - '0');
}
if(id > 255) return false;
char value[16];
const size_t n = length < sizeof(value) - 1 ? length : sizeof(value) - 1;
memcpy(value, payload, n);
value[n] = '\0';
const int val = atoi(value);
const segment_t &cmd = seg[2];
shade_request_t r;
r.id = static_cast<uint8_t>(id);
if(seg[0].is("shades")) {
if(cmd.is("target")) {
if(!inRange(val)) return false;
r.type = shade_request_type_t::shade_command;
r.target = static_cast<int8_t>(val);
}
else if(cmd.is("tiltTarget")) {
if(!inRange(val)) return false;
r.type = shade_request_type_t::tilt_command;
r.target = static_cast<int8_t>(val);
}
else if(cmd.is("direction")) {
r.type = shade_request_type_t::shade_command;
r.command = directionCommand(val);
}
else if(cmd.is("mypos")) {
if(!inRange(val)) return false;
r.type = shade_request_type_t::set_favorite;
r.position = static_cast<int8_t>(val);
}
else if(cmd.is("myTiltPos")) {
if(!inRange(val)) return false;
r.type = shade_request_type_t::set_favorite;
r.tiltPosition = static_cast<int8_t>(val);
}
else if(cmd.is("sunFlag")) {
r.type = shade_request_type_t::shade_command;
r.command = sunFlagCommand(val > 0);
}
else if(cmd.is("sunny")) {
if(val < 0) return false;
r.type = shade_request_type_t::shade_sensor;
r.sunny = val > 0 ? 1 : 0;
}
else if(cmd.is("windy")) {
if(val < 0) return false;
r.type = shade_request_type_t::shade_sensor;
r.windy = val > 0 ? 1 : 0;
}
else if(cmd.is("position")) {
if(!inRange(val)) return false;
r.type = shade_request_type_t::set_positions;
r.position = static_cast<int8_t>(val);
}
else if(cmd.is("tiltPosition")) {
if(!inRange(val)) return false;
r.type = shade_request_type_t::set_positions;
r.tiltPosition = static_cast<int8_t>(val);
}
else return false;
}
else if(seg[0].is("groups")) {
if(cmd.is("direction")) {
r.type = shade_request_type_t::group_command;
r.command = directionCommand(val);
}
else if(cmd.is("sunFlag")) {
r.type = shade_request_type_t::group_command;
r.command = sunFlagCommand(val > 0);
}
else if(cmd.is("sunny")) {
if(val < 0) return false;
r.type = shade_request_type_t::group_sensor;
r.sunny = val > 0 ? 1 : 0;
}
else if(cmd.is("windy")) {
if(val < 0) return false;
r.type = shade_request_type_t::group_sensor;
r.windy = val > 0 ? 1 : 0;
}
else return false;
}
else return false;
req = r;
return true;
}