Use Raspberry Pi as a Home Assistant MQTT Node

[Electronics] Use Raspberry Pi as a Home Assistant MQTT Node

This is part of my Chevy Bolt Caputer project, check it out here.


As you may know, I've been tinkering with my 2023 Chevy Bolt to add a Raspberry Pi to it to turn it into a smart car, remote door operations, starting the vehicle, monitoring car status, and toggling the WiFi hotspot. I'm hooking it all up to Home Assistant for a seamless, unified control center. Essentially, my Raspberry Pi is going to be a sensor node for HASS, constantly reporting statuses and standing by for any commands. Here's how I'm making it all happen.

The conversation between the Raspberry Pi and HASS will be using MQTT protocol.

Test things out

1. MQTT Client Installation on Pi
First, we need to install the MQTT client on our Pi. Fire up your terminal and punch in the following command:

sudo apt install -y mosquitto mosquitto-clients

2. Setting Up Communication
Next, we need to open two SSH windows, one for our HASS server and one for Pi.

In the HASS SSH window, we're going to put HASS in listening mode. Here's the command to do that:

mosquitto_sub -v -t 'chevy/#' -h localhost -u USERNAME -P PASSWORD

3. Sending an MQTT Message from Pi
Now, let's make some noise! Back in the Pi SSH window, we're going to send an MQTT message:

mosquitto_pub -h SERVER_IP -u USERNAME -P PASSWORD -m "test message" -t chevy/test

4. Checking the Communication
Head back to your HASS SSH window. If all has gone well, you should see the test message we just sent from the Pi.

Set Raspberry Pi as a HASS node using Paho-MQTT

1. Install Python and Paho-MQTT
You'll need Python and the Paho-MQTT library to write your MQTT client. Here's how you can install them:

sudo apt-get install -y python3 python3-pip
pip3 install paho-mqtt

2. Write your MQTT Client
This script is written in Python and uses the Paho MQTT library to communicate with an MQTT broker, and it interacts with Home Assistant home automation platform.

1. In the on_connect function, which is executed when the MQTT client connects to the broker:

def on_connect(client, userdata, flags, rc):
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(18, GPIO.OUT)
    print("Connected with result code "+str(rc))
    client.subscribe("chevy_bolt_carputer/gpio18/set")

The GPIO mode is set to BCM and GPIO pin 18 is set to be an output.
The client subscribes to the topic "chevy_bolt_carputer/gpio18/set".

2. Two payloads are created for a switch and a sensor, and published to Home Assistant via MQTT. This is done to integrate the MQTT device with Home Assistant. These payloads include information about the device and the topics to which Home Assistant should subscribe to control the device and receive its state updates.

    # Publish the switch (GPIO pin in this example) to Home Assistant
    switch_payload = {
        "name": "Chevy Bolt Carputer GPIO18",
        "command_topic": "chevy_bolt_carputer/gpio18/set",  # Send commands to '/set' topic
        "state_topic": "chevy_bolt_carputer/gpio18/state",  # Report state on '/state' topic
        "payload_on": "on",
        "payload_off": "off",
        "device": {
            "identifiers": "chevy_bolt_carputer",
            "name": "Chevy Bolt Carputer",
            "model": "1",
            "manufacturer": "Geek Talks"
    }
    }
    client.publish("homeassistant/switch/chevy_bolt_carputer_gpio18/config", json.dumps(switch_payload))

    # Publish the sensor (uptime in this example) to Home Assistant
    sensor_payload = {
        "name": "Chevy Bolt Carputer Uptime",
        "state_topic": "chevy_bolt_carputer/uptime",
        "device": {
            "identifiers": "chevy_bolt_carputer",
            "name": "Chevy Bolt Carputer",
            "model": "1",
            "manufacturer": "Geek Talks"
        }
    }
    client.publish("homeassistant/sensor/chevy_bolt_carputer_uptime/config", json.dumps(sensor_payload))

3. The on_message function is executed when a message is received on a subscribed topic. If the message payload is "on", GPIO 18 is set to HIGH and its state is published to the "chevy_bolt_carputer/gpio18/state" topic. If the message payload is "off", GPIO 18 is set to LOW and its state is also published.

def on_message(client, userdata, msg):
    print(msg.topic+" "+str(msg.payload))
    if msg.payload == b'on':
        GPIO.output(18, GPIO.HIGH)
        client.publish("chevy_bolt_carputer/gpio18/state", "on")  # Report state on '/state' topic
        print("GPIO 18 is ON ")
    elif msg.payload == b'off':
        GPIO.output(18, GPIO.LOW)
        client.publish("chevy_bolt_carputer/gpio18/state", "off")  # Report state on '/state' topic
        print("GPIO 18 is OFF ")

4. The on_disconnect function is executed when the MQTT client disconnects from the broker. It prints the result code of the disconnection and cleans up the GPIO settings.

def on_disconnect(client, userdata, rc):
    print("Disconnected with result code "+str(rc))
    GPIO.cleanup()

5. An MQTT client is created, and the on_connect, on_message, and on_disconnect functions are assigned to the corresponding client methods.

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message

6. The client's username and password are set, and it connects to the MQTT broker at IP address "MQTT_BROKER_IP" and port 1883.

client.username_pw_set("Username", "Password")
client.connect("MQTT_BROKER_IP", 1883, 60)

7. The client's loop is started in a separate thread, allowing it to handle incoming messages and maintain the connection to the broker.

client.on_disconnect = on_disconnect
client.loop_start()

8. In a try block, the client's loop is started again (which is redundant and can be removed, as the loop was already started earlier). A while True loop is started, which constantly publishes the system's uptime to the "chevy_bolt_carputer/uptime" topic every 60 seconds.

try:
    client.loop_start()
    while True:
        uptime = os.popen('uptime -p').readline()
        client.publish("chevy_bolt_carputer/uptime", uptime)
        time.sleep(60)

9. If a KeyboardInterrupt (typically caused by pressing Ctrl+C) is detected, a message is printed and the script exits.

except KeyboardInterrupt:
    print("Exiting gracefully")

10. In the finally block, which is executed no matter how the try block is exited, the client's loop is stopped and the client is disconnected from the broker.

finally:
    client.loop_stop()  # Stop the background loop
    client.disconnect()

Leave a Comment

This post is created on January 14, 2024 and last updated on January 25, 2024