Lots of changes.

Closer to a working model. 2-way communication is working and
devices can report temperature readings.

Need to get the actual control logic done and clean this garbage up.
This commit is contained in:
Chris Giacofei 2023-01-26 16:53:50 -05:00
parent 4619ce1d17
commit 74806fe198
11 changed files with 268 additions and 277 deletions

1
.gitignore vendored
View File

@ -9,4 +9,5 @@ doc/
debug.txt debug.txt
*.geany *.geany
*.tags *.tags
tags
*.bin *.bin

View File

@ -1,11 +1,20 @@
#include "communicator.h" #include "Communicator.h"
Communicator::Communicator(WiFiClient& network, void (*cmd)(char *topic, byte *payload, unsigned int length)) { Communicator::Communicator(WiFiClient& network, CALLBACK_SIGNATURE) {
this->_net = network; this->_net = network;
this->mqttCallback = cmd; this->mqttCallback = mqttCallback;
this->_mqtt_client.setBufferSize(1536); this->_mqtt_client.setBufferSize(1536);
} }
Communicator::Communicator(WiFiClient& network) {
this->_net = network;
this->_mqtt_client.setBufferSize(1536);
}
void Communicator::connectCallback(CALLBACK_SIGNATURE) {
this->mqttCallback = mqttCallback;
}
void Communicator::loop() { void Communicator::loop() {
_mqtt_client.loop(); _mqtt_client.loop();
} }
@ -80,6 +89,11 @@ bool Communicator::ConnectMQTT(const String &server, const String &name, const S
} }
void Communicator::Subscribe(const char* topic) {
Serial.println("Subscribe to: " + String(topic));
_mqtt_client.subscribe(topic);
}
void Communicator::mqtt_discovery(const String topic, StaticJsonDocument<1536> &entity) { void Communicator::mqtt_discovery(const String topic, StaticJsonDocument<1536> &entity) {
String payload; String payload;
serializeJson(entity, payload); serializeJson(entity, payload);
@ -89,21 +103,34 @@ void Communicator::mqtt_discovery(const String topic, StaticJsonDocument<1536> &
{ {
Serial.print("Sending discovery payload to "); Serial.print("Sending discovery payload to ");
Serial.println(topic); Serial.println(topic);
Serial.println("");
Serial.println(payload); Serial.println(payload);
Serial.println("");
_mqtt_client.publish(topic.c_str(), payload.c_str(), false); _mqtt_client.publish(topic.c_str(), payload.c_str(), false);
if (entity.containsKey("mode_cmd_t")) _mqtt_client.subscribe(entity["mode_cmd_t"]); const char* mode = entity["mode_cmd_t"];
if (entity.containsKey("temp_cmd_t")) _mqtt_client.subscribe(entity["temp_cmd_t"]); if (mode) {
if (entity.containsKey("temp_hi_cmd_t")) _mqtt_client.subscribe(entity["temp_hi_cmd_t"]); Subscribe(mode);
if (entity.containsKey("temp_lo_cmd_t")) _mqtt_client.subscribe(entity["temp_lo_cmd_t"]); }
const char* temp = entity["temp_cmd_t"];
if (temp) {
Subscribe(temp);
}
const char* temp_hi = entity["temp_hi_cmd_t"];
if (temp_hi) {
Subscribe(temp_hi);
}
const char* temp_lo = entity["temp_lo_cmd_t"];
if (temp_lo) {
Subscribe(temp_lo);
}
_mqtt_client.loop(); _mqtt_client.loop();
} }
} }
void Communicator::publish_data(String topic, String value) { void Communicator::publish_data(String topic, String value) {
_mqtt_client.publish(topic.c_str(), value.c_str(), false); Serial.print(topic);
Serial.print(" --> ");
Serial.println(value);
_mqtt_client.publish(topic.c_str(), value.c_str(), true);
} }

View File

@ -0,0 +1,31 @@
#ifndef MyClass_h
#define MyClass_h
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <Global.h>
#include <secrets.h>
#define CALLBACK_SIGNATURE void (*mqttCallback)(char *topic, byte *payload, unsigned int length)
class Communicator {
public:
Communicator(WiFiClient&);
Communicator(WiFiClient&, CALLBACK_SIGNATURE);
CALLBACK_SIGNATURE;
void connectCallback(CALLBACK_SIGNATURE);
bool ConnectMQTT(const String&, const String&, const String&, const String&);
void mqtt_discovery(const String, StaticJsonDocument<1536>&);
void publish_data(String topic, String value);
void publish_data(String topic, int value);
void Subscribe(const char* topic);
void loop();
private:
PubSubClient _mqtt_client;
WiFiClient _net;
};
#endif

View File

@ -1,25 +0,0 @@
#ifndef MyClass_h
#define MyClass_h
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include "secrets.h"
class Communicator {
public:
Communicator(WiFiClient&, void (*mqttCallback)(char*, byte*, unsigned int));
void (*mqttCallback)(char*, byte*, unsigned int);
bool ConnectMQTT(const String&, const String&, const String&, const String&);
void mqtt_discovery(const String, StaticJsonDocument<1536>&);
void publish_data(String, String);
void loop();
private:
PubSubClient _mqtt_client;
WiFiClient _net;
};
#endif

View File

@ -14,6 +14,8 @@ const char version[] = "0.0.1";
#define ACTION_TPC "action/state" #define ACTION_TPC "action/state"
#define MODE_SET "mode/set" #define MODE_SET "mode/set"
#define MODE_STATE "mode/state" #define MODE_STATE "mode/state"
#define SWING_STATE "swing/state"
#define SWING_SET "swing/set"
#define TEMP_SET "temp/set" #define TEMP_SET "temp/set"
#define TEMP_STATE "temp/state" #define TEMP_STATE "temp/state"
#define TEMP_CURRENT "temp/current" #define TEMP_CURRENT "temp/current"
@ -53,34 +55,68 @@ const char version[] = "0.0.1";
#define TOPIC_LIMIT 4 #define TOPIC_LIMIT 4
typedef void (*ptr)(uint8_t*); #define OFF 0
#define COOL 1
#define HEAT 2
#define AUTO 3
uint8_t CHILLER_SP = 70;
uint8_t FERMA_SP = 70;
uint8_t FERMB_SP = 70;
uint8_t CHILLER_MD = OFF;
uint8_t FERMA_MD = OFF;
uint8_t FERMB_MD = OFF;
//~ typedef void (*ptr)(uint8_t*, uint8_t*);
//typedef ptr (*pm)(); //typedef ptr (*pm)();
const size_t DOC_SIZE = JSON_OBJECT_SIZE(29) + JSON_ARRAY_SIZE(4);
struct CommandTopic { struct CommandTopic {
ptr CmdFunc; //~ ptr CmdFunc;
byte* Setting;
String CmdTopic; String CmdTopic;
}; };
struct Mode {
String CmdTopic;
String StateTopic;
String Setting;
};
struct SetPoint {
String CmdTopic;
String StateTopic;
int Setting;
};
struct ControlDevice { struct ControlDevice {
String name; // "Glycol Chiller" String name; // "Glycol Chiller"
String topic_root; // "brewhouse/" String topic_root; // "brewhouse/"
CommandTopic command_topics[TOPIC_LIMIT]; String current_temp_topic;
byte temp_sensor[8];
String action_topic;
String action_state;
String swing_topic;
String swing_state;
Mode mode;
SetPoint setpoints[3];
bool dual;
}; };
ptr CmdLookup(String lookup_topic, ControlDevice devices[], int device_count) { float UpdateTemperature(DallasTemperature *sensors, DeviceAddress glycol_tank) {
for (int i=0;i<device_count;i++) { // method 2 - faster
ControlDevice this_device = devices[i]; float tempC = sensors->getTempC(glycol_tank);
for (int j=0;j<TOPIC_LIMIT;j++) { if(tempC == DEVICE_DISCONNECTED_C)
CommandTopic this_topic = this_device.command_topics[j]; {
String topic = this_topic.CmdTopic; Serial.println("Error: Could not read temperature data");
if (topic == lookup_topic) { return 0;
return this_topic.CmdFunc;
}
}
} }
return 0; Serial.print("Temp C: ");
} Serial.print(tempC);
Serial.print(" Temp F: ");
return DallasTemperature::toFahrenheit(tempC);
}
#endif #endif

View File

@ -1,11 +0,0 @@
TOPIC_ROOT + suffix;
TOPIC_ROOT + suffix;
TOPIC_ROOT + "mode/set";
TOPIC_ROOT + "mode/state";
TOPIC_ROOT + "temp/set";
TOPIC_ROOT + "temp/state";
TOPIC_ROOT + "temp/current";
TOPIC_ROOT + "temp_hi/set";
TOPIC_ROOT + "temp_hi/state";
TOPIC_ROOT + "temp_lo/set";
TOPIC_ROOT + "temp_lo/state";

View File

@ -7,11 +7,14 @@ source repository.
#define GLOBAL_H #define GLOBAL_H
#define DEVICE_SW "0.0.1" #define DEVICE_SW "0.0.1"
auto chipid = String(ESP.getChipId(), HEX);
#define DEVICE_NAME "Glycol Chiller" #define DEVICE_NAME "Glycol Chiller"
#define DEVICE_MDL "Chillenator v0.1" #define DEVICE_MDL "Chillenator v0.1"
#define DEVICE_MF "Damn Yankee Brewing" #define DEVICE_MF "Damn Yankee Brewing"
#define FERMENTER_COUNT 2 #define FERMENTER_COUNT 2
#define ONE_WIRE_BUS D2
const size_t DOC_SIZE = JSON_OBJECT_SIZE(29) + JSON_ARRAY_SIZE(4);
#endif #endif

View File

@ -1,109 +0,0 @@
#include "communicator.h"
Communicator::Communicator(WiFiClient& network, void (*cmd)(char *topic, byte *payload, unsigned int length)) {
this->_net = network;
this->mqttCallback = cmd;
this->_mqtt_client.setBufferSize(1536);
}
void Communicator::loop() {
_mqtt_client.loop();
}
bool Communicator::ConnectMQTT(const String &server, const String &name, const String &user, const String &password) {
_mqtt_client.setClient(_net);
_mqtt_client.setServer(server.c_str(), 1883);
_mqtt_client.setCallback(mqttCallback);
byte i = 0;
if (_mqtt_client.connected()) return true;
while (!_mqtt_client.connected() && (i < 3)) {
Serial.println("Attempt MQTT Connection.");
boolean ret;
ret = _mqtt_client.connect(name.c_str(), user.c_str(), password.c_str());
if (ret) {
Serial.println("Connected to MQTT");
return true;
} else {
int Status = _mqtt_client.state();
switch (Status)
{
case -4:
Serial.println(F("Connection timeout"));
break;
case -3:
Serial.println(F("Connection lost"));
break;
case -2:
Serial.println(F("Connect failed"));
break;
case -1:
Serial.println(F("Disconnected"));
break;
case 1:
Serial.println(F("Bad protocol"));
break;
case 2:
Serial.println(F("Bad client ID"));
break;
case 3:
Serial.println(F("Unavailable"));
break;
case 4:
Serial.println(F("Bad credentials"));
break;
case 5:
Serial.println(F("Unauthorized"));
break;
}
}
Serial.print(".");
i++;
delay(5000);
}
return false;
}
void Communicator::mqtt_discovery(const String topic, StaticJsonDocument<1536> &entity) {
String payload;
serializeJson(entity, payload);
bool response = ConnectMQTT(MQTT_BROKER.toString(), MQTT_NAME, MQTT_USER, MQTT_PASSWORD);
if (response)
{
Serial.print("Sending discovery payload to ");
Serial.println(topic);
Serial.println("");
Serial.println(payload);
Serial.println("");
_mqtt_client.publish(topic.c_str(), payload.c_str(), false);
if (entity.containsKey("mode_cmd_t")) _mqtt_client.subscribe(entity["mode_cmd_t"]);
if (entity.containsKey("temp_cmd_t")) _mqtt_client.subscribe(entity["temp_cmd_t"]);
if (entity.containsKey("temp_hi_cmd_t")) _mqtt_client.subscribe(entity["temp_hi_cmd_t"]);
if (entity.containsKey("temp_lo_cmd_t")) _mqtt_client.subscribe(entity["temp_lo_cmd_t"]);
_mqtt_client.loop();
}
}
void Communicator::publish_data(String topic, String value) {
_mqtt_client.publish(topic.c_str(), value.c_str(), false);
}

View File

@ -1,25 +0,0 @@
#ifndef MyClass_h
#define MyClass_h
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include "secrets.h"
class Communicator {
public:
Communicator(WiFiClient&, void (*mqttCallback)(char*, byte*, unsigned int));
void (*mqttCallback)(char*, byte*, unsigned int);
bool ConnectMQTT(const String&, const String&, const String&, const String&);
void mqtt_discovery(const String, StaticJsonDocument<1536>&);
void publish_data(String, String);
void loop();
private:
PubSubClient _mqtt_client;
WiFiClient _net;
};
#endif

View File

@ -1,42 +0,0 @@
#ifndef SLOWPWM_h
#define SLOWPWM_h
#include <Arduino.h>
class slowPWM {
private:
byte outputPin;
unsigned long period;
unsigned long lastSwitchTime;
byte outputState;
public:
void begin(byte pin, unsigned long per) {
outputPin = pin;
period = per;
lastSwitchTime = 0;
outputState = LOW;
pinMode(pin, OUTPUT);
Serial.println("Setup PWM");
}
byte compute(byte duty) {
unsigned long onTime = (duty * period) / 100;
unsigned long offTime = period - onTime;
unsigned long currentTime = millis();
if (duty == 0) {
outputState = LOW;
} else if (outputState == HIGH && (currentTime - lastSwitchTime >= onTime)) {
lastSwitchTime = currentTime;
outputState = LOW;
} else if (outputState == LOW && (currentTime - lastSwitchTime >= offTime)) {
lastSwitchTime = currentTime;
outputState = HIGH;
}
return outputState;
}
};
#endif

View File

@ -1,29 +1,73 @@
//#include <PubSubClient.h> //#include <PubSubClient.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <IPAddress.h> #include <IPAddress.h>
#include <DallasTemperature.h>
// My Libraries // My Libraries
#include <secrets.h> #include <secrets.h>
#include <Global.h> #include <Global.h>
#include <communicator.h> #include <Communicator.h>
#include <Device.h> #include <Device.h>
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
byte glycol_ds18b20[8] = {0x28,0xFF,0x64,0x0E,0x7F,0x57,0x09,0x66};
byte fermA_ds18b20[8] = {0x28,0xFF,0x64,0x0E,0x7F,0x57,0x09,0x66};
byte fermB_ds18b20[8] = {0x28,0xFF,0x64,0x0E,0x7F,0x57,0x09,0x66};
String chiller_state = "idle"; String chiller_state = "idle";
int tank_setpoint = 28; int tank_setpoint = 28;
WiFiClient net; WiFiClient net;
void mqttCallback(char *topic, byte *payload, unsigned int length) { ControlDevice DEVICE_LIST[3];
Serial.print("incoming: "); int TOTAL_DEVICES = 0;
Serial.println(topic);
for (unsigned int i = 0; i < length; i++)
{
Serial.print((char)payload[i]);
}
Serial.println("");
}
Communicator hass_comm = Communicator(net, &mqttCallback); auto chipid = String(ESP.getChipId(), HEX);
Communicator hass_comm = Communicator(net);
unsigned long lastMillis;
unsigned long currentMillis;
void mqttCallback(char *topic, byte *payload, unsigned int length) {
payload[length] = '\0';
for (int i=0;i<TOTAL_DEVICES;i++) {
if (strcmp((char *)topic, (DEVICE_LIST[i].mode.CmdTopic).c_str() ) == 0){
DEVICE_LIST[i].mode.Setting = (char*)payload;
Serial.print("Swing: ");
Serial.println(DEVICE_LIST[i].swing_topic);
if (DEVICE_LIST[i].mode.Setting == "auto") {
DEVICE_LIST[i].swing_state = "off";
hass_comm.publish_data(DEVICE_LIST[i].swing_topic, "on");
} else {
DEVICE_LIST[i].swing_state = "on";
hass_comm.publish_data(DEVICE_LIST[i].swing_topic, "off");
}
hass_comm.publish_data(DEVICE_LIST[i].mode.StateTopic, DEVICE_LIST[i].mode.Setting);
break;
}
int setpoint_count;
bool dual_mode = DEVICE_LIST[i].dual;
if (dual_mode) {
setpoint_count = 3;
} else {
setpoint_count = 1;
}
for (int j=0;j<setpoint_count;j++) {
String thistopic = DEVICE_LIST[i].setpoints[j].CmdTopic;
if (strcmp((char *)topic, thistopic.c_str()) == 0){
DEVICE_LIST[i].setpoints[j].Setting = atoi((char *)payload);
hass_comm.publish_data(DEVICE_LIST[i].setpoints[j].StateTopic, String(DEVICE_LIST[i].setpoints[j].Setting));
break;
}
}
}
}
void merge(JsonObject dest, JsonObjectConst src) { void merge(JsonObject dest, JsonObjectConst src) {
for (auto kvp : src) { for (auto kvp : src) {
@ -35,43 +79,72 @@ void merge(JsonObject dest, JsonObjectConst src) {
* *
* Using climate device. * Using climate device.
*/ */
void climateDevice(String name, boolean multimode=false) { void climateDevice(String name, byte ds18b20[], bool multimode=false) {
int setpoint_count = 0;
String name_slug = slugify(name); String name_slug = slugify(name);
String config_topic = "homeassistant/climate/" + name_slug + "_" + chipid + "/config"; String config_topic = "homeassistant/climate/" + name_slug + "_" + chipid + "/config";
String topic_root = "brewhouse/" + name_slug + "/"; String topic_root = "brewhouse/" + name_slug + "/";
ControlDevice device;
device.dual = false;
device.name = name;
device.topic_root = topic_root;
for (int i=0;i<8;i++) {
device.temp_sensor[i] = ds18b20[i];
}
StaticJsonDocument<1536> entity; StaticJsonDocument<1536> entity;
entity["uniq_id"] = chipid + "_" + name_slug; entity["uniq_id"] = chipid + "_" + name_slug;
entity["name"] = name; entity["name"] = name;
entity["value_template"] = "{{ value }}";
entity["temp_unit"] = "F"; entity["temp_unit"] = "F";
entity["max_temp"] = 100;
entity["min_temp"] = 0;
entity["swing_mode_stat_t"] = topic_root + SWING_STATE;
device.swing_topic = topic_root + SWING_STATE;
entity["act_t"] = topic_root + ACTION_TPC;
entity["act_tpl"] = "{{ value }}";
device.action_topic = topic_root + ACTION_TPC;
// Mode setup // Mode setup
entity["mode_cmd_t"] = topic_root + "mode/set"; entity["mode_cmd_t"] = topic_root + MODE_SET;
Mode mode = {topic_root + MODE_SET, topic_root + MODE_STATE, "off"};
device.mode = mode;
entity["mode_cmd_tpl"] = "{{ value }}"; entity["mode_cmd_tpl"] = "{{ value }}";
entity["mode_stat_t"] = topic_root + "mode/state"; entity["mode_stat_t"] = topic_root + MODE_STATE;
entity["mode_stat_tpl"] = "{{ value_json }}";
JsonArray modes = entity.createNestedArray("modes"); JsonArray modes = entity.createNestedArray("modes");
modes.add("off"); modes.add("off");
modes.add("cool"); modes.add("cool");
entity["temp_cmd_t"] = topic_root + "temp/set"; entity["temp_cmd_t"] = topic_root + TEMP_SET;
SetPoint temp = {topic_root + TEMP_SET,topic_root + TEMP_STATE,70};
device.setpoints[setpoint_count] = temp;
setpoint_count++;
entity["temp_cmd_tpl"] = "{{ value }}"; entity["temp_cmd_tpl"] = "{{ value }}";
entity["temp_stat_t"] = topic_root + "temp/state"; entity["temp_stat_t"] = topic_root + TEMP_STATE;
entity["temp_stat_tpl"] = "{{ value_json }}"; entity["curr_temp_t"] = topic_root + TEMP_CURRENT;
entity["curr_temp_t"] = topic_root + "temp/current";
entity["curr_temp_tpl"] = "{{ value }}"; entity["curr_temp_tpl"] = "{{ value }}";
device.current_temp_topic= topic_root + TEMP_CURRENT;
if (multimode == true) { if (multimode == true) {
entity["temp_hi_cmd_t"] = topic_root + "temp_hi/set"; device.dual = true;
entity["temp_hi_cmd_t"] = topic_root + TEMP_HI_SET;
SetPoint temp_hi = {topic_root + TEMP_HI_SET, topic_root + TEMP_HI_STATE, 70};
device.setpoints[setpoint_count] = temp_hi;
setpoint_count++;
entity["temp_hi_cmd_tpl"] = "{{ value }}"; entity["temp_hi_cmd_tpl"] = "{{ value }}";
entity["temp_hi_stat_t"] = topic_root + "temp_hi/state"; entity["temp_hi_stat_t"] = topic_root + TEMP_HI_STATE;
entity["temp_hi_stat_tpl"] = "{{ value_json }}";
entity["temp_lo_cmd_t"] = topic_root + "temp_lo/set"; entity["temp_lo_cmd_t"] = topic_root + TEMP_LO_SET;
SetPoint temp_lo = {topic_root + TEMP_LO_SET, topic_root + TEMP_LO_STATE, 70};
device.setpoints[setpoint_count] = temp_lo;
setpoint_count++;
entity["temp_lo_cmd_tpl"] = "{{ value }}"; entity["temp_lo_cmd_tpl"] = "{{ value }}";
entity["temp_lo_stat_t"] = topic_root + "temp_lo/state"; entity["temp_lo_stat_t"] = topic_root + TEMP_LO_STATE;
entity["temp_lo_stat_tpl"] = "{{ value_json }}";
modes.add("heat"); modes.add("heat");
modes.add("auto"); modes.add("auto");
} }
@ -84,12 +157,41 @@ void climateDevice(String name, boolean multimode=false) {
ids.add(chipid); ids.add(chipid);
//dev["ids"] = "[\"" + chipid +"\"]"; //dev["ids"] = "[\"" + chipid +"\"]";
DEVICE_LIST[TOTAL_DEVICES] = device;
TOTAL_DEVICES++;
hass_comm.mqtt_discovery(config_topic, entity); hass_comm.mqtt_discovery(config_topic, entity);
} }
void SendUpdate() {
int setpoint_count = 0;
float device_temp;
for (int i=0;i<TOTAL_DEVICES;i++) {
device_temp = UpdateTemperature(&sensors, DEVICE_LIST[i].temp_sensor);
hass_comm.publish_data(DEVICE_LIST[i].current_temp_topic, String(device_temp));
hass_comm.publish_data(DEVICE_LIST[i].mode.StateTopic, DEVICE_LIST[i].mode.Setting);
hass_comm.publish_data(DEVICE_LIST[i].action_topic, ACTION_COOLING);
bool dual_mode = DEVICE_LIST[i].dual;
if (dual_mode) {
setpoint_count = 3;
} else {
setpoint_count = 1;
}
for (int j=0;j<setpoint_count;j++) {
hass_comm.publish_data(DEVICE_LIST[i].setpoints[j].StateTopic, String(DEVICE_LIST[i].setpoints[j].Setting));
}
}
}
void setup() { void setup() {
hass_comm.connectCallback(&mqttCallback);
const char* ssid = WIFI_SSID; const char* ssid = WIFI_SSID;
const char* password = WIFI_PASSWORD; const char* password = WIFI_PASSWORD;
sensors.begin();
sensors.setResolution(glycol_ds18b20, 9);
Serial.begin(115200); Serial.begin(115200);
WiFi.begin(ssid, password); WiFi.begin(ssid, password);
@ -101,20 +203,23 @@ void setup() {
Serial.print("IP Address: "); Serial.print("IP Address: ");
Serial.println(WiFi.localIP()); Serial.println(WiFi.localIP());
climateDevice("Coolant Tank"); climateDevice("Coolant Tank", glycol_ds18b20);
climateDevice("Fermenter 1", fermA_ds18b20,true);
climateDevice("Fermenter 2", fermB_ds18b20,true);
String f_name = "Fermenter "; SendUpdate();
for (int i=0;i<FERMENTER_COUNT;i++) { lastMillis = millis();
int f_num = i+1;
Serial.println(f_name + f_num);
climateDevice(f_name + f_num, true);
}
} }
void loop() { void loop() {
delay(5000); currentMillis = millis();
hass_comm.publish_data("brewhouse/coolant_tank/temp/current", String(tank_setpoint));
hass_comm.publish_data("brewhouse/coolant_tank/mode/state", chiller_state); if (currentMillis - lastMillis >= 10000) {
sensors.requestTemperatures();
SendUpdate();
lastMillis = currentMillis;
}
delay(100);
hass_comm.loop(); hass_comm.loop();
} }