Python MQTT System Restart und Shutdown Script

Aus Laub-Home Wiki

Dieses Python Script startet via MQTT Publish ein Linux System neu oder fährt es herunter. Das Ganze ist ideal in Kombination mit dem Raspberry Pi, einer openHAB Installation und einem Touchdisplay nutzbar. In meinem Szenario kann ich so via HABPanel entweder das unterliegende oder ein anderes Linux System Neustarten oder gar ganz ausschalten. HABPanel sendet hier den "reboot" oder "shutdown" Befehl über ein openHAB Item an den MQTT Broker. Das dortige Topic ist in einem Python Script, welches als System Daemon läuft abonniert und führt dann entsprechend einen reboot oder ein halt aus. Optional kann auch das Homie Convention MQTT kompatible Script verwendet werden, was eine Autodiscovery von openHAB zum Beispiel möglich macht.

Voraussetzungen

apt install -y python3 python3-paho-mqtt

Python restart_shutdown-mqtt.py

hier nun das Script, welches via MQTT Topic Payload einen Neustart oder einen Shutdown des Systems ausführt. Am besten legt ihr es unter /usr/local/sbin ab.

cd /usr/local/sbin

restart_shutdown-mqtt.py

#!/usr/bin/python3
import os, systemd.daemon, time
import paho.mqtt.client as mqtt, ssl

### set the variables
# MQTT Config
broker = "FQDN / IP ADDRESS"
port = 8883
publish_topic="home/attic/office"
clientid = "client-power"
hostname = "clientname"
username = "mosquitto"
password = "password"
insecure = True
qos = 1
retain_message = True
# Retry to connect to mqtt broker
mqttretry = 5

### do the stuff
print('Starting up Restart & Shutdown MQTT Service ...')

# just give some used variables an initial value
powerswitch = "Null"

### Functions
def publish(topic, payload):
  client.publish(publish_topic + "/" + topic,payload,qos,retain_message)

def on_connect(client, userdata, flags, rc):
  print("MQTT Connection established, Returned code=",rc)
  client.subscribe(publish_topic + "/" + hostname + "/system_power_switch", qos)

def on_message(client, userdata, message):
  global powerswitch
  if "system_power_switch" in message.topic:
    #print("MQTT system power payload:", str(message.payload.decode("utf-8")))
    powerswitch = str(message.payload.decode("utf-8"))

#MQTT Connection
mqttattempts = 0
while mqttattempts < mqttretry:
  try:
    client=mqtt.Client(clientid)
    client.username_pw_set(username, password)
    client.tls_set(cert_reqs=ssl.CERT_NONE) #no client certificate needed
    client.tls_insecure_set(insecure)
    client.connect(broker, port)
    client.loop_start()
    mqttattempts = mqttretry
  except :
    print("Could not establish MQTT Connection! Try again " + str(mqttretry - mqttattempts) + "x times")
    mqttattempts += 1
    if mqttattempts == mqttretry:
      print("Could not connect to MQTT Broker! exit...")
      exit (0)
    time.sleep(5)

# MQTT Subscription
client.on_message = on_message
client.on_connect = on_connect

# Tell systemd that our service is ready
systemd.daemon.notify('READY=1')

# finaly the loop
while True:
  try:
    #print("powerswitch Loop wert: ", powerswitch)
    if powerswitch == "shutdown":
      publish(hostname + "/system_power_switch", "on")
      print("System Shutdown")
      os.system('halt')
    elif powerswitch == "reboot":
      publish(hostname + "/system_power_switch", "on")
      print("System Reboot")
      os.system('reboot')
    time.sleep(1)

  except KeyboardInterrupt:
    print("Goodbye!")
    # At least close MQTT Connection
    client.disconnect()
    client.loop_stop()
    exit (0)

  except :
    print("An Error accured ... ")
    time.sleep(3)
    continue

# At least close MQTT Connection
client.disconnect()
client.loop_stop()

bitte passt die Variablen oben im Script an eure Bedürfnisse an!

Nun geben wir dem Script noch das Execute Recht:

chmod +x restart_shutdown-mqtt.py

einen ersten Test einfach durch das Aufrufen des Scripts machen.

restart_shutdown-mqtt.py

mit STRG-C könnt ihr es beenden.

Alternative: Python restart_shutdown-homie.py

Alternative, falls ihr in openHAB eine MQTT Autodiscovery bevorzugt, könnt ihr dieses Homie MQTT taugliche Script verwenden. Bei Verwendung dieses Skriptes solltet ihr beim Starten des Skriptes in openHAB in der Inbox direkt das Thing zum hinzufügen finden.

Download here: https://raw.githubusercontent.com/alaub81/restart_shutdown-mqtt/main/restart_shutdown-homie.py

restart_shutdown-homie.py

#!/usr/bin/python3
import os, systemd.daemon, time
import paho.mqtt.client as mqtt, ssl

