Title: M1.2.4: MQTT und Python
Course: LF07 - Cyber-physische Systeme ergänzen - FI/ITSE - Raspberry Pi > Section: LS7.1: Messdatenerfassungssysteme funktional zusammenführen

Mit einem Pythonprogramm Werte in ein Topic schreiben und auslesen.

Einen MQTT Client als Pythonprogramm realisieren

Literatur:

- https://schroederdennis.de/tutorial-howto/mqtt-mit-python-benutzen-guide-tutorial-teil1/
- How to Install PIP for Python on Windows: https://www.liquidweb.com/kb/install-pip-windows/
- Dokumentation paho-mqtt: https://eclipse.org/paho/index.php?page=clients/python/docs/index.php
- Dokumentation ssl library: https://docs.python.org/3/library/ssl.html

Weit verbreitet ist die Bibliothek paho-mqtt aus der Eclipse Foundation. Sie macht den Zugriff auf einen MQTT-Broker über Python kinderleicht.

PIP, der Pythonpaketmanager ist im Normalfall mit der Pythoninstallation zusammen installiert worden. Falls nicht, muss er nachinstalliert werden. Siehe Hinweis weiter oben.

Paho installieren:

```pip3 install paho-mqtt```

Das war's schon. Wir können mit dem Coden beginnen. In VS Code, IDLE, notpad++ oder einem anderen geeigneten Editor schreiben wir unser Quellprogramm für einen Publisher, hier erstmal ohne Useraccounts und unverschlüsselt (erste Zeile, Shebang, kann bei Windows entfallen):

```#!/usr/bin/env python
import paho.mqtt.client as mqtt
mqttc = mqtt.Client()
mqttc.connect("10.2.5.140", 1883, 60)
mqttc.loop()
mqttc.publish("sensor1/motd", "Be happy!")
mqttc.disconnect()```

Ganze fünf Zeilen reichen für einen Zugriff auf den MQTT Broker. Nicht vergessen, dass der Broker einen Listener auf Defaultport 1883 benötigt. Daher in /etc/mosquitto/conf.d/default.conf einfügen:

listener 1883
allow_anonymous true

Wenn nun noch über TLS kommuniziert werden soll, was dringend angeraten ist, und eine Userauthentifizierung hinzukommen soll, wird es etwas aufwändiger.

Aus der Paho Doku kann man entnehmen, welche Funktionen man für eine sichere Kommunikation über TLS benötigt. Vorher müssen noch Zertifikatsdatei der CA auf den Client kopiert und Zertifikat und Schlüssel des Clients, von dem die Anfrage kommen soll, generiert werden.

Aus der paho-Doku: Configure network encryption and authentication options. Enables SSL/TLS support.

ca_certs
a string path to the Certificate Authority certificate files that are to be treated as trusted by this client. If this is the only option given then the client will operate in a similar manner to a web browser. That is to say it will require the broker to have a certificate signed by the Certificate Authorities in ca_certs and will communicate using TLS v1, but will not attempt any form of authentication. This provides basic network encryption but may not be sufficient depending on how the broker is configured. By default, on Python 2.7.9+ or 3.4+, the default certification authority of the system is used. On older Python version this parameter is mandatory.
certfile, keyfile
strings pointing to the PEM encoded client certificate and private keys respectively. If these arguments are not None then they will be used as client information for TLS based authentication. Support for this feature is broker dependent. Note that if either of these files in encrypted and needs a password to decrypt it, Python will ask for the password at the command line. It is not currently possible to define a callback to provide the password.
cert_reqs
defines the certificate requirements that the client imposes on the broker. By default this is ssl.CERT_REQUIRED, which means that the broker must provide a certificate. See the ssl pydoc for more information on this parameter.
tls_version
specifies the version of the SSL/TLS protocol to be used. By default (if the python version supports it) the highest TLS version is detected. If unavailable, TLS v1 is used. Previous versions (all versions beginning with SSL) are possible but not recommended due to possible security problems.
ciphers
a string specifying which encryption ciphers are allowable for this connection, or None to use the defaults. See the ssl pydoc for more information.

Must be called before connect*().

Programmcode Publisher wahlweise ohne oder mit TLS und ohne oder mit Userauthentifizierung

```import paho.mqtt.client as mqtt
import ssl
from time import sleep, ctime, time

#------ Variables
broker_address = "10.2.5.140" #IP-address or URL
broker_port = 1896
topic = "sensor1/motd"
message = "My time is: " + ctime(time()) # this message is to be sent to the broker, the actual time

is_tls = False # Transmission secured (True) or unencrypted (False)
is_accounting = True # Use username and password (True) or anonymous (False)

username = "hermine" # case sensitive!
password = "Gryffindor"

certdir = "C:\\Muell\\cert\\" # put the CA and client cert files here or feel free to use another location
key_file = certdir + "client_2.key"
crt_file = certdir + "client_2.crt"
#ca_file = ""  # without CA, haven't got that to work, even though paho manual mentions that possible
ca_file = certdir + "ca.crt" # fill in location of CA file here, self certified certs seem to be a burden for mosquitto, never have got communication to work
# ------- end Variables

# ------- Functions
def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))
    # Subscribing in on_connect() means that if we lose the connection and
    # reconnect then subscriptions will be renewed.
    client.subscribe("$SYS/#")

# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
    print(msg.topic+" "+str(msg.payload))

# asynchronous logging information will be displayed in this way
def on_log(client, userdata, level, buf):
    print("")
    print ("client: ", client)
    print ("userdata: ", userdata)
    print ("level: ", level)
    print ("buf: ", buf)
    print("")
# -------- end Functions

mqttc = mqtt.Client()
mqttc.on_connect = on_connect
mqttc.on_message = on_message
# if connection issues come up, activate the logging function (just uncomment next line). Every single communication action will be displayed on the command line then
#mqttc.on_log = on_log

if is_tls:
    if ca_file == "":
        mqttc.tls_set(ca_certs=None,certfile=crt_file, keyfile=key_file, cert_reqs=ssl.CERT_NONE, tls_version=ssl.PROTOCOL_TLS, ciphers=None)
    else:
        mqttc.tls_set(ca_certs= ca_file, certfile=crt_file, keyfile=key_file, cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLS, ciphers=None)

if is_accounting:
    mqttc.username_pw_set(username=username, password=password)

print("Connecting...")

# not every connection trial will succeed at the first time, so give it a chance to try again
c=4
while True:
    try:
        c=mqttc.connect(broker_address, broker_port,60) # works even without exception handling, but was important for debugging cert errors. Open a discussion in your lesson on event handling: https://docs.python.org/3.9/tutorial/errors.html?highlight=exception

    except Exception as e:
        print ("Exception while connecting: ",type(e), str(e))  # Should give you the exception type
        mqttc.disconnect()
        sleep (0.1)
        c=c-1

    if int(c) == 0:
        break

# since all communication with the broker is kind of asynchronous, we take our time and wait
sleep (0.1)
mqttc.loop() # don't forget about this command, it's essential

# waiting again for an instant
sleep (0.1)

mqttc.publish(topic, message)

mqttc.disconnect()

print ("Bye...")
```