About

The following pages describe an IoT build that transforms a heater into a device that can be remotely controlled via Alexa. Additionally, the build will use a temperature sensor that will be build-in and will report temperature readings to a base where some level of automation can be performed.

Similar to most Wizardry and Steamworks builds, rather than automating the devices, data is centralized and the actual automation is performed using software rather than hardware. In doing so, some of the portability is lost but quick an inexpensive changes can be made in software rather than in hardware.

Selecting a Heater

For this build, we looked for a small heating device that could heat up a small room, not too expensive but sturdy enough to be effective. One good candidate at about USD14 is a small heater that has a lot of five star on Amazon:

The heater is also incidentally analog with the on-off switching consisting in a button whose behavior can be substituted with a relay. Digital builds have been made before, and consisted in using a transistor switch to make the digital contact but analog switches are best to emulate with ease with a very simple relay.

Finally, the heater chosen is not too complex, with too many controls that would have to be rewired and remotely accessed, and as a bonus, the heater also is delivered with a tilt mechanism that makes the heater turn off in case the case falls over.

Heater Disassembly and Inspection

Disassembling the heater is very easy involving only some pressure that must be applied around the oval-shaped ring that literally just holds the entire inner-assembly in place. With the colored oval ring popped out, all the interior of the heater can be accessed.

The first noticeable component is the giant "heat resistor" as a giant grill, with wires running across the top. The heater is supposed to have two stages of heating that can be activated by a button on the back of the heater, each of these stages offering more heat than the other.

Interestingly, the build is extremely simple: the grill up front is electrified via the top wires hating up and a micro-controller (the small PCB) powers a simple $5V$ fan that just blows the heat through the grill and into the room. The switch that selects the two speeds, simply heats the grill partly or fully thereby achieving more heat.

On the bottom of the heater is the "tilt mechanism" that turns the heater off if it has fallen over. The "mechanism", in quotes because it does not include a typical sensor, similarly has a simple design: a momentary switch (a microswitch) pushes a button that is pressed whenever the heater stands on its bottom foot and when the button is depressed, the momentary switch breaks the connection.

Realization

The first worry in realizing this project is to think about how power will be drawn in order to power the circuitry. Intuitively, a good guess would be to latch onto the PCB that the heater already has, and correctly so, because the PCB is just a drop-down AC-DC converter that converters mains voltage $220V$ to $5V$ to power the rear fan. However, the PCB is only powered whenever the heater is actually operating, being behind the pressure-switch, as well as the main switch on the back, which means that an ESP micro-controller would not be live to receive the command in other to turn the heater on or off.

Unfortunately, there is no easy solution with this heater, such that mains voltage is pulled directly off the main power cable of the heater. Needless to say that the inner circuitry of the heater relies on some plastic isolator cups to quickly make the connection between several wires, such that all the wires have to be released from the plastic isolator cups, tied with the additional wires (black, in the image), then soldered together, isolated with heat-shrink tubing and finally secured with plastic glue.

Now that the mains power supply cables have been pulled, a step-down and converter is needed to change mains voltage $220V$ to $5V$, respectively $3.3V$ as required by micro-controllers and digital circuits. One cute solution, and not really a hack is to use a small travel USB charger as a step-down.

USB chargers with a single slot are capable of up to one, two or more amperes without being too bulky. Typically, either an U.S. or an European charger would be great due to U.K. chargers being more bulky due to the additional fuse.

Gutting these chargers should be performed with care! Due to space restrictions the plastic casing is typically molded tightly around the electronics such that it is very easy to damage the circuit when trying to pry open the plastic casing.

Either way, when the plastic is removed, the circuitry is very straight-forward, with a bridge AC-DC converter, a transformer that separates the AC and DC circuits, feeding a rather banal USB slot that typically is used to charge any USB device. It is perhaps more tidy to remove the USB port and then connect leads to the freshly desoldered pads of the USB port but another solution is to flip the converter over and then solder wires to the pin connectors of the USB port. The converter used for this project was from a BOSH power-tool and incidentally happened to have some decently large solder pads, aside from the USB port, but the easiest is still hooking onto the pins of the USB port.

As illustrated, some hot-melt glue is then used to seal the connections and is really meant to just increase the tensile strength when the heater will have to be mounted back and cables will have to be stuffed back into the casing.