### set the variables
# MQTT Config
broker = "FQDN / IP ADDRESS"
port = 8883
mqttclientid = "client-power-homie"
clientname="clientname"
clientid = "clientname-power"
nodes="power"
username = "mosquitto"
password = "password"
insecure = True
qos = 1
retain_message = True
# Retry to connect to mqtt broker
mqttretry = 5

### do the stuff
print('Starting up Restart & Shutdown MQTT Service ...')

# just give some used variables an initial value
powerswitch = "Null"

### Functions
def publish(topic, payload):
  client.publish("homie/" + clientid + "/" + topic,payload,qos,retain_message)

def on_connect(client, userdata, flags, rc):
  print("MQTT Connection established, Returned code=",rc)
  client.subscribe("homie/" + clientid + "/" + nodes + "/systempowerswitch/set", qos)
  # homie client config
  publish("$state","init")
  publish("$homie","4.0")
  publish("$name",clientname)
  publish("$nodes",nodes)
  # homie node config
  publish(nodes + "/$name","System Power")
  publish(nodes + "/$properties","systempowerswitch")
  publish(nodes + "/systempowerswitch", "on")
  publish(nodes + "/systempowerswitch/$name","System Power Switch")
  publish(nodes + "/systempowerswitch/$datatype","enum")
  publish(nodes + "/systempowerswitch/$format","shutdown,reboot")
  publish(nodes + "/systempowerswitch/$retained","true")
  publish(nodes + "/systempowerswitch/$settable","true")
  # homie stae ready
  publish("$state","ready")

def on_message(client, userdata, message):
  global powerswitch
  global powerstate
  if "systempowerswitch/set" in message.topic:
    #print("MQTT system power set payload:", str(message.payload.decode("utf-8")))
    powerswitch = str(message.payload.decode("utf-8"))

def on_disconnect(client, userdata, rc):
  print("MQTT Connection disconnected, Returned code=",rc)

#MQTT Connection
mqttattempts = 0
while mqttattempts < mqttretry:
  try:
    client=mqtt.Client(mqttclientid)
    client.username_pw_set(username, password)
    client.tls_set(cert_reqs=ssl.CERT_NONE) #no client certificate needed
    client.tls_insecure_set(insecure)
    client.will_set("homie/" + clientid + "/$state","lost",qos,retain_message)
    client.connect(broker, port)
    client.loop_start()
    mqttattempts = mqttretry
  except :
    print("Could not establish MQTT Connection! Try again " + str(mqttretry - mqttattempts) + "x times")
    mqttattempts += 1
    if mqttattempts == mqttretry:
      print("Could not connect to MQTT Broker! exit...")
      exit (0)
    time.sleep(5)

# MQTT Subscription
client.on_message = on_message
client.on_connect = on_connect
client.on_disconnect = on_disconnect

# Tell systemd that our service is ready
systemd.daemon.notify('READY=1')

# finaly the loop
while True:
  try:
    #print("powerswitch Loop wert: ", powerswitch)
    if powerswitch == "shutdown":
      powerswitch = "Null"
      print("System Shutdown")
      publish("$state","disconnected")
      publish(nodes + "/systempowerswitch", "off")
      client.disconnect()
      client.loop_stop()
      os.system('halt')
    elif powerswitch == "reboot":
      powerswitch = "Null"
      print("System Reboot")
      publish("$state","disconnected")
      publish(nodes + "/systempowerswitch", "reboot")
      client.disconnect()
      client.loop_stop()
      os.system('reboot')
    time.sleep(1)

  except KeyboardInterrupt:
    print("Goodbye!")
    # At least close MQTT Connection
    publish("$state","disconnected")
    time.sleep(1)
    client.disconnect()
    client.loop_stop()
    exit (0)

  except :
    print("An Error accured ... ")
    time.sleep(3)
    continue

# At least close MQTT Connection
print("Script stopped")
publish("$state","disconnected")
time.sleep(1)
client.disconnect()
client.loop_stop()

bitte passt die Variablen oben im Script an eure Bedürfnisse an!

Nun geben wir dem Script noch das Execute Recht:

chmod +x restart_shutdown-homie.py

einen ersten Test einfach durch das Aufrufen des Scripts machen.

restart_shutdown-homie.py

mit STRG-C könnt ihr es beenden.

Systemd Service

Um das Ganze nun noch als tätig laufenden Service zu haben, erstellen wir ein Systemd Service file und aktivieren dieses. Hierzu legen wir als erstes das Service File an:

/etc/systemd/system/restart_shutdown.service

# systemd unit file for the restart_shutdown-mqtt Python Script
[Unit]

# Human readable name of the unit
Description=Python MQTT System Restart and Shutdown Service

# Starting after System is online and docker is running
# Only needed if MQTT is used
Wants=network-online.target
After=network-online.target
# Only needed if MQTT Broker is running in a Docker Container on the same Host
#After=docker.service
#After=docker.socket

[Service]

