Home Assistant automation for preserving last door opened state

Home Assistant Automation in Depth: Fusing Sensors Together for Stateful Automations

This post may contain affiliate links. Please see the disclaimer for more information.

This post is part of my “Home Assistant Automation in Depth” series. Here is the series index:

One of the most powerful things about Home Automation is being able to undertake many actions simultaneously, based upon a single input. For example, almost every automation system has the concept of scenes which allow you to set the state of multiple devices.

Just as powerful, if not more, is the ability to perform an action based on multiple inputs. We see this in the Home Assistant automation language which introduces the concepts of triggers and conditions. Triggers are the initial event that that the automation reacts to. Conditions are extra inputs which should be in the correct state to proceed.

With these simple concepts, we can easily create automation routines which operate only at certain times, are dependent on the state of the sun or even the moon (if you happen to be a werewolf). However, how do we track the state of something that happened, given that it’s current state may have changed?

State and Events

As a brief aside, we’ll discuss the difference between the state of something and events which occur. I’ll discuss this in general computing terms as well as within HASS.

State is data which describes the current situation. For example if the temperature is 24°C, then the state of the temperature entity should have a numeric value of 24 and a unit of Celsius. In Home Assistant states come in two varieties, the primary state and attributes. The primary state is generally just referred to as the state of the entity. Attributes are more like ancillary data, or metadata, but can be just as useful as the main state.

Events are things that happen. As such they are transient and only exist at the exact point in time at which they occur. Home Assistant automation events can be triggered when something changes state, when a particular time occurs, when a message is received and many others. Even an entity being in a particular state for a certain length of time can be considered an event. See the HASS Triggers documentation for more details. It should be noted that all the items on which an automation can trigger are events in the general computing sense not just those which trigger the ‘event’ trigger.

A system is ‘event driven’ if it primarily reacts to events occurring in the environment rather then polling the state of the environment. In this way, a Home Assistant automation can be seen as an event driven system, since they are primarily triggered by events.

Just get on with it!

Okay, okay.

Enough with the computer science lesson. How is this relevant to triggering an automation when multiple things happen? Well let me describe a situation:

You get up in the middle of the night, open the bedroom door and walk down the hall. You go into the bathroom, where the motion detector senses your presence and helpfully turns the light on. At 100% brightness. Temporarily blind you then proceeds to do whatever it was that got you out of bed.

The light controlled by Home Assistant automation
A very bad picture of the light in question.

Now I can guess what you are saying. Why not just adjust the brightness based on time of day. Well we could, but what happens when we have multiple people in the house, some of whom are awake and some asleep?

[For those that are wondering, the light pictured above is a Mi-Light RGB-CCT downlight. I think that link is to the correct one, but I can’t be sure as I bought mine from LimitlessLED before they closed down. Being an NZ company they were able to provide the documentation for my electrician to install these. I had several of these installed when the house was built, but I actually wish I’d had the whole house done with them.]

Sensor Fusion

Hopefully, we have more than just the time of day and the motion event to go on. With the help of another sensor (or set of sensors) we can integrate the data together and not blind anyone. It should be noted that this isn’t really sensor fusion in the strict mathematical sense! However, the definition fits quite well and I like the name!

In my case the second source of data are my zigbee door sensors. One of these is sensing the state of the bedroom door and the other is sensing the state of the kitchen door to the main living space. The logic is simple, if the bedroom door is opened the light comes on dim when motion is detected. If the kitchen door is opened, the light comes on bright when motion is detected.

Here’s where the difference between state and events becomes important. If I close the door behind me, the system cannot determine the correct door that should drive the lighting. This is because either neither is open, or the wrong door is open (if it was left open). It turns out the event of the door opening was the important part. Not the actual state at the time of the motion event.

We need to convert the door opening event into a state which we can store somewhere else. Luckily, we can do this easily in Home Assistant using an input_select entity.

Finally, some YAML

input_select:
  last_door:
    name: "Last Opened Door"
    options:
      - "Kitchen"
      - "Bedroom"

Here, we create an input_select entity which will store the last opened door. Note, you can extend this to store more states. For my purposes I only need to differentiate between bedrooms and kitchen.

