Brewhouse/lib/Device/Device.cpp
2023-01-25 21:00:25 -05:00

359 lines
9.2 KiB
C++

#include "Device.h"
Device::Device(const uint8_t _pin_cool, const uint8_t _pin_heat, const uint8_t _pin_wire) {
this->_pin_cool = _pin_cool;
this->_pin_heat = _pin_heat;
this->_pin_wire = _pin_wire;
}
void Device::CoolSet(bool set_state) {
// Set pin state
if (set_state) {
Serial.println("Starting cooling.");
strcpy(_curr_action, ACTION_COOLING);
SendState(ACTION_TPC, _curr_action);
digitalWrite(_pin_heat, LOW); // Just to be sure.
digitalWrite(_pin_cool, HIGH);
} else {
Serial.println("Stopping cooling.");
strcpy(_curr_action, ACTION_IDLE);
SendState(ACTION_TPC, _curr_action);
digitalWrite(_pin_cool, LOW);
digitalWrite(_pin_heat, LOW);
}
}
void Device::HeatSet(bool set_state) {
if (set_state) {
Serial.println("Starting heat.");
strcpy(_curr_action, ACTION_HEATING);
SendState(ACTION_TPC, _curr_action);
digitalWrite(_pin_cool, LOW); // Just to be sure.
digitalWrite(_pin_heat, HIGH);
} else {
Serial.println("Stopping heat.");
strcpy(_curr_action, ACTION_IDLE);
SendState(ACTION_TPC, _curr_action);
digitalWrite(_pin_cool, LOW);
digitalWrite(_pin_heat, LOW);
}
}
void Device::Hyst(float new_value){
_hyst = new_value;
}
float Device::Hyst(){
return _hyst;
}
void Device::Update(){
float currentMillis = millis();
//Read temp sensor.
if (currentMillis - _lastSensor >= _sensor_period) {
//Read temperature from DS18b20
float tempC = _sensors.getTempC(_ds_serial);
if (tempC == DEVICE_DISCONNECTED_C) {
Serial.println("Error: Could not read temperature data");
} else {
_curr_temp = DallasTemperature::toFahrenheit(tempC);
}
}
// Some helpful variables.
bool heating = _curr_action==ACTION_HEATING;
bool cooling = _curr_action==ACTION_COOLING;
bool running = (heating || cooling);
// Adjust cool/heat on or off
if (_mode == "cool"){
if (_curr_temp > _temp + _hyst && !cooling) {
CoolSet(true);
} else if (_curr_temp <= _temp && cooling) {
CoolSet(false);
}
} else if (_mode == "heat"){
if (_curr_temp < _temp - _hyst && !heating) {
HeatSet(true);
} else if (_curr_temp >= _temp && heating) {
HeatSet(false);
}
} else if (_mode == "auto"){
if ((_curr_temp < _temp_lo - _hyst) && !heating) {
HeatSet(true);
} else if ((_curr_temp > _temp_hi + _hyst) && !cooling) {
CoolSet(true);
} else if (running && (_curr_temp >= _temp_lo) && (_curr_temp <= _temp_hi)) {
if (heating) HeatSet(false);
if (cooling) CoolSet(false);
}
} else {
// IS OFF
if (heating) HeatSet(false);
if (cooling) CoolSet(false);
}
//Send Data to broker
if (currentMillis - _lastSend >= _send_period) {
// Time's up, send data
char temp[7];
dtostrf(_curr_temp, 6, 2, temp);
SendState(TEMP_CURRENT, temp);
_lastSend = currentMillis;
}
_mqtt_client.loop();
}
void Device::AttachNet(WiFiClient &network) {
this->_net = network;
this->_mqtt_client.setClient(_net);
this->_mqtt_client.setBufferSize(DOC_SIZE);
}
void Device::AttachSensor(DallasTemperature &sensors, uint8_t serial[8]){
_sensors = sensors;
_ds_serial = serial;
}
//void Device::topicRoot(const String &root) { _device_prefix = root; }
//byte* Device::topicRoot() { return _device_prefix; }
void Device::SendConfig(char* broker, char* name, String &chipid, bool multimode) {
String name_slug = slugify(name);
String CMD_TPL = "{{ value }}";
String STAT_TPL = "{{ value_json }}";
_topic_root = ROOT + name_slug;
String _config_topic = CONFIG_ROOT + name_slug + "_" + chipid + "/config";
StaticJsonDocument<DOC_SIZE> payload_doc;
payload_doc["uniq_id"] = chipid + "_" + name_slug;
payload_doc["name"] = name;
payload_doc["temp_unit"] = "F";
payload_doc["max_temp"] = _max_temp;
payload_doc["min_temp"] = _min_temp;
payload_doc["initial"] = _init_temp;
// Action Topic
payload_doc["action_topic"] = _topic_root + ACTION_TPC;
payload_doc["action_template"] = STAT_TPL;
// Mode setup
payload_doc["mode_cmd_t"] = _topic_root + MODE_SET;
payload_doc["mode_cmd_tpl"] = CMD_TPL;
payload_doc["mode_stat_t"] = _topic_root + MODE_STATE;
payload_doc["mode_stat_tpl"] = STAT_TPL;
JsonArray modes = payload_doc.createNestedArray("modes");
modes.add("off");
modes.add("cool");
payload_doc["temp_cmd_t"] = _topic_root + TEMP_SET;
payload_doc["temp_cmd_tpl"] = CMD_TPL;
payload_doc["temp_stat_t"] = _topic_root + TEMP_STATE;
payload_doc["temp_stat_tpl"] = STAT_TPL;
payload_doc["curr_temp_t"] = _topic_root + TEMP_CURRENT;
payload_doc["curr_temp_tpl"] = CMD_TPL;
if (multimode) {
payload_doc["temp_hi_cmd_t"] = _topic_root + TEMP_HI_SET;
payload_doc["temp_hi_cmd_tpl"] = CMD_TPL;
payload_doc["temp_hi_stat_t"] = _topic_root + TEMP_HI_STATE;
payload_doc["temp_hi_stat_tpl"] = STAT_TPL;
payload_doc["temp_lo_cmd_t"] = _topic_root + TEMP_LO_SET;
payload_doc["temp_lo_cmd_tpl"] = CMD_TPL;
payload_doc["temp_lo_stat_t"] = _topic_root + TEMP_LO_STATE;
payload_doc["temp_lo_stat_tpl"] = STAT_TPL;
modes.add("heat");
modes.add("auto");
}
// Attach Device
JsonObject dev = payload_doc.createNestedObject("dev");
dev["name"] = DEVICE_NAME;
dev["mdl"] = DEVICE_MDL;
dev["sw"] = String(version);
dev["mf"] = DEVICE_MF;
JsonArray ids = dev.createNestedArray("ids");
ids.add(chipid);
String payload;
serializeJson(payload_doc, payload);
bool response = ConnectMQTT(broker, MQTT_NAME, MQTT_USER, MQTT_PASSWORD);
if (response) {
_mqtt_client.publish(_config_topic.c_str(), payload.c_str(), MSG_RETAIN);
_mqtt_client.loop();
}
_mqtt_client.subscribe((_topic_root + MODE_SET).c_str());
_mqtt_client.subscribe((_topic_root + TEMP_SET).c_str());
if (multimode) {
_mqtt_client.subscribe((_topic_root + TEMP_HI_SET).c_str());
_mqtt_client.subscribe((_topic_root + TEMP_LO_SET).c_str());
}
_mqtt_client.loop();
}
void Device::_Temp(byte value){
Serial.print("Set Temp");
_temp = value;
SendState(TEMP_STATE, (char*)value);
}
void Device::_Mode(char* value){
Serial.print("Set Mode");
strcpy(_mode, value);
SendState(MODE_STATE, (char*)value);
}
void Device::_TempHi(byte value){
Serial.print("Set High Temp");
_temp_hi = value;
SendState(TEMP_HI_STATE, (char*)value);
}
void Device::_TempLo(byte value){
Serial.print("Set Low Temp");
_temp_lo = value;
SendState(TEMP_LO_STATE, (char*)value);
}
void Device::SendState(String suffix, String payload){
String topic = _topic_root + suffix;
_mqtt_client.publish(topic.c_str(), payload.c_str(), MSG_RETAIN);
}
/** Callback function for MQTT client.
Looks up a command function based on topic and executes it.
@param topic
@param payload
@param length
*/
void Device::_mqttCallback(char *topic, uint8_t *payload, unsigned int length) {
char data [16] = {'\0'};
// Remove root from the incoming topic.
char suffix[100] = topic[_topic_root.length()];
Serial.print("Incoming topic -> ");
Serial.print(suffix);
Serial.print(": ");
int i;
for (i; i < length; i++)
{
data[i] = ((char)payload[i]);
Serial.print(data[i]);
}
data[i] = '\0';
switch (suffix) {
case MODE_SET:
_Mode(data);
break;
case TEMP_SET:
_Temp(atoi(data));
break;
case TEMP_LO_SET:
_TempLo(atoi(data));
break;
case TEMP_HI_SET:
_TempHi(atoi(data));
break;
default:
Serial.println("Command function not found for " + String(topic));
}
}
/** Connect to MQTT broker.
@param server IP address string of the server.
@param name the name used for this connection
@param user MQTT broker username
@param password MQTT broker password
@return boolean indicating success or failure of connection.
*/
bool Device::ConnectMQTT(const String &server, const String &name, const String &user, const String &password) {
_mqtt_client.setServer((server.c_str(), 1883);
_mqtt_client.setCallback(&Device::_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;
}