Inspired from this idea from Vex, I couldn’t help myself but implement a little RESTful server, that I can roll out to my (Rhasspy) satellites to control their GPIO-pins.
I will provide all my code here, not only for the server and the systemd-configuaration, but also the scripts for HA and even a full example how to control a simple light (on/off) in HA with the status always updated.
I worked on that all day, learned a lot, and provide it here in case someone finds it useful.
The RESTful-server Python-script:
#!/usr/bin/env python3
import RPi.GPIO as GPIO
from flask import Flask, request, make_response
from os import chdir
chdir("/tmp") # because of temp-files created by gpizero/RPi.GPIO/lgpio
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
chan_list = [17]
GPIO.setup(chan_list, GPIO.OUT, initial=GPIO.LOW)
application = Flask(__name__)
def create_response(message, http_status_code=200):
resp = make_response(message, http_status_code)
resp.headers['message'] = message
return resp
@application.route('/gpio_status', methods=['GET', 'POST'])
def get_gpio_status():
pin = request.args.get('pin', default=None, type = int)
if pin == None:
return create_response("Kein Pin angegeben!", 400)
elif pin >=2 and pin <= 27:
try:
GPIO.input(pin)
except RuntimeError as ex:
return create_response(str(ex.args[0]), 400)
if request.method == 'GET':
return create_response("HIGH" if GPIO.input(pin) else "LOW")
else:
if not request.form and not request.data:
return create_response("Es muss 'status=HIGH' oder 'status=LOW' als form- oder payload-Daten angegeben werden!", 400)
elif (len(request.form) != 1 or not request.form["status"] in ("HIGH", "LOW")) \
and (request.data not in (b'status=HIGH', b'status=LOW')):
return create_response("Es wird nur 'status=HIGH' oder 'status=LOW' als form- oder payload-Daten akzeptiert!", 400)
else:
status = None
if request.data:
if request.data == b'status=HIGH':
status = "HIGH"
elif request.data == b'status=LOW':
status = "LOW"
elif request.form:
if request.form["status"] == "HIGH":
status = "HIGH"
elif request.form["status"] == "LOW":
status = "LOW"
if status == None:
return create_response("Kein gueltiger Status in den Daten enthalten!", 400)
GPIO.output(pin, GPIO.HIGH if (status == "HIGH") else GPIO.LOW)
return create_response("HIGH" if GPIO.input(pin) else "LOW")
else:
return create_response("Ungueltiger Pin angegeben!")
if __name__ == '__main__':
application.run(host="0.0.0.0")
GPIO.cleanup()
The systemd.service:
[Unit]
Description=A minimal RESTful server for getting and setting the status of GPIO-pins
Requires=network.target
After=network.target
[Install]
WantedBy=multi-user.target
[Service]
User=pi
Group=pi
Type=simple
Restart=always
WorkingDirectory=/tmp
ExecStart=/usr/bin/env python3 /home/pi/scripts/rest_server_for_gpio.py
With that you can have an always running RESTful server that can control the GPIO-pins.
You can interact with it with wget, curl, a browser, or a RESTful client.
Now into HA:
In configuration.yaml
define the corresponding rest_commands:
rest_command:
get_gpio_status:
url: http://{{ host }}:{{ port }}/gpio_status?pin={{ pin }}
method: GET
set_gpio_status:
url: http://{{ host }}:{{ port }}/gpio_status?pin={{ pin }}
method: POST
payload: '{{ payload }}'
headers:
content-type: 'text/plain'
Than have a script in HA for conveniently using the rest_command:
sequence:
- action: rest_command.set_gpio_status
data:
host: "{{ host }}"
port: "{{ port }}"
pin: "{{ pin }}"
payload: |
status={{ status }}
fields:
host:
selector:
text: null
name: Host
required: true
port:
selector:
number:
min: 1
max: 65534
name: Port
default: 5000
required: true
pin:
selector:
number:
min: 2
max: 27
name: Pin
required: true
status:
selector:
select:
options:
- HIGH
- LOW
name: Status
required: true
alias: "[GPIO] GPIO-Status setzen"
description: ""
With that, you can control every GPIO-pin on every host, that is running the RESTful-server script with ease from within HA.
Last but not least: An example for controling a simple light with always updated status:
First, let’s have a binary_sensor, that holds the state of the light. In configuration.yaml
:
template:
- trigger:
- platform: homeassistant
event: start
- platform: time_pattern
minutes: /1
- platform: state
entity_id: script.lights_deckenfluter_im_schlafzimmer_umschalten
from: 'on'
to: 'off'
action:
- service: rest_command.get_gpio_status
data:
host: rpi0-2W-3
port: 5000
pin: 17
response_variable: response
binary_sensor:
- name: Deckenfluter im Schlafzimmer
unique_id: deckenfluter_im_schlafzimmer
device_class: light
state: "{{ response['content'] == 'HIGH' }}"
icon: mdi:lightbulb
And another script, that controls exactly this light (the one GPIO-pin on the one host) - just for convenience:
sequence:
- action: script.gpio_gpio_status_setzen
data:
port: 5000
host: rpi0-2W-3
pin: 17
status: |-
{% if status | length > 0 %}
{% if status == 'AUS' %}
LOW
{% else %}
HIGH
{% endif %}
{% else %}
{% if is_state('binary_sensor.deckenfluter_im_schlafzimmer', 'on') %}
LOW
{% else %}
HIGH
{% endif %}
{% endif %}
fields:
status:
selector:
select:
options:
- AN
- AUS
name: Status
alias: "[Lights] Deckenfluter im Schlafzimmer umschalten"
description: ""
And finally: A button in the Lovelace-dashboard for switching the light on and off:
show_name: true
show_icon: true
type: button
tap_action:
action: perform-action
perform_action: script.lights_deckenfluter_im_schlafzimmer_umschalten
entity: binary_sensor.deckenfluter_im_schlafzimmer
icon_height: 20px
There is much that could be optimised, or simplified.
But for now I’m both happy and exhausted, so I won’t change a running system anymore.
So long.