An automation is used to catch the door open events and translate that to the correct state of the input_select:

automation:
  - alias: Set Last Opened Door
    trigger:
      - platform: state
        entity_id: binary_sensor.kitchen_hall_door_contact
        from: "off"
        to: "on"
      - platform: state
        entity_id: binary_sensor.bedroom_door_contact
        from: "off"
        to: "on"
        # further triggers for other bedroom doors can go here
    action:
      service: input_select.select_option
      entity_id: input_select.last_door
      data_template:
          option: "{% if trigger.entity_id == 'binary_sensor.kitchen_hall_door_contact' %}Kitchen{% else %}Bedroom{% endif %}"

I’ll walk through this automation step by step:

  • First we have our triggers, one for each door contact sensor we have. Obviously you can add as may of these as you want. For now I just have two.
  • There are no conditions, the automation just runs whenever triggered.
  • We call a single action which calls the select_option service of our input_select entity.
  • A simple template is used to compare the triggering entity ID (i.e. which contact sensor triggered the automation) with the entity ID of the kitchen door sensor. In that case we set the option to ‘Kitchen’ otherwise we set it to ‘Bedroom’. In this way, this automation will scale to multiple bedrooms, but can only have one kitchen. If you have multiple kitchens (!) or other relevant rooms, you’ll need a more complex template.

Stateful Home Assistant Automation

Next comes our automation to control the light. This automation is stateful, in that it executes differently depending on the state of the input_select we just set:

automation:
  - alias: Bathroom Motion Light
    trigger:
      platform: state
      entity_id: binary_sensor.bathroom_motion
      from: "off"
      to: "on"
    condition:
      condition: or
      conditions:
        - condition: sun
          after: sunset
          after_offset: "-00:20:00"
        - condition: sun
          before: sunrise
          before_offset: "00:20:00"
    action:
      - service: timer.start
        entity_id: timer.bathroom_light_timeout
      - condition: template
        value_template: "{{ states.light.bathroom.state == 'off' }}"
      - service: light.turn_on
        entity_id: light.bathroom
        data_template:
          brightness_pct: >
            {% if now().hour >= 20 or now().hour < 7 %}
            {% if states.input_select.last_door.state == 'Bedroom' %}
            1
            {% else %}
            100
            {% endif %}
            {% else %}
            100
            {% endif %}

Breaking it down

Again, I’ll go through the automation step by step:

  • First we have our trigger, in this case from our motion sensor.
  • Next we have a couple of conditions to only run the automation when it’s dark enough. In this case 20 minutes before sunset and 20 minutes after sunrise. We wrap these in an or block so that only one has to match.
  • Now we get to the actions, the first of these is to start a timer. This will be used later for turning off the light. For completeness the YAML for the timer looks like this:
timer:
  bathroom_light_timeout:
    duration: "00:02:00"
  • The next action is actually another condition, which basically matches only if the light is already off. If this is not matched the action block will terminate here. This condition ensures that the brightness of the light does not get changed if the door state changes. Placing this after the timer start also ensures that the timer is restarted by motion events in the bathroom.
  • The final action is obviously to turn the light on. The interesting part is the template logic. This contains a nested if block, the first part of which checks against some pre-defined times where we only want the light bright (before 8pm and after 7am). This is mostly useful in winter, since the sunset and sunrise rules will prevent the automation from running before/after these times in summer.
  • The inner if statement is where we check our door state. Here we check whether the last door was to a bedroom. In that case we set the brightness to 1% (plenty bright enough for night time use on these lights). Otherwise we go to full brightness.

Finishing It Off

The final piece of the puzzle is turning off the light when the timer expires:

automation:
  - alias: Bathroom Light Timeout
    trigger:
      platform: event
      event_type: timer.finished
      event_data:
        entity_id: timer.bathroom_light_timeout
    action:
      - service: light.turn_off
        entity_id: light.bathroom

This is reasonably self explanatory. The only odd looking bit is in the trigger where we must use the event platform to directly catch the timer.finished event, since the timer doesn’t have it’s own trigger type.

Conclusion

