| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- #include <stddef.h>
- #include <MqttClient.h>
- #include <TokenIterator.h>
- #include <UrlTokenBindings.h>
- #include <IntParsing.h>
- #include <ArduinoJson.h>
- #include <WiFiClient.h>
- #include <MiLightRadioConfig.h>
- #include <AboutHelper.h>
- static const char* STATUS_CONNECTED = "connected";
- static const char* STATUS_DISCONNECTED = "disconnected_clean";
- static const char* STATUS_LWT_DISCONNECTED = "disconnected_unclean";
- MqttClient::MqttClient(Settings& settings, MiLightClient*& milightClient)
- : mqttClient(tcpClient),
- milightClient(milightClient),
- settings(settings),
- lastConnectAttempt(0),
- connected(false)
- {
- String strDomain = settings.mqttServer();
- this->domain = new char[strDomain.length() + 1];
- strcpy(this->domain, strDomain.c_str());
- }
- MqttClient::~MqttClient() {
- String aboutStr = generateConnectionStatusMessage(STATUS_DISCONNECTED);
- mqttClient.publish(settings.mqttClientStatusTopic.c_str(), aboutStr.c_str(), true);
- mqttClient.disconnect();
- delete this->domain;
- }
- void MqttClient::onConnect(OnConnectFn fn) {
- this->onConnectFn = fn;
- }
- void MqttClient::begin() {
- #ifdef MQTT_DEBUG
- printf_P(
- PSTR("MqttClient - Connecting to: %s\nparsed:%s:%u\n"),
- settings._mqttServer.c_str(),
- settings.mqttServer().c_str(),
- settings.mqttPort()
- );
- #endif
- mqttClient.setServer(this->domain, settings.mqttPort());
- mqttClient.setCallback(
- [this](char* topic, byte* payload, int length) {
- this->publishCallback(topic, payload, length);
- }
- );
- reconnect();
- }
- bool MqttClient::connect() {
- char nameBuffer[30];
- sprintf_P(nameBuffer, PSTR("milight-hub-%u"), ESP.getChipId());
- #ifdef MQTT_DEBUG
- Serial.println(F("MqttClient - connecting"));
- #endif
- if (settings.mqttUsername.length() > 0 && settings.mqttClientStatusTopic.length() > 0) {
- return mqttClient.connect(
- nameBuffer,
- settings.mqttUsername.c_str(),
- settings.mqttPassword.c_str(),
- settings.mqttClientStatusTopic.c_str(),
- 2,
- true,
- generateConnectionStatusMessage(STATUS_LWT_DISCONNECTED).c_str()
- );
- } else if (settings.mqttUsername.length() > 0) {
- return mqttClient.connect(
- nameBuffer,
- settings.mqttUsername.c_str(),
- settings.mqttPassword.c_str()
- );
- } else if (settings.mqttClientStatusTopic.length() > 0) {
- return mqttClient.connect(
- nameBuffer,
- settings.mqttClientStatusTopic.c_str(),
- 2,
- true,
- generateConnectionStatusMessage(STATUS_LWT_DISCONNECTED).c_str()
- );
- } else {
- return mqttClient.connect(nameBuffer);
- }
- }
- void MqttClient::sendBirthMessage() {
- if (settings.mqttClientStatusTopic.length() > 0) {
- String aboutStr = generateConnectionStatusMessage(STATUS_CONNECTED);
- mqttClient.publish(settings.mqttClientStatusTopic.c_str(), aboutStr.c_str(), true);
- }
- }
- void MqttClient::reconnect() {
- if (lastConnectAttempt > 0 && (millis() - lastConnectAttempt) < MQTT_CONNECTION_ATTEMPT_FREQUENCY) {
- return;
- }
- if (! mqttClient.connected()) {
- if (connect()) {
- subscribe();
- sendBirthMessage();
- #ifdef MQTT_DEBUG
- Serial.println(F("MqttClient - Successfully connected to MQTT server"));
- #endif
- } else {
- Serial.println(F("ERROR: Failed to connect to MQTT server"));
- }
- }
- lastConnectAttempt = millis();
- }
- void MqttClient::handleClient() {
- reconnect();
- mqttClient.loop();
- if (!connected && mqttClient.connected()) {
- this->connected = true;
- this->onConnectFn();
- } else if (!mqttClient.connected()) {
- this->connected = false;
- }
- }
- void MqttClient::sendUpdate(const MiLightRemoteConfig& remoteConfig, uint16_t deviceId, uint16_t groupId, const char* update) {
- publish(settings.mqttUpdateTopicPattern, remoteConfig, deviceId, groupId, update);
- }
- void MqttClient::sendState(const MiLightRemoteConfig& remoteConfig, uint16_t deviceId, uint16_t groupId, const char* update) {
- publish(settings.mqttStateTopicPattern, remoteConfig, deviceId, groupId, update, true);
- }
- void MqttClient::subscribe() {
- String topic = settings.mqttTopicPattern;
- topic.replace(":device_id", "+");
- topic.replace(":hex_device_id", "+");
- topic.replace(":dec_device_id", "+");
- topic.replace(":group_id", "+");
- topic.replace(":device_type", "+");
- topic.replace(":device_alias", "+");
- #ifdef MQTT_DEBUG
- printf_P(PSTR("MqttClient - subscribing to topic: %s\n"), topic.c_str());
- #endif
- mqttClient.subscribe(topic.c_str());
- }
- void MqttClient::send(const char* topic, const char* message, const bool retain) {
- size_t len = strlen(message);
- size_t topicLen = strlen(topic);
- if ((topicLen + len + 10) < MQTT_MAX_PACKET_SIZE ) {
- mqttClient.publish(topic, message, retain);
- } else {
- const uint8_t* messageBuffer = reinterpret_cast<const uint8_t*>(message);
- mqttClient.beginPublish(topic, len, retain);
- #ifdef MQTT_DEBUG
- Serial.printf_P(PSTR("Printing message in parts because it's too large for the packet buffer (%d bytes)"), len);
- #endif
- for (size_t i = 0; i < len; i += MQTT_PACKET_CHUNK_SIZE) {
- size_t toWrite = std::min(static_cast<size_t>(MQTT_PACKET_CHUNK_SIZE), len - i);
- mqttClient.write(messageBuffer+i, toWrite);
- #ifdef MQTT_DEBUG
- Serial.printf_P(PSTR(" Wrote %d bytes\n"), toWrite);
- #endif
- }
- mqttClient.endPublish();
- }
- }
- void MqttClient::publish(
- const String& _topic,
- const MiLightRemoteConfig &remoteConfig,
- uint16_t deviceId,
- uint16_t groupId,
- const char* message,
- const bool retain
- ) {
- if (_topic.length() == 0) {
- return;
- }
- BulbId bulbId(deviceId, groupId, remoteConfig.type);
- String topic = bindTopicString(_topic, bulbId);
- #ifdef MQTT_DEBUG
- printf("MqttClient - publishing update to %s\n", topic.c_str());
- #endif
- send(topic.c_str(), message, retain);
- }
- void MqttClient::publishCallback(char* topic, byte* payload, int length) {
- uint16_t deviceId = 0;
- uint8_t groupId = 0;
- const MiLightRemoteConfig* config = &FUT092Config;
- char cstrPayload[length + 1];
- cstrPayload[length] = 0;
- memcpy(cstrPayload, payload, sizeof(byte)*length);
- #ifdef MQTT_DEBUG
- printf("MqttClient - Got message on topic: %s\n%s\n", topic, cstrPayload);
- #endif
- char topicPattern[settings.mqttTopicPattern.length()];
- strcpy(topicPattern, settings.mqttTopicPattern.c_str());
- TokenIterator patternIterator(topicPattern, settings.mqttTopicPattern.length(), '/');
- TokenIterator topicIterator(topic, strlen(topic), '/');
- UrlTokenBindings tokenBindings(patternIterator, topicIterator);
- if (tokenBindings.hasBinding("device_alias")) {
- String alias = tokenBindings.get("device_alias");
- auto itr = settings.groupIdAliases.find(alias);
- if (itr == settings.groupIdAliases.end()) {
- Serial.printf_P(PSTR("MqttClient - WARNING: could not find device alias: `%s'. Ignoring packet.\n"), alias.c_str());
- return;
- } else {
- BulbId bulbId = itr->second;
- deviceId = bulbId.deviceId;
- config = MiLightRemoteConfig::fromType(bulbId.deviceType);
- groupId = bulbId.groupId;
- }
- } else {
- if (tokenBindings.hasBinding(GroupStateFieldNames::DEVICE_ID)) {
- deviceId = parseInt<uint16_t>(tokenBindings.get(GroupStateFieldNames::DEVICE_ID));
- } else if (tokenBindings.hasBinding("hex_device_id")) {
- deviceId = parseInt<uint16_t>(tokenBindings.get("hex_device_id"));
- } else if (tokenBindings.hasBinding("dec_device_id")) {
- deviceId = parseInt<uint16_t>(tokenBindings.get("dec_device_id"));
- }
- if (tokenBindings.hasBinding(GroupStateFieldNames::GROUP_ID)) {
- groupId = parseInt<uint16_t>(tokenBindings.get(GroupStateFieldNames::GROUP_ID));
- }
- if (tokenBindings.hasBinding(GroupStateFieldNames::DEVICE_TYPE)) {
- config = MiLightRemoteConfig::fromType(tokenBindings.get(GroupStateFieldNames::DEVICE_TYPE));
- } else {
- Serial.println(F("MqttClient - WARNING: could not find device_type token. Defaulting to FUT092.\n"));
- }
- }
- if (config == NULL) {
- Serial.println(F("MqttClient - ERROR: unknown device_type specified"));
- return;
- }
- StaticJsonDocument<400> buffer;
- deserializeJson(buffer, cstrPayload);
- JsonObject obj = buffer.as<JsonObject>();
- #ifdef MQTT_DEBUG
- printf("MqttClient - device %04X, group %u\n", deviceId, groupId);
- #endif
- milightClient->prepare(config, deviceId, groupId);
- milightClient->update(obj);
- }
- String MqttClient::bindTopicString(const String& topicPattern, const BulbId& bulbId) {
- String boundTopic = topicPattern;
- String deviceIdHex = bulbId.getHexDeviceId();
- boundTopic.replace(":device_id", deviceIdHex);
- boundTopic.replace(":hex_device_id", deviceIdHex);
- boundTopic.replace(":dec_device_id", String(bulbId.deviceId));
- boundTopic.replace(":group_id", String(bulbId.groupId));
- boundTopic.replace(":device_type", MiLightRemoteTypeHelpers::remoteTypeToString(bulbId.deviceType));
- auto it = settings.findAlias(bulbId.deviceType, bulbId.deviceId, bulbId.groupId);
- if (it != settings.groupIdAliases.end()) {
- boundTopic.replace(":device_alias", it->first);
- } else {
- boundTopic.replace(":device_alias", "__unnamed_group");
- }
- return boundTopic;
- }
- String MqttClient::generateConnectionStatusMessage(const char* connectionStatus) {
- if (settings.simpleMqttClientStatus) {
- // Don't expand disconnect type for simple status
- if (0 == strcmp(connectionStatus, STATUS_CONNECTED)) {
- return connectionStatus;
- } else {
- return "disconnected";
- }
- } else {
- StaticJsonDocument<1024> json;
- json[GroupStateFieldNames::STATUS] = connectionStatus;
- // Fill other fields
- AboutHelper::generateAboutObject(json, true);
- String response;
- serializeJson(json, response);
- return response;
- }
- }
|