As usual, the next step is to decide on the ESP to use. The usual conundrum of choice between the ESP-01S and the battle-proven ESP8266 comes up, with the ESP32 always and as usual being considered an excessive choice for such builds. Due to the use of sensors, and the GPIO requirements, the WeMoS ESP8266 wins again, being an affordable development kit that is so cheap it can be bought in bulk and without further ado, the circuit is created by pulling $5V$, ground and a GPIO (at first) for the relay.

A perforated PCB is chosen as the base to build the circuitry on, even though the various components will be separated and dispersed through the heater enclosure, just for the easy-of-use and for the ability to gut-and-reuse the components if need be. For instance, the WeMoS ESP8266 is mounted on rails, being able to be detached at short notice. Next, more leads are pulled for the heat sensor that will also be connected to the WeMoS.

One cool way to add some isolation at low-cost is to use painter's tape with electrical/thermal properties to wrap up the components thereby ensuring no stray contact takes place between components or exposed wires. In the image, the blue tape masks the USB AC-DC converter and works both as a fire-retardant as well as an electrical isolator.

With all components connected, some testing is performed to ensure that everything works correctly. This is done by first toggling the relay on and off using just command-line tools and a corresponding Arduino sketch, and then by attempting to retrieve the temperature from the DHT11 sensor.

Putting everything back together becomes an arduous task, with the various cables not behaving and poking out from everywhere, making us regret that the chosen wires had not been chosen to be longer, but eventually everything is successfully squashed inside the casing.

One remarkable thing about the heater is that there is, in fact, lots of space inside the case. Furthermore, one thought was to eliminate the momentary switch altogether and use a sensor for the tilt, which is a good solution that would produce an enormous amount of space on the bottom of the heater.

Fortunately, the heater is designed with a collimator of sorts, consisting in a plastic piece that gets laid over the resistor and is meant to focus the heat emission to the outside of the heater, thereby not allowing heat to escape on the side of the grill. This is great, because there are a lot of wires around the heater that should not be heated or overheated, along with the new WeMoS controller and the relay. Albeit not pictured, the temperature sensor is slid to the back of the heater, next to the fan such that the sensor will remain unimpressed by the generated heat.

The outer grill can now be assembled, and the oval ring pressed back into place, holding the heater together. Finally, that marks the end of the hardware part of the assembly, with a pretty good looking result.

Now Alexa has someone to heat her up!

Software

Here are the various software components used in this build.

Arduino Sketch

The Arduino sketch is just a blend between the pin-toggle template and the DHT11 polling template being nothing too novel.

