{"id":1096,"date":"2017-01-02T07:12:21","date_gmt":"2017-01-02T15:12:21","guid":{"rendered":"https:\/\/partofthething.com\/thoughts\/?p=1096"},"modified":"2017-11-20T21:38:34","modified_gmt":"2017-11-21T05:38:34","slug":"esp8266-furnace-code","status":"publish","type":"post","link":"https:\/\/partofthething.com\/thoughts\/esp8266-furnace-code\/","title":{"rendered":"ESP8266 Furnace Code"},"content":{"rendered":"<p>Here&#8217;s the code for the <a href=\"https:\/\/partofthething.com\/thoughts\/enlighten-your-old-furnace-with-a-raspberry-pi-home-assistant-an-esp8266-and-some-relays\/\">smart furnace<\/a> project. This is provided &#8220;as is&#8221;. Use at your own risk; I take no responsibility if this code damages or destroys your equipment or home or injures or kills anyone. Controlling household\/industrial equipment is a non-trivial task.<\/p>\n<pre class=\"toolbar:1 wrap:false lang:c decode:true \" title=\"The Code\">\/*\r\n* Furnace Controller for ESP8266 and MQTT.\r\n*\r\n*\r\n* Connections: GPIOs go to inputs, obviously. Connect 5V to VCC and jumper VCC to\r\n* VCC-JD to power the coils. Note that GPIO HIGH corresponds to relay off with this\r\n* board.\r\n*\r\n* D8 is a bad choice because it's actually GPIO 15 which messes things up during\r\n* reboot.\r\n*\r\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n* FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\n* SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR\r\n* ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\n* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\n* DEALINGS IN THE SOFTWARE.\r\n*\r\n* Safety Features:\r\n* 1. Should shut down if disconnected from internet (would be more robust\r\n*    with internal MQTT broker, but then couldn't message nick)\r\n* 2. Should shut down if furnace pin has been on for a really long time.\r\n*    This could happen if the RPi\/hass dies but the internet is still up.\r\n*\r\n*  See https:\/\/partofthething.com\/thoughts\/enlighten-your-old-furnace-with-a-raspberry-pi-home-assistant-an-esp8266-and-some-relays\/\r\n*\/\r\n\r\n#include &lt;ESP8266WiFi.h&gt;\r\n#include &lt;ESP8266mDNS.h&gt;\r\n#include &lt;WiFiUdp.h&gt;\r\n#include &lt;ArduinoOTA.h&gt;\r\n#include &lt;PubSubClient.h&gt;\r\n\r\n#define wifi_ssid \"[redacted]\"\r\n#define wifi_password \"[redacted]\"\r\n\r\n#define mqtt_server \"[redacted]\"\r\n#define mqtt_user \"[redacted]\"\r\n#define mqtt_password \"[redacted]\"\r\n#define mqtt_error_topic \"mom\/status\/furnace_error\"\r\n#define mqtt_status_topic \"mom\/status\/furnace\"\r\n#define listen_topic \"mom\/furnace\/#\"\r\n\r\n#define ota_password \"[redacted]\"\r\n#define ota_hostname \"[redacted]\"\r\n\r\n#define FOUR_HOURS 14400000 \/\/ milliseconds\r\n\r\n#define NUM_PINS 6\r\n\r\n#define FOUR_HOURS 14400000 \/\/ milliseconds\r\n\r\n#define NUM_PINS 6\r\n\r\nint MOLLY_PIN=D5;\r\nint UPSTAIRS_PIN=D6;\r\nint LIVING_ROOM_PIN=D7;\r\nint BASEMENT_PIN=D4;\r\nint NICK_BEDROOM=D1;\r\nint DEN=D2;\r\n\r\nint allPins[NUM_PINS] = {LIVING_ROOM_PIN, UPSTAIRS_PIN, MOLLY_PIN, BASEMENT_PIN, NICK_BEDROOM, DEN};\r\n\r\n\/\/ track how long each pin has been on for auto-shutoff function \r\n\/\/ (for when MQTT controller on Pi dies but net is up)\r\nunsigned long onTime;\r\nunsigned long now;\r\nint numPinsOn = 0;\r\n\r\nWiFiClientSecure espClient;\r\nPubSubClient client(espClient);\r\n\r\nvoid setup() {\r\n  Serial.begin(115200);\r\n  setup_gpio();\r\n  setup_wifi();\r\n  setup_mqtt();\r\n  setup_ota();\r\n}\r\n\r\nvoid setup_gpio() {\r\n  \/\/ Connect each relay pin to each GPIO. \r\n  \/\/ Hook +5V up to relay module. \r\n  for (int i=0;i<NUM_PINS+1;i++) {\r\n     pinMode(allPins[i], OUTPUT);\r\n  }\r\n  all_pins_off();\r\n}\r\n\r\nvoid all_pins_off() {\r\n  for (int i=0;i<NUM_PINS+1;i++) {\r\n    digitalWrite(allPins[i], HIGH); \r\n  }\r\n  numPinsOn = 0;\r\n}\r\n\r\nvoid setup_wifi() {\r\n  delay(10);\r\n  Serial.println();\r\n  Serial.print(\"Connecting to \");\r\n  Serial.println(wifi_ssid);\r\n  WiFi.mode(WIFI_STA);\r\n  WiFi.begin(wifi_ssid, wifi_password);\r\n  while (WiFi.status() != WL_CONNECTED) {\r\n    delay(500);\r\n    Serial.print(\".\");\r\n  }\r\n  Serial.println(\"\");\r\n  Serial.println(\"WiFi connected\");\r\n  Serial.println(\"IP address: \");\r\n  Serial.println(WiFi.localIP());\r\n}\r\n\r\nvoid callback(char* topic, byte* payload, unsigned int length) {\r\n  Serial.print(\"Command arrived [\");\r\n  Serial.print(topic);\r\n  Serial.print(\"] \");\r\n  for (int i = 0; i < length; i++) {\r\n    Serial.print((char)payload[i]);\r\n  }\r\n  Serial.println();\r\n\r\n  \/\/ Switch on the heat if an 1 was received as first character\r\n  int pin = -1;\r\n  if(strcmp(topic,\"mom\/furnace\/livingroom\")==0) {\r\n    pin = LIVING_ROOM_PIN;\r\n  }\r\n  else if (strcmp(topic,\"mom\/furnace\/upstairs\")==0){\r\n    pin = UPSTAIRS_PIN;\r\n  }\r\n  else if (strcmp(topic,\"mom\/furnace\/molly\")==0) {\r\n    pin = MOLLY_PIN;\r\n  }\r\n  else if (strcmp(topic,\"mom\/furnace\/basement\")==0) {\r\n    pin = BASEMENT_PIN;\r\n  }\r\n  else if (strcmp(topic,\"mom\/furnace\/nick_room\")==0) {\r\n    pin = NICK_BEDROOM;\r\n  }\r\n  else if (strcmp(topic,\"mom\/furnace\/den\")==0) {\r\n    pin = DEN;\r\n  }\r\n\r\n  if (pin == -1){\r\n    Serial.print(\"Unknown Topic. Aborting command.\\n\");\r\n    return;\r\n  }\r\n  else {\r\n    Serial.print(\"Pin is \");\r\n    Serial.print(pin);\r\n    Serial.println();\r\n  }\r\n  \r\n  if ((char)payload[0] == '1') {\r\n    Serial.print(\"Turning on\");\r\n    digitalWrite(pin, LOW);   \r\n    numPinsOn = 1; \/\/ will be a problem if we start doing more than 1 zone. Upgrade then. \r\n    onTime = millis();  \/\/ will roll over every 72 hours or so. \r\n  } else {\r\n    Serial.print(\"Turning off\");\r\n    digitalWrite(pin, HIGH); \r\n    numPinsOn = 0;\r\n  }\r\n\r\n}\r\n\r\nvoid setup_mqtt() {\r\n    client.setServer(mqtt_server, 8883);\r\n    client.setCallback(callback);\r\n}\r\n\r\nvoid reconnect() {\r\n  \/\/ Loop until we're reconnected.\r\n  int reconnectAttempts = 0;\r\n  while (!client.connected()) {\r\n    Serial.print(\"Attempting MQTT connection...\");\r\n    \/\/ Add a will message in case we get kicked offline for some reason. \r\n    if (client.connect(\"ESP8266Furnace\", mqtt_user, mqtt_password, mqtt_status_topic,\r\n                       1, 1, \"0\")) {\r\n      Serial.println(\"connected\");\r\n      client.subscribe(listen_topic, 1); \/\/ important to get QoS of 1 to ensure message makes it. \r\n      client.publish(mqtt_status_topic, \"1\", true);\r\n    } else {\r\n      reconnectAttempts++;\r\n      Serial.print(\"failed, rc=\");\r\n      Serial.print(client.state());\r\n      Serial.println(\" try again in 5 seconds\");\r\n      \r\n      if (reconnectAttempts > 60) {\r\n          \/\/ If we cannot connect for a long time, shut off all pins. Internet is dead\r\n          \/\/ and we can't be controlled anymore. Fallback on manual thermostats at this point\r\n          \/\/ so we don't burn the house down.\r\n        all_pins_off();\r\n        client.publish(mqtt_error_topic, \"1\", true);  \/\/ for sensing errors. \r\n        reconnectAttempts=0;\r\n        \r\n      }\r\n      delay(5000); \/\/ Wait 5 seconds before retrying\r\n    }\r\n  }\r\n  Serial.print(\"MQTT connected.\");\r\n}\r\n\r\nvoid setup_ota() {\r\n  ArduinoOTA.setPort(8266);\r\n  ArduinoOTA.setHostname(ota_hostname);\r\n  ArduinoOTA.setPassword(ota_password);\r\n\r\n  ArduinoOTA.onStart([]() {\r\n    Serial.println(\"Starting OTA update.\");\r\n    all_pins_off();\r\n  });\r\n  ArduinoOTA.onEnd([]() {\r\n    Serial.println(\"\\nOTA update ended.\");\r\n  });\r\n  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {\r\n    Serial.printf(\"OTA Progress: %u%%\\r\", (progress \/ (total \/ 100)));\r\n  });\r\n  ArduinoOTA.onError([](ota_error_t error) {\r\n    Serial.printf(\"Error[%u]: \", error);\r\n    if (error == OTA_AUTH_ERROR) Serial.println(\"Auth Failed\");\r\n    else if (error == OTA_BEGIN_ERROR) Serial.println(\"Begin Failed\");\r\n    else if (error == OTA_CONNECT_ERROR) Serial.println(\"Connect Failed\");\r\n    else if (error == OTA_RECEIVE_ERROR) Serial.println(\"Receive Failed\");\r\n    else if (error == OTA_END_ERROR) Serial.println(\"End Failed\");\r\n  });\r\n  ArduinoOTA.begin();\r\n}\r\n\r\n\r\nvoid failsafe() {\r\n  \/\/ If any pin has been on for more than four hours it seems like something's wrong. \r\n  \/\/ Possibly the Rpi died and isn't sending an off signal. \r\n  \/\/ Granted, if a bunch of zones were coming on and off staggered, then \r\n  \/\/ this would have to be upgraded to get fancier. \r\n  now = millis();\r\n  if (numPinsOn > 0 && (now-onTime)> FOUR_HOURS) {\r\n    all_pins_off();\r\n    Serial.println(\"Seems stuck on. Rebooting.\");\r\n    client.publish(mqtt_error_topic, \"2\", true);  \/\/ for sensing errors.\r\n    ESP.restart(); \r\n  }\r\n  \r\n}\r\n\r\nvoid loop() {\r\n  if (!client.connected()) {\r\n    reconnect();\r\n  }\r\n  client.loop();\r\n  failsafe();\r\n  ArduinoOTA.handle();\r\n  delay(500);\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Here&#8217;s the code for the smart furnace project. This is provided &#8220;as is&#8221;. Use at your own risk; I take no responsibility if this code damages or destroys your equipment or home or injures or kills anyone. Controlling household\/industrial equipment is a non-trivial task. \/* * Furnace Controller for ESP8266 and MQTT. * * * &hellip; <a href=\"https:\/\/partofthething.com\/thoughts\/esp8266-furnace-code\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">ESP8266 Furnace Code<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":4,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"","footnotes":""},"categories":[75],"tags":[],"class_list":["post-1096","post","type-post","status-publish","format-standard","hentry","category-home-automation"],"_links":{"self":[{"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/posts\/1096","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/comments?post=1096"}],"version-history":[{"count":19,"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/posts\/1096\/revisions"}],"predecessor-version":[{"id":1520,"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/posts\/1096\/revisions\/1520"}],"wp:attachment":[{"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/media?parent=1096"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/categories?post=1096"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/tags?post=1096"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}