3 minute read

I’ve decided to start off 2020 by converting my blog to Hugo and start blogging again! This is my first of many posts on home automation! This post is going to cover generically detecting battery levels with Home Assistant and Node-RED.

TLDR: Use the Function node defined in the Node-RED snippet here to properly handle all the edge cases.

I decided to share my experiences based on seeing what others were doing in this thread. For the most part, I saw users not accounting for edge cases or the Switch node wasn’t going to work how they thought it was going to work.

Two of the main gotchas I’ve come across with device types that report battery state:

  • storing it in different locations (msg.state, msg.payload.attributes["Battery Level"], etc.)
  • differing values/data types (off, Charging, "100", 100)

This really makes it difficult to use the Switch or Change node with conditional logic as it might not behave how you think it does. This happened to me when I was debugging my first few attempts and getting 10 outputted in a 100 bucket via the gt condition. The best way I’ve found to handle these scenarios is with a Function node.

Home Assistant Feedback and Edge Cases

I think it may be worth opening a GitHub issue for this. It sure would be nice if devices that can charge have their charging state normalized, battery level state in the same spot with a last changed in attribute. Additionally it would be nice to see something for dead batteries or if the device just hasn’t reported in a long time. I say this because I’ve have many devices I know are charged and on, but they are being reported as off. What do you think?


Off, clearly the battery has a level and it lasts 10 years and it’s reported in the nest app as OK.

{"entity_id":"binary_sensor.nest_protect_battery_health","state":"off","attributes":{"friendly_name":"Nest Battery","device_class":"battery"},"last_changed":"2020-01-11T11:42:50.976581+00:00","last_updated":"2020-01-11T11:42:50.976581+00:00","context":{"id":"2a2f5c1503cd44c7bb3ebd9be69a4144","parent_id":null,"user_id":null},"timeSinceChangedMs":2731510}

Charging - battery level stored in attributes.

{"entity_id":"sensor.battery_state_2","state":"Charging","attributes":{"Battery Level":52,"friendly_name":"Battery State","icon":"mdi:battery-charging-40","device_class":"battery"},"last_changed":"2020-01-11T11:43:06.616903+00:00","last_updated":"2020-01-11T11:43:06.616903+00:00","context":{"id":"a9cc54cd33cf4cf1954c4cd2f2f633a8","parent_id":null,"user_id":null},"timeSinceChangedMs":2715885}

Same device as Charging example above but duplicate battery device sensor with state in the correct location.

{"entity_id":"sensor.battery_level_2","state":"52","attributes":{"Battery State":"Charging","unit_of_measurement":"%","friendly_name":"Battery Level","icon":"mdi:battery-charging-40","device_class":"battery"},"last_changed":"2020-01-11T11:43:06.599389+00:00","last_updated":"2020-01-11T11:43:06.599389+00:00","context":{"id":"60469c2964504dc4bb11cbf5a307ceb9","parent_id":null,"user_id":null},"timeSinceChangedMs":3766731}

August lock reported from august integration missing information.

{"entity_id":"binary_sensor.august_door_locked_battery_level","state":"off","attributes":{"friendly_name":"August Door Lock Battery Level","device_class":"battery"},"last_changed":"2020-01-11T11:42:50.892228+00:00","last_updated":"2020-01-11T11:42:50.892228+00:00","context":{"id":"567d9c771c91440c826ed26d14187513","parent_id":null,"user_id":null},"timeSinceChangedMs":2731591}

August lock as reported from Z-Wave.

{"entity_id":"sensor.august_asl_03_smart_lock_battery_level","state":"100","attributes":{"node_id":2,"value_index":0,"value_instance":1,"value_id":"72057594077773825","unit_of_measurement":"%","friendly_name":"August ASL-03 Smart Lock Battery Level","device_class":"battery"},"last_changed":"2020-01-11T11:47:39.195031+00:00","last_updated":"2020-01-11T11:47:39.195031+00:00","context":{"id":"406c8c33527a4daf8d9b23531953d54f","parent_id":null,"user_id":null},"timeSinceChangedMs":2443740}

Handling the edge cases

The best way to handle all the edge cases above is with a Function node. It will be extremely tough to handle normalizing the battery level with any other node type with out a really crazy series.

It should be everyone’s goal to keep automations as simple as possible. This keeps the maintenance super low and the acceptance factor has high as possible!

So lets get started by creating a new Function node with 6 outputs.

// NOTE: We could probably look at the unit_of_measurement if specified to properly normalize the battery level.
// Check for battery level as it's set when state is Charging,Not Charging,etc...
const batteryLevelAttr = msg.payload.attributes && msg.payload.attributes["Battery Level"];
const state = !isNaN(batteryLevelAttr) ? batteryLevelAttr : msg.payload.state;
const level = parseInt(state);
if (!isFinite(level)) {
    return [msg];
} else if (level < 10) {
    return [null, msg];
} else if (level < 25) {
    return [null, null, msg];
} else if (level < 50) {
    return [null, null, null, msg];
} else if (level < 75) {
    return [null, null, null, null, msg];
} else {
    return [null, null, null, null, null, msg];

By creating 6 outputs and outputting the message to the desired output, we can wire up routines to fire on only the battery level ranges we care about (e.g., 0-10%, 10-25%, etc.)!

Bringing it all together

I’ve created a Node-RED flow to get every device that supports battery levels and output them to a Debug Node using the above Function Node.

Node-RED Home Assistant Battery Level Flow

You can grab the complete Node-RED snippet here.

Join the mailing list

Get notified of new posts and related content to your inbox. I will never sell you anything and I will NEVER sell your email address.