summaryrefslogtreecommitdiff
path: root/platformio/relayctl/src/mqtt.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'platformio/relayctl/src/mqtt.cpp')
-rw-r--r--platformio/relayctl/src/mqtt.cpp172
1 files changed, 172 insertions, 0 deletions
diff --git a/platformio/relayctl/src/mqtt.cpp b/platformio/relayctl/src/mqtt.cpp
new file mode 100644
index 0000000..cca215b
--- /dev/null
+++ b/platformio/relayctl/src/mqtt.cpp
@@ -0,0 +1,172 @@
+#include "mqtt.h"
+#include "logging.h"
+#include "wifi.h"
+#include "config.def.h"
+#include "relay.h"
+#include "config.h"
+
+namespace homekit::mqtt {
+
+static const uint8_t MQTT_CA_FINGERPRINT[] = DEFAULT_MQTT_CA_FINGERPRINT;
+static const char MQTT_SERVER[] = DEFAULT_MQTT_SERVER;
+static const uint16_t MQTT_PORT = DEFAULT_MQTT_PORT;
+static const char MQTT_USERNAME[] = DEFAULT_MQTT_USERNAME;
+static const char MQTT_PASSWORD[] = DEFAULT_MQTT_PASSWORD;
+static const char MQTT_CLIENT_ID[] = DEFAULT_MQTT_CLIENT_ID;
+
+static const char MQTT_SECRET[] = SECRET;
+static const char TOPIC_RELAY_POWER[] = "relay/power";
+static const char TOPIC_STAT[] = "stat";
+static const char TOPIC_STAT1[] = "stat1";
+static const char TOPIC_ADMIN[] = "admin";
+static const char TOPIC_RELAY[] = "relay";
+
+
+using namespace homekit;
+
+MQTT::MQTT() : client(wifiClient) {
+ randomSeed(micros());
+
+ wifiClient.setFingerprint(MQTT_CA_FINGERPRINT);
+
+ client.setServer(MQTT_SERVER, MQTT_PORT);
+ client.setCallback([&](char* topic, byte* payload, unsigned int length) {
+ this->callback(topic, payload, length);
+ });
+}
+
+void MQTT::connect() {
+ reconnect();
+}
+
+void MQTT::reconnect() {
+ char buf[128] {0};
+
+ if (client.connected()) {
+ PRINTLN("warning: already connected");
+ return;
+ }
+
+ // Attempt to connect
+ if (client.connect(MQTT_CLIENT_ID, MQTT_USERNAME, MQTT_PASSWORD)) {
+ PRINTLN("mqtt: connected");
+
+ sendInitialStat();
+
+ subscribe(TOPIC_RELAY);
+ subscribe(TOPIC_ADMIN);
+ } else {
+ PRINTF("mqtt: failed to connect, rc=%d\n", client.state());
+ wifiClient.getLastSSLError(buf, sizeof(buf));
+ PRINTF("SSL error: %s\n", buf);
+
+ reconnectTimer.once(2, [&]() {
+ reconnect();
+ });
+ }
+}
+
+void MQTT::disconnect() {
+ // TODO test how this works???
+ reconnectTimer.detach();
+ client.disconnect();
+ wifiClient.stop();
+}
+
+bool MQTT::loop() {
+ return client.loop();
+}
+
+bool MQTT::publish(const char* topic, uint8_t *payload, size_t length) {
+ char full_topic[40] {0};
+ strcpy(full_topic, "/hk/");
+ strcat(full_topic, wifi::NODE_ID);
+ strcat(full_topic, "/");
+ strcat(full_topic, topic);
+ return client.publish(full_topic, payload, length);
+}
+
+bool MQTT::subscribe(const char *topic) {
+ char full_topic[40] {0};
+ strcpy(full_topic, "/hk/");
+ strcat(full_topic, wifi::NODE_ID);
+ strcat(full_topic, "/");
+ strcat(full_topic, topic);
+ strcat(full_topic, "/#");
+ bool res = client.subscribe(full_topic, 1);
+ if (!res)
+ PRINTF("error: failed to subscribe to %s\n", full_topic);
+ return res;
+}
+
+void MQTT::sendInitialStat() {
+ auto cfg = config::read();
+ InitialStatPayload stat {
+ .ip = wifi::getIPAsInteger(),
+ .fw_version = FW_VERSION,
+ .rssi = wifi::getRSSI(),
+ .free_heap = ESP.getFreeHeap(),
+ .flags = StatFlags {
+ .state = static_cast<uint8_t>(relay::getState() ? 1 : 0),
+ .config_changed_value_present = 1,
+ .config_changed = static_cast<uint8_t>(cfg.flags.node_configured || cfg.flags.wifi_configured ? 1 : 0)
+ }
+ };
+ publish(TOPIC_STAT1, reinterpret_cast<uint8_t*>(&stat), sizeof(stat));
+ statStopWatch.save();
+}
+
+void MQTT::sendStat() {
+ StatPayload stat {
+ .rssi = wifi::getRSSI(),
+ .free_heap = ESP.getFreeHeap(),
+ .flags = StatFlags {
+ .state = static_cast<uint8_t>(relay::getState() ? 1 : 0),
+ .config_changed_value_present = 0,
+ .config_changed = 0
+ }
+ };
+
+ PRINTF("free heap: %d\n", ESP.getFreeHeap());
+
+ publish(TOPIC_STAT, reinterpret_cast<uint8_t*>(&stat), sizeof(stat));
+ statStopWatch.save();
+}
+
+void MQTT::callback(char* topic, uint8_t* payload, uint32_t length) {
+ const size_t bufsize = 16;
+ char relevant_topic[bufsize];
+ strncpy(relevant_topic, topic+strlen(wifi::NODE_ID)+5, bufsize);
+
+ if (strncmp(TOPIC_RELAY_POWER, relevant_topic, bufsize) == 0) {
+ handleRelayPowerPayload(payload, length);
+ } else {
+ PRINTF("error: invalid topic %s\n", topic);
+ }
+}
+
+void MQTT::handleRelayPowerPayload(uint8_t *payload, uint32_t length) {
+ if (length != sizeof(PowerPayload)) {
+ PRINTF("error: size of payload (%ul) does not match expected (%ul)\n",
+ length, sizeof(PowerPayload));
+ return;
+ }
+
+ auto pd = reinterpret_cast<struct PowerPayload*>(payload);
+ if (strncmp(pd->secret, MQTT_SECRET, sizeof(pd->secret)) != 0) {
+ PRINTLN("error: invalid secret");
+ return;
+ }
+
+ if (pd->state == 1) {
+ relay::setOn();
+ } else if (pd->state == 0) {
+ relay::setOff();
+ } else {
+ PRINTLN("error: unexpected state value");
+ }
+
+ sendStat();
+}
+
+} \ No newline at end of file