Hilfe bei Wägezellen Skript

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
mango94
User
Beiträge: 10
Registriert: Mittwoch 29. August 2018, 11:19

Hi. Seit einigen Tagen versuche ich, meine Wägezelle richtig auszulesen. Bis jetzt hab ich es nur manuell im Terminal geschafft, einen richtigen Messwert zu erhalten.
Jedoch brauch ich aber laufend eine Abfrage des Gewichts, weshalb mir das gefundene Skript nicht wirklich was bringt.

Wägezellen:
https://de.aliexpress.com/item/1kg-5kg- ... a69079e8-0

Angeschlossen wurden sie, so wie laut Anleitung https://tutorials-raspberrypi.de/raspbe ... sor-hx711/

Es wurden aber andere Pins benutzt, wurden unten auch im Skript geändert.

Jedoch +5V und 0V hole ich nicht vom Pi, sondern von einer Externen Spannungsquelle. Alle 0V Anschlüsse der Netzteile wurden verbunden.



Kommen wir nun zum Skript. Hier fangen die Probleme, bzw das Halbwissen an.

Ich habe einige durchprobiert, und hab es leider nur mit einem geschafft, eine korrekte Messung zu machen, jedoch wird dieser Skript über das Raspberry Pi 3B+ Terminal ausgeführt, und ich muss jedes mal Enter drücken, um eine Kalibrierung zu machen.
Da ich das ja über Pimatic (automatisch) ausführen lassen möchte, klappt es nicht, wenn ich dauernd Enter drücken muss, da ich eine 24/7 Überwachung brauche.



In den Codes wurde nur eine Wägezelle eingetragen, um damit mal etwas experimentieren zu können.

Code: Alles auswählen

#!/usr/bin/env python3
import RPi.GPIO as GPIO  # import GPIO
from hx711 import HX711  # import the class HX711

try:
    GPIO.setmode(GPIO.BCM)  # set GPIO pin mode to BCM numbering
    # Create an object hx which represents your real hx711 chip
    # Required input parameters are only 'dout_pin' and 'pd_sck_pin'
    hx = HX711(dout_pin=14, pd_sck_pin=15)
    # measure tare and save the value as offset for current channel
    # and gain selected. That means channel A and gain 128
    err = hx.zero()
    # check if successful
    if err:
        raise ValueError('Tare is unsuccessful.')

    reading = hx.get_raw_data_mean()
    if reading:  # always check if you get correct value or only False
        # now the value is close to 0
        print('Data subtracted by offset but still not converted to units:',
              reading)
    else:
        print('invalid data', reading)

    # In order to calculate the conversion ratio to some units, in my case I want grams,
    # you must have known weight.
    input('Put known weight on the scale and then press Enter')
    reading = hx.get_data_mean()
    if reading:
        print('Mean value from HX711 subtracted by offset:', reading)
        known_weight_grams = input(
            'Write how many grams it was and press Enter: ')
        try:
            value = float(known_weight_grams)
            print(value, 'grams')
        except ValueError:
            print('Expected integer or float and I have got:',
                  known_weight_grams)

        # set scale ratio for particular channel and gain which is
        # used to calculate the conversion to units. Required argument is only
        # scale ratio. Without arguments 'channel' and 'gain_A' it sets
        # the ratio for current channel and gain.
        ratio = reading / value  # calculate the ratio for channel A and gain 128
        hx.set_scale_ratio(ratio)  # set ratio for current channel
        print('Ratio is set.')
    else:
        raise ValueError('Cannot calculate mean value. Try debug mode. Variable reading:', reading)

    # Read data several times and return mean value
    # subtracted by offset and converted by scale ratio to
    # desired units. In my case in grams.
    print("Now, I will read data in infinite loop. To exit press 'CTRL + C'")
    input('Press Enter to begin reading')
    print('Current weight on the scale in grams is: ')
    while True:
        print(hx.get_weight_mean(20), 'g')

except (KeyboardInterrupt, SystemExit):
    print('Bye :)')

finally:
    GPIO.cleanup()



Mit diesem Skript hier wird mir ein Wert im Pimatic angezeigt, jedoch wird der Wert nicht als die Gewichtseinheit g ausgegeben:

Code: Alles auswählen

#!/usr/bin/env python3
import RPi.GPIO as GPIO  # import GPIO
from hx711 import HX711  # import the class HX711