Phew! Hopefully you got this far and didn’t get lost in the weeds of the states vs events stuff!

The automations shown here have been running for several months pretty much flawlessly. I’ve given them some minor tweaks such as introducing the outer if statement to check the hours, when winter came around and it became apparent that it was needed.

The usual complaint is that motion sensing lights tend to turn off when people are still in the room but not moving. Since the motion sensor in this case is so sensitive we’ve not had much of an issue with this, it will trigger even on the slightest motion. Interestingly it also seems not to false trigger, so I think the balance is just right.

Thanks for following along! I don’t have any more plans for another Home Assistant Automation in Depth article, but I’m sure there will be another installment in the future. I just need to write some more interesting automations.

If you liked this post and want to see more, please consider subscribing to the mailing list (below) or the RSS feed. You can also follow me on Twitter. If you want to show your appreciation, feel free to buy me a coffee.

Installed room sensor

Room Sensor Project: Part 2 – Infrastructure and Mounting

This post may contain affiliate links. Please see the disclaimer for more information.

This post is part of a series on this project. Here is the series so far:


Looking back, it seems to have been a ridiculously long time since my last room sensor post. It’s well over a year, but it doesn’t seem that long ago. This project has been majorly delayed by a few issues and generally hasn’t been top of my todo list. However, I’m now at the point where I have the prototype sensor installed and working. Actually, it’s been working for several months, but I hadn’t got around to writing it up! In this post I’ll mostly be detailing the infrastructure used to get power to the sensor. There will also be some discussion on the case and mounting as well as a few words on the software.

Finding a Case

It would have been really nice just to 3D print a case and mounting bracket for the sensors. Unfortunately, I don’t (yet) have a 3D printer and it was cheaper to buy cases than to get a 3D printing service to print them. I settled on a 100x60x25mm case and ordered 15 of them. Once they arrived I was able to fit all the electronics inside and cut a slot in the bottom for the DHT22 sensor. A dremel-like tool would have helped a lot here, but I managed to do it manually and it looks OK. I actually reversed the case so that the lid became the rear as this looks a little nicer and helped with the mounting of the components.

I also fitted a light sensor based on an LDR voltage divider circuit to the front of the case. Unfortunately I had issues with the ADC pin on the bare ESP8266 module I used for the prototype. It’s odd because I’ve had this working with the Wemos D1 modules before (which have their own voltage divider on the input also). In the end I didn’t manage to get this working and have resolved to replace the prototype board with a Wemos based one when I do the sensors for the rest of the house.

I drilled a hole in the front of the case for the PIR sensor and mounted the diffuser over the hole. The PIR board itself was hot glued to the reverse of this. This works really well – in fact if anything the sensor is a little too sensitive. I need to tweak the pots a little to dial this down.

You can see pictures from during the assembly as well as the fully assembled case below:

Main room sensor board mounted to case lid.
Main room sensor board mounted to case lid.
PIR difuser mounted to case (front view)
PIR diffuser mounted to case (front view)
PIR difuser mounted to case (top view)
PIR diffuser mounted to case (top view)
PIR sensor mounted to inside of case.
PIR sensor mounted to inside of case.
Side view of room sensor board showing power connections
Side view of room sensor board showing power connections
Fully assembled case (upside down)
Fully assembled case (upside down)
Fully assembled case (side view)
Fully assembled case (side view)
Fully assembled case (standing on the DHT sensor)
Fully assembled case (standing on the DHT sensor)

Power Setup and Wiring

In order to get power to the sensors I had decided on running 12V lines through the roof space of the house. These would then come down through the ceilings in the corner of each room for the sensors. The cables would be fed from a central distribution board mounted near the loft hatch in the ceiling. Since the power requirements for the 12 sensors I wish to eventually install are minimal a single 2A is enough to power the whole lot with some room to spare. Some pictures of the distribution board (before and after installation) are shown below:

Room sensor power distribution board (before installation)
Unwired room sensor power distribution board (before installation)
Room sensor power distribution board (post installation)
Room sensor power distribution board (post installation)

