Skip to main content

Home Assistant - Python Scripts as Service

Guangzhou, China

The discovery of MQTT devices will enable one to use MQTT devices with only minimal configuration effort on the side of Home Assistant. Two parts are required on the device side: The configuration topic which contains the necessary device type and unique identifier, and the remaining device configuration without the device type.

In Part I I looked into the configuration API and described the topic payloads needed to have Home Assistant add your camera as a new device when you connect it to the MQTT broker. Now I want to make sure that Home Assistant does not forget the device configuration if my broker "looses" those configuration topics.

In Part II I looked into using an automation that is triggered when my camera comes alive and feeds all those topics to my MQTT broker. But I needed to write my own mqtt client in an external Python script.

NOTE: I was unable to run the Python script directly. I received a lot of error message - I assume that they are related to the virtual environment that HA uses to isolate external scripts. Back in Part II of this tutorial I used the Shell Script option to execute the Python script. This worked like a charm.

Luckily the Home Assistant Docker Container has Python - obviously - and the Python library Paho MQTT installed that I can use. But it seems that HA starts external scripts inside a virtual environment - can you add dependencies to it by adding the following lines to configuration.yaml (?):

python_script:
requirements:
- paho-mqtt>=1.6.1

Using Python Scripts in Home Assistant

Data Preparation

So in the previous step I ended up with a bunch of MQTT Topics and Payloads that I need to register with my MQTT Broker to configure my INSTAR camera. For example the topic that configures a switch in Home Assistant to toggle the red Motion Detection Area:

  • Configuration topic: homeassistant/switch/in9408_garden/alarm_area_red/config
  • Payload:
{
"device": {
"identifiers": "in9408_garden",
"manufacturer": "INSTAR Deutschland GmbH",
"model": "INSTAR 2k+ IN-9408 WLAN",
"name": "IN-9408 2k+ Garden",
"configuration_url": "http://192.168.2.115:80"
},
"availability": {
"topic": "cameras/115/status/testament",
"payload_available": "{\"val\":\"alive\"}",
"payload_not_available": "{\"val\":\"dead\"}"
},
"object_id":"in9408_garden_alarm_area_red",
"unique_id": "in9408_garden_alarm_area_red",
"name": "IN-9408 2k+ Garden Alarm Area Red",
"icon": "mdi:camera-metering-matrix",
"command_topic": "cameras/115/alarm/areas/red/enable",
"payload_on": "{\"val\":\"1\"}",
"payload_off": "{\"val\":\"0\"}",
"state_topic": "cameras/115/status/alarm/areas/red/enable",
"state_on": 1,
"state_off": 0,
"value_template": "{{ value_json.val }}",
"qos": 1
}

To use this topic with your own camera you will need to adjust the following values according to your camera configuration:

  • configuration_url: The IP address of your camera, e.g. http://192.168.2.115:80
  • MQTT Prefix & Device ID: e.g. cameras and 115
  • MQTT Last-Will Topic: e.g. status/testament
  • payload_available: The Last-Will payload used to signify that your camera is connected to the MQTT broker
  • payload_not_available: The Last-Will payload used to signify that your camera lost connection to the MQTT broker

Home Assistant MQTT Auto-Discovery

Now I want to wrap all those topics into an JSON Array - which means that I have to escape the JSON payload to have it handled as a string - find the entire list on Github:

{
"entities": [{
"topic": "homeassistant/switch/in9408_garden/alarm_area_red/config",
"payload": "{\"device\":{\"identifiers\":\"in9408_garden\",\"manufacturer\":\"INSTAR Deutschland GmbH\",\"model\":\"INSTAR 2k+ IN-9408 WLAN\",\"name\":\"IN-9408 2k+ Garden\",\"configuration_url\":\"http://192.168.2.115:80\"},\"availability\":{\"topic\":\"cameras/115/status/testament\",\"payload_available\":\"{\\\"val\\\":\\\"alive\\\"}\",\"payload_not_available\":\"{\\\"val\\\":\\\"dead\\\"}\"},\"object_id\":\"in9408_garden_alarm_area_red\",\"unique_id\":\"in9408_garden_alarm_area_red\",\"name\":\"Alarm Area Red\",\"icon\":\"mdi:camera-metering-matrix\",\"command_topic\":\"cameras/115/alarm/areas/red/enable\",\"payload_on\":\"{\\\"val\\\":\\\"1\\\"}\",\"payload_off\":\"{\\\"val\\\":\\\"1\\\"}\",\"state_topic\":\"cameras/115/status/alarm/areas/red/enable\",\"state_on\":1,\"state_off\":0,\"value_template\":\"{{ value_json.val}}\",\"qos\":1}"
},

...

]
}

Note: that you have to "double-escape" the parts that were already escaped before, e.g. {\\\"val\\\":\\\"1\\\"}.

And to be able to connect with my broker I will create a configuration file with all the details - see on Github:

mqtt_server_host = "192.168.2.115"
mqtt_server_port = 1883
mqtt_bind_address = ""
mqtt_bind_port = 0
mqtt_username = "admin"
mqtt_password = "instar"
mqtt_transport = "tcp"
mqtt_keepalive = 60
mqtt_client_id = "mqtt5_client"

Paho MQTT Client

Now I can go ahead building the MQTT client, have it connect to my broker and then loop over all the MQTT topics prepared above - see on Github:

import json
import paho.mqtt.client as mqtt
import sys
import time

from config import * # import configuration from config.py

f = open('config_topics.json') # read json file with all mqtt topics and payloads

json_array = json.load(f) # read json data into an array variable

def on_connect(client, userdata, flags, rc, properties=None):
if rc==0:
print("INFO :: Connected to MQTT Broker")
else:
print("ERROR :: Connection failed:", rc)

if __name__ == "__main__":
client = mqtt.Client(client_id = mqtt_client_id, protocol = mqtt.MQTTv5, transport = mqtt_transport)
client.username_pw_set(username = mqtt_username, password = mqtt_password)
client.connect(host = mqtt_server_host, port = mqtt_server_port,
keepalive = mqtt_keepalive, bind_address = mqtt_bind_address, bind_port = mqtt_bind_port, properties = None)
client.on_connect = on_connect

for entity in json_array['entities']: # loop through all entities in array
client.publish(entity['topic'],entity['payload'],retain=True) # and use data to publish the mqtt topics
time.sleep(0.100) # I tried running without cooldown and it works just fine but kept this in
f.close()

client.loop_stop()
client.disconnect()
print("INFO :: Closed connection to broker")
sys.exit()

Python Scripts as a Service in Home Assistant

Home Assistant expects external Python scripts to be stored inside the Config directory within a folder called python_scripts. Create the folder and copy all 3 files into it. Each Python file created in the <config>/python_scripts/ folder will be exposed as a service.

  • Add python_script: to configuration.yaml
  • Create folder <config>/python_scripts
  • Copy the Python files into it
  • Restart Home Assistant
  • Call your new python_script.mqtt5_client service

Home Assistant MQTT Auto-Discovery

NOTE: I was unable to run the Python script directly. I received a lot of error message - I assume that they are related to the virtual environment that HA uses to isolate external scripts. Back in Part II of this tutorial I used the Shell Script option to execute the Python script. This worked like a charm.