GPIO.setmode(GPIO.BCM)  # set GPIO pin mode to BCM numbering
hx = HX711(dout_pin=14, pd_sck_pin=15)  # create an object
print(hx.get_raw_data_mean())  # get raw data reading from hx711
GPIO.cleanup()

Kennt zufällig jemand einen Skript, das für mein vorhaben passen würde, bzw würde sich jemand die Zeit nehmen, um mir da etwas unter die Arme zu greifen?



Danke für eure Hilfe im vorraus
Benutzeravatar
sparrow
User
Beiträge: 4164
Registriert: Freitag 17. April 2009, 10:28

Ich verstehe deine konkrete Frage nicht.
Das Script, dass man am Anfang kalibrieren muss, läuft doch anschließend in einer Schleife. Wenn du die Kalibrierung nicht brauchst, nimm sie raus. Wenn du mit den anfallenden Daten etwas machen willst, tue das doch in der Schleifen.
Woran hapert es denn?
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Dieses `hx711`-Modul hat eine schräge bis kaputte API. Das liefert bei den Messungen `False` wenn etwas nicht geklappt hat. Blöderweise ist `False` aber ein `bool` und das ist von `int` abgeleitet, und so kann man gar nicht unterscheiden zwischen `False` und einem gültigen 0-Wert:

Code: Alles auswählen

In [6]: False == 0
Out[6]: True
Die Klasse hat auch gar keine `zero()` oder `set_ratio()` Methode‽
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
mango94
User
Beiträge: 10
Registriert: Mittwoch 29. August 2018, 11:19

sparrow hat geschrieben: Mittwoch 13. Februar 2019, 11:30 Ich verstehe deine konkrete Frage nicht.
Das Script, dass man am Anfang kalibrieren muss, läuft doch anschließend in einer Schleife. Wenn du die Kalibrierung nicht brauchst, nimm sie raus. Wenn du mit den anfallenden Daten etwas machen willst, tue das doch in der Schleifen.
Woran hapert es denn?
Mein Problem ist, dass ich keine Ahnung habe von Python...
Welchen Teil muss ich da jetzt löschen/verändern?

So wäre es perfekt:
Pimatic startet,
Das Grundgewicht von Tag 1 sollte gespeichert werden, da ich jeden Tag einen neustart des Raspberry´s durchführe.
Dann würde ich gerne die Veränderung messen.
Es handelt sich dabei um eine Gewichtsmessung eines mit Erde gefüllten Zimmerpflanzen Topf.
Das heißt, ich würde gerne wissen, wie viel Wasser im topf ist.


Wie bearbeite ich das jetzt am besten als blutiger anfänger??

LG
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mango94: Wenn das Problem ist keine Ahnung von Python zu haben, dann solltest Du das beheben. Zum Beispiel mal das Tutorial in der Python-Dokumentation durcharbeiten.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
mango94
User
Beiträge: 10
Registriert: Mittwoch 29. August 2018, 11:19

Ich hab mir deinen Tipp zu Herzen genommen, und einige Tutorials durchgearbeitet. Da nach mehr als 3 Wochen noch immer kein Ende in Sicht ist, versuch ich es nochmals anders:

Gibt es hier jemanden, der mir im Tausch gegen Paypal Guthaben ein Skript programmiert, dass eine Wägezelle vom Raspberry ausließt? Das Problem ist das Tarieren. Ich komme da einfach nicht hin, dass ich das Skipt automatisch starten kann. Die Halterung von der Waage soll abgezogen werden, und übrig bleiben soll nur das Tatsächliche Gewicht, dass auf der Waage liegt.


LG
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

__blackjack__ hat geschrieben: Mittwoch 13. Februar 2019, 11:34 Blöderweise ist `False` aber ein `bool` und das ist von `int` abgeleitet, und so kann man gar nicht unterscheiden zwischen `False` und einem gültigen 0-Wert:

Code: Alles auswählen

In [6]: False == 0
Out[6]: True
Doch kann man, da True und False nur einmal im Speicher als Objekt existiert, ergo kannst du mit is False auch testen, ob genau dieses Obekt zurückgeliefert worden ist.
Wenn die blöde API nervt, kann man sich ja einen Wrapper drum herum bauen, der einfach eine Exception auslöst, wenn False als Wert zurückgeliefert wird.
Diese Exception dann abfangen und entsprechend reagieren. Die Methode sollte nur Werte zurückgeben, wenn diese auch gültig sind und kein Fehler auftrat.

