Files
lightbar2mqtt/mqtt.cpp
2024-12-05 17:06:24 +01:00

421 lines
13 KiB
C++

#include <Arduino_JSON.h>
#include <esp_mac.h>
#include "mqtt.h"
MQTT::MQTT(WiFiClient *wifiClient, const char *mqttServer, int mqttPort, const char *mqttUser, const char *mqttPassword, const char *mqttRootTopic, bool homeAssistantAutoDiscovery, const char *homeAssistantAutoDiscoveryPrefix)
{
this->mqttServer = mqttServer;
this->mqttPort = mqttPort;
this->mqttUser = mqttUser;
this->mqttPassword = mqttPassword;
this->mqttRootTopic = String(mqttRootTopic);
this->homeAssistantDiscovery = homeAssistantAutoDiscovery;
this->homeAssistantDiscoveryPrefix = String(homeAssistantAutoDiscoveryPrefix);
this->remoteCommandHandler = std::bind(&MQTT::sendAction, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
this->client = new PubSubClient(*wifiClient);
String mac = "";
unsigned char mac_base[6] = {0};
if (esp_efuse_mac_get_default(mac_base) == ESP_OK)
{
char buffer[13]; // 6*2 characters for hex + 5 characters for colons + 1 character for null terminator
sprintf(buffer, "%02X%02X%02X%02X%02X%02X", mac_base[0], mac_base[1], mac_base[2], mac_base[3], mac_base[4], mac_base[5]);
mac = buffer;
}
this->clientId = "l2m_" + mac;
this->combinedRootTopic = this->mqttRootTopic + "/" + this->clientId;
}
MQTT::~MQTT()
{
delete this->client;
}
const String MQTT::getCombinedRootTopic()
{
return this->combinedRootTopic;
}
const String MQTT::getClientId()
{
return this->clientId;
}
void MQTT::onMessage(char *topic, byte *payload, unsigned int length)
{
Serial.print("[MQTT] New Message (");
Serial.print(topic);
Serial.print("): ");
for (int i = 0; i < length; i++)
{
Serial.print((char)payload[i]);
}
Serial.println();
JSONVar command = JSON.parse(String(payload, length));
Lightbar *lightbar = nullptr;
for (int i = 0; i < this->lightbarCount; i++)
{
lightbar = this->lightbars[i];
if (!strcmp(topic, String(this->getCombinedRootTopic() + "/" + lightbar->getSerialString() + "/pair").c_str()))
{
lightbar->pair();
return;
}
if (strcmp(topic, String(this->getCombinedRootTopic() + "/" + lightbar->getSerialString() + "/command").c_str()))
continue;
if (JSON.typeof(command) != "object")
continue;
if (command.hasOwnProperty("state"))
{
const char *state = command["state"];
lightbar->setOnOff(strcmp(state, "ON"));
}
if (command.hasOwnProperty("brightness"))
{
lightbar->setBrightness((uint8_t)command["brightness"]);
}
if (command.hasOwnProperty("color_temp"))
{
lightbar->setMiredTemperature((uint)command["color_temp"]);
}
}
}
void MQTT::setup()
{
Serial.print("[MQTT] Device ID: ");
Serial.println(this->clientId);
Serial.print("[MQTT] Root Topic: ");
Serial.println(this->getCombinedRootTopic());
this->client->setServer(this->mqttServer, this->mqttPort);
this->client->setCallback(std::bind(&MQTT::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
uint retries = 0;
Serial.println("[MQTT] Connecting to MQTT broker...");
while (!this->client->connected())
{
if (!this->client->connect(this->clientId.c_str(), this->mqttUser, this->mqttPassword, String(this->getCombinedRootTopic() + "/availability").c_str(), 1, true, "offline"))
{
Serial.print("[MQTT] Connection failed! rc=");
Serial.print(this->client->state());
Serial.println(" trying again in 1 second.");
delay(1000);
retries++;
if (retries > 60)
ESP.restart();
}
}
Serial.println("[MQTT] connected!");
this->client->publish(String(this->getCombinedRootTopic() + "/availability").c_str(), "online", true);
this->client->subscribe(String(this->getCombinedRootTopic() + "/+/command").c_str());
this->client->subscribe(String(this->getCombinedRootTopic() + "/+/pair").c_str());
this->sendAllHomeAssistantDiscoveryMessages();
}
bool MQTT::addLightbar(Lightbar *lightbar)
{
if (this->lightbarCount >= constants::MAX_LIGHTBARS)
{
Serial.println("[MQTT] Could not add light bar, because too many light bars are saved!");
Serial.println("[MQTT] Please check if you actually want to save more than " + String(constants::MAX_LIGHTBARS, DEC) + " light bars.");
Serial.println("[MQTT] If you do, increase MAX_LIGHTBARS in constants.h and recompile.");
return false;
}
this->lightbars[this->lightbarCount] = lightbar;
this->lightbarCount++;
this->sendHomeAssistantLightbarDiscoveryMessages(lightbar);
return true;
}
bool MQTT::removeLightbar(Lightbar *lightbar)
{
for (int i = 0; i < this->lightbarCount; i++)
{
if (this->lightbars[i] == lightbar)
{
for (int j = i; j < this->lightbarCount - 1; j++)
{
this->lightbars[j] = this->lightbars[j + 1];
}
this->lightbarCount--;
return true;
}
}
return false;
}
bool MQTT::addRemote(Remote *remote)
{
if (this->remoteCount >= constants::MAX_REMOTES)
{
Serial.println("[MQTT] Could not add remote, because too many remotes are saved!");
Serial.println("[MQTT] Please check if you actually want to save more than " + String(constants::MAX_REMOTES, DEC) + " remotes.");
Serial.println("[MQTT] If you do, increase MAX_REMOTES in constants.h and recompile.");
return false;
}
this->remotes[this->remoteCount] = remote;
this->remoteCount++;
remote->registerCommandListener(this->remoteCommandHandler);
this->sendHomeAssistantRemoteDiscoveryMessages(remote);
return true;
}
bool MQTT::removeRemote(Remote *remote)
{
for (int i = 0; i < this->remoteCount; i++)
{
if (this->remotes[i] == remote)
{
this->remotes[i]->registerCommandListener(this->remoteCommandHandler);
for (int j = i; j < this->remoteCount - 1; j++)
{
this->remotes[j] = this->remotes[j + 1];
}
this->remoteCount--;
return true;
}
}
return false;
}
void MQTT::sendAllHomeAssistantDiscoveryMessages()
{
if (!this->homeAssistantDiscovery)
return;
for (int i = 0; i < this->lightbarCount; i++)
{
this->sendHomeAssistantLightbarDiscoveryMessages(this->lightbars[i]);
}
for (int i = 0; i < this->remoteCount; i++)
{
this->sendHomeAssistantRemoteDiscoveryMessages(this->remotes[i]);
}
}
void MQTT::sendHomeAssistantLightbarDiscoveryMessages(Lightbar *lightbar)
{
if (!this->homeAssistantDiscovery)
return;
Serial.print("[MQTT] Sending lightbar discovery messages for ");
Serial.println(lightbar->getSerialString());
const String topicClient = this->clientId + "_" + lightbar->getSerialString();
const String baseConfig = R"json(
"schema": "json",
"o": {
"name": "lightbar2mqtt",
"sw_version": ")json" +
constants::VERSION +
R"json(",
"support_url": "https://github.com/ebinf/lightbar2mqtt"
},
"~": ")json" + this->getCombinedRootTopic() +
"/" +
lightbar->getSerialString() +
R"json(",
"availability_topic": ")json" +
this->getCombinedRootTopic() + R"json(/availability",
"dev":
{
"ids" : ")json" + topicClient +
R"json(",
"name": ")json" +
lightbar->getName() +
R"json(",
"mdl": "Mi Computer Monitor Light Bar (MJGJD01YL)",
"mf": "Xiaomi",
"sw": "lightbar2mqtt )json" +
constants::VERSION +
R"json(",
"sn": ")json" +
lightbar->getSerialString() +
R"json("
},)json";
String rendevous_str = "{" +
baseConfig +
R"json(
"supported_color_modes": [
"color_temp"
],
"brightness": true,
"brightness_scale": 15,
"name": "Light bar",
"cmd_t": "~/command",
"uniq_id": ")json" + topicClient +
R"json(_lightbar",
"max_mireds": 370,
"min_mireds":153,
"p": "light",
"icon": "mdi:wall-sconce-flat"
)json" + "}";
this->client->beginPublish(String(homeAssistantDiscoveryPrefix + "/light/" + topicClient + "/lightbar/config").c_str(), rendevous_str.length(), true);
this->client->print(rendevous_str);
this->client->endPublish();
rendevous_str = "{" +
baseConfig +
R"json(
"name": "Pair",
"cmd_t": "~/pair",
"uniq_id": ")json" +
topicClient + R"json(_pair",
"p": "button"
)json" + "}";
this->client->beginPublish(String(homeAssistantDiscoveryPrefix + "/button/" + topicClient + "/pair/config").c_str(), rendevous_str.length(), true);
this->client->print(rendevous_str);
this->client->endPublish();
}
void MQTT::sendHomeAssistantRemoteDiscoveryMessages(Remote *remote)
{
if (!this->homeAssistantDiscovery)
return;
Serial.print("[MQTT] Sending remote discovery messages for ");
Serial.println(remote->getSerialString());
const String topicClient = this->clientId + "_" + remote->getSerialString();
const String baseConfig = R"json(
"schema": "json",
"o": {
"name": "lightbar2mqtt",
"sw_version": ")json" +
constants::VERSION +
R"json(",
"support_url": "https://github.com/ebinf/lightbar2mqtt"
},
"~": ")json" + this->getCombinedRootTopic() +
"/" +
remote->getSerialString() +
R"json(",
"availability_topic": ")json" +
this->getCombinedRootTopic() + R"json(/availability",
"dev": {
"ids": ")json" + topicClient +
R"json(",
"name": ")json" + remote->getName() +
R"json(",
"mdl": "Mi Computer Monitor Light Bar Remote Control (MJGJD01YL)",
"mf": "Xiaomi",
"sw": "lightbar2mqtt )json" +
constants::VERSION +
R"json(",
"sn": ")json" + remote->getSerialString() +
R"json("
},)json";
String rendevous_str = "{" +
baseConfig +
R"json(
"name": "Remote",
"state_topic": "~/state",
"uniq_id": ")json" +
topicClient + R"json(_remote",
"value_template": "{{ value }}",
"enabled_by_default": true,
"entity_category": "diagnostic",
"icon": "mdi:gesture-double-tap"
)json" + "}";
this->client->beginPublish(String(homeAssistantDiscoveryPrefix + "/sensor/" + topicClient + "/remote/config").c_str(), rendevous_str.length(), true);
this->client->print(rendevous_str);
this->client->endPublish();
const char *commands[] = {
"press",
"turn_clockwise",
"turn_counterclockwise",
"press_turn_clockwise",
"press_turn_counterclockwise",
"hold"};
String cmd;
for (int i = 0; i < 6; i++)
{
cmd = commands[i];
rendevous_str = "{" +
baseConfig +
R"json(
"automation_type": "trigger",
"payload": ")json" + cmd +
R"json(",
"subtype": ")json" + cmd +
R"json(",
"type": "action",
"topic": "~/state",
"p": "device_automation"
)json" + "}";
this->client->beginPublish(String(homeAssistantDiscoveryPrefix + "/device_automation/" + topicClient + "/action_" + cmd + "/config").c_str(), rendevous_str.length(), true);
this->client->print(rendevous_str);
this->client->endPublish();
}
}
void MQTT::loop()
{
if (!this->client->connected())
{
Serial.println("[MQTT] connection lost!");
this->setup();
}
this->client->loop();
}
void MQTT::sendAction(Remote *remote, byte command, byte options)
{
String action;
switch ((uint8_t)command)
{
case Lightbar::Command::ON_OFF:
action = "press";
break;
case Lightbar::Command::BRIGHTER:
action = "turn_clockwise";
break;
case Lightbar::Command::DIMMER:
action = "turn_counterclockwise";
break;
case Lightbar::Command::WARMER:
action = "press_turn_counterclockwise";
break;
case Lightbar::Command::COOLER:
action = "press_turn_clockwise";
break;
case Lightbar::Command::RESET:
action = "hold";
break;
default:
return;
}
String topic = String(this->getCombinedRootTopic() + "/" + remote->getSerialString() + "/state");
Serial.print("[MQTT] Sending message (");
Serial.print(topic);
Serial.print("): ");
Serial.println(action);
this->client->publish(topic.c_str(), action.c_str());
delay(200);
this->client->publish(topic.c_str(), NULL);
}