I had initially wanted to mount the power supply down in the ‘server rack’ and run the power up through an (existing) whole in the wall to the loft. However, after an abortive attempt at running a cable through the wall (in which I was foiled by pesky insulation and there was much swearing), I eventually came to the conclusion that mains power was needed in the roof space.

Time passes…

It took a while to really commit to and allocate funds to this option. Eventually the electrician came and installed four shiny new power points in the roof space just next to the loft hatch. Four power points is obviously overkill for this project. However, in the intervening time some other projects had come up for which the remaining plugs would be useful.

Once the power points were in place I ran the cabling for the 12V line to the room in which the sensor was to be installed and connected it all up. As if by magic power flowed and the sensor sprung into life! (barring various frustrating issues with loose connections).

I had quite an interesting time working out how to mount the sensor in the corner of the room. I initially stuck it up with 3M double sided sticky pads, but ended up pulling it up and down several times so that I ran out of these. Eventually I opted for good old fashioned blu-tack as a temporary solution! I’m intending to replace this with a 3D printed bracket which will fit the oddly shaped space between the case and the wall. This will allow the sensor to be attached much more permanently. However, for now the blu-tack does the job and proves the concept.

Installed room sensor
Installed room sensor

Relay Power Control

I mentioned above that I had installed several extra power sockets in the roof space for other projects. One of those other projects required putting a Raspberry Pi in the roof space. The other project is now completed and I’m hoping to document it soon. For this I pressed into duty my old Raspberry Pi Model B (the one with 512Mb of RAM). Although old, this hardware is sufficient for running a small Node-RED instance as well as performing it’s other intended duty.

This gave me a nice way to control the power supply to the room sensor distribution board and hence all the sensors. That meant that if a sensor were to go offline I could cycle the power remotely without having to climb up in the roof space. To do this I inserted a relay into the power 12V line between the power supply and the power distribution board.

The Raspberry Pi with the relay assembly
The Raspberry Pi with the relay assembly

I connected this to the normally closed input of the relay so that the relay must be switched on to kill the power. The state is then inverted in Node-RED. In this way the switch in Home Assistant shows as on most of the time. I only had 5V relays sitting in my parts box. So, I soldered up a quick transistor circuit on a breadboard to allow me to drive the relay from the 3.3V logic of the Pi. Doing this is left as an exercise for the reader, since I forgot to document what I built!

Driving the Relay in Node-RED

To drive the relay I use a variation of my MQTT discovery switch in Node-RED. This is implemented via the following flow:

The room sensor relay control flow
The room sensor relay control flow

The JSON for this is shown below (copy it and import into Node-RED):