Code: Alles auswählen

class HX_Error(Exception):
    pass

class HX(HX711):
    def get_data_mean(self):
        value = super().get_data_mean()
        if value is False:
            raise HX_Error("Messung fehlerhaft")
        return value


hx = HX(dout_pin=14, pd_sck_pin=15)

try:
    reading = hx.get_data_mean()
except HX_Error:
    print("Messung fehlerhaft")
else:
    print("Gewicht:", reading, "g")
Wird nun 0 als Wert zurückgeliefert, wird dieser auch ausgegeben. Wird False zurückgeliefert, wird die Exception HX_Error ausgelöst.
Wenn du mehrere Messungen machen willst, kannst du das in einer Schleife machen.

Code: Alles auswählen

messungen = []
while len(messungen) < 10:
    try:
        reading = hx.get_data_mean()
    except HX_Error:
        continue
    else:
        messungen.append(reading)
print(messungen)
Nachteil ist, dass der solange weiter macht, bis er 10 gültige Messungen hat.
Wäre nun irgendwas anderes nicht Ordnung, würde diese Schleife niemals aufhören und es käme zu keinem Fortschritt.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@DeaD_EyE: das Problem ist, dass die ungünstige Behandlung von 0 und `False` schon eine Ebene tiefer passiert, nämlich bei _read. Wenn also zur Mittelung 30 mal read aufgerufen wird, und bei 15 Versuchen geht`s schief und bei 15 kappt's, dann ist das Ergebnis nicht 0 sondern irgend ein anderer Werte, der aber nur halb so groß ist, wie er sein sollte.

Daneben ist das `_read` noch an vielen anderen Stellen kaputt:
`ready_counter` geht bis 40 wird dann aber auf 50 geprüft.
Es wird versucht, Zeiten < 60µs zu messen, was bei einem Multitasking-OS nicht garantiert sicher funktionieren kann. Das Lesen muß irgendwie in Hardware gelöst sein. Deshalb gibt es keine wirkliche Lösung, die vermeidbaren Fehler sind aber damit ausgeräumt:

Code: Alles auswählen

class HX(HX711):
    def _read(self):
        """
        _read method reads bits from hx711, converts to INT
        and validate the data.
        
        Returns: the value as int
        """
        GPIO.output(self._pd_sck, False)  # start by setting the pd_sck to 0
        for _ in range(40):
            if self._ready():
                break
        else:
            if self._debug_mode:
                print('self._ready() not ready after 40 trials\n')
            raise RuntimeError('self._ready() not ready after 40 trials')

        # read first 24 bits of data
        data_in = 0  # 2's complement data from hx 711
        for _ in range(24):
            start_counter = time.perf_counter()
            # request next bit from hx 711
            GPIO.output(self._pd_sck, True)
            GPIO.output(self._pd_sck, False)
            end_counter = time.perf_counter()
            if end_counter - start_counter >= 0.00006:  # check if the hx 711 did not turn off...
                # if pd_sck pin is HIGH for 60 us and more than the HX 711 enters power down mode.
                if self._debug_mode:
                    print('Not enough fast while reading data')
                    print('Time elapsed: {}'.format(end_counter - start_counter))
                raise RuntimeError('Not enough fast while reading data')
            data_in = (data_in << 1) | GPIO.input(self._dout)

        if self._wanted_channel == 'A' and self._gain_channel_A == 128:
            bits = 1 # send only one bit which is 1
        elif self._wanted_channel == 'A' and self._gain_channel_A == 64:
            bits = 3 # send three ones
        else:
            bits = 2
        if not self._set_channel_gain(bit):
            raise RuntimeError("channel was not set properly")
        self._current_channel = self._wanted_channel

        if self._debug_mode:  # print 2's complement value
            print('Binary value as received: {}\n'.format(bin(data_in)))

        #check for overflow or underflow
        if (data_in == 0x7fffff # 0x7fffff is the highest possible value from hx711
                or data_in == 0x800000):  # 0x800000 is the lowest possible value from hx711
            if self._debug_mode:
                print('Invalid data detected: {}\n'.format(data_in))
            raise RuntimeError("the data is invalid")

        # calculate int from 2's complement
        if data_in & 0x800000:
            data_in = - (data_in ^ 0xffffff) - 1

        if self._debug_mode:
            print("Converted 2's complement value: {}\n".format(data_in))

        return data_in
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@DeaD_EyE: Von ``is False`` würde ich dringend abraten. Um den Style Guide zu zitieren:
Yes: if greeting:
No: if greeting == True:
Worse: if greeting is True:
Und das kann in sehr unerwarteten Situationen nicht funktionieren, weil das ja wirklich nur beim eingebauten `False` zutrifft, die meisten Python-Programmierer aber davon ausgehen, dass auch jeder ”falsy”-Wert funktionieren sollte. Da kommt man dann in Situationen wo es mal funktioniert und mal nicht, je nach dem welcher Weg durch eine Funktion genommen wird, oder man stellt eine Funktion auf die Verwendung von Numpy um und dann geht's plötzlich an anderer Stelle nicht mehr wo ein so zustande gekommener Wahrheitswert nicht richtig erkannt wird. Beispiel:

Code: Alles auswählen

In [23]: np.array([1,2,3]).any()
Out[23]: True

In [24]: np.array([1,2,3]).any() is True
Out[24]: False
Überraschung!

Der Autor von der API testet auch fröhlich mit ``== False``, das heisst der weiss das wohl selbst nicht das True/False Singletons sind. Der BDFL hat soweit ich weiss auch explizit abgeraten weil das in den meisten Fällen ein Fehler ist und schlägt wohl für den Fall wo es tatsächlich so gemeint ist, ein ``isinstance(x, bool) and not x`` statt ``is`` vor. Quelle dafür habe ich allerdings gerade nicht zur Hand.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Ist ja interessant, dass numpy ein anderes True/False zurückliefert, als Python selbst. Wahrscheinlich kommst du nicht drum herum den Code zu verbessern.
Monkey-Patching geht bis zu einem gewissen Grad, aber irgendwann wird es pervers und man macht es am besten gleich vernünftig.

Ich denke mal, dass wir uns da einig sind, dass Funktionen/Methoden den Datentyp nicht magisch wechseln sollen, wenn irgendwas schief geht.
Dafür sind die Exceptions viel besser geeignet.

Leg deinen Fokus zuerst darauf den Code für die Auswertung zu verbessern. Kannst ja einen Fork von dem Projekt machen.
Dann haben wir alle was davon :-)
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
eagleflight
User
Beiträge: 28
Registriert: Dienstag 12. Februar 2019, 19:45

Ich hatte dasselbe Probleme und auch nahezu die selbe Anwendung für den hx711. Bei mir dreht es sich um einen Langzeitversuch mit einem neuen Düngertyp im Vergleich zu herkömmlichen Primedüngern. Dazu müssen viele Werte über eine ganze Saison aufgezeichnet werden um nachzuweisen, dass unser Dünger viel besser ist als alle anderen auf dem Markt. (Eigenlob stinkt :))

Der Ansatz war zunächst gleich, nämlich alles über den Raspberry zu erledigen. Sollte natürlich auch nicht so viel kosten.
Nachdem es mit dem Raspberry und Python immer zu Problemen bei der Abfrage des hx711 kam (ich nehme mal an das sind Timing Probleme auf dem I2C Bus bei zu vielen gleichzeitigen Anwendungen), ich dazu den Raspberry auch nicht draussen an den Blumentöpfen stehen haben wollte, dazu lange Kabel an den Waagen die ich brauche nicht funktionieren, habe ich das Projekt erweitert und jetzt folgende funktionierende Lösung:

Nochmals Danke an die Jungs hier im Forum, die mir bei meinen ersten Python Schritten geholfen haben. :)

Ich habe es dezentralisiert und alles per WiFi verbunden.
Auf dem Raspberry läuft jetzt permanent ein Auswertungsskript in Python3 mit externer msql Datenbank auf einer Synology, das x Datenwerte alle 10 Min. einsammelt und abspeichert. Dazu wird ein MQTT Server (auch auf Raspberry) ständig vom Skript abgefragt. Auf diesen MQTT schicke ich u.a. vom Waagesensor, der mit dem hx711 und einem Nodemcu verbunden ist, die Gewichtsdaten per WiFi als JSON String. Auf dem Nodemcu läuft ein C Skript und ich habe den Sensor anfangs nur einmal kalibrieren müssen und jetzt stimmt er. Ich lasse den Sensor 15x messen und nehme dann den Durchschnitt als richtig an.Der Kalibrierungswert steht im Eprom und bleibt damit unveränderbar beim reboot.
Über eine manuelles MQTT Publish kann man ihn auch nachträglich ändern ohne den Nodemcu neu zu booten. Als Waage benutze ich eine 9€ Amazon China Waage die über einen kompatiblen Wägebalken zum hx711 verfügt und 0-5 kg in 1g Schritten anzeigt. Diese hat sogar ein Fach in das der Nodemcu genau rein passt und geschützt ist. Damit sind mehrere Fliegen mit einer Klappe erschlagen.

Die Waage läuft unabhängig über Wlan, der Raspberry liefert den MQTT Server für die WiFi Verbindung und schreibt die Werte fortlaufend in die msql Datenbank. Angekoppelt sind noch MiaFlora Sensoren über Bluetooth und etliche weitere Nodemcu's mit allen möglichen anderen Sensoren für Bodenfeuchte, Temperatur, UV, Licht, Relais usw. Andere Werte (z.B. Regen, Licht) hole ich mir von meinen Homematic Wettersensoren. Zur Zeit arbeite ich noch an einem Skriptteil mit dem das Ganze auch noch automatisch jede Pflanze einzeln bewässert und den Wasserverbrauch misst.

In ca. 4 Wochen ist alles fertig, dann stelle ich das Projekt gerne mal zur Diskussion. Der Python Code ist grausam, aber funktioniert :)
Eben Anfängercode, aber nur so lernt man. Ich bin mir sicher, dass dann die Profis von hier den Code (lol, fast 800 Zeilen bis jetzt) auf die Hälfte kürzen können :)

Gruß Heinz
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

In der Automatisierungstechnik setzt man normalerweise so etwas ein: https://w3.siemens.com/mcms/sensor-syst ... x-fta.aspx

Kommunikation erfolgt über Profibus und wenn man will, kann man einen OPC UA Server auf der S7-1500 starten.
Zusätzlich gibt es noch Diagnoseinformationen, Drahtbruchüberwachung etc. Für MQTT gibt es eine SCL-Library.

Jedenfalls sind die weniger anfällig, dafür aber teurer und die Entwicklersoftware muss man auch käuflich erwerben.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
eagleflight
User
Beiträge: 28
Registriert: Dienstag 12. Februar 2019, 19:45

Wir benutzen im Geschäft Beckhoff mit Codesys, EtherCat und redundante ATEX Sensoren. Das ist ein ganz andere Welt.
Und nebenbei, wir hatten auch schon HighTech Sensoren, die nur Mist abgeliefert haben :/ und über Sensoren und Programme, die nicht immer das tun was sie sollen braucht man nicht zu reden. Siehe Boing MAX8 :/

Nur, wie schon gesagt, im Bastelsektor unerschwinglich.
MQTT läuft total stabil, ist schnell und mit einem Raspberry im Preis unschlagbar. Sogar Rückmeldung bei Fehlern gibt es und, wenn man will, Verschlüsselung der Daten.

Raspberry ~€ 35,00
Nodemcu ~€ 3,95
Sensoren ~€ 3.50
dazu ein bisschen Draht und alte Steckernetzteile aus der Bastelkiste und viel Zeit bis man alles verstanden hat (...)
Dafür bekommt man bei den großen Herstellern noch nicht mal ein konfektioniertes CanBus Kabel :)

Und natürlich der Bastel und Programmierspaß (...) Dafür muss man leider in Kauf nehmen, dass die Rückgabewerte nur so "ungefähr" +- 5% sind und
beim loggen halt ab und an mal ein Wert verloren geht.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Ein RJ45 Stecker für Profinet kostet bei Siemens 50€. Die Software zum programmieren kostet so ca. 3000€.
Ich wünsche mir so etwas wie RobotDK für SPS.

Über teure technische Komponenten, die nicht so funktionieren wie sie sollten, könnte ich einen ganzen Roman schreiben.

Was mir gerade noch zu dem Raspberry Pi einfällt, ist folgendes: Der ändert seine Taktfrequenz. Möglicherweise hat das Einfluss auf die Ansteuerung. Nur so ein Gedanke.

PS: Basteln macht immer Spaß :-D
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Antworten