359 lines
9.2 KiB
C++
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;
|
|
|
|
}
|