Hilfe bei Schleifen und thread

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Grantelbart
User
Beiträge: 8
Registriert: Freitag 13. Dezember 2019, 20:07

Guten Tag zusammen,
Ich bin leider nicht sehr fit in Programmieren aber vllt kann mir jmd helfen.
Das ist der soll Zustand den ich gerne hätte. Ich hoffe es ist verständlich.
Bild

Code: Alles auswählen

    #!/usr/bin/env python3
import time

import gpiozero
import lcddriver
import RPi.GPIO as GPIO
import sys
import paho.mqtt.client as mqtt
import os
from gpiozero import DigitalOutputDevice

################################################################

name = "kein name"
def on_message(client, userdata, message):
    global name
    name = str(message.payload.decode("utf-8"))
topics = ["name"]
client = mqtt.Client()
client.on_message=on_message
client.connect("127.0.0.1", 1883, 180)
client.loop_start()
for name in topics:
        client.subscribe("name/reader")
        client.loop_start()

client = mqtt.Client()
client.connect("127.0.0.1", 1883, 60)
client.loop_start()

################################################################


class HP4067Mux:
    def __init__(self, s0, s1, s2, s3):
        """
        Arguments s0 to s3 represent the address input as in the datasheet
        """
        self._outs = [DigitalOutputDevice(pin=pin) for pin in [s0, s1, s2, s3]]
        self.channel(0)
    def channel(self, value):
        """
        Select the muxed channel from 0 to 15
        """
        assert 0 <= value <= 15, "Can only mux 4 lines"
        for i, pin in enumerate(self._outs):
            pin.value = bool(1 << i & value)


mymux = HP4067Mux(22, 27, 23, 24)


####################################################################




GPIO.setmode(GPIO.BCM) 

GPIO.setup(26, GPIO.IN, pull_up_down=GPIO.PUD_UP)
##GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(20, GPIO.IN, pull_up_down=GPIO.PUD_UP) 





################################################################

EMULATE_HX711 = False

if EMULATE_HX711:
    from emulated_hx711 import HX711
else:
    from hx711 import HX711


def main():
    try:
        mymux.channel(15)
        #lcd = lcddriver.lcd()
        weight_sensor = HX711(17, 18)
        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:

            input_state = GPIO.input(26)
            if input_state == False:
                lcd.lcd_display_string("Tara ausgefuehrt", 4)
                weight_sensor.tare()
                time.sleep(0.2)
              
            input_state = GPIO.input(20)
            if input_state == False:
               print("gedrueckt")
               lcd.lcd_display_string("Reboot ausgeloest", 4)
               time.sleep(3.3)
               lcd.lcd_clear()
               os.system("reboot")
               sys.exit()
            time.sleep(0.3)
            weight = max(0, int(weight_sensor.get_weight(5)))
            client.publish("huhn/waage1", weight)
            print(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(str(name), 3)

    except KeyboardInterrupt:
        pass  # User can end this with Ctrl+C.
    finally:
        print("Cleaning...")
        if not EMULATE_HX711:
            GPIO.cleanup()
        print("Bye!")


if __name__ == "__main__":
    main()   
Grantelbart
User
Beiträge: 8
Registriert: Freitag 13. Dezember 2019, 20:07

Hier ist noch die aktuelle Schaltung falls es jmd interessiert
Bild
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Aus dieser Beschreibung wird nicht wirklich klar, was du da machen möchtest. Wenn ich dir nicht schon im PI Forum ein bisschen unter die Arme gegriffen hätte, wüsste ich gar nicht, was da passieren soll. Du hast ja offensichtlich vor, eine ganze Reihe von Wägezellen abzufragen. Zu welchem Zweck, und was dabei die gewünschten Abläufe sind - 🤷🏼‍♂️

Meiner ehrlichen Meinung nach verlangt diese Aufgabe Stunden an Mitarbeit von Seiten von jemandem, der programmieren kann. Das werde ich nicht sein, und auch wer anders macht das eher nicht umsonst - außer das Projekt hat einen persönlichen Charakter.
Grantelbart
User
Beiträge: 8
Registriert: Freitag 13. Dezember 2019, 20:07

Ja dafür bin ich dir auch sehr dankbar.

Das ganze soll in einem Hühnerstall verbaut werden.
Die Waage Zellen werden mit rfid im Nest verbaut.

1. Wiegen der Hühner
2. Erkennung des Huhns

Pro Nest benötige ich demnach eine Waage
Ist der ablaufplan nicht verständlich? Sonst muss ich den ggf. Neu machen
Grantelbart
User
Beiträge: 8
Registriert: Freitag 13. Dezember 2019, 20:07

Also
Wenn die Waagen gestartet werden soll einmal tara ausgeführt werden habe wohl gelesen das mir da evtl __init__ helfen könnte.

Dadurch das ich die Waagen wiederholt abfragen will benötige ich wohl ne Schleife in einem Loop.

Demnach muss ich die channel vom muxer hochzählen
Und übergeben.

Wenn ich weiteren tara auslösen möchte muss dies Manual passieren
Benutzeravatar
kbr
User
Beiträge: 1501
Registriert: Mittwoch 15. Oktober 2008, 09:27

Hühner zu wiegen ist ja ein nettes Projekt. Aber das Schaltdiagramm ist nicht nachvollziehbar und das Ablaufdiagramm, in Bezug auf die Entscheidungswege, und was in den Loops passieren soll, gleichfalls unklar.

Im Groben könnte das ungefähr so aussehen, wobei die Punkte in sich durchaus wieder komplexer mit eigenen Ablaufdiagrammen gestaltet sein können.

Code: Alles auswählen

                Start
                  |
                  |
           Tara einstellen    <-------------\
                  |                         |
                  |                         |
/------  Stop Signal erhalten?   <------\   |
|                 |                     |   |
| ja              | nein                |   |
|                 |                     |   |
|         Messung vornehmen             |   |
|                 |                     |   |
|         Ergebnis ausgeben             |   |
|                 |                     |   |
|               Delay(?)                |   |
|                 |                     |   |
|                 |                     |   |
|      Signal für Tara-Einstellung      |   |
|             erhalten?            ---- /   |
|                 |                 nein    |
|                 | ja                      |
|                 \-------------------------/
|
|
\-----------------\
                  |
                Stop            
Weiter teile ich die Einschätzung von __deets__, dass für eine erfolgreiche Umsetzung Programmierkenntnisse erforderlich sind. Und die RFID Erkennung der Hühner ist in obigen Diagramm noch gar nicht berücksichtig.
Grantelbart
User
Beiträge: 8
Registriert: Freitag 13. Dezember 2019, 20:07

Die RFiD Erkennung hab ich schon
Ich muss nur diesen part umbauendan


Tja dann ist das wohl so und ich werd den thread wieder zu machen ich denke nächste Woche werd ich damit fertig sein so oder so.
Benutzeravatar
__blackjack__
User
Beiträge: 13938
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@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.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

@__blackjack__: ich habe ihm den Muxer-Code geschrieben, und dafuer bewusst gpiozero verwendet. Statt den also in RPi.GPIO zu wandeln waere es besser, den HX711-Code in gpiozero zu konvertieren. Habe ich ihm auch geraten, ist offensichtlich nicht passiert.
Benutzeravatar
__blackjack__
User
Beiträge: 13938
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Stimmt, `gpiozero` wäre die bessere Wahl. Ich hatte mich für das entschieden was ad hoc einfacher war. :-)
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Antworten