#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 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; }