# Command to execute when the service is started
ExecStart=/usr/bin/python3 /usr/local/sbin/restart_shutdown-mqtt.py
# Alternative the Homie Script:
#ExecStart=/usr/bin/python3 /usr/local/sbin/restart_shutdown-homie.py

# Disable Python's buffering of STDOUT and STDERR, so that output from the
# service shows up immediately in systemd's logs
Environment=PYTHONUNBUFFERED=1

# Automatically restart the service if it crashes
Restart=on-failure

# Our service will notify systemd once it is up and running
Type=notify

# Use a dedicated user to run our service
User=root

# Send CTRL+C tot python Script to terminate it clean
KillSignal=SIGINT

[Install]

# Tell systemd to automatically start this service when the system boots
# (assuming the service is enabled)
WantedBy=default.target

Bitte prüft hier den Part mit Wants und After falls der MQTT Broker im Docker Container läuft.

nun aktivieren wir den Service, damit er auch beim Neustart gestartet wird:

systemd daemon-reload
systemctl enable restart_shutdown.service

einen ersten Start können wir so ausführen:

systemctl start restart_shutdown.service

der Status des Skriptes sollte dann wie folgt aussehen:

systemctl status restart_shutdown.service

nun kann man das System einmal durchstarten und schauen ob der Service auch beim Reboot hochgefahren wird.

Installation via GIT Repository

Hier findet ihr das Projekt in GitHub:

Die Installation via Git erfolgt so:

# Installation
cd /usr/src
git clone https://github.com/alaub81/restart_shutdown-mqtt.git
cp restart_shutdown-mqtt/restart_shutdown-mqtt.py /usr/local/sbin/
cp restart_shutdown-mqtt/restart_shutdown.service /etc/systemd/system/
chmod +x /usr/local/sbin/restart_shutdown-mqtt.py
# Falls du das Homie MQTT verwenden möchtest:
cp restart_shutdown-mqtt/restart_shutdown-homie.py /usr/local/sbin/
chmod +x /usr/local/sbin/restart_shutdown-homie.py
# Editieren der Dateien
nano /etc/systemd/system/restart_shutdown.service
nano /usr/local/sbin/restart_shutdown-mqtt.py
# für das Homie MQTT Script:
nano /usr/local/sbin/restart_shutdown-homie.py
# Aktivieren des Autostart
systemctl enable restart_shutdown.service
systemctl start restart_shutdown.service

openHAB / HABPanel Anbindung

Um nun die MQTT Anbindung via openHAB zu bewerkstelligen muss als erstes das MQTT Binding installiert werden. Mittels diesem legt man dann Items an, die man wiederum in einer Sitemap und oder HABPanel verwenden kann.

Ein Detaillierte Anleitung wie man MQTT an openHAB anbindet findet ihr hier:

Wie man einen Browser im Kiosk Mode auf dem Pi startet findet ihr hier:

Dies ist das MQTT Topic das durch das Script verfügbar ist:

restart_shutdown-mqtt.py Topics
Topic Wert Beispiel Topic Beschreibung
hostname/system_power_switch on / reboot / shutdown home/attic/office/laub-raspi3/system_power_switch on wird gesetzt nachdem ein reboot oder shutdown empfangen wurde, damit es nicht in eine Endlosschleife läuft. reboot bewirkt einen Neustart des Systems, shutdown, ein Herunterfahren des Systems

openHAB Items

Nun müssen wir für die oben stehenden Topics in openHAB Items einrichten. Dafür habe ich zunächst für den Raspberry Pi ein eigenes MQTT Generic Thing angelegt.

Dann fügen wir für den Topic (system_power_switch) einen eigenen Channel dem Thing hinzu:

nun sollte man den Channel im Thing sehen. Nun können wir über den Channel einfach ein Item anlegen:


Das Ergebnis kann man sich nun in PaperUI Control anschauen und direkt testen:

als nächstes wollen wir die Items natürlich verwenden in HABPanel und in der Sitemap um sie via Handy steuern zu können.

openHAB sitemap

Nun kann man die beiden Befehle, reboot und shutdown noch in die sitemap einbauen. Hierzu dient die folgende Zeile:

Switch item=LaubRaspi3MQTT_SystemPowerSwitch label="Laub-Raspi3" mappings=["shutdown"="off", "reboot"="reboot"]
// bei Homie MQTT Script
Switch item=LaubRaspi3Power_Power_SystemPowerSwitch label="Laub-Raspi3" mappings=["shutdown"="off", "reboot"="reboot"]

Das Ergebnis sieht dann zum Beispiel so aus:

Mehr Informationen wie man Sitemaps für openHAB erstellt findet ihr hier:

HABPanel Widget

Da ich bei mir auf den Touchdisplay HABPanel anzeigen lasse, möchte ich natürlich auch via Touchdisplay, das System Neustarten oder Herunterfahren. Deshalb legen wir zwei Schaltflächen in HABPanel an:

Das Ergebnis sieht dann so aus: