mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 15:59:52 +03:00
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Added model type (Buderus, Sieger, Junkers, Nefit, Bosch, Worcester) to device names
|
- Added model type (Buderus, Sieger, Junkers, Nefit, Bosch, Worcester) to device names
|
||||||
- `boiler wwonetime` command from Telnet
|
- `boiler wwonetime` command from Telnet
|
||||||
- `set bus_id <ID>` to support multiple EMS-ESP circuits. Default is 0x0B to mimic a service key.
|
- `set bus_id <ID>` to support multiple EMS-ESP circuits. Default is 0x0B to mimic a service key.
|
||||||
|
- MQTT publish messages are queued and gracefully published every second to avoid TCP blocks
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- set boiler warm water temp on Junkers/Bosch HT3
|
- set boiler warm water temp on Junkers/Bosch HT3
|
||||||
@@ -28,7 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- `autodetect scan`
|
- `autodetect scan`
|
||||||
|
- `mqttlog` and showing MQTT log in the web interface - no point showing history of previous mqtt publishes in ESP's precious memory. For debugging recommend using MQTT Explorer or another external tool.
|
||||||
|
|
||||||
## [1.9.4] 15-12-2019
|
## [1.9.4] 15-12-2019
|
||||||
|
|
||||||
|
|||||||
297
src/MyESP.cpp
297
src/MyESP.cpp
@@ -23,6 +23,15 @@ union system_rtcmem_t {
|
|||||||
uint32_t value;
|
uint32_t value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct mqtt_message_t {
|
||||||
|
uint16_t packetId = 0;
|
||||||
|
char * topic = nullptr;
|
||||||
|
char * payload = nullptr;
|
||||||
|
bool retain = false;
|
||||||
|
uint8_t retry_count = 0;
|
||||||
|
};
|
||||||
|
std::deque<mqtt_message_t> _mqtt_queue;
|
||||||
|
|
||||||
// nasty global variables that are called from internal ws functions
|
// nasty global variables that are called from internal ws functions
|
||||||
static char * _general_password = nullptr;
|
static char * _general_password = nullptr;
|
||||||
static bool _shouldRestart = false;
|
static bool _shouldRestart = false;
|
||||||
@@ -111,14 +120,6 @@ MyESP::MyESP() {
|
|||||||
|
|
||||||
// get the build time
|
// get the build time
|
||||||
_buildTime = _getBuildTime();
|
_buildTime = _getBuildTime();
|
||||||
|
|
||||||
// MQTT log
|
|
||||||
for (uint8_t i = 0; i < MYESP_MQTTLOG_MAX; i++) {
|
|
||||||
MQTT_log[i].type = 0;
|
|
||||||
MQTT_log[i].timestamp = 0;
|
|
||||||
MQTT_log[i].topic = nullptr;
|
|
||||||
MQTT_log[i].payload = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MyESP::~MyESP() {
|
MyESP::~MyESP() {
|
||||||
@@ -383,21 +384,17 @@ bool MyESP::mqttSubscribe(const char * topic) {
|
|||||||
if (mqttClient.connected() && (strlen(topic) > 0)) {
|
if (mqttClient.connected() && (strlen(topic) > 0)) {
|
||||||
char * topic_s = _mqttTopic(topic);
|
char * topic_s = _mqttTopic(topic);
|
||||||
|
|
||||||
uint16_t packet_id = mqttClient.subscribe(topic_s, _mqtt_qos);
|
|
||||||
#ifdef MYESP_DEBUG
|
#ifdef MYESP_DEBUG
|
||||||
myDebug_P(PSTR("[MQTT] Subscribing to %s"), topic_s);
|
myDebug_P(PSTR("[MQTT] Subscribing to %s"), topic_s);
|
||||||
#endif
|
#endif
|
||||||
|
uint16_t packet_id = mqttClient.subscribe(topic_s, _mqtt_qos);
|
||||||
if (packet_id) {
|
if (!packet_id) {
|
||||||
// add to mqtt log
|
|
||||||
_addMQTTLog(topic_s, "", MYESP_MQTTLOGTYPE_SUBSCRIBE); // Has an empty payload for now
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
myDebug_P(PSTR("[MQTT] Error subscribing to %s, error %d"), _mqttTopic(topic), packet_id);
|
myDebug_P(PSTR("[MQTT] Error subscribing to %s, error %d"), _mqttTopic(topic), packet_id);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false; // didn't work
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MQTT unsubscribe
|
// MQTT unsubscribe
|
||||||
@@ -408,33 +405,152 @@ void MyESP::mqttUnsubscribe(const char * topic) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// print MQTT log
|
||||||
|
void MyESP::_printMQTTLog() {
|
||||||
|
myDebug_P(PSTR("MQTT publish queue:"));
|
||||||
|
|
||||||
|
if (_mqtt_queue.empty()) {
|
||||||
|
myDebug_P(PSTR(" queue is empty!"));
|
||||||
|
myDebug_P(PSTR("")); // newline
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (mqtt_message_t it : _mqtt_queue) {
|
||||||
|
if (it.retry_count == 0) {
|
||||||
|
if (it.packetId == 0) {
|
||||||
|
myDebug_P(PSTR(" topic=%s payload=%s"), it.topic, it.payload);
|
||||||
|
} else {
|
||||||
|
myDebug_P(PSTR(" topic=%s payload=%s (pid %d)"), it.topic, it.payload, it.packetId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
myDebug_P(PSTR(" topic=%s payload=%s (pid %d, retry #%d)"), it.topic, it.payload, it.packetId, it.retry_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
myDebug_P(PSTR("")); // newline
|
||||||
|
}
|
||||||
|
|
||||||
// Publish using the user's custom retain flag
|
// Publish using the user's custom retain flag
|
||||||
bool MyESP::mqttPublish(const char * topic, const char * payload) {
|
void MyESP::mqttPublish(const char * topic, const char * payload) {
|
||||||
return mqttPublish(topic, payload, _mqtt_retain);
|
mqttPublish(topic, payload, _mqtt_retain);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MQTT Publish
|
// MQTT Publish
|
||||||
// returns true if all good
|
void MyESP::mqttPublish(const char * topic, const char * payload, bool retain) {
|
||||||
bool MyESP::mqttPublish(const char * topic, const char * payload, bool retain) {
|
if (!_hasValue(topic)) {
|
||||||
if (!mqttClient.connected() || !_hasValue(topic) || !_hasValue(payload)) {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_mqttQueue(topic, payload, retain); // queue the message
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MyESP::_mqttQueue(const char * topic, const char * payload, bool retain) {
|
||||||
|
// Queue is not meant to send message "offline"
|
||||||
|
// We must prevent the queue does not get full while offline
|
||||||
|
if (!mqttClient.connected() || (_mqtt_queue.size() >= MQTT_QUEUE_MAX_SIZE)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create a new message
|
||||||
|
mqtt_message_t element;
|
||||||
|
element.topic = strdup(topic);
|
||||||
|
element.retain = retain;
|
||||||
|
element.packetId = 0;
|
||||||
|
element.retry_count = 0;
|
||||||
|
if (NULL != payload) {
|
||||||
|
element.payload = strdup(payload);
|
||||||
|
}
|
||||||
#ifdef MYESP_DEBUG
|
#ifdef MYESP_DEBUG
|
||||||
myDebug_P(PSTR("[MQTT] Sending publish to %s with payload %s"), _mqttTopic(topic), payload);
|
myDebug_P(PSTR("[MQTT] Adding to queue: #%d [%s] %s"), _mqtt_queue.size(), element.topic, element.payload);
|
||||||
#endif
|
#endif
|
||||||
uint16_t packet_id = mqttClient.publish(_mqttTopic(topic), _mqtt_qos, retain, payload);
|
_mqtt_queue.push_back(element);
|
||||||
|
|
||||||
if (packet_id) {
|
|
||||||
_addMQTTLog(topic, payload, MYESP_MQTTLOGTYPE_PUBLISH); // add to the log
|
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// called when an MQTT Publish ACK is received
|
||||||
|
// check if ACK matches the last Publish we sent, if not report an error
|
||||||
|
// and always remove from queue
|
||||||
|
void MyESP::_mqttOnPublish(uint16_t packetId) {
|
||||||
|
#ifdef MYESP_DEBUG
|
||||||
|
myDebug_P(PSTR("[MQTT] Publish ACK for PID %d"), packetId);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// find the MQTT message in the queue and remove it
|
||||||
|
if ((_mqtt_queue.empty()) || (_mqtt_qos == 0)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// it failed, we should try again https://github.com/proddy/EMS-ESP/issues/264
|
mqtt_message_t element = _mqtt_queue.front(); // get top of list
|
||||||
myDebug_P(PSTR("[MQTT] Error publishing to %s with payload %s [error %d]"), _mqttTopic(topic), payload, packet_id);
|
|
||||||
_mqtt_publish_fails++; // increment failure counter
|
|
||||||
|
|
||||||
return false; // failed
|
// if the last published failed, don't bother checking it. wait for the re-try
|
||||||
|
if (element.packetId == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.packetId == packetId) {
|
||||||
|
#ifdef MYESP_DEBUG
|
||||||
|
myDebug_P(PSTR("[MQTT] Found PID %d. Removing from queue."), packetId);
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
#ifdef MYESP_DEBUG
|
||||||
|
myDebug_P(PSTR("[MQTT] Mismatch, expecting PID %d, got %d."), element.packetId, packetId);
|
||||||
|
_mqtt_publish_fails++; // increment error count
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
_mqttRemoveLastPublish(); // always remove
|
||||||
|
}
|
||||||
|
|
||||||
|
// removes top of queue
|
||||||
|
void MyESP::_mqttRemoveLastPublish() {
|
||||||
|
mqtt_message_t element = _mqtt_queue.front(); // get top of list
|
||||||
|
free(element.topic);
|
||||||
|
if (element.payload) {
|
||||||
|
free(element.payload);
|
||||||
|
}
|
||||||
|
_mqtt_queue.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
// take top from queue and try and publish it
|
||||||
|
void MyESP::_mqttPublishQueue() {
|
||||||
|
if ((!mqttClient.connected()) || (_mqtt_queue.empty())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mqtt_message_t element = _mqtt_queue.front(); // fetch from queue
|
||||||
|
|
||||||
|
// try and publish it
|
||||||
|
uint16_t packet_id = mqttClient.publish(_mqttTopic(element.topic), _mqtt_qos, element.retain, element.payload);
|
||||||
|
#ifdef MYESP_DEBUG
|
||||||
|
myDebug_P(PSTR("[MQTT] Sent publish (attempt #%d, pid %d) [%s] [%s]"), element.retry_count, packet_id, _mqttTopic(element.topic), element.payload);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (packet_id == 0) {
|
||||||
|
// it failed
|
||||||
|
// if we retried 3 times, give up. remove from queue
|
||||||
|
if (element.retry_count == 2) {
|
||||||
|
myDebug_P(PSTR("[MQTT] Failed to publish to %s with payload %s"), _mqttTopic(element.topic), element.payload);
|
||||||
|
_mqtt_publish_fails++; // increment failure counter
|
||||||
|
_mqttRemoveLastPublish();
|
||||||
|
} else {
|
||||||
|
_mqtt_queue[0].retry_count++;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we have ACK set with QOS 1 or 2, leave on queue and let the ACK process remove it
|
||||||
|
// but add the packet_id so we can check it later
|
||||||
|
if (_mqtt_qos != 0) {
|
||||||
|
_mqtt_queue[0].packetId = packet_id;
|
||||||
|
#ifdef MYESP_DEBUG
|
||||||
|
myDebug_P(PSTR("[MQTT] Setting packetID %d"), packet_id);
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete it from queue
|
||||||
|
_mqttRemoveLastPublish();
|
||||||
}
|
}
|
||||||
|
|
||||||
// MQTT onConnect - when a connect is established
|
// MQTT onConnect - when a connect is established
|
||||||
@@ -456,7 +572,7 @@ void MyESP::_mqttOnConnect() {
|
|||||||
mqttPublish(MQTT_TOPIC_START, MQTT_TOPIC_START_PAYLOAD, false);
|
mqttPublish(MQTT_TOPIC_START, MQTT_TOPIC_START_PAYLOAD, false);
|
||||||
|
|
||||||
// send heartbeat if enabled
|
// send heartbeat if enabled
|
||||||
_heartbeatCheck(true);
|
heartbeatCheck(true);
|
||||||
|
|
||||||
// call custom function to handle mqtt receives
|
// call custom function to handle mqtt receives
|
||||||
(_mqtt_callback_f)(MQTT_CONNECT_EVENT, nullptr, nullptr);
|
(_mqtt_callback_f)(MQTT_CONNECT_EVENT, nullptr, nullptr);
|
||||||
@@ -493,7 +609,8 @@ void MyESP::_mqtt_setup() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) { myDebug_P(PSTR("[MQTT] Subscribe ACK for PID %d"), packetId); });
|
//mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) { myDebug_P(PSTR("[MQTT] Subscribe ACK for PID %d"), packetId); });
|
||||||
//mqttClient.onPublish([this](uint16_t packetId) { myDebug_P(PSTR("[MQTT] Publish ACK for PID %d"), packetId); });
|
|
||||||
|
mqttClient.onPublish([this](uint16_t packetId) { _mqttOnPublish(packetId); });
|
||||||
|
|
||||||
mqttClient.onMessage([this](char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
|
mqttClient.onMessage([this](char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
|
||||||
_mqttOnMessage(topic, payload, len);
|
_mqttOnMessage(topic, payload, len);
|
||||||
@@ -719,7 +836,7 @@ void MyESP::_consoleShowHelp() {
|
|||||||
myDebug_P(PSTR("*"));
|
myDebug_P(PSTR("*"));
|
||||||
myDebug_P(PSTR("* Commands:"));
|
myDebug_P(PSTR("* Commands:"));
|
||||||
myDebug_P(PSTR("* ?/help=show commands, CTRL-D/quit=end telnet session"));
|
myDebug_P(PSTR("* ?/help=show commands, CTRL-D/quit=end telnet session"));
|
||||||
myDebug_P(PSTR("* set, system, restart, mqttlog [all], kick, save"));
|
myDebug_P(PSTR("* set, system, restart, mqttlog, kick, save"));
|
||||||
|
|
||||||
#ifdef CRASH
|
#ifdef CRASH
|
||||||
myDebug_P(PSTR("* crash <dump | clear | test [n]>"));
|
myDebug_P(PSTR("* crash <dump | clear | test [n]>"));
|
||||||
@@ -1048,7 +1165,7 @@ void MyESP::_telnetCommand(char * commandLine) {
|
|||||||
|
|
||||||
// print mqtt log command
|
// print mqtt log command
|
||||||
if (strcmp(ptrToCommandName, "mqttlog") == 0) {
|
if (strcmp(ptrToCommandName, "mqttlog") == 0) {
|
||||||
_printMQTTLog(wc != 1);
|
_printMQTTLog();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1461,14 +1578,14 @@ void MyESP::showSystemStats() {
|
|||||||
/*
|
/*
|
||||||
* Send heartbeat via MQTT with all system data
|
* Send heartbeat via MQTT with all system data
|
||||||
*/
|
*/
|
||||||
void MyESP::_heartbeatCheck(bool force) {
|
void MyESP::heartbeatCheck(bool force) {
|
||||||
static uint32_t last_heartbeat = 0;
|
static uint32_t last_heartbeat = 0;
|
||||||
|
|
||||||
if ((millis() - last_heartbeat > MYESP_HEARTBEAT_INTERVAL) || force) {
|
if ((millis() - last_heartbeat > MYESP_HEARTBEAT_INTERVAL) || force) {
|
||||||
last_heartbeat = millis();
|
last_heartbeat = millis();
|
||||||
|
|
||||||
#ifdef MYESP_DEBUG
|
#ifdef MYESP_DEBUG
|
||||||
_printHeap("[HEARTBEAT] ");
|
_printHeap("[HEARTBEAT]");
|
||||||
#endif
|
#endif
|
||||||
if (!isMQTTConnected() || !(_mqtt_heartbeat)) {
|
if (!isMQTTConnected() || !(_mqtt_heartbeat)) {
|
||||||
return;
|
return;
|
||||||
@@ -2498,30 +2615,6 @@ void MyESP::_sendStatus() {
|
|||||||
sprintf(uptime, "%d day%s %d hour%s %d minute%s %d second%s", d, (d == 1) ? "" : "s", h, (h == 1) ? "" : "s", m, (m == 1) ? "" : "s", sec, (sec == 1) ? "" : "s");
|
sprintf(uptime, "%d day%s %d hour%s %d minute%s %d second%s", d, (d == 1) ? "" : "s", h, (h == 1) ? "" : "s", m, (m == 1) ? "" : "s", sec, (sec == 1) ? "" : "s");
|
||||||
root["uptime"] = uptime;
|
root["uptime"] = uptime;
|
||||||
|
|
||||||
char topic_s[MQTT_MAX_TOPIC_SIZE] = {0};
|
|
||||||
if (_hasValue(_mqtt_base)) {
|
|
||||||
strlcpy(topic_s, _mqtt_base, sizeof(topic_s));
|
|
||||||
strlcat(topic_s, "/", sizeof(topic_s));
|
|
||||||
strlcat(topic_s, _general_hostname, sizeof(topic_s));
|
|
||||||
} else {
|
|
||||||
strlcpy(topic_s, _general_hostname, sizeof(topic_s));
|
|
||||||
}
|
|
||||||
strlcat(topic_s, "/", sizeof(topic_s));
|
|
||||||
root["mqttloghdr"] = topic_s;
|
|
||||||
|
|
||||||
// create MQTT log
|
|
||||||
JsonArray list = root.createNestedArray("mqttlog");
|
|
||||||
|
|
||||||
// only send Publish
|
|
||||||
for (uint8_t i = 0; i < MYESP_MQTTLOG_MAX; i++) {
|
|
||||||
if ((MQTT_log[i].type == 1) && (MQTT_log[i].topic != nullptr)) {
|
|
||||||
JsonObject item = list.createNestedObject();
|
|
||||||
item["topic"] = MQTT_log[i].topic;
|
|
||||||
item["payload"] = MQTT_log[i].payload;
|
|
||||||
item["time"] = MQTT_log[i].timestamp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char buffer[MQTT_MAX_PAYLOAD_SIZE_LARGE];
|
char buffer[MQTT_MAX_PAYLOAD_SIZE_LARGE];
|
||||||
size_t len = serializeJson(root, buffer);
|
size_t len = serializeJson(root, buffer);
|
||||||
|
|
||||||
@@ -2688,80 +2781,6 @@ void MyESP::_printHeap(const char * prefix) {
|
|||||||
100 * free_memory / total_memory);
|
100 * free_memory / total_memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
// print MQTT log - everything that was published last per topic
|
|
||||||
void MyESP::_printMQTTLog(bool show_sub = false) {
|
|
||||||
myDebug_P(PSTR("MQTT publish log:"));
|
|
||||||
uint8_t i;
|
|
||||||
|
|
||||||
for (i = 0; i < MYESP_MQTTLOG_MAX; i++) {
|
|
||||||
if ((MQTT_log[i].topic != nullptr) && (MQTT_log[i].type == MYESP_MQTTLOGTYPE_PUBLISH)) {
|
|
||||||
myDebug_P(PSTR(" (%02d:%02d:%02d) Topic: %s Payload: %s"),
|
|
||||||
to_hour(MQTT_log[i].timestamp),
|
|
||||||
to_minute(MQTT_log[i].timestamp),
|
|
||||||
to_second(MQTT_log[i].timestamp),
|
|
||||||
MQTT_log[i].topic,
|
|
||||||
MQTT_log[i].payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// show subscriptions
|
|
||||||
if (show_sub) {
|
|
||||||
myDebug_P(PSTR("")); // newline
|
|
||||||
myDebug_P(PSTR("MQTT subscriptions:"));
|
|
||||||
|
|
||||||
for (i = 0; i < MYESP_MQTTLOG_MAX; i++) {
|
|
||||||
if ((MQTT_log[i].topic != nullptr) && (MQTT_log[i].type == MYESP_MQTTLOGTYPE_SUBSCRIBE)) {
|
|
||||||
myDebug_P(PSTR(" Topic: %s"), MQTT_log[i].topic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
myDebug_P(PSTR("")); // newline
|
|
||||||
}
|
|
||||||
|
|
||||||
// add an MQTT log entry to our buffer
|
|
||||||
void MyESP::_addMQTTLog(const char * topic, const char * payload, const MYESP_MQTTLOGTYPE_t type) {
|
|
||||||
static uint8_t logCount = 0;
|
|
||||||
uint8_t logPointer = 0;
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
#ifdef MYESP_DEBUG
|
|
||||||
myDebug("_addMQTTLog [#%d] %s (%d) [%s] (%d)", logCount, topic, strlen(topic), payload, strlen(payload));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// find the topic
|
|
||||||
// topics must be unique for either publish or subscribe
|
|
||||||
while ((logPointer < MYESP_MQTTLOG_MAX) && (_hasValue(MQTT_log[logPointer].topic))) {
|
|
||||||
if ((strcmp(MQTT_log[logPointer].topic, topic) == 0) && (MQTT_log[logPointer].type == type)) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
logPointer++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if not found add it and increment next free space pointer
|
|
||||||
if (!found) {
|
|
||||||
logPointer = logCount;
|
|
||||||
if (++logCount == MYESP_MQTTLOG_MAX) {
|
|
||||||
logCount = 0; // rotate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete old record
|
|
||||||
if (MQTT_log[logPointer].topic) {
|
|
||||||
free(MQTT_log[logPointer].topic);
|
|
||||||
}
|
|
||||||
if (MQTT_log[logPointer].payload) {
|
|
||||||
free(MQTT_log[logPointer].payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
// and add new record
|
|
||||||
MQTT_log[logPointer].type = type;
|
|
||||||
MQTT_log[logPointer].topic = strdup(topic);
|
|
||||||
MQTT_log[logPointer].payload = strdup(payload);
|
|
||||||
MQTT_log[logPointer].timestamp = now();
|
|
||||||
}
|
|
||||||
|
|
||||||
// send UTC time via ws
|
// send UTC time via ws
|
||||||
void MyESP::_sendTime() {
|
void MyESP::_sendTime() {
|
||||||
StaticJsonDocument<MYESP_JSON_MAXSIZE_SMALL> doc;
|
StaticJsonDocument<MYESP_JSON_MAXSIZE_SMALL> doc;
|
||||||
@@ -2898,7 +2917,7 @@ void MyESP::begin(const char * app_hostname, const char * app_name, const char *
|
|||||||
void MyESP::loop() {
|
void MyESP::loop() {
|
||||||
_calculateLoad();
|
_calculateLoad();
|
||||||
_systemCheckLoop();
|
_systemCheckLoop();
|
||||||
_heartbeatCheck();
|
heartbeatCheck();
|
||||||
_bootupSequence(); // see if a reset was pressed during bootup
|
_bootupSequence(); // see if a reset was pressed during bootup
|
||||||
|
|
||||||
jw.loop(); // WiFi
|
jw.loop(); // WiFi
|
||||||
@@ -2911,6 +2930,14 @@ void MyESP::loop() {
|
|||||||
|
|
||||||
_mqttConnect(); // MQTT
|
_mqttConnect(); // MQTT
|
||||||
|
|
||||||
|
// every second check MQTT queue for publishing
|
||||||
|
static unsigned long lastMqttPoll = 0;
|
||||||
|
unsigned long currentMillis = millis();
|
||||||
|
if ((unsigned long)(currentMillis - lastMqttPoll) >= MQTT_PUBLISH_WAIT) {
|
||||||
|
_mqttPublishQueue();
|
||||||
|
lastMqttPoll = currentMillis;
|
||||||
|
}
|
||||||
|
|
||||||
// SysLog
|
// SysLog
|
||||||
uuid::loop();
|
uuid::loop();
|
||||||
syslog.loop();
|
syslog.loop();
|
||||||
@@ -2939,4 +2966,6 @@ void MyESP::loop() {
|
|||||||
delay(MYESP_DELAY); // some time to WiFi and everything else to catch up, calls yield, and also prevent overheating
|
delay(MYESP_DELAY); // some time to WiFi and everything else to catch up, calls yield, and also prevent overheating
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
MyESP myESP;
|
MyESP myESP;
|
||||||
|
|||||||
37
src/MyESP.h
37
src/MyESP.h
@@ -9,7 +9,7 @@
|
|||||||
#ifndef MyESP_h
|
#ifndef MyESP_h
|
||||||
#define MyESP_h
|
#define MyESP_h
|
||||||
|
|
||||||
#define MYESP_VERSION "1.2.28"
|
#define MYESP_VERSION "1.2.29"
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <ArduinoOTA.h>
|
#include <ArduinoOTA.h>
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <FS.h>
|
#include <FS.h>
|
||||||
#include <JustWifi.h>
|
#include <JustWifi.h>
|
||||||
|
#include <deque> // for MQTT publish queue
|
||||||
|
|
||||||
// SysLog
|
// SysLog
|
||||||
#include <uuid/common.h>
|
#include <uuid/common.h>
|
||||||
@@ -97,6 +98,8 @@ extern struct rst_info resetInfo;
|
|||||||
#define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT topic
|
#define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT topic
|
||||||
#define MQTT_MAX_PAYLOAD_SIZE 700 // max size of a JSON object. See https://arduinojson.org/v6/assistant/
|
#define MQTT_MAX_PAYLOAD_SIZE 700 // max size of a JSON object. See https://arduinojson.org/v6/assistant/
|
||||||
#define MQTT_MAX_PAYLOAD_SIZE_LARGE 2000 // max size of a large JSON object, like for sending MQTT log
|
#define MQTT_MAX_PAYLOAD_SIZE_LARGE 2000 // max size of a large JSON object, like for sending MQTT log
|
||||||
|
#define MQTT_QUEUE_MAX_SIZE 20 // Size of the MQTT queue
|
||||||
|
#define MQTT_PUBLISH_WAIT 1000 // every 2 seconds check MQTT queue
|
||||||
|
|
||||||
// Internal MQTT events
|
// Internal MQTT events
|
||||||
#define MQTT_CONNECT_EVENT 0
|
#define MQTT_CONNECT_EVENT 0
|
||||||
@@ -107,8 +110,6 @@ extern struct rst_info resetInfo;
|
|||||||
#define MYESP_JSON_MAXSIZE_MEDIUM 800 // for medium Dynamic json files
|
#define MYESP_JSON_MAXSIZE_MEDIUM 800 // for medium Dynamic json files
|
||||||
#define MYESP_JSON_MAXSIZE_SMALL 200 // for smaller Static json documents
|
#define MYESP_JSON_MAXSIZE_SMALL 200 // for smaller Static json documents
|
||||||
|
|
||||||
#define MYESP_MQTTLOG_MAX 60 // max number of log entries for MQTT publishes and subscribes
|
|
||||||
|
|
||||||
#define MYESP_MQTT_PAYLOAD_ON '1' // for MQTT switch on
|
#define MYESP_MQTT_PAYLOAD_ON '1' // for MQTT switch on
|
||||||
#define MYESP_MQTT_PAYLOAD_OFF '0' // for MQTT switch off
|
#define MYESP_MQTT_PAYLOAD_OFF '0' // for MQTT switch off
|
||||||
|
|
||||||
@@ -237,16 +238,6 @@ typedef enum {
|
|||||||
MYESP_BOOTSTATUS_RESETNEEDED = 3
|
MYESP_BOOTSTATUS_RESETNEEDED = 3
|
||||||
} MYESP_BOOTSTATUS_t; // boot messages
|
} MYESP_BOOTSTATUS_t; // boot messages
|
||||||
|
|
||||||
typedef enum { MYESP_MQTTLOGTYPE_NONE, MYESP_MQTTLOGTYPE_PUBLISH, MYESP_MQTTLOGTYPE_SUBSCRIBE } MYESP_MQTTLOGTYPE_t;
|
|
||||||
|
|
||||||
// for storing all MQTT publish messages
|
|
||||||
typedef struct {
|
|
||||||
uint8_t type; // 0=none, 1=publish, 2=subscribe
|
|
||||||
char * topic;
|
|
||||||
char * payload;
|
|
||||||
time_t timestamp;
|
|
||||||
} _MQTT_Log_t;
|
|
||||||
|
|
||||||
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
|
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
|
||||||
typedef std::function<void()> wifi_callback_f;
|
typedef std::function<void()> wifi_callback_f;
|
||||||
typedef std::function<void()> ota_callback_f;
|
typedef std::function<void()> ota_callback_f;
|
||||||
@@ -291,8 +282,8 @@ class MyESP {
|
|||||||
bool isMQTTConnected();
|
bool isMQTTConnected();
|
||||||
bool mqttSubscribe(const char * topic);
|
bool mqttSubscribe(const char * topic);
|
||||||
void mqttUnsubscribe(const char * topic);
|
void mqttUnsubscribe(const char * topic);
|
||||||
bool mqttPublish(const char * topic, const char * payload);
|
void mqttPublish(const char * topic, const char * payload);
|
||||||
bool mqttPublish(const char * topic, const char * payload, bool retain);
|
void mqttPublish(const char * topic, const char * payload, bool retain);
|
||||||
void setMQTT(mqtt_callback_f callback);
|
void setMQTT(mqtt_callback_f callback);
|
||||||
|
|
||||||
// OTA
|
// OTA
|
||||||
@@ -341,22 +332,21 @@ class MyESP {
|
|||||||
bool _have_ntp_time;
|
bool _have_ntp_time;
|
||||||
unsigned long getSystemTime();
|
unsigned long getSystemTime();
|
||||||
void heartbeatPrint();
|
void heartbeatPrint();
|
||||||
|
void heartbeatCheck(bool force = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// mqtt
|
// mqtt
|
||||||
void _mqttOnMessage(char * topic, char * payload, size_t len);
|
void _mqttOnMessage(char * topic, char * payload, size_t len);
|
||||||
|
void _mqttOnPublish(uint16_t packetId);
|
||||||
void _mqttConnect();
|
void _mqttConnect();
|
||||||
void _mqtt_setup();
|
void _mqtt_setup();
|
||||||
void _mqttOnConnect();
|
void _mqttOnConnect();
|
||||||
void _sendStart();
|
void _sendStart();
|
||||||
char * _mqttTopic(const char * topic);
|
char * _mqttTopic(const char * topic);
|
||||||
|
bool _mqttQueue(const char * topic, const char * payload, bool retain);
|
||||||
// mqtt log
|
void _printMQTTLog();
|
||||||
_MQTT_Log_t MQTT_log[MYESP_MQTTLOG_MAX]; // log for publish and subscribe messages
|
void _mqttPublishQueue();
|
||||||
|
void _mqttRemoveLastPublish();
|
||||||
void _printMQTTLog(bool show_sub);
|
|
||||||
void _addMQTTLog(const char * topic, const char * payload, const MYESP_MQTTLOGTYPE_t type);
|
|
||||||
|
|
||||||
AsyncMqttClient mqttClient; // the MQTT class
|
AsyncMqttClient mqttClient; // the MQTT class
|
||||||
uint32_t _mqtt_reconnect_delay;
|
uint32_t _mqtt_reconnect_delay;
|
||||||
mqtt_callback_f _mqtt_callback_f;
|
mqtt_callback_f _mqtt_callback_f;
|
||||||
@@ -489,9 +479,6 @@ class MyESP {
|
|||||||
uint32_t _getInitialFreeHeap();
|
uint32_t _getInitialFreeHeap();
|
||||||
uint32_t _getUsedHeap();
|
uint32_t _getUsedHeap();
|
||||||
|
|
||||||
// heartbeat
|
|
||||||
void _heartbeatCheck(bool force = false);
|
|
||||||
|
|
||||||
// web
|
// web
|
||||||
web_callback_f _web_callback_f;
|
web_callback_f _web_callback_f;
|
||||||
const char * _http_username;
|
const char * _http_username;
|
||||||
|
|||||||
@@ -930,7 +930,7 @@ void publishEMSValues(bool force) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Publish shower data
|
// Publish shower data
|
||||||
bool do_publishShowerData() {
|
void do_publishShowerData() {
|
||||||
StaticJsonDocument<200> doc;
|
StaticJsonDocument<200> doc;
|
||||||
JsonObject rootShower = doc.to<JsonObject>();
|
JsonObject rootShower = doc.to<JsonObject>();
|
||||||
rootShower[TOPIC_SHOWER_TIMER] = EMSESP_Settings.shower_timer ? "1" : "0";
|
rootShower[TOPIC_SHOWER_TIMER] = EMSESP_Settings.shower_timer ? "1" : "0";
|
||||||
@@ -953,24 +953,20 @@ bool do_publishShowerData() {
|
|||||||
myDebugLog("Publishing shower data via MQTT");
|
myDebugLog("Publishing shower data via MQTT");
|
||||||
|
|
||||||
// Publish MQTT forcing retain to be off
|
// Publish MQTT forcing retain to be off
|
||||||
return (myESP.mqttPublish(TOPIC_SHOWER_DATA, data, false));
|
myESP.mqttPublish(TOPIC_SHOWER_DATA, data, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// call PublishValues with forcing forcing
|
// call PublishValues with forcing forcing
|
||||||
void do_publishValues() {
|
void do_publishValues() {
|
||||||
if (EMSESP_Settings.publish_time == -1) {
|
if (EMSESP_Settings.publish_time == -1) {
|
||||||
myDebugLog("publish_time is set to -1. Publishing disabled.");
|
myDebugLog("Publishing is disabled.");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// automatic mode
|
|
||||||
if (EMSESP_Settings.publish_time == 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
myDebugLog("Starting scheduled MQTT publish...");
|
myDebugLog("Starting scheduled MQTT publish...");
|
||||||
publishEMSValues(false);
|
publishEMSValues(false);
|
||||||
publishSensorValues();
|
publishSensorValues();
|
||||||
|
myESP.heartbeatCheck(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// callback to light up the LED, called via Ticker every second
|
// callback to light up the LED, called via Ticker every second
|
||||||
@@ -1162,11 +1158,13 @@ MYESP_FSACTION_t SetListCallback(MYESP_FSACTION_t action, uint8_t wc, const char
|
|||||||
// shower timer
|
// shower timer
|
||||||
if ((strcmp(setting, "shower_timer") == 0) && (wc == 2)) {
|
if ((strcmp(setting, "shower_timer") == 0) && (wc == 2)) {
|
||||||
if (strcmp(value, "on") == 0) {
|
if (strcmp(value, "on") == 0) {
|
||||||
|
do_publishShowerData();
|
||||||
EMSESP_Settings.shower_timer = true;
|
EMSESP_Settings.shower_timer = true;
|
||||||
ok = do_publishShowerData() ? MYESP_FSACTION_OK : MYESP_FSACTION_ERR;
|
ok = MYESP_FSACTION_OK;
|
||||||
} else if (strcmp(value, "off") == 0) {
|
} else if (strcmp(value, "off") == 0) {
|
||||||
|
do_publishShowerData();
|
||||||
EMSESP_Settings.shower_timer = false;
|
EMSESP_Settings.shower_timer = false;
|
||||||
ok = do_publishShowerData() ? MYESP_FSACTION_OK : MYESP_FSACTION_ERR;
|
ok = MYESP_FSACTION_OK;
|
||||||
} else {
|
} else {
|
||||||
myDebug_P(PSTR("Error. Usage: set shower_timer <on | off>"));
|
myDebug_P(PSTR("Error. Usage: set shower_timer <on | off>"));
|
||||||
}
|
}
|
||||||
@@ -1175,11 +1173,13 @@ MYESP_FSACTION_t SetListCallback(MYESP_FSACTION_t action, uint8_t wc, const char
|
|||||||
// shower alert
|
// shower alert
|
||||||
if ((strcmp(setting, "shower_alert") == 0) && (wc == 2)) {
|
if ((strcmp(setting, "shower_alert") == 0) && (wc == 2)) {
|
||||||
if (strcmp(value, "on") == 0) {
|
if (strcmp(value, "on") == 0) {
|
||||||
|
do_publishShowerData();
|
||||||
EMSESP_Settings.shower_alert = true;
|
EMSESP_Settings.shower_alert = true;
|
||||||
ok = do_publishShowerData() ? MYESP_FSACTION_OK : MYESP_FSACTION_ERR;
|
ok = MYESP_FSACTION_OK;
|
||||||
} else if (strcmp(value, "off") == 0) {
|
} else if (strcmp(value, "off") == 0) {
|
||||||
|
do_publishShowerData();
|
||||||
EMSESP_Settings.shower_alert = false;
|
EMSESP_Settings.shower_alert = false;
|
||||||
ok = do_publishShowerData() ? MYESP_FSACTION_OK : MYESP_FSACTION_ERR;
|
ok = MYESP_FSACTION_OK;
|
||||||
} else {
|
} else {
|
||||||
myDebug_P(PSTR("Error. Usage: set shower_alert <on | off>"));
|
myDebug_P(PSTR("Error. Usage: set shower_alert <on | off>"));
|
||||||
}
|
}
|
||||||
@@ -1368,7 +1368,6 @@ void TelnetCommandCallback(uint8_t wc, const char * commandLine) {
|
|||||||
|
|
||||||
if (strcmp(first_cmd, "devices") == 0) {
|
if (strcmp(first_cmd, "devices") == 0) {
|
||||||
if (wc == 1) {
|
if (wc == 1) {
|
||||||
// print
|
|
||||||
ems_printDevices();
|
ems_printDevices();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1628,7 +1628,7 @@ void _process_ISM1Set(_EMS_RxTelegram * EMS_RxTelegram) {
|
|||||||
// only trigger if at offset 6
|
// only trigger if at offset 6
|
||||||
_setValue(EMS_RxTelegram, &EMS_SolarModule.setpoint_maxBottomTemp, 6);
|
_setValue(EMS_RxTelegram, &EMS_SolarModule.setpoint_maxBottomTemp, 6);
|
||||||
|
|
||||||
// TODO: we may need to convert this to a single byte like
|
// Note: we may need to convert this to a single byte like
|
||||||
// EMS_SolarModule.setpoint_maxBottomTemp = EMS_RxTelegram->data[0];
|
// EMS_SolarModule.setpoint_maxBottomTemp = EMS_RxTelegram->data[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1932,7 +1932,7 @@ void ems_discoverModels() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print the Tx queue - for debugging
|
* Print the Tx queue
|
||||||
*/
|
*/
|
||||||
void ems_printTxQueue() {
|
void ems_printTxQueue() {
|
||||||
_EMS_TxTelegram EMS_TxTelegram;
|
_EMS_TxTelegram EMS_TxTelegram;
|
||||||
@@ -2254,10 +2254,10 @@ void ems_printDevices() {
|
|||||||
it->version);
|
it->version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
myDebug_P(PSTR("")); // newline
|
|
||||||
|
|
||||||
if (have_unknowns) {
|
if (have_unknowns) {
|
||||||
myDebug_P(PSTR("One or more devices are not recognized by EMS-ESP. Please report this in GitHub."));
|
myDebug_P(PSTR("")); // newline
|
||||||
|
myDebug_P(PSTR("One or more devices are not recognized by EMS-ESP. Please report this back in GitHub."));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
myDebug_P(PSTR("No devices were recognized. This may be because Tx is disabled or failing."));
|
myDebug_P(PSTR("No devices were recognized. This may be because Tx is disabled or failing."));
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
#define APP_VERSION "1.9.5b37"
|
#define APP_VERSION "1.9.5b38"
|
||||||
|
|||||||
@@ -508,16 +508,6 @@
|
|||||||
<table class="table table-hover table-striped table-condensed" border=1>
|
<table class="table table-hover table-striped table-condensed" border=1>
|
||||||
<caption>MQTT <div id="mqttconnected"></div> <div id="mqttheartbeat"></div>
|
<caption>MQTT <div id="mqttconnected"></div> <div id="mqttheartbeat"></div>
|
||||||
</caption>
|
</caption>
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Time</th>
|
|
||||||
<th>Topic<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
|
||||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
|
||||||
id="mqttloghdr"></i></th>
|
|
||||||
<th>Payload</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="mqttlog"></tbody>
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -391,40 +391,6 @@ function listStats() {
|
|||||||
document.getElementById("mqttheartbeat").className = "label label-primary";
|
document.getElementById("mqttheartbeat").className = "label label-primary";
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("mqttloghdr").setAttribute('data-content', "Topics are prefixed with " + ajaxobj.mqttloghdr);
|
|
||||||
|
|
||||||
var mtable = document.getElementById("mqttlog");
|
|
||||||
var obj = ajaxobj.mqttlog;
|
|
||||||
var tr, td;
|
|
||||||
|
|
||||||
for (var i = 0; i < obj.length; i++) {
|
|
||||||
tr = document.createElement("tr");
|
|
||||||
|
|
||||||
td = document.createElement("td");
|
|
||||||
|
|
||||||
if (obj[i].time < 1563300000) {
|
|
||||||
td.innerHTML = "(" + obj[i].time + ")";
|
|
||||||
} else {
|
|
||||||
var vuepoch = new Date(obj[i].time * 1000);
|
|
||||||
td.innerHTML = vuepoch.getUTCFullYear() +
|
|
||||||
"-" + twoDigits(vuepoch.getUTCMonth() + 1) +
|
|
||||||
"-" + twoDigits(vuepoch.getUTCDate()) +
|
|
||||||
" " + twoDigits(vuepoch.getUTCHours()) +
|
|
||||||
":" + twoDigits(vuepoch.getUTCMinutes()) +
|
|
||||||
":" + twoDigits(vuepoch.getUTCSeconds());
|
|
||||||
}
|
|
||||||
tr.appendChild(td);
|
|
||||||
|
|
||||||
td = document.createElement("td");
|
|
||||||
td.innerHTML = obj[i].topic
|
|
||||||
tr.appendChild(td);
|
|
||||||
|
|
||||||
td = document.createElement("td");
|
|
||||||
td.innerHTML = obj[i].payload
|
|
||||||
tr.appendChild(td);
|
|
||||||
|
|
||||||
mtable.appendChild(tr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContent(contentname) {
|
function getContent(contentname) {
|
||||||
|
|||||||
@@ -119,17 +119,7 @@ function sendStatus() {
|
|||||||
"systemload": 0,
|
"systemload": 0,
|
||||||
"mqttconnected": true,
|
"mqttconnected": true,
|
||||||
"mqttheartbeat": false,
|
"mqttheartbeat": false,
|
||||||
"uptime": "0 days 0 hours 1 minute 45 seconds",
|
"uptime": "0 days 0 hours 1 minute 45 seconds"
|
||||||
"mqttloghdr": "home/ems-esp/",
|
|
||||||
"mqttlog": [
|
|
||||||
{ "topic": "start", "payload": "start", "time": 1565956388 },
|
|
||||||
{ "topic": "shower_timer", "payload": "1", "time": 1565956388 },
|
|
||||||
{ "topic": "shower_alert", "payload": "0", "time": 1565956388 },
|
|
||||||
{ "topic": "boiler_data", "payload": "{\"wWComfort\":\"Hot\",\"wWSelTemp\":60,\"selFlowTemp\":5,\"selBurnPow\":0,\"curBurnPow\":0,\"pumpMod\":0,\"wWCurTmp\":48.4,\"wWCurFlow\":0,\"curFlowTemp\":49.3,\"retTemp\":49.3,\"sysPress\":1.8,\"boilTemp\":50.5,\"wWActivated\":\"on\",\"burnGas\":\"off\",\"heatPmp\":\"off\",\"fanWork\":\"off\",\"ignWork\":\"off\",\"wWCirc\":\"off\",\"wWHeat\":\"on\",\"burnStarts\":223397,\"burnWorkMin\":366019,\"heatWorkMin\":294036,\"ServiceCode\":\"0H\",\"ServiceCodeNumber\":203}", "time": 1565956463 },
|
|
||||||
{ "topic": "tapwater_active", "payload": "0", "time": 1565956408 },
|
|
||||||
{ "topic": "heating_active", "payload": "0", "time": 1565956408 },
|
|
||||||
{ "topic": "thermostat_data", "payload": "{\"thermostat_hc\":\"1\",\"thermostat_seltemp\":15,\"thermostat_currtemp\":23,\"thermostat_mode\":\"auto\"}", "time": 1565956444 }
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
wss.broadcast(stats);
|
wss.broadcast(stats);
|
||||||
|
|||||||
Reference in New Issue
Block a user