/*************************************************************************/
/*    Copyright (C) 2023 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
 
// Removing comment for debugging over the first serial port.
// #define DEBUG 1
// The AP to connect to via Wifi.
#define STA_SSID ""
// The AP Wifi password.
#define STA_PSK ""
// The MQTT broker to connect to.
#define MQTT_HOST ""
// The MQTT broker username.
#define MQTT_USERNAME ""
// The MQTT broker password.
#define MQTT_PASSWORD ""
// The MQTT broker port.
#define MQTT_PORT 1883
// The default MQTT client ID is "esp-CHIPID" where CHIPID is the ESP8266
// or ESP32 chip identifier.
#define MQTT_CLIENT_ID() String("esp-" + String(GET_CHIP_ID(), HEX))
// The authentication password to use for OTA updates.
#define OTA_PASSWORD ""
// The OTA port on which updates take place.
#define OTA_PORT 8266
// The default topic that the sketch subscribes to is "esp/CHIPID" where
// CHIPID is the ESP8266 or ESP32 chip identifier.
#define MQTT_TOPIC() String("esp/" + String(GET_CHIP_ID(), HEX))
 
// Platform specific defines.
#if defined(ARDUINO_ARCH_ESP8266)
#define GET_CHIP_ID() (ESP.getChipId())
#elif defined(ARDUINO_ARCH_ESP32)
#define GET_CHIP_ID() ((uint16_t)(ESP.getEfuseMac() >> 32))
#endif
 
// Miscellaneous defines.
//#define CHIP_ID_HEX (String(GET_CHIP_ID()).c_str())
#define HOSTNAME() String("esp-" + String(GET_CHIP_ID(), HEX))
 
// Platform specific libraries.
#if defined(ARDUINO_ARCH_ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#elif defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#include <ESPmDNS.h>
#endif
// General libraries.
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#if defined(ARDUINO_ARCH_ESP32)
#include <FS.h>
#include <SPIFFS.h>
#endif
 
// DHT11
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>
// Uncomment the type of sensor in use:
#define DHTTYPE DHT11  // DHT 11
//#define DHTTYPE    DHT22     // DHT 22 (AM2302)
//#define DHTTYPE    DHT21     // DHT 21 (AM2301)
 
WiFiClient espClient;
PubSubClient mqttClient(espClient);
 
// Define GPIO pins for supported architectures.
#if defined(ARDUINO_ARCH_ESP8266)
int PINS[] = { D0, D1, D2, D3, D4, D5, D6, D7, D8 };
#elif defined(ARDUINO_ARCH_ESP32)
int PINS[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
               12, 13, 14, 15, 16, 17, 18, 19, 21, 22,
               23, 25, 26, 27, 32, 33, 34, 35, 36, 37,
               38, 39 };
#endif
 
String mqttSerialize(StaticJsonDocument<256> msg) {
  char output[256];
  serializeJson(msg, output, 256);
  return String(output);
}
 
void mqttCallback(char *topic, byte *payload, unsigned int length) {
  String msgTopic = String(topic);
  // payload is not null terminated and casting will not work
  char msgPayload[length + 1];
  snprintf(msgPayload, length + 1, "%s", payload);
#ifdef DEBUG
  Serial.println("Message received on topic: " + String(topic) + " with payload: " + String(msgPayload));
#endif
 
  // Parse the payload sent to the MQTT topic as a JSON document.
  StaticJsonDocument<256> doc;
#ifdef DEBUG
  Serial.println("Deserializing message....");
#endif
  DeserializationError error = deserializeJson(doc, msgPayload);
  if (error) {
#ifdef DEBUG
    Serial.println("Failed to parse MQTT payload as JSON: " + String(error.c_str()));
#endif
    return;
  }
 
  // Do not process messages without an action key.
  if (!doc.containsKey("action")) {
    return;
  }
 
  String action = (const char *)doc["action"];
  if (action == "set") {
    String state = (const char *)doc["state"];
    const int pin = (const int)doc["pin"];
#ifdef DEBUG
    Serial.println("Setting pin: " + String(pin) + " to state: " + String(state));
#endif
    pinMode(PINS[pin], OUTPUT);
 
    if (state == "on") {
      digitalWrite(PINS[pin], HIGH);
      int status = digitalRead(PINS[pin]);
#ifdef DEBUG
      Serial.println("Pin " + String(pin) + " state is now: " + String(status));
#endif
      return;
    }
 
    digitalWrite(PINS[pin], LOW);
    int status = digitalRead(PINS[pin]);
#ifdef DEBUG
    Serial.println("Pin " + String(pin) + " state is now: " + String(status));
#endif
    return;
  }
 
  if (action == "get") {
    const int pin = (const int)doc["pin"];
#ifdef DEBUG
    Serial.println("Getting pin: " + String(pin) + " state.");
#endif
    int status = digitalRead(PINS[pin]);
#ifdef DEBUG
    Serial.println("Pin " + String(pin) + " state is now: " + String(status));
#endif
    // Announce the action.
    StaticJsonDocument<256> msg;
    msg["pin"] = pin;
    switch (status) {
      case 1:
        msg["state"] = "on";
        break;
      case 0:
        msg["state"] = "off";
        break;
      default:
        msg["state"] = "unknown";
        break;
    }
 
    mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg).c_str());
    return;
  }
 
  sensors_event_t event;
  if (action == "temperature") {
    const int pin = (const int)doc["pin"];
    StaticJsonDocument<256> msg;
    msg["id"] = String(MQTT_CLIENT_ID().c_str());
    DHT_Unified dht(PINS[pin], DHTTYPE);
    dht.begin();
    // Print temperature sensor details.
    dht.temperature().getEvent(&event);
    switch (!isnan(event.temperature)) {
      case true:
        msg["temperature"] = event.temperature;
        break;
      default:
        Serial.println("Error reading temperature...");
        // Eeek?
        //dht.end();
        return;
    }
 
    // Publish the sensor data.
    mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg).c_str());
 
    // Eeek?
    //dht.end();
    return;
  }
 
  if (action == "humidity") {
    const int pin = (const int)doc["pin"];
    StaticJsonDocument<256> msg;
    msg["id"] = String(MQTT_CLIENT_ID().c_str());
    DHT_Unified dht(PINS[pin], DHTTYPE);
    dht.begin();
    dht.humidity().getEvent(&event);
    switch (!isnan(event.relative_humidity)) {
      case true:
        msg["humidity"] = event.relative_humidity;
        break;
      default:
        Serial.println("Error reading humidity...");
        // Eeek?
        //dht.end();
        return;
    }
 
    // Publish the sensor data.
    mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg).c_str());
 
    // Eeek?
    //dht.end();
    return;
  }
}
 
bool mqttConnect() {
#ifdef DEBUG
  Serial.println("Attempting to connect to MQTT broker: " + String(MQTT_HOST));
#endif
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
 
  StaticJsonDocument<256> msg;
  if (mqttClient.connect(MQTT_CLIENT_ID().c_str(), MQTT_USERNAME, MQTT_PASSWORD)) {
#ifdef DEBUG
    Serial.println("Established connection with MQTT broker using client ID: " + MQTT_CLIENT_ID());
#endif
    mqttClient.setCallback(mqttCallback);
    msg["action"] = "connected";
    mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg).c_str());
#ifdef DEBUG
    Serial.println("Attempting to subscribe to MQTT topic: " + MQTT_TOPIC());
#endif
    if (!mqttClient.subscribe(MQTT_TOPIC().c_str())) {
#ifdef DEBUG
      Serial.println("Failed to subscribe to MQTT topic: " + MQTT_TOPIC());
#endif
      return false;
    }
#ifdef DEBUG
    Serial.println("Subscribed to MQTT topic: " + MQTT_TOPIC());
#endif
    msg.clear();
    msg["action"] = "subscribed";
    mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg).c_str());
    return true;
  }
#ifdef DEBUG
  Serial.println("Connection to MQTT broker failed with MQTT client state: " + String(mqttClient.state()));
#endif
  return false;
}
 
bool loopWifiConnected() {
  // Process OTA loop first since emergency OTA updates might be needed.
  ArduinoOTA.handle();
 
  // Process MQTT client loop.
  if (!mqttClient.connected()) {
    // If the connection to the MQTT broker has failed then sleep before carrying on.
    if (!mqttConnect()) {
      return false;
    }
  }
  mqttClient.loop();
 
  return true;
}
 
void setup() {
  Serial.begin(115200);
#ifdef DEBUG
  Serial.println("Booted, setting up Wifi in 10s...");
#endif
  delay(10000);
 
  WiFi.mode(WIFI_STA);
#if defined(ARDUINO_ARCH_ESP8266)
  WiFi.hostname(HOSTNAME().c_str());
#elif defined(ARDUINO_ARCH_ESP32)
  WiFi.setHostname(HOSTNAME().c_str());
#endif
  WiFi.begin(STA_SSID, STA_PSK);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
#ifdef DEBUG
    Serial.println("Failed to connect to Wifi, rebooting in 5s...");
#endif
    delay(5000);
    ESP.restart();
  }
#ifdef DEBUG
  Serial.print("Connected to Wifi: ");
#endif
  Serial.println(WiFi.localIP());
#ifdef DEBUG
  Serial.println("Setting up OTA in 10s...");
#endif
  delay(10000);
 
  // Port defaults to 8266
  ArduinoOTA.setPort(OTA_PORT);
 
  // Hostname defaults to esp-[ChipID]
  ArduinoOTA.setHostname(HOSTNAME().c_str());
 
  // Set the OTA password
  ArduinoOTA.setPassword(OTA_PASSWORD);
 
  ArduinoOTA.onStart([]() {
    switch (ArduinoOTA.getCommand()) {
      case U_FLASH:  // Sketch
#ifdef DEBUG
        Serial.println("OTA start updating sketch.");
#endif
        break;
#if defined(ARDUINO_ARCH_ESP8266)
      case U_FS:
#elif defined(ARDUINO_ARCH_ESP32)
      case U_SPIFFS:
#endif
#ifdef DEBUG
        Serial.println("OTA start updating filesystem.");
#endif
        SPIFFS.end();
        break;
      default:
#ifdef DEBUG
        Serial.println("Unknown OTA update type.");
#endif
        break;
    }
  });
  ArduinoOTA.onEnd([]() {
#ifdef DEBUG
    Serial.println("OTA update complete.");
#endif
    SPIFFS.begin();
#if defined(ARDUINO_ARCH_ESP8266)
    // For what it's worth, check the filesystem on ESP8266.
    SPIFFS.check();
#endif
    ESP.restart();
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
#ifdef DEBUG
    Serial.printf("OTA update progress: %u%%\r", (progress / (total / 100)));
#endif
  });
  ArduinoOTA.onError([](ota_error_t error) {
#ifdef DEBUG
    Serial.printf("OTA update error [%u]: ", error);
#endif
    switch (error) {
      case OTA_AUTH_ERROR:
#ifdef DEBUG
        Serial.println("OTA authentication failed");
#endif
        break;
      case OTA_BEGIN_ERROR:
#ifdef DEBUG
        Serial.println("OTA begin failed");
#endif
        break;
      case OTA_CONNECT_ERROR:
#ifdef DEBUG
        Serial.println("OTA connect failed");
#endif
        break;
      case OTA_RECEIVE_ERROR:
#ifdef DEBUG
        Serial.println("OTA receive failed");
#endif
        break;
      case OTA_END_ERROR:
#ifdef DEBUG
        Serial.println("OTA end failed");
#endif
        break;
      default:
#ifdef DEBUG
        Serial.println("Unknown OTA failure");
#endif
        break;
    }
    ESP.restart();
  });
  ArduinoOTA.begin();
 
  // Set up MQTT client.
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  mqttClient.setCallback(mqttCallback);
 
  // Touchdown.
#ifdef DEBUG
  Serial.println("Setup complete.");
#endif
}
 
void loop() {
  // Check the Wifi connection status.
  int wifiStatus = WiFi.status();
  switch (wifiStatus) {
    case WL_CONNECTED:
      if (!loopWifiConnected()) {
        delay(1000);
        break;
      }
      delay(1);
      break;
    case WL_NO_SHIELD:
#ifdef DEBUG
      Serial.println("No Wifi shield present.");
#endif
      goto DEFAULT_CASE;
      break;
    case WL_NO_SSID_AVAIL:
#ifdef DEBUG
      Serial.println("Configured SSID not found.");
#endif
      goto DEFAULT_CASE;
      break;
    // Temporary statuses indicating transitional states.
    case WL_IDLE_STATUS:
    case WL_SCAN_COMPLETED:
      delay(1000);
      break;
    // Fatal Wifi statuses trigger a delayed ESP restart.
    case WL_CONNECT_FAILED:
    case WL_CONNECTION_LOST:
    case WL_DISCONNECTED:
    default:
#ifdef DEBUG
      Serial.println("Wifi connection failed with status: " + String(wifiStatus));
#endif
DEFAULT_CASE:
      delay(10000);
      ESP.restart();
      break;
  }
}

Node-Red

The Node-Red sketch has some exotic nodes due to Amazon Alexa being used, which requires some sort of bridge to Amazon itself, but the rest just relies on built-in nodes.

The "timestamp" and "template" cluster just implement the the pin-toggle template semantics that ar then delivered to the MQTT bus that the Arduino sketch subscribes to; with the "timestamp" node just being a manual override for triggering the controls.

The bottom "timestamp" and "template" pair are made to query the temperature, by delivering a JSON payload with the format:

{
    "pin": "3",
    "action": "temperature"
}

which is the only particularity of the povided Arduino sketch.

Perhaps the most interesting thing for this template is that the lower "timestamp" node is set to re-deliver the above JSON payload on repeat to the MQTT bus, thereby querying the temperature from the Arduino sketch and WeMoS inside the heater. The WeMoS responds with the temperature and the result is fed-back into the Node-Red Smart Home Control node.

As usual, any further automation will be created within Node-Red instead of modifying the Arduino sketch. With the "temperature" being known, it now becomes trivial to automate the heating based on the perceived temperature. The WeMoS could be made to turn on when the temperature drops below a set value and then turn off when another maximal value is exceeded.

Flow Export

[{"id":"23e0eab953f3e600","type":"tab","label":"Heater","disabled":false,"info":"","env":[]},{"id":"9d0a67ae24cb18bf","type":"debug","z":"23e0eab953f3e600","name":"debug 39","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":440,"y":580,"wires":[]},{"id":"d0521f600a3e2f06","type":"mqtt in","z":"23e0eab953f3e600","name":"","topic":"esp/f7189f","qos":"2","datatype":"auto-detect","broker":"5b0ca401533c273c","nl":false,"rap":true,"rh":0,"inputs":0,"x":260,"y":400,"wires":[["21cb9c024377ad17"]]},{"id":"25fc79fcb56aef44","type":"mqtt out","z":"23e0eab953f3e600","name":"","topic":"esp/f7189f","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"5b0ca401533c273c","x":830,"y":400,"wires":[]},{"id":"310b2a845b87b159","type":"template","z":"23e0eab953f3e600","name":"","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n    \"pin\": \"2\",\n    \"state\": \"on\",\n    \"action\": \"set\"\n}","output":"json","x":640,"y":300,"wires":[["25fc79fcb56aef44"]]},{"id":"fb7d4e7cc367bf69","type":"inject","z":"23e0eab953f3e600","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":640,"y":260,"wires":[["310b2a845b87b159"]]},{"id":"d449f50f666c45ae","type":"template","z":"23e0eab953f3e600","name":"","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n    \"pin\": \"2\",\n    \"state\": \"off\",\n    \"action\": \"set\"\n}","output":"json","x":640,"y":380,"wires":[["25fc79fcb56aef44"]]},{"id":"fac633ad41adfc24","type":"inject","z":"23e0eab953f3e600","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":640,"y":340,"wires":[["d449f50f666c45ae"]]},{"id":"32adb0275b659dd2","type":"inject","z":"23e0eab953f3e600","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"60","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":650,"y":420,"wires":[["4cfd396ea20e4a03"]]},{"id":"4cfd396ea20e4a03","type":"template","z":"23e0eab953f3e600","name":"","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n    \"pin\": \"3\",\n    \"action\": \"temperature\"\n}","output":"json","x":640,"y":460,"wires":[["25fc79fcb56aef44"]]},{"id":"14206957491d2d0b","type":"alexa-smart-home-v3","z":"23e0eab953f3e600","conf":"646e7cbc924ee6e0","device":"61533","acknowledge":true,"name":"Bedroom Heating","topic":"","x":240,"y":320,"wires":[["9d0a67ae24cb18bf","fcfafacae0af5d97"]]},{"id":"fcfafacae0af5d97","type":"switch","z":"23e0eab953f3e600","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"ON","vt":"str"},{"t":"eq","v":"OFF","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":3,"x":430,"y":320,"wires":[["310b2a845b87b159"],["d449f50f666c45ae"],[]]},{"id":"93b4e6fa6de53537","type":"alexa-smart-home-v3-state","z":"23e0eab953f3e600","conf":"646e7cbc924ee6e0","device":"61533","name":"Bedroom Heating","x":670,"y":520,"wires":[]},{"id":"11c3d49880026590","type":"debug","z":"23e0eab953f3e600","name":"debug 40","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":640,"y":580,"wires":[]},{"id":"21cb9c024377ad17","type":"change","z":"23e0eab953f3e600","name":"","rules":[{"t":"set","p":"acknowledge","pt":"msg","to":"true","tot":"bool"},{"t":"set","p":"payload.state","pt":"msg","to":"{}","tot":"jsonata"},{"t":"set","p":"payload.state.temperature","pt":"msg","to":"payload.temperature","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":460,"y":520,"wires":[["11c3d49880026590","93b4e6fa6de53537"]]},{"id":"5b0ca401533c273c","type":"mqtt-broker","name":"","broker":"iot","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closeRetain":"false","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"646e7cbc924ee6e0","type":"alexa-smart-home-v3-conf","username":"","mqttserver":"mq-red.cb-net.co.uk","webapiurl":"red.cb-net.co.uk","contextName":"memory"}]

Further Work

The tilt sensor is a very simple way to determine if the heater has fallen over, consisting in a simple button with a microswitch, but given the ESP addition, it is fairly trivial to remove the entire tilt mechanism and replace it with a tilt sensor. One very cool sensor that seems to fit the build and budget is a very simple tilt sensor such as the KY-020 tilt sensor.

The KY-020 tilt sensor is based on a ball that is contained within the sensor that will roll and create a contact whenever the sensor is tilted. There are better solutions than the KY-020 ball sensor, for instance a full gyroscopic sensor that can detect tilt on the three axes but that is definitely overkill for such a project.


iot/build_an_alexa_controlled_heater.txt ยท Last modified: 2024/09/29 16:33 by office

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.