Commit aedbed70d121a026b53042ce963765fc77bace0b
1 parent
a7de2e9c
Changed MQTT client back to PubSubClient but TCPIP is handled by sketch, added E…
…SP8266 support, changed shortID() to pick the least significant bytes of chip ID, made StaticJsonDocument DynamicJsonDocument, small ISR related changes in OTPhysicalLayer
Showing
5 changed files
with
257 additions
and
235 deletions
examples/Advanced_Thermostat/Advanced_Thermostat.ino
| ... | ... | @@ -112,21 +112,16 @@ static const char * TAG = __FILE__; |
| 112 | 112 | |
| 113 | 113 | #include <Arduino.h> |
| 114 | 114 | |
| 115 | +#if defined(ESP8266) | |
| 116 | +#include <ESP8266WiFi.h> | |
| 117 | +#else | |
| 115 | 118 | #include <WiFi.h> |
| 119 | +#endif | |
| 116 | 120 | |
| 117 | 121 | #include <time.h> |
| 118 | 122 | |
| 119 | 123 | #include <ArduinoJson.h> |
| 120 | 124 | |
| 121 | -// This example does not use the more popular PubSubClient, because that has too many issues, like losing a connection without possibilities to signal it and correct it | |
| 122 | -// This MQTT client runs rock solid :) | |
| 123 | -// https://github.com/monstrenyatko/ArduinoMqtt | |
| 124 | -// Enable MqttClient logs | |
| 125 | -#define MQTT_LOG_ENABLED 1 | |
| 126 | -// Include library | |
| 127 | -#include <MqttClient.h> | |
| 128 | - | |
| 129 | - | |
| 130 | 125 | #include <OneWire.h> |
| 131 | 126 | #include <DallasTemperature.h> |
| 132 | 127 | |
| ... | ... | @@ -149,6 +144,7 @@ const char * password = "YOUR WIFI PASSWORD"; |
| 149 | 144 | const char * mqtt_server = "YOUR MQTT SERVER NAME OR IP ADDRESS"; |
| 150 | 145 | const char * mqtt_user = "YOUR MQTT USER NAME"; |
| 151 | 146 | const char * mqtt_password = "YOUR MQTT PASSWORD"; |
| 147 | +const char * MQTT_ID = "EasyOpenTherm"; // Should be unique for your broker, see e.g. https://stackoverflow.com/questions/40078252/mosquitto-prevent-duplicate-login | |
| 152 | 148 | |
| 153 | 149 | |
| 154 | 150 | // Your time zone, used to display times correctly (and needed for WiFiClientSecure TLS certificate validation, if used) |
| ... | ... | @@ -156,8 +152,8 @@ const char * mqtt_password = "YOUR MQTT PASSWORD"; |
| 156 | 152 | #define TIME_ZONE TZ_Europe_Amsterdam |
| 157 | 153 | |
| 158 | 154 | |
| 159 | -// Define OT_RX_PIN, the GPIO pin used to read data from the boiler or HVAC. Must support interrupts | |
| 160 | -// Define OT_TX_PIN, the GPIO pin used to send data to the boiier or HVAC. Must not be a 'read only' GPIO | |
| 155 | +// Define OT_RX_PIN, the GPIO pin used to read data from the boiler or HVAC (TxD). Must support interrupts | |
| 156 | +// Define OT_TX_PIN, the GPIO pin used to send data to the boiier or HVAC (RxD). Must not be a 'read only' GPIO | |
| 161 | 157 | // Define DALLAS, the GPIO pin used for the Dallas sensor, if used |
| 162 | 158 | |
| 163 | 159 | #if defined(ARDUINO_LOLIN_S2_MINI) |
| ... | ... | @@ -168,12 +164,32 @@ const char * mqtt_password = "YOUR MQTT PASSWORD"; |
| 168 | 164 | #define OT_RX_PIN (10) |
| 169 | 165 | #define OT_TX_PIN (8) |
| 170 | 166 | #define DALLAS (4) |
| 167 | +#elif defined(ARDUINO_ESP8266_WEMOS_D1MINIPRO) | |
| 168 | +// I can't get my "D1 MINI PRO Based ESP8266EX" passed a WiFi connection | |
| 169 | +// D1 is GPIO5 | |
| 170 | +#define OT_RX_PIN (5) | |
| 171 | +// D2 is GPIO4 | |
| 172 | +#define OT_TX_PIN (4) | |
| 173 | +// D7 is GPIO13 | |
| 174 | +#define DALLAS (13) | |
| 175 | +#elif defined(ESP32) | |
| 176 | +#define OT_RX_PIN (33) | |
| 177 | +#define OT_TX_PIN (16) | |
| 178 | +#define DALLAS (17) | |
| 179 | +#elif defined(ESP8266) | |
| 180 | +// GPIO5 is D1 | |
| 181 | +#define OT_RX_PIN (5) | |
| 182 | +// GPIO4 is D2 | |
| 183 | +#define OT_TX_PIN (4) | |
| 184 | +// D7 is GPIO13 | |
| 185 | +#define DALLAS (13) | |
| 171 | 186 | #else |
| 172 | 187 | #define OT_RX_PIN (35) |
| 173 | 188 | #define OT_TX_PIN (33) |
| 174 | 189 | #define DALLAS (-1) |
| 175 | 190 | #endif |
| 176 | 191 | |
| 192 | + | |
| 177 | 193 | // The maximum room temperature |
| 178 | 194 | #define ROOM_TEMPERATURE_MAX_SETPOINT (30.0f) |
| 179 | 195 | // The minimum room temperature |
| ... | ... | @@ -212,8 +228,7 @@ const size_t MSG_BUFFER_RECV_SIZE = 256; // Too small a receive buffer will fai |
| 212 | 228 | // Define a global WiFiClient instance for WiFi connection |
| 213 | 229 | WiFiClient wiFiClient; |
| 214 | 230 | |
| 215 | -#define MQTT_ID "EasyOpenTherm" | |
| 216 | -static MqttClient *mqtt = NULL; | |
| 231 | +PubSubClient *mqtt = NULL; | |
| 217 | 232 | |
| 218 | 233 | |
| 219 | 234 | // GPIO where the DS18B20 is connected to, set to '-1' if not used |
| ... | ... | @@ -282,12 +297,32 @@ float roomTemperature; |
| 282 | 297 | |
| 283 | 298 | |
| 284 | 299 | // ============== Subscription callbacks ======================================== |
| 285 | -void processRoomTemperatureMessage(MqttClient::MessageData& md) { | |
| 300 | +void PubSubClientCallback(char* topic, byte* payload, unsigned int length) { | |
| 301 | + // handle message arrived | |
| 302 | + char payloadReceived[length + 1]; | |
| 303 | + memcpy(payloadReceived, payload, length); | |
| 304 | + payloadReceived[length] = '\0'; | |
| 305 | + | |
| 306 | + Serial.printf("Received topic '%s' with payload '%s'\n", topic, payloadReceived); | |
| 307 | + | |
| 308 | + const char * subscriptionTopic = topicByReference("temperature_command_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_CLIMATE); | |
| 309 | + if(subscriptionTopic == nullptr) return; | |
| 310 | + if(strcmp(topic, subscriptionTopic) == 0) return processSetpointTemperatureMessage(payloadReceived); | |
| 311 | + | |
| 312 | + subscriptionTopic = topicByReference("mode_command_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_CLIMATE); | |
| 313 | + if(subscriptionTopic == nullptr) return; | |
| 314 | + if(strcmp(topic, subscriptionTopic) == 0) return processClimateMessage(payloadReceived); | |
| 315 | + | |
| 316 | + subscriptionTopic = topicByReference("current_temperature_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_CLIMATE); | |
| 317 | + if(subscriptionTopic == nullptr) return; | |
| 318 | + if(strcmp(topic, subscriptionTopic) == 0) return processRoomTemperatureMessage(payloadReceived); | |
| 319 | + | |
| 320 | + Serial.printf("Received unhandled topic '%s' with payload '%s'\n", topic, payloadReceived); | |
| 321 | +} | |
| 322 | + | |
| 323 | + | |
| 324 | +void processRoomTemperatureMessage(const char * payload) { | |
| 286 | 325 | Serial.println("processRoomTemperatureMessage"); |
| 287 | - const MqttClient::Message& msg = md.message; | |
| 288 | - char payload[msg.payloadLen + 1]; | |
| 289 | - memcpy(payload, msg.payload, msg.payloadLen); | |
| 290 | - payload[msg.payloadLen] = '\0'; | |
| 291 | 326 | |
| 292 | 327 | StaticJsonDocument<32> roomTemperatureMsgDoc; |
| 293 | 328 | DeserializationError error = deserializeJson(roomTemperatureMsgDoc, payload); |
| ... | ... | @@ -314,12 +349,7 @@ Serial.printf("Received room temperature %.01f ºC\n", roomTemperature); |
| 314 | 349 | } |
| 315 | 350 | |
| 316 | 351 | |
| 317 | -void processSetpointTemperatureMessage(MqttClient::MessageData& md) { | |
| 318 | - const MqttClient::Message& msg = md.message; | |
| 319 | - char payload[msg.payloadLen + 1]; | |
| 320 | - memcpy(payload, msg.payload, msg.payloadLen); | |
| 321 | - payload[msg.payloadLen] = '\0'; | |
| 322 | - | |
| 352 | +void processSetpointTemperatureMessage(const char * payload) { | |
| 323 | 353 | float setpoint; |
| 324 | 354 | if(sscanf(payload, "%f", &setpoint) != 1) { |
| 325 | 355 | ESP_LOGE(TAG, "Payload is not a float: '%s'", payload); |
| ... | ... | @@ -333,11 +363,7 @@ Serial.printf("Received room temperature setpoint '%s'\n", payload); |
| 333 | 363 | } |
| 334 | 364 | |
| 335 | 365 | |
| 336 | -void processClimateMessage(MqttClient::MessageData& md) { | |
| 337 | - const MqttClient::Message& msg = md.message; | |
| 338 | - char payload[msg.payloadLen + 1]; | |
| 339 | - memcpy(payload, msg.payload, msg.payloadLen); | |
| 340 | - payload[msg.payloadLen] = '\0'; | |
| 366 | +void processClimateMessage(const char * payload) { | |
| 341 | 367 | ESP_LOGI(TAG, |
| 342 | 368 | "Message arrived: qos %d, retained %d, dup %d, packetid %d, payload:[%s]", |
| 343 | 369 | msg.qos, msg.retained, msg.dup, msg.id, payload |
| ... | ... | @@ -362,27 +388,28 @@ void setup() { |
| 362 | 388 | delay(5000); // For debug only: give the Serial Monitor some time to connect to the native USB of the MCU for output |
| 363 | 389 | Serial.println("\n\nStarted"); |
| 364 | 390 | Serial.printf("Chip ID is %s\n", chipID()); |
| 365 | - ESP_LOGI(TAG, "Chip ID is %s", chipID()); | |
| 391 | + Serial.printf("Short ID is %s\n", shortID()); | |
| 366 | 392 | |
| 367 | 393 | Serial.printf("OpenTherm RX pin is %d, TX pin is %d\n", OT_RX_PIN, OT_TX_PIN); |
| 368 | 394 | |
| 369 | - pinMode(LED_BUILTIN, OUTPUT); | |
| 395 | +// pinMode(LED_BUILTIN, OUTPUT); | |
| 370 | 396 | |
| 371 | 397 | // Connect WiFi |
| 372 | 398 | WiFi.mode(WIFI_STA); |
| 373 | 399 | WiFi.setAutoReconnect(true); |
| 400 | + WiFi.setAutoReconnect(false); | |
| 374 | 401 | WiFi.begin(ssid, password); |
| 375 | 402 | |
| 376 | 403 | uint32_t startMillis = millis(); |
| 377 | 404 | while (WiFi.status() != WL_CONNECTED) { |
| 378 | 405 | delay(500); |
| 379 | - digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) == LOW ? HIGH : LOW); | |
| 406 | +// digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) == LOW ? HIGH : LOW); | |
| 380 | 407 | |
| 381 | 408 | if(millis() - startMillis > 15000) ESP.restart(); |
| 382 | 409 | } |
| 383 | 410 | |
| 384 | - ESP_LOGI(TAG, "WiFi connected to %s", ssid); | |
| 385 | - ESP_LOGI(TAG, "IP address: %s", WiFi.localIP().toString().c_str()); | |
| 411 | + Serial.printf("WiFi connected to %s\n", ssid); | |
| 412 | + Serial.printf("IP address: %s\n", WiFi.localIP().toString().c_str()); | |
| 386 | 413 | |
| 387 | 414 | // Set time and date, necessary for HTTPS certificate validation |
| 388 | 415 | configTzTime(TIME_ZONE, "pool.ntp.org", "time.nist.gov"); |
| ... | ... | @@ -400,7 +427,11 @@ void setup() { |
| 400 | 427 | |
| 401 | 428 | if(millis() - startMillis > 15000) ESP.restart(); |
| 402 | 429 | } |
| 430 | +#if defined(ESP8266) | |
| 431 | + digitalWrite(LED_BUILTIN, HIGH); | |
| 432 | +#else | |
| 403 | 433 | digitalWrite(LED_BUILTIN, LOW); |
| 434 | +#endif | |
| 404 | 435 | Serial.println(); |
| 405 | 436 | |
| 406 | 437 | const struct tm * timeinfo = localtime(&now); |
| ... | ... | @@ -417,32 +448,8 @@ void setup() { |
| 417 | 448 | |
| 418 | 449 | } |
| 419 | 450 | |
| 420 | - // Setup MQTT client | |
| 421 | - MqttClient::System *mqttSystem = new System; | |
| 422 | -#if defined(ARDUINO_LOLIN_S2_MINI) | |
| 423 | - MqttClient::Logger *mqttLogger = new MqttClient::LoggerImpl<USBCDC>(Serial); | |
| 424 | -#elif defined(ARDUINO_LOLIN_C3_MINI) | |
| 425 | - MqttClient::Logger *mqttLogger = new MqttClient::LoggerImpl<HWCDC>(Serial); | |
| 426 | -#else | |
| 427 | - MqttClient::Logger *mqttLogger = new MqttClient::LoggerImpl<HardwareSerial>(Serial); | |
| 428 | -#endif | |
| 429 | - MqttClient::Network * mqttNetwork = new MqttClient::NetworkClientImpl<WiFiClient>(wiFiClient, *mqttSystem); | |
| 430 | - //// Make MSG_BUFFER_SIZE bytes send buffer | |
| 431 | - MqttClient::Buffer *mqttSendBuffer = new MqttClient::ArrayBuffer<MSG_BUFFER_SEND_SIZE>(); | |
| 432 | - //// Make MSG_BUFFER_SIZE bytes receive buffer | |
| 433 | - MqttClient::Buffer *mqttRecvBuffer = new MqttClient::ArrayBuffer<MSG_BUFFER_RECV_SIZE>(); | |
| 434 | - //// Allow up to 4 subscriptions simultaneously | |
| 435 | - MqttClient::MessageHandlers *mqttMessageHandlers = new MqttClient::MessageHandlersDynamicImpl<4>(); | |
| 436 | - // Note: the MessageHandlersDynamicImpl does not copy the topic string. The second parameter to MessageHandlersStaticImpl is the maximum topic size | |
| 437 | - // NOT TRUE: https://github.com/monstrenyatko/ArduinoMqtt/blob/15091f0b8c05f843f93b73db9a98f7b59ffb4dfa/src/MqttClient.h#L390 | |
| 438 | -// MqttClient::MessageHandlers *mqttMessageHandlers = new MqttClient::MessageHandlersStaticImpl<4, 128>(); | |
| 439 | - //// Configure client options | |
| 440 | - MqttClient::Options mqttOptions; | |
| 441 | - ////// Set command timeout to 10 seconds | |
| 442 | - mqttOptions.commandTimeoutMs = 10000; | |
| 443 | - //// Make client object | |
| 444 | - mqtt = new MqttClient(mqttOptions, *mqttLogger, *mqttSystem, *mqttNetwork, *mqttSendBuffer, *mqttRecvBuffer, *mqttMessageHandlers); | |
| 445 | - | |
| 451 | + // Do not let PubSubClient handle the TCP/IP socket connection, because of connection issues | |
| 452 | + Serial.println("Connect to MQTT broker..."); | |
| 446 | 453 | wiFiClient.connect(mqtt_server, 1883); |
| 447 | 454 | if(!wiFiClient.connected()) { |
| 448 | 455 | ESP_LOGE(TAG, "Can't establish the TCP connection"); |
| ... | ... | @@ -450,7 +457,10 @@ void setup() { |
| 450 | 457 | ESP.restart(); |
| 451 | 458 | } |
| 452 | 459 | |
| 453 | - Serial.println("Connect MQTT client..."); | |
| 460 | + // Setup MQTT client | |
| 461 | + mqtt = new PubSubClient(wiFiClient); | |
| 462 | + mqtt->setBufferSize(1024); | |
| 463 | + mqtt->setCallback(PubSubClientCallback); | |
| 454 | 464 | |
| 455 | 465 | bool MQTTConnected = connectMQTT(*mqtt, MQTT_ID, mqtt_user, mqtt_password); |
| 456 | 466 | if(MQTTConnected) { |
| ... | ... | @@ -487,14 +497,14 @@ Serial.println("MQTT NOT connected"); |
| 487 | 497 | if(!validJson(EASYOPENTHERM_MQTT_DISCOVERY_MSG_DHW_BINARY_SENSOR)) Serial.printf("Invalid JSON '%s'\n", EASYOPENTHERM_MQTT_DISCOVERY_MSG_DHW_BINARY_SENSOR); |
| 488 | 498 | |
| 489 | 499 | // Add all 'always present' entities (the other entities are added only if supported by the boiler) |
| 500 | + Serial.println("Add default entities to Home Assistant by publishing discovery messages to MQTT broker..."); | |
| 490 | 501 | addEntity(*mqtt, "climate", "climate", EASYOPENTHERM_MQTT_DISCOVERY_MSG_CLIMATE); |
| 491 | 502 | addEntity(*mqtt, "sensor", "boiler_setpoint", EASYOPENTHERM_MQTT_DISCOVERY_MSG_SETPOINT_SENSOR); |
| 492 | 503 | addEntity(*mqtt, "sensor", "thermostat_rssi", EASYOPENTHERM_MQTT_DISCOVERY_MSG_RSSI_SENSOR); |
| 493 | 504 | addEntity(*mqtt, "binary_sensor", "boiler_flame", EASYOPENTHERM_MQTT_DISCOVERY_MSG_FLAME_BINARY_SENSOR); |
| 494 | 505 | |
| 495 | - char payload[16]; | |
| 496 | - snprintf(payload, sizeof payload, "%.01f", ROOM_TEMPERATURE_MIN_SETPOINT); | |
| 497 | 506 | // Set all sensors, except those that are directly updated, to 'None', the binary sensor to 'Unknown' |
| 507 | + Serial.println("Set sensor values to 'none' by publishing to MQTT sensor state topics..."); | |
| 498 | 508 | publish(*mqtt, topicByReference("state_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_SETPOINT_SENSOR), "{\"ch_setpoint\":\"None\"}"); |
| 499 | 509 | publish(*mqtt, topicByReference("state_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_FLOW_TEMPERATURE_SENSOR), "{\"flow_temperature\":\"None\"}"); |
| 500 | 510 | publish(*mqtt, topicByReference("state_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_RETURN_TEMPERATURE_SENSOR), "{\"return_temperature\":\"None\"}"); |
| ... | ... | @@ -511,7 +521,8 @@ Serial.println("MQTT NOT connected"); |
| 511 | 521 | // If the boikler can both heat and cool adds 'auto', 'off', 'cool' and 'heat' to the thermmostat modes |
| 512 | 522 | // If the boikler can only heat adds 'off' and 'heat' to the thermmostat modes |
| 513 | 523 | void updateClimateEntity(bool canCool) { |
| 514 | - StaticJsonDocument<fullJsonDocSize> discoveryMsgDoc; | |
| 524 | +// StaticJsonDocument<fullJsonDocSize> discoveryMsgDoc; | |
| 525 | + DynamicJsonDocument discoveryMsgDoc(fullJsonDocSize); | |
| 515 | 526 | |
| 516 | 527 | if(discoveryMsgToJsonDoc(discoveryMsgDoc, EASYOPENTHERM_MQTT_DISCOVERY_MSG_CLIMATE)) { |
| 517 | 528 | JsonArray modesArray = discoveryMsgDoc["modes"]; |
| ... | ... | @@ -552,7 +563,6 @@ void updateDHWEntity(bool enableDHW) { |
| 552 | 563 | void updateFlameSensor(uint8_t statusFlags) { |
| 553 | 564 | static bool flameSensorInitialised = false; |
| 554 | 565 | static uint8_t previousStatusFlags = 0; |
| 555 | -Serial.printf("Secondary status flags is 0x%02x\n", statusFlags); | |
| 556 | 566 | |
| 557 | 567 | if(!flameSensorInitialised || (statusFlags & uint8_t(OpenTherm::STATUS_FLAGS::SECONDARY_FLAME_STATUS)) != (previousStatusFlags & uint8_t(OpenTherm::STATUS_FLAGS::SECONDARY_FLAME_STATUS))) { |
| 558 | 568 | const char * topic = topicByReference("state_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_FLAME_BINARY_SENSOR); |
| ... | ... | @@ -591,10 +601,11 @@ void updateDHWSensor(uint8_t statusFlags) { |
| 591 | 601 | // updateSensors |
| 592 | 602 | // Update the values of all 'interval' sensors in Home Assistant by sending the value in the right format to the topic looked up in the discovery JSON. OpenTherm sensor can be read and do not have |
| 593 | 603 | // an entity yet in Home Assistant are created by sending the discovery JSON to the right '/config' topic. |
| 594 | -void updateSensors(MqttClient & client, | |
| 604 | +void updateSensors(PubSubClient & client, | |
| 605 | + bool isConnectedOT, | |
| 595 | 606 | float CHSetpoint, |
| 596 | 607 | uint32_t & previousOTCommunicationMs) { |
| 597 | - if(client.isConnected()) { | |
| 608 | + if(client.connected()) { | |
| 598 | 609 | char payload[64]; |
| 599 | 610 | const char * topic; |
| 600 | 611 | |
| ... | ... | @@ -611,74 +622,80 @@ void updateSensors(MqttClient & client, |
| 611 | 622 | publish(client, topic, payload); |
| 612 | 623 | } |
| 613 | 624 | |
| 614 | - // Publish the Central Heating setpoint temperature | |
| 615 | - snprintf(payload, sizeof payload, "{\"ch_setpoint\":%.01f}", CHSetpoint); | |
| 616 | - topic = topicByReference("state_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_SETPOINT_SENSOR); | |
| 617 | - publish(client, topic, payload); | |
| 625 | + if(isConnectedOT) { | |
| 626 | + // Publish the Central Heating setpoint temperature | |
| 627 | + snprintf(payload, sizeof payload, "{\"ch_setpoint\":%.01f}", CHSetpoint); | |
| 628 | + topic = topicByReference("state_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_SETPOINT_SENSOR); | |
| 629 | + publish(client, topic, payload); | |
| 618 | 630 | |
| 619 | - float value; | |
| 620 | - // Test if Relative Modulation Level can be read from the boiler | |
| 621 | - if(readSensor(thermostat, OpenTherm::READ_DATA_ID::RELATIVE_MODULATION_LEVEL, value, previousOTCommunicationMs)) { | |
| 622 | - Serial.printf("Relative Modulation level is %.01f %\n", value); | |
| 623 | - // Use a static variable to keep track of the entity already being created | |
| 624 | - static bool relativeModulationLevelDiscoveryPublished = false; | |
| 625 | - if(!relativeModulationLevelDiscoveryPublished) { | |
| 626 | - // Create the entity | |
| 627 | - addEntity(client, "sensor", "relative_modulation", EASYOPENTHERM_MQTT_DISCOVERY_MSG_RELATIVE_MODULATION_SENSOR); | |
| 628 | - relativeModulationLevelDiscoveryPublished = true; | |
| 631 | + float value; | |
| 632 | + // Test if Relative Modulation Level can be read from the boiler | |
| 633 | + if(readSensor(thermostat, OpenTherm::READ_DATA_ID::RELATIVE_MODULATION_LEVEL, value, previousOTCommunicationMs)) { | |
| 634 | + Serial.printf("Relative Modulation level is %.01f %\n", value); | |
| 635 | + // Use a static variable to keep track of the entity already being created | |
| 636 | + static bool relativeModulationLevelDiscoveryPublished = false; | |
| 637 | + if(!relativeModulationLevelDiscoveryPublished) { | |
| 638 | + // Create the entity | |
| 639 | + addEntity(client, "sensor", "relative_modulation", EASYOPENTHERM_MQTT_DISCOVERY_MSG_RELATIVE_MODULATION_SENSOR); | |
| 640 | + relativeModulationLevelDiscoveryPublished = true; | |
| 641 | + } | |
| 642 | + // Publish the Relative Modulation Level | |
| 643 | + snprintf(payload, sizeof payload, "{\"relative_modulation\":%.01f}", value); | |
| 644 | + topic = topicByReference("state_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_RELATIVE_MODULATION_SENSOR); | |
| 645 | + publish(client, topic, payload); | |
| 629 | 646 | } |
| 630 | - // Publish the Relative Modulation Level | |
| 631 | - snprintf(payload, sizeof payload, "{\"relative_modulation\":%.01f}", value); | |
| 632 | - topic = topicByReference("state_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_RELATIVE_MODULATION_SENSOR); | |
| 633 | - publish(client, topic, payload); | |
| 634 | - } | |
| 635 | - // Test if Relative Central Heating Water Pressure can be read from the boiler | |
| 636 | - if(readSensor(thermostat, OpenTherm::READ_DATA_ID::CH_WATER_PRESSURE, value, previousOTCommunicationMs)) { | |
| 637 | - Serial.printf("Central Heating water pressure is %.01f bar\n", value); | |
| 638 | - // Use a static variable to keep track of the entity already being created | |
| 639 | - static bool waterPressureEntityAdded = false; | |
| 640 | - // Create the entity | |
| 641 | - if(!waterPressureEntityAdded) { | |
| 642 | - addEntity(client, "sensor", "water_pressure", EASYOPENTHERM_MQTT_DISCOVERY_MSG_WATER_PRESSURE_SENSOR); | |
| 643 | - waterPressureEntityAdded = true; | |
| 647 | + | |
| 648 | + // Test if Relative Central Heating Water Pressure can be read from the boiler | |
| 649 | + if(readSensor(thermostat, OpenTherm::READ_DATA_ID::CH_WATER_PRESSURE, value, previousOTCommunicationMs)) { | |
| 650 | + Serial.printf("Central Heating water pressure is %.01f bar\n", value); | |
| 651 | + // Use a static variable to keep track of the entity already being created | |
| 652 | + static bool waterPressureEntityAdded = false; | |
| 653 | + // Create the entity | |
| 654 | + if(!waterPressureEntityAdded) { | |
| 655 | + addEntity(client, "sensor", "water_pressure", EASYOPENTHERM_MQTT_DISCOVERY_MSG_WATER_PRESSURE_SENSOR); | |
| 656 | + waterPressureEntityAdded = true; | |
| 657 | + } | |
| 658 | + // Publish the Central Heating Water Pressure | |
| 659 | + snprintf(payload, sizeof payload, "{\"water_pressure\":%.01f}", value); | |
| 660 | + topic = topicByReference("state_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_WATER_PRESSURE_SENSOR); | |
| 661 | + publish(client, topic, payload); | |
| 644 | 662 | } |
| 645 | - // Publish the Central Heating Water Pressure | |
| 646 | - snprintf(payload, sizeof payload, "{\"water_pressure\":%.01f}", value); | |
| 647 | - topic = topicByReference("state_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_WATER_PRESSURE_SENSOR); | |
| 648 | - publish(client, topic, payload); | |
| 649 | - } | |
| 650 | - // Test if Flow Temperature can be read from the boiler | |
| 651 | - if(readSensor(thermostat, OpenTherm::READ_DATA_ID::BOILER_WATER_TEMP, value, previousOTCommunicationMs)) { | |
| 652 | - Serial.printf("Flow water temperature from boiler is %.01f %\n", value); | |
| 653 | - // Use a static variable to keep track of the entity already being created | |
| 654 | - static bool flowTemperatureEntityAdded = false; | |
| 655 | - // Create the entity | |
| 656 | - if(!flowTemperatureEntityAdded) { | |
| 657 | - addEntity(client, "sensor", "flow_temperature", EASYOPENTHERM_MQTT_DISCOVERY_MSG_FLOW_TEMPERATURE_SENSOR); | |
| 658 | - flowTemperatureEntityAdded = true; | |
| 663 | + | |
| 664 | + // Test if Flow Temperature can be read from the boiler | |
| 665 | + if(readSensor(thermostat, OpenTherm::READ_DATA_ID::BOILER_WATER_TEMP, value, previousOTCommunicationMs)) { | |
| 666 | + Serial.printf("Flow water temperature from boiler is %.01f %\n", value); | |
| 667 | + // Use a static variable to keep track of the entity already being created | |
| 668 | + static bool flowTemperatureEntityAdded = false; | |
| 669 | + // Create the entity | |
| 670 | + if(!flowTemperatureEntityAdded) { | |
| 671 | + addEntity(client, "sensor", "flow_temperature", EASYOPENTHERM_MQTT_DISCOVERY_MSG_FLOW_TEMPERATURE_SENSOR); | |
| 672 | + flowTemperatureEntityAdded = true; | |
| 673 | + } | |
| 674 | + // Publish the Flow Temperature | |
| 675 | + snprintf(payload, sizeof payload, "{\"flow_temperature\":%.01f}", value); | |
| 676 | + topic = topicByReference("state_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_FLOW_TEMPERATURE_SENSOR); | |
| 677 | + publish(client, topic, payload); | |
| 659 | 678 | } |
| 660 | - // Publish the Flow Temperature | |
| 661 | - snprintf(payload, sizeof payload, "{\"flow_temperature\":%.01f}", value); | |
| 662 | - topic = topicByReference("state_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_FLOW_TEMPERATURE_SENSOR); | |
| 663 | - publish(client, topic, payload); | |
| 664 | - } | |
| 665 | - // Test if Return Temperature can be read from the boiler | |
| 666 | - if(readSensor(thermostat, OpenTherm::READ_DATA_ID::RETURN_WATER_TEMPERATURE, value, previousOTCommunicationMs)) { | |
| 667 | - Serial.printf("Return water temperature to boiler is %.01f %\n", value); | |
| 668 | - // Use a static variable to keep track of the entity already being created | |
| 669 | - static bool returnTemperatureEntityAdded = false; | |
| 670 | - // Create the entity | |
| 671 | - if(!returnTemperatureEntityAdded) { | |
| 672 | - addEntity(client, "sensor", "return_temperature", EASYOPENTHERM_MQTT_DISCOVERY_MSG_RETURN_TEMPERATURE_SENSOR); | |
| 673 | - returnTemperatureEntityAdded = true; | |
| 679 | + | |
| 680 | + // Test if Return Temperature can be read from the boiler | |
| 681 | + if(readSensor(thermostat, OpenTherm::READ_DATA_ID::RETURN_WATER_TEMPERATURE, value, previousOTCommunicationMs)) { | |
| 682 | + Serial.printf("Return water temperature to boiler is %.01f %\n", value); | |
| 683 | + // Use a static variable to keep track of the entity already being created | |
| 684 | + static bool returnTemperatureEntityAdded = false; | |
| 685 | + // Create the entity | |
| 686 | + if(!returnTemperatureEntityAdded) { | |
| 687 | + addEntity(client, "sensor", "return_temperature", EASYOPENTHERM_MQTT_DISCOVERY_MSG_RETURN_TEMPERATURE_SENSOR); | |
| 688 | + returnTemperatureEntityAdded = true; | |
| 689 | + } | |
| 690 | + // Publish the Return Temperature | |
| 691 | + snprintf(payload, sizeof payload, "{\"return_temperature\":%.01f}", value); | |
| 692 | + topic = topicByReference("state_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_RETURN_TEMPERATURE_SENSOR); | |
| 693 | + publish(client, topic, payload); | |
| 674 | 694 | } |
| 675 | - // Publish the Return Temperature | |
| 676 | - snprintf(payload, sizeof payload, "{\"return_temperature\":%.01f}", value); | |
| 677 | - topic = topicByReference("state_topic", EASYOPENTHERM_MQTT_DISCOVERY_MSG_RETURN_TEMPERATURE_SENSOR); | |
| 678 | - publish(client, topic, payload); | |
| 695 | + | |
| 696 | + // Any other OpenTherm sensor that returns a float (f8.8) can be added in the same way as above by using the correct OpenTherm::READ_DATA_ID::, adding a dicovery JSON in JSONs.h, calling | |
| 697 | + // addEntity with the according parameters and publishing the value to the right topic in the right format | |
| 679 | 698 | } |
| 680 | - // Any other OpenTherm sensor that returns a float (f8.8) can be added in the same way as above by using the correct OpenTherm::READ_DATA_ID::, adding a dicovery JSON in JSONs.h, calling | |
| 681 | - // addEntity with the according parameters and publishing the value to the right topic in the right format | |
| 682 | 699 | } |
| 683 | 700 | } |
| 684 | 701 | |
| ... | ... | @@ -701,17 +718,15 @@ void updateRoomTemperatureStale(bool stale) { |
| 701 | 718 | // subscribeAll() |
| 702 | 719 | // Subsbribe to all needed topics: one for the room temperature setpoint, one for the mode (OFF / HEAT / COOL / AUTO) and one for |
| 703 | 720 | // the actual room temperature |
| 704 | -bool subscribeAll(MqttClient & client) { | |
| 705 | - if(client.isConnected()) { | |
| 706 | - subscribe(client, processSetpointTemperatureMessage, EASYOPENTHERM_MQTT_DISCOVERY_MSG_CLIMATE, "temperature_command_topic"); | |
| 707 | - subscribe(client, processClimateMessage, EASYOPENTHERM_MQTT_DISCOVERY_MSG_CLIMATE, "mode_command_topic"); | |
| 708 | - subscribe(client, processRoomTemperatureMessage, EASYOPENTHERM_MQTT_DISCOVERY_MSG_CLIMATE, "current_temperature_topic"); | |
| 721 | +bool subscribeAll(PubSubClient & client) { | |
| 722 | + if(client.connected()) { | |
| 723 | + subscribe(client, EASYOPENTHERM_MQTT_DISCOVERY_MSG_CLIMATE, "temperature_command_topic"); | |
| 724 | + subscribe(client, EASYOPENTHERM_MQTT_DISCOVERY_MSG_CLIMATE, "mode_command_topic"); | |
| 725 | + subscribe(client, EASYOPENTHERM_MQTT_DISCOVERY_MSG_CLIMATE, "current_temperature_topic"); | |
| 709 | 726 | // subscribe(client, processClimateMessage, EASYOPENTHERM_MQTT_DISCOVERY_MSG_CLIMATE, "preset_mode_command_topic"); // Not implemented yet |
| 710 | - | |
| 711 | - return true; | |
| 712 | 727 | } |
| 713 | 728 | |
| 714 | - return false; | |
| 729 | + return client.connected(); | |
| 715 | 730 | } |
| 716 | 731 | |
| 717 | 732 | |
| ... | ... | @@ -743,35 +758,49 @@ void loop() { |
| 743 | 758 | |
| 744 | 759 | |
| 745 | 760 | // Check if we are still connected to the MQTT broker. If not, reconnect and resubscribe to the topics we are interested in |
| 746 | - if(!mqtt->isConnected()) { | |
| 747 | - ESP_LOGI(TAG, "MQTT disconnected"); | |
| 748 | -Serial.println("MQTT disconnected"); | |
| 749 | - wiFiClient.stop(); | |
| 750 | - wiFiClient.connect(mqtt_server, 1883); | |
| 751 | - connectMQTT(*mqtt, MQTT_ID, mqtt_user, mqtt_password); | |
| 752 | - subscribed = false; | |
| 761 | + if(!mqtt->connected()) { | |
| 762 | + Serial.println("MQTT disconnected"); | |
| 763 | + // handle the TCP/IP socket connection outside of PubSubClient because of connection issues | |
| 764 | + if(!wiFiClient.connected()) { | |
| 765 | + Serial.printf("WiFi client disconnected, WiFi status = %d\n", WiFi.status()); | |
| 766 | +// wiFiClient.stop(); | |
| 767 | + wiFiClient.connect(mqtt_server, 1883); | |
| 768 | + if(wiFiClient.connected()) { | |
| 769 | + Serial.println("WiFi client reconnected"); | |
| 770 | + } | |
| 771 | + } | |
| 772 | + if(wiFiClient.connected()) { | |
| 773 | + Serial.printf("WiFi client is connected, WiFi status = %d\n", WiFi.status()); | |
| 774 | + bool MQTTConnected = connectMQTT(*mqtt, MQTT_ID, mqtt_user, mqtt_password); | |
| 775 | + Serial.printf("MQTT reconnected is %s\n", MQTTConnected ? "true" : "false"); | |
| 776 | + subscribed = false; | |
| 777 | + } else { | |
| 778 | + Serial.println("WiFi client did not reconnected"); | |
| 779 | + } | |
| 753 | 780 | } |
| 754 | 781 | |
| 755 | 782 | // Subscribe to all relevant topics after a new connection |
| 756 | 783 | if(!subscribed) { |
| 784 | + Serial.println("Subscribe to all relevant MQTT topics..."); | |
| 757 | 785 | if(subscribeAll(*mqtt)) { |
| 758 | 786 | subscribed = true; |
| 759 | 787 | } |
| 760 | 788 | } |
| 761 | 789 | |
| 762 | - uint32_t thermostatStateTimestampAligned = ldiv(time(nullptr), PUBLISH_STATE_UPDATE_INTERVAL).quot * PUBLISH_STATE_UPDATE_INTERVAL; // Align to exact intervals | |
| 790 | + uint32_t thermostatStateTimestampAligned = (time(nullptr) / PUBLISH_STATE_UPDATE_INTERVAL) * PUBLISH_STATE_UPDATE_INTERVAL; // Align to exact intervals | |
| 763 | 791 | uint32_t thermostatStateTimestampMs = millis(); |
| 764 | - | |
| 765 | 792 | if(millis() - previousOTCommunicationMs >= 1000) { // OpenTherm specifies that primary to secondary communication should take place at least every second |
| 766 | 793 | // connected() becomes 'true' after communication has taken place between thermostat and boiler and the boiler's configuration flags are read |
| 794 | + | |
| 767 | 795 | if(!thermoStateMachine.connected()) { |
| 768 | 796 | // Try to contact the boiler and construct the thermostat's primary flags from the boiler's capabilities |
| 769 | 797 | OpenTherm::CONFIGURATION_FLAGS configurationFlags; |
| 770 | 798 | if(readSecondaryConfiguration(thermostat, configurationFlags, previousOTCommunicationMs)) { |
| 799 | + Serial.println("OpenTherm secondary connected"); | |
| 771 | 800 | thermoStateMachine.initPrimaryFlags(configurationFlags); |
| 772 | 801 | |
| 773 | - updateClimateEntity((uint8_t(configurationFlags) & uint8_t(OpenTherm::CONFIGURATION_FLAGS::SECONDARY_COOLING)) != 0); | |
| 774 | - updateDHWEntity((uint8_t(configurationFlags) & uint8_t(OpenTherm::CONFIGURATION_FLAGS::SECONDARY_DHW_PRESENT)) != 0); | |
| 802 | + updateClimateEntity(((uint8_t) configurationFlags & (uint8_t) OpenTherm::CONFIGURATION_FLAGS::SECONDARY_COOLING) != 0); | |
| 803 | + updateDHWEntity(((uint8_t) configurationFlags & (uint8_t) OpenTherm::CONFIGURATION_FLAGS::SECONDARY_DHW_PRESENT) != 0); | |
| 775 | 804 | } |
| 776 | 805 | } |
| 777 | 806 | |
| ... | ... | @@ -829,7 +858,7 @@ Serial.println("MQTT disconnected"); |
| 829 | 858 | // Read status in every loop, to meet the 'communication each second' requirement |
| 830 | 859 | if(readStatus(thermostat, primaryFlags, statusFlags, previousOTCommunicationMs)) { |
| 831 | 860 | // Inform Home Assitant directly about the status; is automatically ignored if the flame status or CH / DHW status did not change |
| 832 | - updateFlameSensor(statusFlags); | |
| 861 | + updateFlameSensor(statusFlags); | |
| 833 | 862 | updateDHWSensor(statusFlags); |
| 834 | 863 | } |
| 835 | 864 | } |
| ... | ... | @@ -837,9 +866,10 @@ Serial.println("MQTT disconnected"); |
| 837 | 866 | |
| 838 | 867 | // Publish the 'interval' sensors' at exact 'PUBLISH_STATE_UPDATE_INTERVAL' intervals |
| 839 | 868 | if(thermostatStateTimestampAligned - publishedThermostatStateTimestamp >= PUBLISH_STATE_UPDATE_INTERVAL) { |
| 840 | - updateSensors(*mqtt, CHSetpoint, previousOTCommunicationMs); | |
| 869 | + Serial.println("Update sensors by publishing sensor values to MQTT broker..."); | |
| 870 | + updateSensors(*mqtt, thermoStateMachine.connected(), CHSetpoint, previousOTCommunicationMs); | |
| 841 | 871 | publishedThermostatStateTimestamp = thermostatStateTimestampAligned; |
| 842 | 872 | } |
| 843 | 873 | |
| 844 | - mqtt->yield(100); | |
| 874 | + mqtt->loop(); | |
| 845 | 875 | } | ... | ... |
examples/Advanced_Thermostat/MQTTHelpers.cpp
| ... | ... | @@ -41,7 +41,7 @@ const char * chipID() { |
| 41 | 41 | return serialNumber; |
| 42 | 42 | } |
| 43 | 43 | |
| 44 | -#include <machine/types.h> | |
| 44 | +//#include <machine/types.h> | |
| 45 | 45 | /* |
| 46 | 46 | * shortID() |
| 47 | 47 | * Store the least significant two bytes of the ESP32 unique ID once. Return a pointer to the stored short ID. The ID is the same as the last two MAC bytes, in the same order |
| ... | ... | @@ -59,7 +59,18 @@ const char * shortID() { |
| 59 | 59 | #endif |
| 60 | 60 | |
| 61 | 61 | uint8_t * MAC = (uint8_t *) &mac; |
| 62 | + | |
| 63 | +// ESP32 Chip ID is cca4f003f784 => 0x84, 0xf7, 0x03, 0xf0, 0xa4, 0xcc, 0x00, 0x00 | |
| 64 | +// ESP8266 Chip ID is 00c2005a3cc2 => 0xc2, 0x3c, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00 | |
| 65 | +// So for the ESP32 MAC[4] and MAC[5] are the least significant bytes, and for the ESP8266 MAC[1] and MAC[0] | |
| 66 | +// Serial.print("Serial number bytes: "); for(size_t index = 0; index < 8; index++) Serial.printf("0x%02x, ", MAC[index]); Serial.println(); | |
| 67 | + | |
| 68 | +// sprintf(shortNumber, "%02X%02X", MAC[4], MAC[5]); | |
| 69 | +#if defined(ESP32) | |
| 62 | 70 | sprintf(shortNumber, "%02X%02X", MAC[4], MAC[5]); |
| 71 | +#else | |
| 72 | + sprintf(shortNumber, "%02X%02X", MAC[1], MAC[0]); | |
| 73 | +#endif | |
| 63 | 74 | ESP_LOGI(TAG, "Short ID is %s", shortNumber); |
| 64 | 75 | } |
| 65 | 76 | |
| ... | ... | @@ -123,6 +134,7 @@ const char * topicByReference(const char * key, |
| 123 | 134 | DeserializationError error = deserializeJson(discoveryJsonPartDoc, discoveryMsgJson, DeserializationOption::Filter(filter)); |
| 124 | 135 | if(error) { |
| 125 | 136 | ESP_LOGE(TAG, "Deserialize error %s for JSON '%s'", error.f_str(), discoveryMsgJson); |
| 137 | +Serial.printf("Deserialize error %s for JSON '%s'\n", error.f_str(), discoveryMsgJson); | |
| 126 | 138 | |
| 127 | 139 | return nullptr; |
| 128 | 140 | } |
| ... | ... | @@ -131,12 +143,14 @@ const char * topicByReference(const char * key, |
| 131 | 143 | const char * topic = discoveryJsonPartDoc[key]; |
| 132 | 144 | if(topic == nullptr) { |
| 133 | 145 | ESP_LOGE(TAG, "Key '%s' not found in JSON '%s'", key, discoveryMsgJson); |
| 146 | +Serial.printf("Key '%s' not found in JSON '%s'\n", key, discoveryMsgJson); | |
| 134 | 147 | |
| 135 | 148 | return nullptr; |
| 136 | 149 | } |
| 137 | 150 | |
| 138 | 151 | if(*topic == '~' && baseTopic == nullptr) { |
| 139 | 152 | ESP_LOGE(TAG, "Base topic '~' not found in JSON '%s'", discoveryMsgJson); |
| 153 | +Serial.printf("Base topic '~' not found in JSON '%s'\n", discoveryMsgJson); | |
| 140 | 154 | |
| 141 | 155 | return nullptr; |
| 142 | 156 | } |
| ... | ... | @@ -149,6 +163,7 @@ const char * topicByReference(const char * key, |
| 149 | 163 | if(topicBuffer == nullptr) { |
| 150 | 164 | topicBufferSize = 0; |
| 151 | 165 | ESP_LOGE(TAG, "topicByReference out of memory"); |
| 166 | +Serial.printf("topicByReference out of memory\n"); | |
| 152 | 167 | |
| 153 | 168 | return nullptr; |
| 154 | 169 | } |
| ... | ... | @@ -179,24 +194,18 @@ bool validJson(const char * discoveryMsgJson) { |
| 179 | 194 | } |
| 180 | 195 | |
| 181 | 196 | |
| 182 | -bool connectMQTT(MqttClient & client, | |
| 197 | +bool connectMQTT(PubSubClient & client, | |
| 183 | 198 | const char * clientID, |
| 184 | 199 | const char * user, |
| 185 | 200 | const char * password) { |
| 186 | 201 | // Start new MQTT connection |
| 187 | - ESP_LOGI(TAG, "Connecting"); | |
| 188 | - MqttClient::ConnectResult connectResult; | |
| 202 | + ESP_LOGI(TAG, "Connecting..."); | |
| 203 | + | |
| 189 | 204 | // Connect |
| 190 | - MQTTPacket_connectData options = MQTTPacket_connectData_initializer; | |
| 191 | - options.MQTTVersion = 4; | |
| 192 | - options.clientID.cstring = (char *)clientID; | |
| 193 | - options.username.cstring = (char *)user; | |
| 194 | - options.password.cstring = (char *)password; | |
| 195 | - options.cleansession = true; | |
| 196 | - options.keepAliveInterval = 15; // 15 seconds | |
| 197 | - MqttClient::Error::type rc = client.connect(options, connectResult); | |
| 198 | - if (rc != MqttClient::Error::SUCCESS) { | |
| 199 | - ESP_LOGE(TAG, "Connection error: %i", rc); | |
| 205 | + bool connectionResult = client.connect(clientID, user, password); | |
| 206 | + | |
| 207 | + if(!connectionResult) { | |
| 208 | + ESP_LOGE(TAG, "Connection error: %d", client.state()); | |
| 200 | 209 | |
| 201 | 210 | return false; |
| 202 | 211 | } |
| ... | ... | @@ -205,25 +214,29 @@ bool connectMQTT(MqttClient & client, |
| 205 | 214 | } |
| 206 | 215 | |
| 207 | 216 | |
| 208 | -bool publish(MqttClient & client, | |
| 217 | +bool publish(PubSubClient & client, | |
| 209 | 218 | const char * topic, |
| 210 | - const char * payload, | |
| 219 | + const char * payload, | |
| 220 | + size_t payloadLength, | |
| 211 | 221 | bool retained) { |
| 212 | - if(!client.isConnected()) return false; | |
| 222 | + if(payloadLength == 0) return true; | |
| 213 | 223 | |
| 214 | - MqttClient::Message message; | |
| 215 | - message.qos = MqttClient::QOS0; | |
| 216 | - message.retained = retained; | |
| 217 | - message.dup = true; | |
| 218 | - message.payload = (void *)payload; | |
| 219 | - message.payloadLen = strlen(payload); | |
| 220 | - client.publish(topic, message); | |
| 221 | -Serial.printf("PUBLISH topic is '%s' message is '%s'\n", topic, message.payload); | |
| 224 | + if(!client.beginPublish(topic, payloadLength, retained)) return false; | |
| 225 | + size_t bytesWritten = client.write((const unsigned char *) payload, payloadLength); | |
| 222 | 226 | |
| 223 | - return true; | |
| 227 | + return client.endPublish() && bytesWritten == payloadLength; | |
| 224 | 228 | } |
| 225 | 229 | |
| 226 | 230 | |
| 231 | +bool publish(PubSubClient & client, | |
| 232 | + const char * topic, | |
| 233 | + const char * payload, | |
| 234 | + bool retained) { | |
| 235 | + size_t payloadLength = strlen(payload); | |
| 236 | + | |
| 237 | + return publish(client, topic, payload, payloadLength, retained); | |
| 238 | +} | |
| 239 | + | |
| 227 | 240 | bool discoveryMsgToJsonDoc(JsonDocument & discoveryMsgDoc, |
| 228 | 241 | const char * discoveryMsgJson) { |
| 229 | 242 | DeserializationError error = deserializeJson(discoveryMsgDoc, discoveryMsgJson); |
| ... | ... | @@ -243,35 +256,30 @@ bool discoveryMsgToJsonDoc(JsonDocument & discoveryMsgDoc, |
| 243 | 256 | * Home Assistant MQTT components like 'climate', 'sensor' or 'binary_sensor' and object a unique ID to differentiate between e.g. two sensors in the same |
| 244 | 257 | * device. |
| 245 | 258 | */ |
| 246 | -bool addEntity(MqttClient & client, | |
| 259 | +bool addEntity(PubSubClient & client, | |
| 247 | 260 | const char * component, |
| 248 | 261 | const char * object, |
| 249 | 262 | JsonDocument & discoveryMsgDoc) { |
| 250 | - if(!client.isConnected()) return false; | |
| 263 | + if(!client.connected()) return false; | |
| 251 | 264 | |
| 252 | 265 | size_t jsonSize = measureJson(discoveryMsgDoc); |
| 253 | 266 | char discoveryMsgJson[jsonSize + 1]; |
| 254 | 267 | serializeJson(discoveryMsgDoc, discoveryMsgJson, sizeof discoveryMsgJson); |
| 255 | 268 | |
| 256 | - ESP_LOGI(TAG, "Discovery topic is '%s'\n", (String("homeassistant/") + String(component) + String("/") + String(chipID()) + String("/") + String(object) + String("/config")).c_str()); | |
| 257 | - ESP_LOGI(TAG, "Discovery message is '%s'", discoveryMsgJson); | |
| 269 | + size_t topicSize = sizeof("homeassistant/") - 1 + strlen(component) + sizeof("/") - 1 + strlen(chipID()) + sizeof("/") - 1 + strlen(object) + sizeof("/config") - 1 + 1; | |
| 270 | + char topic[topicSize]; | |
| 271 | + sprintf(topic, "homeassistant/%s/%s/%s/config", component, chipID(), object); | |
| 258 | 272 | |
| 259 | - MqttClient::Message message; | |
| 260 | - message.qos = MqttClient::QOS0; | |
| 261 | - message.retained = false; | |
| 262 | - message.dup = false; | |
| 263 | - message.payload = (void*) discoveryMsgJson; | |
| 264 | - message.payloadLen = jsonSize; | |
| 265 | - client.publish((String("homeassistant/") + String(component) + String("/") + String(chipID()) + String("/") + String(object) + String("/config")).c_str(), message); | |
| 266 | -Serial.printf("PUBLISH topic is '%s' message is '%s'\n", (String("homeassistant/") + String(component) + String("/") + String(chipID()) + String("/") + String(object) + String("/config")).c_str(), message.payload); | |
| 273 | + ESP_LOGI(TAG, "Discovery topic is '%s'\n", topic); | |
| 274 | + ESP_LOGI(TAG, "Discovery message is '%s'", discoveryMsgJson); | |
| 267 | 275 | |
| 268 | - return true; | |
| 276 | + return publish(client, topic, discoveryMsgJson); | |
| 269 | 277 | } |
| 270 | 278 | |
| 271 | 279 | |
| 272 | 280 | // addEntity() |
| 273 | 281 | // Same as above but providing the discovery message as a const char * JSON |
| 274 | -bool addEntity(MqttClient & client, | |
| 282 | +bool addEntity(PubSubClient & client, | |
| 275 | 283 | const char * component, |
| 276 | 284 | const char * object, |
| 277 | 285 | const char * discoveryMsgJson) { |
| ... | ... | @@ -287,23 +295,14 @@ bool addEntity(MqttClient & client, |
| 287 | 295 | * Subscribe to the topic at at the given key in the discovery message. Before subscription the topic in the discovery |
| 288 | 296 | * message is expanded to the full topic by prefixing the "~" path. |
| 289 | 297 | */ |
| 290 | -bool subscribe(MqttClient & client, | |
| 291 | - MqttClient::MessageHandlerCbk cbk, | |
| 298 | +bool subscribe(PubSubClient & client, | |
| 292 | 299 | const char * discoveryMessage, |
| 293 | 300 | const char * key) { |
| 294 | 301 | const char * topic = topicByReference(key, discoveryMessage); |
| 295 | 302 | if(topic == nullptr) return false; |
| 296 | 303 | |
| 297 | - MqttClient::Error::type rc = client.subscribe(topic, MqttClient::QOS0, cbk); | |
| 298 | - if (rc != MqttClient::Error::SUCCESS) { | |
| 299 | - ESP_LOGE(TAG, "Subscribe error: %i for topic '%s'", rc, topic); | |
| 300 | - ESP_LOGE(TAG, "Drop connection"); | |
| 301 | - client.disconnect(); | |
| 304 | + ESP_LOGI(TAG, "Subscribe to '%s'", topic); | |
| 305 | +Serial.printf("Subscribe to '%s'\n", topic); | |
| 302 | 306 | |
| 303 | - return false; | |
| 304 | - } | |
| 305 | - ESP_LOGI(TAG, "Subscribed to '%s'", topic); | |
| 306 | -Serial.printf("Subscribed to '%s'\n", topic); | |
| 307 | - | |
| 308 | - return true; | |
| 309 | -} | |
| 310 | 307 | \ No newline at end of file |
| 308 | + return client.subscribe(topic); | |
| 309 | +} | ... | ... |
examples/Advanced_Thermostat/MQTTHelpers.h
| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | |
| 3 | 3 | |
| 4 | 4 | #include <Arduino.h> |
| 5 | -#include <MqttClient.h> | |
| 5 | +#include <PubSubClient.h> | |
| 6 | 6 | #include <ArduinoJson.h> |
| 7 | 7 | |
| 8 | 8 | |
| ... | ... | @@ -12,20 +12,6 @@ const char * chipID(); |
| 12 | 12 | |
| 13 | 13 | const char * shortID(); |
| 14 | 14 | |
| 15 | - | |
| 16 | -// ============== Object to supply system functions ============================ | |
| 17 | -class System: public MqttClient::System { | |
| 18 | -public: | |
| 19 | - | |
| 20 | - unsigned long millis() const { | |
| 21 | - return ::millis(); | |
| 22 | - } | |
| 23 | - | |
| 24 | - void yield(void) { | |
| 25 | - ::yield(); | |
| 26 | - } | |
| 27 | -}; | |
| 28 | - | |
| 29 | 15 | void discoveryMessageSetIDs(char * discoveryMsgJson); |
| 30 | 16 | |
| 31 | 17 | const char * topicByReference(const char * key, |
| ... | ... | @@ -33,12 +19,18 @@ const char * topicByReference(const char * key, |
| 33 | 19 | |
| 34 | 20 | bool validJson(const char * discoveryMsgJson); |
| 35 | 21 | |
| 36 | -bool connectMQTT(MqttClient & client, | |
| 22 | +bool connectMQTT(PubSubClient & client, | |
| 37 | 23 | const char * clientID, |
| 38 | 24 | const char * user, |
| 39 | 25 | const char * password); |
| 40 | 26 | |
| 41 | -bool publish(MqttClient & client, | |
| 27 | +bool publish(PubSubClient & client, | |
| 28 | + const char * topic, | |
| 29 | + const char * payload, | |
| 30 | + size_t payloadLength, | |
| 31 | + bool retained = false); | |
| 32 | + | |
| 33 | +bool publish(PubSubClient & client, | |
| 42 | 34 | const char * topic, |
| 43 | 35 | const char * payload, |
| 44 | 36 | bool retained = false); |
| ... | ... | @@ -46,17 +38,16 @@ bool publish(MqttClient & client, |
| 46 | 38 | bool discoveryMsgToJsonDoc(JsonDocument & discoveryMsgDoc, |
| 47 | 39 | const char * discoveryMsgJson); |
| 48 | 40 | |
| 49 | -bool addEntity(MqttClient & client, | |
| 41 | +bool addEntity(PubSubClient & client, | |
| 50 | 42 | const char * component, |
| 51 | 43 | const char * object, |
| 52 | 44 | JsonDocument & discoveryMsgDoc); |
| 53 | 45 | |
| 54 | -bool addEntity(MqttClient & client, | |
| 46 | +bool addEntity(PubSubClient & client, | |
| 55 | 47 | const char * component, |
| 56 | 48 | const char * object, |
| 57 | 49 | const char * discoveryMsgJson); |
| 58 | 50 | |
| 59 | -bool subscribe(MqttClient & client, | |
| 60 | - MqttClient::MessageHandlerCbk cbk, | |
| 51 | +bool subscribe(PubSubClient & client, | |
| 61 | 52 | const char * discoveryMessage, |
| 62 | 53 | const char * key); |
| 63 | 54 | \ No newline at end of file | ... | ... |
examples/Advanced_Thermostat/OpenThermHelpers.cpp
| ... | ... | @@ -134,7 +134,7 @@ bool readSecondaryConfiguration(OpenTherm & thermostat, |
| 134 | 134 | return true; |
| 135 | 135 | } |
| 136 | 136 | |
| 137 | - configurationFlags = OpenTherm::CONFIGURATION_FLAGS(flags); | |
| 137 | + configurationFlags = (OpenTherm::CONFIGURATION_FLAGS) flags; | |
| 138 | 138 | |
| 139 | 139 | return true; |
| 140 | 140 | } else { | ... | ... |
examples/Advanced_Thermostat/ThermoStateMachine.cpp
| ... | ... | @@ -25,19 +25,20 @@ bool ThermoStateMachine::connected() { |
| 25 | 25 | |
| 26 | 26 | |
| 27 | 27 | void ThermoStateMachine::initPrimaryFlags(OpenTherm::CONFIGURATION_FLAGS configurationFlags) { |
| 28 | - _configurationFlags = uint8_t(configurationFlags); | |
| 28 | + _configurationFlags = (uint8_t) configurationFlags; | |
| 29 | 29 | _primaryFlags = 0; |
| 30 | 30 | // Enable DHW if boiler is capable of DHW |
| 31 | - if(_configurationFlags & uint8_t(OpenTherm::CONFIGURATION_FLAGS::SECONDARY_DHW_PRESENT)) { | |
| 32 | - _primaryFlags |= uint8_t(OpenTherm::STATUS_FLAGS::PRIMARY_DHW_ENABLE); | |
| 31 | + if(_configurationFlags & ((uint8_t) OpenTherm::CONFIGURATION_FLAGS::SECONDARY_DHW_PRESENT)) { | |
| 32 | + _primaryFlags |= (uint8_t) OpenTherm::STATUS_FLAGS::PRIMARY_DHW_ENABLE; | |
| 33 | 33 | } |
| 34 | 34 | // If boiler is capable of cooling, signal this by setting 'canCool' |
| 35 | - _canCool = (_configurationFlags & uint8_t(OpenTherm::CONFIGURATION_FLAGS::SECONDARY_COOLING)) != 0; | |
| 35 | + _canCool = (_configurationFlags & (uint8_t) OpenTherm::CONFIGURATION_FLAGS::SECONDARY_COOLING) != 0; | |
| 36 | 36 | |
| 37 | 37 | // Do not enable OTC |
| 38 | - // _primaryFlags |= uint8_t(OpenTherm::STATUS_FLAGS::PRIMARY_OTC_ENABLE); | |
| 38 | + // _primaryFlags |= (uint8_t) OpenTherm::STATUS_FLAGS::PRIMARY_OTC_ENABLE; | |
| 39 | 39 | |
| 40 | 40 | _state = ThermostatState::OFF; |
| 41 | + _state_c_str = "Off"; | |
| 41 | 42 | } |
| 42 | 43 | |
| 43 | 44 | |
| ... | ... | @@ -195,8 +196,11 @@ bool ThermoStateMachine::update(float ro |
| 195 | 196 | _previousRoomTemperatureSetpoint = roomTemperatureSetpoint; |
| 196 | 197 | |
| 197 | 198 | return false; |
| 199 | + } else if(_state == ThermostatState::OFF) { | |
| 200 | + | |
| 201 | + return false; | |
| 198 | 202 | } else { |
| 199 | - Serial.println("Unknown state"); | |
| 203 | + Serial.printf("Unknown state %d\n", _state); | |
| 200 | 204 | |
| 201 | 205 | return false; |
| 202 | 206 | } |
| ... | ... | @@ -263,5 +267,3 @@ bool ThermoStateMachine::getRoomTemperatureStale() { |
| 263 | 267 | const char * ThermoStateMachine::c_str() { |
| 264 | 268 | return _state_c_str; |
| 265 | 269 | } |
| 266 | - | |
| 267 | - | ... | ... |