@Grantelbart: Ein paar Anmerkungen zum Quelltext:
`gpiozero` wird importiert aber nicht verwendet. Es ist auch keine gute Idee mehr als eine Bibliothek zum ansteuern der GPIOs zu verwenden. Entweder `RPi.GPIO` *oder* `gpiozero`. Warum beides? Und wonach hast Du entschieden für welche Pins Du die eine und für welche die andere Bibliothek verwendest?
``as`` beim Import ist zum umzubennenen gedacht, `GPIO` wird aber gar nicht umbenannt.
Diese Trennlinienkommentare machen den Code nicht wirklich übersichtlicher weil Code und Variablen auf Modulebene an sich schon so unübersichtlich ist, dass man das gar nicht erst so anfängt. Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht komplett in der `main()`-Funktion. Wenn dort zu viel gemacht wird, dann zieht man das in sinnvolle Funktionen heraus. ``global`` und globale Variablen auf Modulebene gehen gar nicht. Vergiss das es das Schlüsselwort ``global`` gibt, das macht mehr Probleme als es löst, insbesondere wenn dann auch noch nebenläufige Programmierung beispielsweise mit Threads in Spiel kommt.
Warum startest Du zwei MQTT-Clients? Und bindest die beide an den gleichen Namen, so dass man an den ersten gar nicht mehr heran kommt? Zum Beispiel um ihn am Ende sauber zu beenden wenn der Benutzer das Programm abbricht.
Es macht keinen Sinn `loop_start()` mehr als einmal auf einem `Client` aufzurufen. Das könnte sogar falsch sein und zu Problemen führen falls da tatsächlich jedes mal ein weiterer Thread gestartet wird.
`topics` und die Schleife darüber macht keinen Sinn, da der Wert in `topics` nirgends verwendet wird. Und die Schleife ist sinnfrei weil, falls in `topics` mehr als ein Element wäre, in der Schleife immer wieder auf die gleiche, feste Zeichenkette ”subscribed” würde.
Um das ``global`` in `on_message()` los zu werden muss man entweder eine Klasse schreiben, oder `userdata` verwenden.
Du übertreibst es ein bisschen mit `str()`-Aufrufen bei dem Namen. Wenn man die Nutzlast der Nachricht dekodiert, dann hat man bereits eine Zeichenkette und muss da nicht noch einmal `str()` mit aufrufen. Und später wenn Du den Namen dann auf das LCD bringst rufst Du *noch mal* `str()` auf.
Anstelle von magischen Pin-Nummern irgendwo tief im Programm versteckt, sollte man die am Anfang als Konstanten mit sprechenden Namen definieren. Das lässt sich dann a) leichter ändern und b) weiss der Leser dort wo die Pin-Nummern verwendet werden, was das bedeutet. Wenn man die Konstanten an einer Stelle im Programm definiert würden auch Fehler wie mehrfach verwendete Pin-Nummern leichter auffallen.
Wenn man anfängt Namen zu nummerieren, wie bei `HP4067Mux.__init__()` dann will man sich entweder bessere Namen überlegen oder gar keine Einzelnamen/-werte sondern eine Datenstruktur. In diesem Falle eine Liste mit den Pin-Nummern.
`channel()` würde ich aktiver bennenen, denn hinter dem Namen `channel` würde man als Leser keine Methode sondern einen Wert erwarten der einen Kanal repräsentiert.
``assert`` ist eigentlich nur für Tests die garantiert nicht schief gehen können es sei denn der Programmierer hat an der Stelle einen Fehler gemacht. Vorbedingungen sollte man damit also nur in nicht-öffentlichen Funktionen und Methoden prüfen.
Die Vorsilbe `my` macht bei Namen eigentlich nie Sinn. Es sei denn es gibt Neben `my_something` noch `our_something` und/oder `their_something` um das gegeneinander abgrenzen zu können. Statt `mymux` beziehungsweise nur `mux` wäre es für den Leser doch interessant *was* damit ausgewählt werden kann.
Wenn man so etwas wie die Definition von `lcd` auskommentiert muss man natürlich auch alles auskommentieren was diesen Namen verwendet, denn sonst läuft man da ja unweigerlich in `NameError`-Ausnahmen.
Man vergleicht nicht auf literale `True` oder `False` denn da kommt ja nur wieder ein Wahrheitswert bei heraus, entweder der den man sowieso schon hatte, oder dessen Gegenteil. Im ersten Fall kann man gleich den Wert nehmen den man schon hat, im zweiten gibt es ``not``.
Wenn der Tara-Knopf gedrückt wird, dann wird "Tara ausgeführt" ausgegeben *bevor* das tatsächlich gemacht wurde.
`os.system()` sollte nicht mehr verwendet werden. Dessen Dokumentation verweist auf das `subprocess`-Modul.
`sys.exit()` ohne das man dem Aufrufer einen Exit-Code zumindest potentiell ungleich 0 übermitteln will ist ein „code smell“ das man es sich zu einfach machen will. Im vorliegenden Code tut es auch ein einfaches ``break`` um die Schleife zu verlassen. Wobei egal was da steht sowieso nicht ausgeführt wird, weil ja ein Neustart des Systems in der Zeile davor gemacht wird.
`GPIO.cleanup()` muss man unabängig davon welches `HX711`-Modul verwendet wird aufrufen. Es werden ja Pins aufgesetzt/verwendet die damit gar nichts zu tun haben, und die müssen ordentlich aufgeräumt werden.
Code: Alles auswählen
#!/usr/bin/env python3
import subprocess
import time
from types import SimpleNamespace
import lcddriver
import paho.mqtt.client as mqtt
from RPi import GPIO
EMULATE_HX711 = False
if EMULATE_HX711:
from emulated_hx711 import HX711
else:
from hx711 import HX711
TARE_BUTTON_PIN = 26
REBOOT_BUTTON_PIN = 20
WEIGHT_SENSOR_PINS = [17, 18]
WEIGHT_SENSOR_ADDRESS_PINS = [22, 27, 23, 24]
WEIGHT_SENSOR_CHANNEL = 15
class HP4067Mux:
def __init__(self, address_pins):
"""
Argument represents the address input as in the datasheet.
"""
self.address_pins = address_pins
GPIO.setup(self.address_pins, GPIO.OUT)
self.select_channel(0)
def select_channel(self, value):
"""
Select the muxed channel from 0 to 15.
"""
if not 0 <= value <= 15:
raise ValueError("can only mux 4 lines")
for i, pin in enumerate(self.address_pins):
GPIO.output(pin, value & (1 << i))
def on_message(_client, userdata, message):
userdata.name = message.payload.decode("utf-8")
def main():
try:
data = SimpleNamespace(name="kein name")
client = mqtt.Client(userdata=data)
client.on_message = on_message
client.connect("127.0.0.1", keepalive=180)
client.subscribe("name/reader")
client.loop_start()
try:
GPIO.setmode(GPIO.BCM)
GPIO.setup(
[TARE_BUTTON_PIN, REBOOT_BUTTON_PIN],
GPIO.IN,
pull_up_down=GPIO.PUD_UP,
)
lcd = lcddriver.lcd()
weight_sensor_mux = HP4067Mux(WEIGHT_SENSOR_ADDRESS_PINS)
weight_sensor_mux.select_channel(WEIGHT_SENSOR_CHANNEL)
weight_sensor = HX711(*WEIGHT_SENSOR_PINS)
weight_sensor.set_reading_format("MSB", "MSB")
weight_sensor.set_reference_unit(339)
weight_sensor.reset()
weight_sensor.tare()
print("Tare done! Add weight now...")
while True:
if not GPIO.input(TARE_BUTTON_PIN):
weight_sensor.tare()
lcd.lcd_display_string("Tara ausgefuehrt", 4)
time.sleep(0.2)
if not GPIO.input(REBOOT_BUTTON_PIN):
lcd.lcd_display_string("Reboot ausgeloest", 4)
time.sleep(3.3)
lcd.lcd_clear()
subprocess.run("reboot", check=True)
time.sleep(0.3)
weight = max(0, int(weight_sensor.get_weight(5)))
client.publish("huhn/waage1", weight)
weight_sensor.power_down()
weight_sensor.power_up()
time.sleep(0.1)
lcd.lcd_clear()
lcd.lcd_display_string(str(weight), 1)
# lcd.lcd_display_string(time.strftime("%d.%m.%Y %H:%M:%S"), 2)
lcd.lcd_display_string(data.name, 3)
finally:
print("Stopping MQTT client.")
client.loop_stop()
client.disconnect()
except KeyboardInterrupt:
pass # User can end this with Ctrl+C.
finally:
GPIO.cleanup()
print("Bye!")
if __name__ == "__main__":
main()
Die `main()` macht für meinen Geschmack zu viel, das sollte man sinnvoll auf Funktionen und eventuell Klassen aufteilen die man dann auch mal separat ausführen und testen kann.