[{"id":"e683c60f.71219","type":"tab","label":"Room Sensors","disabled":false,"info":""},{"id":"49f1f109.608fa","type":"rpi-gpio out","z":"e683c60f.71219","name":"Relay 2","pin":"13","set":true,"level":"0","freq":"","out":"out","x":760,"y":60,"wires":[]},{"id":"80843251.fcdf58","type":"mqtt in","z":"e683c60f.71219","name":"Room Sensors Command","topic":"homeassistant/switch/room_sensors/cmd","qos":"2","broker":"65d3656f.217c14","x":170,"y":80,"wires":[["afc07304.5d321"]]},{"id":"afc07304.5d321","type":"switch","z":"e683c60f.71219","name":"On or Off?","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"ON","vt":"str"},{"t":"eq","v":"OFF","vt":"str"}],"checkall":"false","repair":false,"outputs":2,"x":430,"y":80,"wires":[["a2b8666b.9e744","6c973ed5.7607f8"],["76f0cbf9.50415c","6c973ed5.7607f8"]]},{"id":"a2b8666b.9e744","type":"change","z":"e683c60f.71219","name":"ON","rules":[{"t":"set","p":"payload","pt":"msg","to":"0","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":60,"wires":[["49f1f109.608fa"]]},{"id":"76f0cbf9.50415c","type":"change","z":"e683c60f.71219","name":"OFF","rules":[{"t":"set","p":"payload","pt":"msg","to":"1","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":100,"wires":[["49f1f109.608fa"]]},{"id":"d673854.bac6478","type":"mqtt out","z":"e683c60f.71219","name":"Send Messages","topic":"","qos":"2","retain":"true","broker":"65d3656f.217c14","x":750,"y":240,"wires":[]},{"id":"95bdf49b.5a011","type":"function","z":"e683c60f.71219","name":"Format config messages","func":"var config = {\n    payload: {\n        name: \"Room Sensors\",\n        command_topic: \"homeassistant/switch/room_sensors/cmd\",\n    },\n    topic: \"homeassistant/switch/room_sensors/config\"\n};\nreturn config;","outputs":1,"noerr":0,"x":430,"y":240,"wires":[["d673854.bac6478"]]},{"id":"c45bb370.032b48","type":"inject","z":"e683c60f.71219","name":"@Startup","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":"3","x":150,"y":240,"wires":[["95bdf49b.5a011"]]},{"id":"6c973ed5.7607f8","type":"function","z":"e683c60f.71219","name":"Set topic","func":"msg.topic = \"homeassistant/switch/room_sensors/state\";\nreturn msg;","outputs":1,"noerr":0,"x":620,"y":160,"wires":[["d673854.bac6478"]]},{"id":"65d3656f.217c14","type":"mqtt-broker","z":"","name":"Home Broker","broker":"mybroker.example.com","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]

Over the following months of usage, I noted several (infrequent) instances where the sensor stopped responding. In these cases it needed a manual (though remote controlled) power cycle. In order to automate this and so reduce downtime, I wrote the following Home Assistant automation:

  - alias: Auto-reset Room Sensors
    trigger:
      - platform: state
        entity_id: binary_sensor.prototype_sensor_status
        to: "off"
        for: "00:10:00"
    action:
      - service: switch.turn_off
        entity_id: switch.room_sensors
      - delay:
          seconds: 30
      - service: switch.turn_on
        entity_id: switch.room_sensors
      - service: notify.notify
        data_template:
          title: "Room Sensors Reset"
          message: "Device '{{ trigger.to_state.name }}' was offline for 10 minutes, room sensors reset."

Basically, this will trigger after the sensor has been offline for 10 minutes. Once triggered it will turn the sensor off and on again (with a 30 seconds delay in between). It will also send a notification to inform me that this has happened. I think this has been triggered twice and as a result the sensor hasn’t been unavailable for any length of time.

Software Changes

Since installing the prototype sensor, I haven’t actually been running my Micropython Room Sensor software on it. Instead I’ve been trying out ESPHome on this and another project, since it’s been getting a lot of attention in the HASS community recently. I specifically wanted to see if ESPHome was an easier/maintenance free option for these types of projects.

My take away from this is that ESPHome is really nice and very easy if you don’t want to do anything complicated. If all you have are a few sensors or actuators that you want to connect, it’s great! In fact it’s almost perfect for this kind of project. You can even do some moderately complicated data conversions and on device automation using the lambda syntax. For this reason, I’d put it in the same basket as the likes of ESPeasy. Although it has some advantages in comparison to other systems, especially if you are already running Home Assistant. Kudos to Otto Winter for coming up with such a great piece of software!

However, it does get more difficult when you want to more complicated things. I ran into some of these issues in my other project, which I’ll detail when I eventually write it up. For now ESPHome gets my wholehearted recommendation.

ESPHome Configuration

I especially like that since the configuration for ESPHome devices is just YAML it’s really easy to store in git. I haven’t got a cleaned up git repo for my projects ready to publish. However, since the configuration for this project is so simple, I can post the whole thing here:

esphome:
  name: room_sensor_prototype
  platform: ESP8266
  board: esp12e

wifi:
  ssid: 'my-wifi'
  password: 'supersecret'

mqtt:
  broker: 'mqtt.example.com'
  username: 'test'
  password: 'supersupersecret'

# Enable logging
logger:

ota:
  password: 'massivelysupersecret'

sensor:
  - platform: dht
    pin: 12
    model: AM2302
    temperature:
      name: "Test Temperature"
    humidity:
      name: "Test Humidity"
    update_interval: 15s

binary_sensor:
  - platform: status
    name: "Prototype Sensor Status"
  - platform: gpio
    pin: 2
    name: "Test Motion"
    device_class: motion

You’ll notice that I’m still using the MQTT transport rather that the native API component with the Home Assistant ESPHome component. This is mainly because I built this before the native API was released and I didn’t need to update it. I understand there are some advantages to using the native API, so I probably will try it at some point, especially if I want to try an esp-cam project.

What’s Next

So far, I’m really happy with the performance of the sensor. I’ve been using it in a few automations which I’m intending to detail in a further post. The next thing to do is build further sensors for the remainder of the house. In order to make this less error prone I’ve decided to design an adapter PCB in Kicad for the Wemos D1 Mini clones I’ve been using in other projects. This will get me away from the fiddling with bare ESP modules and hopefully mean that the light sensor will work.

As mentioned above, I also want to design a 3D printed bracket to fit the oddly shaped space in the corner behind the sensor. This will have to wait until I get a 3D printer, which will hopefully happen later this year.

Aside from that the only other job will be deploying the new sensors once they are built. This will mean running all the remaining power cables through the roof space, so lots of crawling around up there (yay! /s).

If you liked this post and want to see more, please consider subscribing to the mailing list (below) or the RSS feed. You can also follow me on Twitter. If you want to show your appreciation, feel free to buy me a coffee.

Micropython Room Sensor: Part 1 – The Initial Prototype

This post may contain affiliate links. Please see the disclaimer for more information.

This post is part of a series on this project. Here is the series so far:


Having recently moved into a new house, I find myself with a profusion of Smart Lights. These are by no means everywhere yet, but in more locations than I have previously had. The main problem is that I currently lack a way to drive these intelligently. Right now I have a few automations that drive them based on sunset/sunrise times and media state through Home Assistant. This is mainly due to having almost no sensors deployed in the new house currently – something I’m aiming to address in this project with my Micropython Room Sensor.

I’m planning to build and deploy a set of ESP8266 based sensors across the house, with at least one in each room. The base hardware for this is the ESP8266 connected to a PIR motion sensors and DHT22 temperature and humidity sensor (so nothing ground breaking). This should give me temperature/humidity data for the whole house as well as motion events that can be used as a starting point to drive the light automation.

Why ESP8266?

I’ve gone through several options for the sensor platform on the way to settling on the ESP8266. I’ve tried out MySensors and even gone as far as building a battery powered prototype complete with PCB. Unfortunately I stuffed up the PCB design so that the radio didn’t work and never got around to working out the problem. Thinking further on this I find the software platform limiting. For example, any kind of OTA firmware update is difficult.

The main disadvantage of the ESP8266 is the power usage. However, given that I now own this house and that these are permanent sensors this becomes less of a problem. I’m planning on powering them via a 12v supply run from the main server cabinet through the roof space to each sensor. There is more than enough power from a 12v 2A supply to provide power to all the sensors.

Also, it’s cheap and easy to work with!

Why Micropython?

I had originally looked at either writing some custom firmware using the Arduino ESP8266 core or using ESPEasy. I’ve tried out ESPEasy on my first prototype and it does what I need, but as an Embedded Software Engineer by day I felt I needed to be more adventurous.

I love Python as my go to language whenever the platforms don’t dictate something else (in the case of embedded this almost always ends up being C or C++). Recently I’ve been getting more engaged with the Python ecosystem via a couple of excellent podcasts (Talk Python and Python Bytes), so this was a good time to get back into writing some Python at home. I had played around with earlier versions of Micropython on both the ESP8266 and the ESP32 so I had a good idea of its capabilities and knew it could do what I needed.

Current Status

As of right now, I have one prototype sensor running using a ‘bare’ ESP-12 module with adapter board, plus the sensors soldered on to some vero-board (photos below). Using the ‘bare’ ESP module added quite a bit of complexity since you are responsible for pulling the various lines required to program/run the chip up or down. It’s not difficult, but it’s more difficult than it should be. For that reason, after I’ve exhausted my one remaining ‘bare’ module/adapter I will be switching to Wemos D1 Mini modules.

In terms of the hardware, I encountered a couple of issues building the first prototype. The ESP8266 adapter board I used has a regulator to take 5V down to the 3V3 required for the ESP. However, I wanted to use 12V to reduce the voltage drop through the wires in the ceiling space. This meant I needed another regulator to go from 12V down to 5V.

A Mistake in Regulation

At first I used a spare linear regulator I had in my parts box for this. As it turns out this was a mistake. Whilst it powered the ESP and sensors just fine, the whole thing got very hot (even with an extra heat sink). This also affected the reading from the temperature sensor. The solution was to order some cheap buck converter modules online. These did the trick without getting the slightest bit warm and are much more efficient.

Flash Issues

The second issue is a weird one. Whilst re-programming the ESP from ESPEasy to Micropython, I noticed that after running the erase flash command the current going to the board would get very high (~400mA at 12V!). This would result in the ESP getting very hot. Seemingly this didn’t damage it because I did it several times and it always continued to work. In addition to this the Micropython firmware wouldn’t boot, instead just spewing incomprehensible (at any of the common baud rates) garbage to the UART. As it turns out, the chip was not being erased correctly, running an erase regions across the full extent of flash did the trick:

esptool.py --port /dev/ttyUSB0 erase_region 0 4194304

If anyone knows what the problem with these modules is, please get in touch in the comments. I couldn’t find any reference to this problem online and the (probably cloned) Wemos modules I have don’t have the same problem.

Anyway, here are some photos of the finished prototype:

Fully assembled prototype micropython room sensor

Fully assembled prototype with PIR sensor connected.

Front of prototype micropython room sensor

Front of prototype showing ESP8266 and adapter board

Back of prototype micropython room sensor

Back of prototype, the wires in the bottom right corner are where it was re-worked after changing the power supply

The Micropython Room Sensor Software

The software is fairly basic, but has a few nice features. I use the boot.py script to load the configuration from the config.json file on the internal filesystem. I then connect to the wifi following the example given in the Micropython documentation.

After this main.py is executed which connects to the broker and initialises the sensors. This is where things get a bit more interesting. I’ve written a minimal implementation of the Home Assistant MQTT Discovery spec, which so far supports binary sensors and standard sensors. This allows the sensors to come up in HASS without any configuration on the HASS server (except enabling MQTT discovery, which is a one time operation).

Reading from the sensors themselves is fairly standard. I use a pin change interrupt for the motion sensor and the standard dht module for the temperature/humidity. The use of uasyncio allows the DHT sensor to be polled at regular intervals. The code for the whole program is available via GitLab (also mirrored on GitHub). You can see a screenshot of the sensors in HASS below:

Home Assistant state card

Home Assistant state card showing the automatically added entities

How Did Micropython Perform?

In comparison to other embedded platforms/languages, writing a Micropython application is awesome. The power of Python makes you extremely productive. I wrote the whole software in a couple of hours, including the MQTT discovery implementation, using a Wemos board with no sensors connected as my development platform.

When I uploaded this to the prototype board it hit a couple of exceptions in the sensor code, which previously hadn’t been exercised. The key thing here is that it hit exceptions! The resulting stack traces of course tell you where and why the exception occurred. What could have been a long and arduous debugging session turned into a few minutes of tweaking. The availability of uasyncio is also a really nice feature and allows direct knowledge transfer from CPython.

Future Work

There is still some work to do in order to finish the project, mostly on the hardware side. Obviously I need to make enough sensors to cover the house, which is just an ongoing assembly task. There is also the in ceiling wiring, assembly into cases and mounting. I’ll be sure to post an update on this as I make progress. There are also a couple of software improvements I would like to make.

Firstly, I would like to add a meta sensor which makes use of the LWT feature of MQTT to report the status of the unit, but also still utilises HASS MQTT discovery. I’m also planning to spin out the current MQTT discovery implementation into it’s own Python module. Ideally this will be published to PyPI. The only other software task is to write a script to automate the deployment of the Micropython firmware, application and device specific configuration. This should be extremely useful when programming multiple units.

Thats all for now. I’ll be posting further parts in this series as the project progresses.

If you liked this post and want to see more, please consider subscribing to the mailing list (below) or the RSS feed. You can also follow me on Twitter. If you want to show your appreciation, feel free to buy me a coffee.