Getting this working was pretty painful, so here it is in case it’ any help to anybody.
Main boobytraps:
mqtt_client.setBufferSize
isn’t big enough then the messages are dropped without any warning.mqtt_client.setBufferSize
has to be bigger than the message.mqtt_client.publish
./* mosquitto_sub -h localhost -t zigbee2mqtt/Home/BoilerTemperature/ESP32 -F "%p" mosquitto_pub -h localhost -t "zigbee2mqtt/Home/BoilerTemperature/set" -m "50" */ #include#include #include const char* ssid = "calleva"; const char* password = "TopSecretPassword_DontTellPutin"; const char* mqtt_broker = "192.168.0.113"; const int mqtt_port = 1883; WiFiClient wifi_client; PubSubClient mqtt_client(wifi_client); int tCurrent = 0; bool heat_h = false; bool heat_l = false; bool heat_w = false; void setup() { Serial.begin(115200); Serial.printf("Connecting to %s ", ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(" CONNECTED"); mqtt_client.setServer(mqtt_broker, mqtt_port); connect_mqtt(); } void connect_mqtt() { while (!mqtt_client.connected()) { Serial.println("-- connect_mqtt --"); if (mqtt_client.connect("ESP32C3")) { mqtt_client.setCallback(mqtt_callback_setup); mqtt_client.setBufferSize(96); // It's actually length 48 but it doesn't work with 64. mqtt_client.subscribe("zigbee2mqtt/Home/BoilerTemperature"); int k = 0; while (tCurrent == 0) { mqtt_client.loop(); delay(256); k++; if (k == 4) { Serial.print("."); } if (k == 80) { Serial.println(); k = 0; } } Serial.printf("Current set value is %d. Subscribing to changes.\n", tCurrent); mqtt_client.setBufferSize(2048); // 1503 might do. mqtt_client.unsubscribe("zigbee2mqtt/Home/BoilerTemperature"); mqtt_client.setCallback(mqtt_callback_set); mqtt_client.subscribe("zigbee2mqtt/Home/BoilerTemperature/set"); mqtt_client.subscribe("zigbee2mqtt/Home/+/Thermostat"); return; } else { Serial.print(" failed, rc="); Serial.print(mqtt_client.state()); Serial.println(". Retrying..."); delay(2000); } } } void mqtt_callback_setup(char* topic, byte* payload, unsigned int length) { Serial.printf("%s len %d\n", topic, length); JsonDocument jDoc; deserializeJson(jDoc, payload); int set = (int)jDoc["set"]; Serial.printf("mqtt_callback_setup got set value %d.\n", set); if (set > 0) { tCurrent = set; } } void mqtt_callback_set(char* topic, byte* payload, unsigned int length) { if (strcmp(topic, "zigbee2mqtt/Home/BoilerTemperature/set") == 0) { long tSet = atoi((char*)payload); if (tSet != tCurrent) { move(tCurrent, tSet); tCurrent = tSet; } return; } // Otherwise it's a thermostat. char* tok = strtok(topic, "/"); // zigbee2mqtt tok = strtok(NULL, "/"); // Home tok = strtok(NULL, "/"); // Room JsonDocument filter; filter["running_state"] = true; filter["local_temperature"] = true; filter["current_heating_setpoint"] = true; filter["preset"] = true; filter["system_mode"] = true; JsonDocument jDoc; deserializeJson(jDoc, payload, DeserializationOption::Filter(filter)); String running_state = jDoc["running_state"]; bool h = running_state == "heat"; String preset = jDoc["preset"]; String system_mode = jDoc["system_mode"]; int lt = (int)jDoc["local_temperature"]; int chs = (int)jDoc["current_heating_setpoint"]; Serial.printf( "%-11s: %-4s %-7s %-4s %-2d -> %-2d\n", tok, system_mode, preset, running_state, lt, chs ); if (strcmp(tok, "Kitchen") == 0) { return; } else if (strcmp(tok, "Hall") == 0) { if (heat_h == h) { return; } heat_h = h; } else if (strcmp(tok, "LivingRoom") == 0) { if (heat_l == h) { return; } heat_l = h; } else if (strcmp(tok, "HotWater") == 0) { if (heat_w == h) { return; } heat_w = h; } else { Serial.printf("Unknown thermostat: %s\n", tok); } } void move(int from, int to) { int d = (512 * (from - to)) / 10; Serial.printf("Moving %d steps to get from %d to %d.", d, from, to); //stepper.step(d); } int n = 0; int m = 0; long reported = millis(); void loop() { if (n > 1000000) { Serial.printf("Loop: %d million. mqtt connected: %d\n", m, mqtt_client.connected()); m++; n = 0; if( !mqtt_client.connected()) { connect_mqtt(); } } n++; int dr = 1000 * 60 * ((heat_h || heat_l || heat_w) ? 1 : 5); if (!(millis() - reported < dr) /* rollover hack */) { report(); reported = millis(); } mqtt_client.loop(); } void report() { JsonDocument jDocReport; //sensors.requestTemperatures(); // Or else initial value of 85. int t_flow = 40 + (n % 7); //sensors.getTemp(1); jDocReport["flow"] = t_flow; int t_return = 35 + (n % 7); //sensors.getTemp(0); jDocReport["return"] = t_return; jDocReport["set"] = tCurrent; char payload[96]; unsigned int length = serializeJson(jDocReport, payload); bool p = mqtt_client.publish("zigbee2mqtt/Home/BoilerTemperature/ESP32", (byte*)payload, length, false); Serial.printf("Reported (h: %d, l: %d, w: %d) (success: %s). Length %u. Value: ", heat_h, heat_l, heat_w, p ? "yes" : "no", length); Serial.println(payload); }
The purpose is to use a stepper motor to turn the boiler temperature up and down.
It needs to be high to get the hot water hot. It needs to me medium if the house has got cold. But otherwise it only needs to be at 40°C to keep the house comfortable; the low tepmerature means that the heating can run continuously rather than making the house hot then turning off until it feels cold, and the boiler is in condensing mode.
It was easy to get running on a Raspberry Pi Zero, but that’s overkill so I want to move to an ESP32. All it needs to do is:
Additionally, I got an ESP32-CAM to take photos of the temperature on the display on the boiler and upload them to my main server to OCR to compare the value with the flow temperature sensor cable-tied shoddily onto the pipe, and to provide a check that the motor has moved to the correct position.