mysensors node red proxy flow

Quick Project: MySensors MQTT Proxy with Node-RED

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

In my previous exploration of MySensors, I encountered issues with the range of the nrf24l01+ radio. One of the simplest solutions to this problem is to move the gateway closer to the intended deployment of the sensors. In my case the sensor is positioned on the back wall of our house, just outside the master bedroom. Connected to the TV inside the master bedroom is a Raspberry Pi running Libreelec. I reasoned that I should be able to use this Pi to proxy the MySensors serial data to Home Assistant via MQTT, using the MySensors Node-RED nodes. With this approach I can make a new MQTT gateway using hardware I already have deployed.

It was also suggested that adding a 10-100uF 6.3/10V capacitor across the power line to the radio would also help with the range issues. I’m definitely going to do this, but the components haven’t arrived yet!

Hardware Setup

If you followed my previous post all you need to do here is plug in the serial gateway to the Pi! Otherwise you’re going to need to build a gateway (and maybe some sensors). Go check out my original post for more details.

It’s probably worth going through how this is going to work. Basically, the idea is that the RF data will be received by our existing MySensors gateway and sent via serial to the RPi. This will be read by Node-RED, re-encoded and sent via MQTT to Home Assistant which will still act as the MySensors controller:

Serial GW ⇒ Serial Connection ⇒ Node-RED (on RPi) ⇒ MQTT ⇒ HASS (Controller)

In the return direction this dataflow operates in reverse:

HASS ⇒ MQTT ⇒ Node-RED ⇒ Serial Connection ⇒ Serial GW

Deploying Node-RED on Libreelec

Libreelec is a cut down appliance-like Linux distribution, specifically for running Kodi. At first glance it seems like installing a third party program such as Node-RED would be difficult. However, Libreelec has Docker support via an add-on, which makes things as easy as running the command:

$ docker run -d --restart=always --device=/dev/ttyUSB0:/dev/ttyUSB0 --group-add dialout -v /storage/node-red-data:/data -p 1880:1880 --name node-red nodered/node-red:latest

This starts the Node-RED Docker container with access to the serial port for our plugged in Arduino board. You’ll need to adjust the path to the TTY device depending on how the Arduino gets enumerated. We are also storing the data for Node-RED (including flows) in the Libreelec storage directory, so that we can re-create the container without losing our flows.

Once this starts up, you should be able to access a nice clean Node-RED instance on port 1880 of your Libreelec Pi. At this point it’s probably a good idea secure your instance and set up projects for backing up your flows via Git.

MySensors Proxy Flow

So here’s what you came here for. Using the MySensors nodes I’ve created a flow which receives MySensors data via the serial port decodes it to a Javascript object and then immediately encodes it and sends it via MQTT. To do this we make use of the serial decode and MQTT encode nodes. In the opposite direction we receive MQTT data on the mysensors/in/# wildcard and decode it to another JS object. This is then immediately encoded for serial and sent out the serial port. The finished flow looks like this:

mysensors node red proxy flow
The MySensors proxy flow

The full JSON for this is below. You’ll need to update the details for the MQTT broker and serial port to match your installation:

[{"id":"750924cf.e87e14","type":"tab","label":"MySensors MQTT Proxy","disabled":false,"info":""},{"id":"e7a50a55.c700d8","type":"mysdecode","z":"750924cf.e87e14","database":"","name":"Decode Serial","mqtt":false,"enrich":false,"x":244,"y":125,"wires":[["1fe04ed1.423d09"]]},{"id":"1fe04ed1.423d09","type":"mysencode","z":"750924cf.e87e14","name":"Encode MQTT","mqtt":true,"mqtttopic":"mysensors/out","x":429,"y":125,"wires":[["73eb8bcf.3be6bc"]]},{"id":"73eb8bcf.3be6bc","type":"mqtt out","z":"750924cf.e87e14","name":"","topic":"","qos":"","retain":"","broker":"61f77eeb.b75e2","x":584,"y":125,"wires":[]},{"id":"1a5ca295.7db125","type":"serial in","z":"750924cf.e87e14","name":"serial in","serial":"31aea0d6.e5a438","x":87,"y":125,"wires":[["e7a50a55.c700d8"]]},{"id":"5a8336a4.b9d398","type":"mqtt in","z":"750924cf.e87e14","name":"","topic":"mysensors/in/#","qos":"2","datatype":"auto","broker":"61f77eeb.b75e2","x":117,"y":179,"wires":[["b8c6fd6f.3ecf08"]]},{"id":"b8c6fd6f.3ecf08","type":"mysdecode","z":"750924cf.e87e14","database":"","name":"Decode MQTT","mqtt":true,"enrich":false,"x":320,"y":180,"wires":[["440bfad4.44eadc"]]},{"id":"440bfad4.44eadc","type":"mysencode","z":"750924cf.e87e14","name":"Encode Serial","mqtt":false,"mqtttopic":"","x":508,"y":179,"wires":[["e8c5f4bd.bc3d3"]]},{"id":"e8c5f4bd.bc3d3","type":"serial out","z":"750924cf.e87e14","name":"serial out","serial":"31aea0d6.e5a438","x":676,"y":179,"wires":[]},{"id":"61f77eeb.b75e2","type":"mqtt-broker","z":"","name":"Home Broker","broker":"mqtt.example.com","port":"1883","tls":"b8f1023d.4df4","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"nodered/status","birthQos":"2","birthRetain":"true","birthPayload":"online","closeTopic":"nodered/status","closeQos":"2","closeRetain":"true","closePayload":"offline","willTopic":"nodered/status","willQos":"2","willRetain":"true","willPayload":"offline"},{"id":"31aea0d6.e5a438","type":"serial-port","z":"","serialport":"/dev/ttyUSB0","serialbaud":"115200","databits":"8","parity":"none","stopbits":"1","waitfor":"","dtr":"none","rts":"none","cts":"none","dsr":"none","newline":"\\n","bin":"false","out":"char","addchar":"","responsetimeout":"10000"},{"id":"b8f1023d.4df4","type":"tls-config","z":"","name":"Home Broker","cert":"","key":"","ca":"","certname":"","keyname":"","caname":"","verifyservercert":true}]

That’s it for the flow. We can integrate this into HASS by changing our previous gateway configuration to use MQTT:

mysensors:
  gateways:
    - device: mqtt
      persistence_file: '/config/mysensors.json'
      topic_in_prefix: 'mysensors/out'
      topic_out_prefix: 'mysensors/in'

Conclusion

With this in place I’m now able to receive data from my sensor in the greenhouse without the reception issues I was experiencing. I like the use of Node-RED for these kinds of protocol conversion jobs. Its dataflow model is supremely suited to these operations.

Other than the range issue, the MySensors node I built in my previous post has been pretty reliable. I did make a minor hardware change to the setup I described earlier, which was to move positive battery connection from the raw power line to the VCC line of the Arduino. This is because the raw line is regulated to 3.3V. The voltage regulator will consume some power and also won’t work once the battery voltage drops below about 3.3V. Connecting to VCC powers the micro directly from the battery without the extra power loss. Since this modification the sensor has been running flawlessly for several weeks.

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.

Road to Docker Part 2

My Road To Docker – Part 2: My Home Automation Stack

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:


In my first post of this series, I outlined my plan to convert my infrastructure over to a layered setup. This would consist of virtual machines (in various VLANs), with most of the services running in Docker. This post details the second stage of my road to Docker, although really was is the first stage since I’m writing these out of order! I actually converted my home automation systems over to Docker before tackling the web stack.

The motivation behind upgrading the home automation system first was to do it at the same time as I did a large update to Home Assistant, since I’d been holding back on updating. The main reason for this was the switch to Lovelace as the default UI, which I was dreading. As it turned out, I waited long enough for the awesome HASS developers to make all my problems go away (or at least the Lovelace related ones).

System Summary

I’ve written about my home automation setup before, but here is a brief recap of what I’m running (only the server side stuff):

I had also been running InfluxDB and Grafana. However, something broke in my setup and I hadn’t got around to fixing it. I therefore decided to cut my losses with that and not reinstall it (for now).

Finding Docker Images

Luckily for me, the four main components of my system all have official/recommended Docker images available. This was useful as I’m always pretty reticent to use some questionably maintained image from the Docker Hub, mainly due to the lack of security updates. I also wanted to avoid building custom Docker images for now, until I work out a decent update strategy.

In addition to the four services above I wanted to run the ESPHome dashboard in order to manage my devices better. I had previously just been using the command line tool to build and upload to them. This also has an official Docker image.

Road to Docker Part 2
Looks like I have a few devices to update!

I also ended up running a MaryTTS container to replace PicoTTS that I had been running for my voice announcements. This is due to the lack of PicoTTS inside the HASS Docker image. It was recommended that I use the silversniper/marytts image. Looking at this image, it hasn’t been updated in three years which. This reinforces my point about random images from Docker Hub. Luckily, this isn’t an externally facing application so it isn’t too critical from a security standpoint. However, I think I’ll look into updating that at some point.

Stacking Containers, again…

I set up a new clean VM inside my home automation VLAN. I was a little reserved about doing this, since it means everything in that VLAN (most of which is blocked from the internet) can see the full HA server. However, my main worry over not doing that was the mDNS used by ESPHome. If I can get Avahi/mDNS working across VLANs at some point I will move it. I still have a big network re-organisation to do, so hopefully it will get done then.

The full docker-compose.yml file for my new stack is given below:

version: '3'

services:
  mosquitto:
    image: eclipse-mosquitto
    restart: always
    ports:
      - 1883:1883
      - 8883:8883
      - 9001:9001
    volumes:
      - /mnt/docker-data/mosquitto/config:/mosquitto/config
      - /mnt/docker-data/mosquitto/data:/mosquitto/data
      - /mnt/docker-data/mosquitto/logs:/mosquitto/logs

  zigbee2mqtt:
    image: koenkk/zigbee2mqtt
    volumes:
      - /mnt/docker-data/zigbee2mqtt:/app/data
    devices:
      - /dev/ttyACM0:/dev/ttyACM0
    depends_on:
      - mosquitto
    restart: always
    network_mode: host

  homeassistant:
    image: homeassistant/home-assistant
    volumes:
      - /mnt/docker-data/home-assistant:/config
      - /etc/localtime:/etc/localtime:ro
    depends_on:
      - mosquitto
    restart: always
    network_mode: host

  nodered:
    image: nodered/node-red-docker:v8
    ports:
      - 1880:1880
    volumes:
      - /mnt/docker-data/node-red:/data
      - /etc/localtime:/etc/localtime:ro
    depends_on:
      - mosquitto
      - homeassistant
    restart: always

  esphome:
    image: esphome/esphome
    volumes:
      - /mnt/docker-data/esphome:/config
    restart: always
    network_mode: host

  marytts:
    image: sliversniper/marytts
    ports:
      - 59125:59125
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /mnt/docker-data/marytts/lib/voice-dfki-poppy-hsmm-5.2.jar:/marytts/marytts-5.1.2/lib/voice-dfki-poppy-hsmm-5.2.jar:ro
    restart: always

There’s nothing particularly earth shattering here. The main point of interest is that I mount the voice file for my preferred MaryTTS voice inside the container. Actually finding the voices is a little interesting. The official way to download them is a GUI tool that won’t run inside the container. I eventually found the XML file which lists all the available voices and extracted the URL of the one I wanted (the online demo helps to decide).

The only other parts worth noting are that I mount all my volumes under /mnt/docker-data, which is an NFS share onto the ZFS array of the virtual machine host. This then gets rolled into my normal backups. I also didn’t bother with a reverse proxy for any of this, since I already have one for HASS sitting in my DMZ (yet to be Dockerised). The other services just get accessed via the machine hostname and port since they are only used internally.

Sometimes Waiting Pays

I didn’t run into any issues particular to setting this up in Docker. At this point I think it’s a pretty well trodden path and this setup is pretty much standard. I did however run into several issues with upgrading to the latest Home Assistant.

First, lets tackle the elephant in the room – Lovelace. I was really worried going into this that it was going to be a huge amount of work. My mind was somewhat put at rest by seeing the UI editor in action via misperry’s video. When I actually came to it, the migration process automatically re-created my existing UI pretty much perfectly. Lesson learned: there is something to be said for waiting for mature software, rather than jumping on the new shiny thing immediately!

Lovelace itself is awesome! The ease of configuration has made me actually focus on making my HASS UI nicer rather than just the bare minimum I could get away with that I had previously. In the screenshot below, you can see my new “Outdoors” panel. This contains weather information, outdoor related sensor readings and a couple of local webcam views.

Road to Docker Part 2
I probably should have taken this screenshot during the day

Remaining Issues

Most of my remaining issues were due to the Home Assistant “Great Migration”. This resulted in a load of entity IDs changing in various components. Obviously, this resulted in my having to update my configuration to change all the names. It took a little while to troubleshoot. This was because if the changed name is used in an automation, the automation has to actually fire to cause an error. In many cases it also just won’t fire, if the name is used in the triggers for the automation.

The final major issue I encountered, was with my frankly awesome vacuuming robot, which appeared to stop reacting to service calls in HASS. The underlying issue appears to be the Botvac D3 returning a different error message than the D7 that the library was tested with. So far this hasn’t been fixed, but I’m currently using the category: 2 workaround suggested and that’s working fine. I think I’ll have a look into fixing that issue and submit a PR when I get time.

Managing Updates

Managing updates to Docker images has always been an bit of an issue for me. In the past I’ve used Watchtower with some success. However, due to the capacity for breaking changes I want to manage HASS updates more carefully. It was suggested to me to just use a bash script which I can run periodically to do this. This isn’t something that had occurred to me before, probably because it’s so simple! Here’s the script I’m using:

#!/bin/bash
set -e

cd /mnt/docker-data/stacks/ha
sudo apt update
sudo apt upgrade -y
sudo apt autoremove -y
sudo apt clean
docker-compose pull
docker-compose down
docker-compose up -d
docker system prune -fa
docker volume prune -f
exit

This works beautifully and allows me to easily keep up with release to HASS and the other components, once I’ve verified that it’s reasonably safe to update.

Conclusion and Next Steps

Overall I’m pretty happy with how this move has turned out. Once the initial teething issues were all worked out the system has been very stable. I’m appreciating the extra utility of the ESPHome dashboard, which makes it very convenient to update my devices. It’s also great to be back on the latest version of Home Assistant.

In terms of next steps, I would like to give InfluxDB and Grafana another try. My main issue here has always been building the dashboards. It seems to be pretty tricky to get something both good looking and useful in Grafana. I also haven’t seen any pre-built dashboards for use with data from Home Assistant. Perhaps this is because they are so peculiar to individual setups.

I also have an LXD container running ZoneMinder. I’d like to re-deploy this as a Docker container on the same VM. Previously, I’ve not had too much luck running ZoneMinder in Docker. I’ll to see if the situation has improved when I tackle this migration.

I’m not actively working on any further migrations of other services to Docker at the moment, so there will probably be a break in this series for now. However, given my current success I’ll definitely be continuing on with this migration. I just want to work on some other projects for a while!

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.

HDMI CEC Flow

HDMI CEC for Home Assistant with Node-RED

I set out on a Sunday morning thinking this was going to be a quick project and, not having decided on a blog topic for this week, it seemed like the ideal candidate. I was wrong – about it being a quick project, hopefully not about it being a reasonable subject for a blog post.

This post is brought to you by issue #12846 in Home Assistant (and the letter ‘C’). That is to say, one of my automations was broken by this issue, which has been sitting open on GitHub since the beginning of march with no progress. I don’t want this to sound like the usual “user of Open Source application complains about free stuff”, because I’m not actually complaining. I understand that software breaks and sometimes there aren’t the resources available to fix it. The solution to this is to get more developers paid to work on Free and Open Source Software (but that’s entirely a discussion for outside of this post).

Actually, this post is here to offer a solution (or at least a temporary one) to the issue, outside of Home Assistant, since I couldn’t fix it myself (I took a look at the code in question and I couldn’t work it out – it needs to be done by someone with more familiarity with the Home Assistant core).

My solution is to use Node-RED along with the HDMI CEC nodes to create an auto-discovered MQTT switch with which I can turn on and off my TV. So, let’s get into the flow…

The Flow

HDMI CEC Flow

The HDMI CEC Switch Flow

This flow runs on an instance of Node-RED running on my OSMC based Raspberry Pi sitting behind my TV (for those keeping up at home, this makes two NR instances on my network – so far). Currently, this is the only flow running on this instance, but I’m considering what else I can run now that I have Node-RED available there. I installed Node-RED on OSMC using the official install/upgrade script. I had fully expected installing Node-RED under OSMC to be a major pain, but it turned out to just amount to running that command.

After the install had finished, I created a user for Node-RED since I like it to run under it’s own user and updated the systemd unit file accordingly. I then installed the CEC nodes linked above from the palette manager. Here I ran into a minor bump in the road in that the CEC nodes couldn’t execute the cec-client program. As it turned out the location of that binary is in a weird location on OSMC, so I added the following in the systemd file to set this up:

Environment="PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/sbin:/usr/sbin:/usr/osmc/bin:/opt/vc/bin"

I also needed to add my new Node-RED user to the video group to allow access to the CEC device:

sudo gpasswd -a node-red video

Where I really got stuck was playing around with the example flow for the CEC nodes. It wasn’t that it didn’t work as advertised, it was that it broke the CEC command passthrough to Kodi running on the same machine, rendering my TV remote useless within Kodi. Many hours, much futile searching and playing with cec-client later, I still wasn’t any closer to a solution. I knew it must work, because somehow the pycec script I was using previously is able to send an receive CEC packets without interfering with Kodi.

The breakthrough was dropping both a CEC-In and a CEC-Out node into my flow and only grabbing a few CEC packet types in the filter of the input node. I say ‘breakthrough’ – this works most of the time, but it throws a few errors and warnings on start up. I found it to be most reliable when I immediately restarted Kodi after deploying it – this also helps Kodi to regain its CEC connection if necessary.

So How Does It Work?

Oh, yeah. I was going to talk about the flow, but I kinda got sidetracked there.

Well, it’s pretty simple there are two sequences in the flow. One which handles the switch state and MQTT discovery configuration (bottom) and one which handles the incoming commands over MQTT and sending the corresponding CEC commands.

Let’s start with the bottom one:

This sequence has two input paths, the bottom of these executes on start up (or at deploy time) and sends the Home Assistant MQTT Discovery configuration, using the same technique I used in my volcano sensors. The start up message also passes through a 3 second delay before passing to an exec node, which restarts Kodi. I added the following to my sudoers file (via visudo), to allow this:

# Allow node-red to restart kodi
node-red ALL=(ALL) NOPASSWD: /bin/systemctl restart mediacenter

The top input path receives incoming CEC messages of the type REPORT_POWER_STATUS. In my setup, this only receives power messages from the TV, but you may receive messages from other devices on the bus, in this case you can add a check on the source address of the packet in the following function node (clue: the TV is usually address 0).

The message passes through a function node, which converts the power status to the switch status expected by HASS and also sets the MQTT topic:

msg.topic = "homeassistant/switch/tv_living_room/state";
if(msg.payload.data.str == "ON")
{
    msg.payload = "ON";
}
else if(msg.payload.data.str == "STANDBY")
{
    msg.payload = "OFF";
}
return msg;

Both input paths are connected to a common MQTT output node to send their respective messages (config and state) out to Home Assistant.

The top sequence simply subscribes to the command topic from HASS and determines whether the command was on or off. The JSON payload for the CEC command is then set respectively in either branch – this JSON is taken directly from the example flow linked above. Then we pass this out to the CEC adapter – done. When the device acts upon the CEC command it should send its new power state back through and update the state of our switch. The state will also be updated if you turn on the TV by other means, e.g. the remote.

Pure JSON

This JSON was made in clean, green New Zealand from 100% natural ingredients (electrons):

[{"id":"8c6731b6.47fe6","type":"cec-out","z":"faa74966.a2471","cec_adapter":"7b6bdd74.05a08c","name":"Send CEC Command","x":620,"y":80,"wires":[]},{"id":"6851e3fd.f0320c","type":"cec-in","z":"faa74966.a2471","cec_adapter":"7b6bdd74.05a08c","name":"TV Power Status","flow_in":true,"flow_out":false,"select_all":"false","active_source":false,"image_view_on":false,"text_view_on":false,"inactive_source":false,"request_active_source":false,"routing_change":false,"routing_information":false,"set_stream_path":false,"standby":false,"record_off":false,"record_on":false,"record_status":false,"record_tv_screen":false,"clear_analogue_timer":false,"clear_digital_timer":false,"clear_external_timer":false,"set_analogue_timer":false,"set_digital_timer":false,"set_external_timer":false,"set_timer_program_title":false,"timer_cleared_status":false,"timer_status":false,"cec_version":false,"get_cec_version":false,"give_physical_address":false,"get_menu_language":false,"report_physical_address":false,"set_menu_language":false,"deck_control":false,"deck_status":false,"give_deck_status":false,"play":false,"give_tuner_device_status":false,"select_analogue_service":false,"select_digital_service":false,"tuner_device_status":false,"tuner_step_decrement":false,"tuner_step_increment":false,"device_vendor_id":false,"give_device_vendor_id":false,"vendor_command":false,"vendor_command_with_id":false,"vendor_remote_button_down":false,"vendor_remote_button_up":false,"set_osd_string":false,"give_osd_name":false,"set_osd_name":false,"menu_request":false,"menu_status":false,"user_control_pressed":false,"user_control_release":false,"give_device_power_status":false,"report_power_status":true,"feature_abort":false,"abort":false,"give_audio_status":false,"give_system_audio_mode_status":false,"report_audio_status":false,"set_system_audio_mode":false,"system_audio_mode_request":false,"system_audio_mode_status":false,"set_audio_rate":false,"start_arc":false,"report_arc_started":false,"report_arc_ended":false,"request_arc_start":false,"request_arc_end":false,"end_arc":false,"cdc":false,"none":false,"x":120,"y":140,"wires":[["cb58724f.7560e"]]},{"id":"cb58724f.7560e","type":"function","z":"faa74966.a2471","name":"Extract Status","func":"msg.topic = \"homeassistant/switch/tv_living_room/state\";\nif(msg.payload.data.str == \"ON\")\n{\n    msg.payload = \"ON\";\n}\nelse if(msg.payload.data.str == \"STANDBY\")\n{\n    msg.payload = \"OFF\";\n}\nreturn msg;","outputs":1,"noerr":0,"x":300,"y":140,"wires":[["fb6e90b5.114e2"]]},{"id":"fb6e90b5.114e2","type":"mqtt out","z":"faa74966.a2471","name":"Send Messages","topic":"","qos":"2","retain":"true","broker":"e320da15.60a5c8","x":520,"y":180,"wires":[]},{"id":"1e1472f2.aa2665","type":"function","z":"faa74966.a2471","name":"Format config messages","func":"var config = {\n    payload: {\n        name: \"Living Room TV\",\n        command_topic: \"homeassistant/switch/tv_living_room/cmd\",\n    },\n    topic: \"homeassistant/switch/tv_living_room/config\"\n};\nreturn config;","outputs":1,"noerr":0,"x":310,"y":200,"wires":[["fb6e90b5.114e2"]]},{"id":"3fba0578.68931a","type":"inject","z":"faa74966.a2471","name":"@Startup","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":100,"y":200,"wires":[["1e1472f2.aa2665","e1ab4a22.f0286"]]},{"id":"bde8cf94.665618","type":"mqtt in","z":"faa74966.a2471","name":"TV Command","topic":"homeassistant/switch/tv_living_room/cmd","qos":"2","broker":"e320da15.60a5c8","x":110,"y":80,"wires":[["60969010.3d95e"]]},{"id":"60969010.3d95e","type":"switch","z":"faa74966.a2471","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":270,"y":80,"wires":[["437a5107.5312"],["533b352b.b8d2fc"]]},{"id":"437a5107.5312","type":"change","z":"faa74966.a2471","name":"Turn TV On","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"source\":null,\"target\":0,\"opcode\":\"IMAGE_VIEW_ON\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":60,"wires":[["8c6731b6.47fe6"]]},{"id":"533b352b.b8d2fc","type":"change","z":"faa74966.a2471","name":"Turn TV Off","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"source\":null,\"target\":\"0.0.0.0\",\"opcode\":\"STANDBY\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":100,"wires":[["8c6731b6.47fe6"]]},{"id":"e1ab4a22.f0286","type":"delay","z":"faa74966.a2471","name":"","pauseType":"delay","timeout":"3","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":260,"y":260,"wires":[["3fb711f5.af2e76"]]},{"id":"3fb711f5.af2e76","type":"exec","z":"faa74966.a2471","command":"/usr/bin/sudo /bin/systemctl restart mediacenter","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"Restart Kodi","x":430,"y":260,"wires":[[],[],[]]},{"id":"7b6bdd74.05a08c","type":"cec-config","z":"","OSDname":"Kodi","comport":"RPI","hdmiport":"1","player":false,"recorder":true,"tuner":false,"audio":false},{"id":"e320da15.60a5c8","type":"mqtt-broker","z":"","name":"Home Broker","broker":"mqtt.webworxshop.com","port":"8883","tls":"3f2e52ab.1b319e","clientid":"","usetls":true,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"3f2e52ab.1b319e","type":"tls-config","z":"","name":"Home Broker","cert":"","key":"","ca":"","certname":"","keyname":"","caname":"","verifyservercert":true}]

Bonus: Home Assistant Automation Rules

Here are the Home Assistant automations that I’m using with this. Basically I’m turning off the TV five minutes after either Kodi or the Chromecast stops playing, unless it started again in the meantime:

- alias: 'Start shut off timer when TV becomes idle'
  trigger:
    - platform: state
      entity_id: media_player.tv
      to: idle
    - platform: state
      entity_id: media_player.living_room
      to: idle
    - platform: state
      entity_id: media_player.living_room
      to: 'off'
  action:
    service: timer.start
    entity_id: timer.tv_off

- alias: 'Cancel shut off timer if TV starts playing'
  trigger:
    - platform: state
      entity_id: media_player.tv
      to: playing
    - platform: state
      entity_id: media_player.living_room
      to: playing
  action:
    service: timer.cancel
    entity_id: timer.tv_off

- alias: 'When timer expires, turn off TV'
  trigger:
    - platform: event
      event_type: timer.finished
      event_data:
        entity_id: timer.tv_off
  action:
    service: switch.turn_off
    data:
      entity_id: switch.living_room_tv

This uses a timer, which is defined as:

tv_off:
  duration: "00:05:00"

Done. Now we can be lazy/forgetful about leaving TV on and also not waste power. Mission Accomplished.

Conclusion

Hopefully, someone will find time to fix the bug above. I’m probably going to stick with this regardless because I had some other issues running pyCEC on top of OSMC – mainly because they don’t build the libcec bindings for Python 3 by default. I had some custom patches to do this, but it would break (in one way or another on every update). Hopefully, this solution should be more robust. Also, the MQTT connection used in this solution runs over TLS (rather than the unencrypted TCP of the pyCEC network mode), so there is a little security win. Plus, as I already mentioned, now I have a Node-RED instance on a Pi in my living room.

Mt. Taranaki

Home Assistant MQTT Discovery Sensors in Node-RED

Alternatively Titled: How I Made Home Assistant Aware of the Volcano Next Door

Mt. Taranaki

If this guy blows, we’re gonna have a bad day

As I’ve previously mentioned, I’m a big fan of the Home Assistant MQTT Discovery feature. I’ve also historically been a fan of Node-RED and have recently been getting back into it. This has been mostly due to the uptick in interest in the platform in the HASS community. So, I decided to have a play around and come up with an implementation of an auto-discovered MQTT sensor in Node-RED. This post documents using this approach pull some interesting data into Home Assistant.

Since moving to a different part of New Zealand last year I’ve wanted implement a sensor in HASS which would monitor the state of the local volcano. Luckily, GeoNet provide a nice API for getting volcanic alert levels for all the volcanic fields in NZ. I was initially going to write a custom component for doing this (and at some point contribute it back). However, being generally even shorter on time than usual at the moment I never quite got there. That was until I was playing around with Node-RED and had a brain wave.

The Flow

I’m going to cut straight to the chase and show a screenshot of the flow I came up with. I’ll then explain it below (the flow JSON can be found later in the post):

The full volcano data flow

The full volcano data flow

The start of the flow is pretty basic – a simple inject node which injects a timestamp every 6 hours. The payload to this is irrelevant since it’s just used to kick off the flow. I didn’t want to hit the API endpoint too often since I’ve so far never seen the data change. If the mountain suddenly goes boom, I think I’ll have more pressing issues than whether my data is up to date.

Next, we have the HTTP Request node which goes out and performs a GET request to the URL given in the API documentation above. I enabled TLS support and opted to get the response data back as a parsed JSON object.

Filtering Data

Since the API returns data for all the volcanic fields in New Zealand, I needed to filter the data. The next node just selects the Taranaki/Egmont field that I am interested in. I used the following code in a function node to do this:

for(var i in msg.payload.features) {
    var feature = msg.payload.features[i];
    if(feature.properties.volcanoID=="taranakiegmont") {
        msg.payload = feature.properties;
        msg.topic = "homeassistant/sensor/volcano_" + msg.payload.volcanoID + "/state";
        break;
    }
}

Basically this just iterates over all the features in the data and finds the one with the ID taranakiegmont and then substitutes it’s data in as the message payload. I also build the topic for the subsequent publish to MQTT based on the volcano ID.

The output of this function branches to another function node on one branch and a delay node on the other. The delay node here is used to make sure that the function node above runs and sends it’s output before the original message passes to the the MQTT publish node.

Building HASS Configuration

The top function node is responsible for building the required configuration payloads and topics for the three sensors this will create via Home Assistant MQTT Discovery. Here I create one sensor for each of the quantities in the data from the API. This is achieved with the following snippet of code:

var config1 = {
    payload: {
        name: msg.payload.volcanoTitle + " Activity Level",
        state_topic: "homeassistant/sensor/volcano_" + msg.payload.volcanoID + "/state",
        value_template: "{{ value_json.level }}"
    },
    topic: "homeassistant/sensor/volcano_" + msg.payload.volcanoID + "_level/config"
};
var config2 = {
    payload: {
        name: msg.payload.volcanoTitle + " Activity Description",
        state_topic: "homeassistant/sensor/volcano_" + msg.payload.volcanoID + "/state",
        value_template: "{{ value_json.activity }}"
    },
    topic: "homeassistant/sensor/volcano_" + msg.payload.volcanoID + "_activity/config"
};
var config3 = {
    payload: {
        name: msg.payload.volcanoTitle + " Hazards",
        state_topic: "homeassistant/sensor/volcano_" + msg.payload.volcanoID + "/state",
        value_template: "{{ value_json.hazards }}"
    },
    topic: "homeassistant/sensor/volcano_" + msg.payload.volcanoID + "_hazards/config"
};
return [config1, config2, config3];

This does the same thing for three new message objects, building a payload and topic for each. I use the ability of HASS to grab data from the payload of the main publish by specifying the state topic. I set this to the topic I built in the previous function node. A value template is also specified for each, pretty much exactly as in the Home Assistant MQTT Discovery documentation.

Output Via MQTT

All three outputs of this node are passed to the MQTT publish node, which publishes with QoS 2 and the retain flag set. This means that whenever Home Assistant comes up after a restart it will see the values in both the configuration and state topics for these sensors and re-create them automatically.

Attentive readers would have also noticed that I publish the configuration messages whenever I publish the state (every 6 hours). This doesn’t matter as HASS will just ignore the configuration messages for sensors which it has already discovered.

So, that’s it. With this in place the sensors should appear in Home Assistant:

Home Assistant Volcano Sensors

Note the reassuring zero for activity level!

The JSON:

As promised, here is the full JSON for the flow. To add this to your Node-RED instance copy it to your clipboard and go to Hamburger->Import->Clipboard in Node-RED and paste the JSON. You can select whether to import to the current flow or a new flow and then hit ‘import’ and you should see the nodes:

[{"id":"1f3ef70f.e7a6b9","type":"http request","z":"9b7b48a9.a28de8","name":"Get Volcano Data","method":"GET","ret":"obj","url":"https://api.geonet.org.nz/volcano/val","tls":"49e1f229.3ce5f4","x":330,"y":120,"wires":[["37e97f8a.c207e8"]]},{"id":"ce39e789.a300f","type":"inject","z":"9b7b48a9.a28de8","name":"Every 6 hours","topic":"","payload":"","payloadType":"date","repeat":"21600","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":120,"wires":[["1f3ef70f.e7a6b9"]]},{"id":"37e97f8a.c207e8","type":"function","z":"9b7b48a9.a28de8","name":"Filter for Taranaki","func":"for(i in msg.payload.features) {\n    var feature = msg.payload.features[i];\n    if(feature.properties.volcanoID==\"taranakiegmont\") {\n        msg.payload = feature.properties;\n        msg.topic = \"homeassistant/sensor/volcano_\" + msg.payload.volcanoID + \"/state\";\n        break;\n    }\n}\nreturn msg;","outputs":1,"noerr":0,"x":530,"y":120,"wires":[["ea23e4a2.eaa928","1ca7b72e.202389"]]},{"id":"104d115c.305927","type":"mqtt out","z":"9b7b48a9.a28de8","name":"Send Messages","topic":"","qos":"2","retain":"true","broker":"d76a3146.667c3","x":1020,"y":120,"wires":[]},{"id":"ea23e4a2.eaa928","type":"function","z":"9b7b48a9.a28de8","name":"Format config messages","func":"var config1 = {\n    payload: {\n        name: msg.payload.volcanoTitle + \" Activity Level\",\n        state_topic: \"homeassistant/sensor/volcano_\" + msg.payload.volcanoID + \"/state\",\n        value_template: \"{{ value_json.level }}\"\n    },\n    topic: \"homeassistant/sensor/volcano_\" + msg.payload.volcanoID + \"_level/config\"\n};\nvar config2 = {\n    payload: {\n        name: msg.payload.volcanoTitle + \" Activity Description\",\n        state_topic: \"homeassistant/sensor/volcano_\" + msg.payload.volcanoID + \"/state\",\n        value_template: \"{{ value_json.activity }}\"\n    },\n    topic: \"homeassistant/sensor/volcano_\" + msg.payload.volcanoID + \"_activity/config\"\n};\nvar config3 = {\n    payload: {\n        name: msg.payload.volcanoTitle + \" Hazards\",\n        state_topic: \"homeassistant/sensor/volcano_\" + msg.payload.volcanoID + \"/state\",\n        value_template: \"{{ value_json.hazards }}\"\n    },\n    topic: \"homeassistant/sensor/volcano_\" + msg.payload.volcanoID + \"_hazards/config\"\n};\nreturn [config1, config2, config3];","outputs":3,"noerr":0,"x":780,"y":60,"wires":[["104d115c.305927"],["104d115c.305927"],["104d115c.305927"]]},{"id":"1ca7b72e.202389","type":"delay","z":"9b7b48a9.a28de8","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":760,"y":120,"wires":[["104d115c.305927"]]},{"id":"49e1f229.3ce5f4","type":"tls-config","z":"","name":"Standard","cert":"","key":"","ca":"","certname":"","keyname":"","caname":"","verifyservercert":true},{"id":"d76a3146.667c3","type":"mqtt-broker","z":"","name":"Home Broker","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"willTopic":"","willQos":"0","willPayload":"","birthTopic":"","birthQos":"0","birthPayload":""}]

If you are importing this directly, you will need to configure your MQTT broker settings under the MQTT publish node before hitting ‘deploy’.

Wrap Up

That’s pretty much all there is to it. I hope this has demonstrated the concept of using Node-RED to create sensors in Home Assistant, without any changes to the HASS configuration. The flow presented is pretty simple but actually serves a useful purpose. Hopefully, you can come up with some uses of your own for this approach. Please feel free to share them in the comments below if you do, so that others may benefit from your ideas.

Thanks for reading. I’m working on a few more things with Node-RED so hopefully I’ll post about them soon. Bye!