Anfänger - Problem mit Kodierung: UnicodeDecodeError

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

neovanmatix hat geschrieben:Ich vermute jetzt mal, "true" und "TRUE" sind nicht gleich "True"?
Warum vermutest du denn, statt es zu testen?

Code: Alles auswählen

In [1]: True
Out[1]: True

In [2]: true
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-2-74d9a83219ca> in <module>()
----> 1 true

NameError: name 'true' is not defined

In [3]: TRUE
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-1159184937f1> in <module>()
----> 1 TRUE

NameError: name 'TRUE' is not defined
Man kann natuerlich `true = TRUE = True` in jedes Modul schreiben .. aber bis dahin gibt es nur eine Schreibweise.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Insgesamt sieht es schon besser aus, aber einige Dinge finde ich immer noch nicht so glücklich. Du solltest bei der Funktion ``transfer_received_data`` besser auf die Command Query Separation achten. Wieso gibt die Funktion einen Booleschen Wert zurück? *transfer* ist doch eine Anweisung und deutet nicht darauf hin, dass da etwas zurück kommt. Zudem ist so ein Stil imho zu C-esque... in Python (und anderen ausdrucksstärkeren Sprachen als C) gibt es das Konzept der Exceptions, wenn wirklich etwas innerhalb so einer Funktion schief laufen sollte - da braucht man keinen Status-Rückgabewert, den Du letztlich implementiert hast.

Ich mag mich irren, aber für folgendes

Code: Alles auswählen

urltoopen = "{0}?r={1}&key={2}{3}".format(URL, RID, KEY, received_data)
bietet ``urllib`` iirc eine Methode an, bei der die Parameter einer URL gesammelt und dann automatisch korrekt an die URL hinzugefügt werden; inkl. der notwendigen Kodierungen von Sonderzeichen. Diese solltest Du imho auch nutzen. Der Name liest sich imho auch mit Underscores besser: ``url_to_open``. Ich würde die Generierung vermutlich sogar in einer Factory-Funktion "verstecken" und nicht "nackt" in der Funktion ``transfer_received_data`` stehen lassen. Wo ich so drüber nachdenke, würde ich das daraus sogar komplett entfernen und die *vollständige* URL an die Funktion übergeben:

Code: Alles auswählen

transfer_received_data(received_data, previous_received_data, build_url(RID, KEY))
Auf das wesentlich elegantere ``requests``-Modul hatten wir dich schon hingewiesen?

Für das Logging solltest Du Dir im nächsten Schritt wirklich mal das logging-Modul angucken.

Generell solltest Du Dir die Frage stellen, *wo* Du das Logging wirklich ansiedeln willst. So etwas wie das Loggen stellt quasi immer einen sogenannten Cross Cutting Concern dar und daher muss man sich fragen, wie man den im Code unterbringt. Speziell in der Funktion ``check_received_data`` ist es imho fraglich, ob wirklich darin geloggt werden sollte, oder nicht eher dort, wo das Ergebnis "interpretiert" wird.

Code: Alles auswählen

def check_received_data(received_data):
    return received_data[2].isdigit() and received_data[4] == 't'

# später
if check_received_data(...):
    # mache etwas
else:
    # logge den Fehler
Ich würde dazu tendieren, das Loggen immer auf einer möglichst hohen Ebene der Domäne einzubauen - sofern es nicht nur temporär zur Fehlersuche dient. Damit kann man Funktionen leichter wiederverwenden; denn nicht in allen Szenarien muss das Loggen gefragt sein.

Du solltest auf jeden Fall den Code auf Modulebene verschwinden lassen und hinter folgendem Pattern verbergen:

Code: Alles auswählen

def main():
    # hier alles, was zuvor auf Modulebene stand
    serialcom = serial.Serial('/dev/ttyACM0', 9600)
    serialdata_previous = ""
    for line in serialcom:
        serialdata = format_received_data(line)
    ...

if __name__ == "__main__":
    main()
Damit kannst Du Dein Modul auch in anderen Modulen importieren und Funktionen daraus nutzen, *ohne* dass Code ausgeführt wird.
Dies ist in Python so üblich und gebräuchlich, dass es sich lohnt, ein Template in seinem Editor zu erstellen, welches dieses beim Erstellen einer neuen, "leeren" Python-Datei gleich mit einbaut.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
neovanmatix
User
Beiträge: 19
Registriert: Samstag 28. Dezember 2013, 20:52

Hallo,

ich habe wieder einige Änderungen gemäß euren Vorschlägen implementiert:
- Logs werden über das logging-Modul erstellt
- Der URL-Aufruf erfolgt nun über das requests-Modul
- Das schreiben von Ereignissen in das Log habe ich auf die oberste Ebene verschoben, sodass die Funktionen selbst nicht mehr loggen - sondern geregelt über den Funktionsaufruf je nach Rückgabewert unter main()
- Das Prüfen, ob ein String "OK" ist, erfolgt nun detaillierter: Ich prüfe, ob bestimmte Zeichen einmalig im Datagramm vorkommen, dann zerlege ich es in Variable + Wert und prüfe, ob die Var ein Zeichen und die Value eine Zahl ist.

Dabei ist mir noch zwei Fragen gekommen:
- Ich habe eine check_received_data()-Funktion, die das Datagramm auseinandernimmt (splittet in var = value). Wie kann ich diese gesplitteten Daten am einfachsten als return-Wert zurückggeben, um sie weiterverarbeiten zu können? Als mehrdimensionales Array?
-> Aus diesem Grund nutze ich auch noch nicht die PARAMS-Funktion der requests-Lib; die möchte VAR+VALUE-Werte - ich habe da aktuell aber einen Mischmasch
- Ich prüfe, ob die Zeichen s, t und v in meinem Datagramm vorkommen mit:

Code: Alles auswählen

if _received_data.count("s") == 1 and _received_data.count("t") == 1 and _received_data.count("v") == 1:
Ich hätte das über reguläre Ausdrücke lösen können - was ich allerdings nicht wollte; gibt es eine elegantere, einfache Art, obiges zu verkürzen? Sollte ich das ggf. wieder in eine eigene Funktion auslagern?

Hier mal der aktuelle Stand:

Code: Alles auswählen

# Benoetigt: python3-serial

# Im cron eintragen, damit es bei jedem Boot/Reboot gestartet wird:
# @reboot python3 /home/pi/serialread/serialread.py &
#
# Empfängt Daten über seriellen Port und übergibt sie an ein PHP-Webscript, dass diese in eine SQL-DB schreibt

# Die empfangenen Daten haben folgendes Format: s=3&t=2000&h=7160&v=4635

# Die komplette URL, die das PHP-WebScript aufruft, sieht wie folgt aus:
# http://www.xxx.de/parse.php?r=1&key=xxx&s=3&t=2000&h=7160&v=4635

# r = Receiver-ID (dieser Empfänger), key = Secret zum Auth am PHP-Script
# s = Sender-ID, t = Temperatur, h = Luftfeuchtigkeit, v = Spannung Stromversorgung

import serial
import logging
import requests


RID = '1'                                               # ID des Receivers
URL = 'http://www.xxx/attiny/parse.php'
KEY = 'xxx'
LOGFILE = '/home/pi/serialread/log-serial.log'

def format_received_data(_rawdata):
    sdatatemp = _rawdata.decode("utf-8")
    sdata = sdatatemp[0:len(sdatatemp)-2]
    return sdata

def check_received_data(_received_data, _previous_received_data):
    if _received_data == _previous_received_data:
        return False
    else:
        if _received_data.count("s") == 1 and _received_data.count("t") == 1 and _received_data.count("v") == 1:
            received_data_splitted = _received_data.split("&")

            for data_value_pair in received_data_splitted:
                data_value_pair_splitted = data_value_pair.split("=")

                var = data_value_pair_splitted[0]
                value = data_value_pair_splitted[1]

                if not var.isalpha():
#                    print("{0} ist KEIN Zeichen".format(var))
                    return False

                if not value.isdigit():
#                    print("{0} ist KEINE Zahl".format(value))
                    return False
            return True

        else:
#            print("Ein erforderlicher Wert ist nicht in den empfangenen Daten enthalten!")
            return False

def transfer_received_data(_received_data, _RID, _URL, _KEY):
    url_to_open = "{0}?r={1}&key={2}&{3}".format(_URL, _RID, _KEY, _received_data)

    transfer_response = requests.get(url_to_open)
    return transfer_response.text.replace("<br>", " ")


def main():
    logging.basicConfig(filename=LOGFILE, level=logging.DEBUG, filemode='a', format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')

    # Requests loggt nur, wenn ein Fehler >= WARNING generiert wird
    requests_log = logging.getLogger("requests")
    requests_log.setLevel(logging.WARNING)

    open_serial_com = serial.Serial('/dev/ttyACM0', 9600)
    previous_received_serial_data = ""

    for dataline in open_serial_com:
        received_serial_data = format_received_data(dataline)

        if check_received_data(received_serial_data, previous_received_serial_data):
            logging.debug('Empfangene Daten haben ein gültiges Format: {0!r}'.format(received_serial_data))

            transfer_received_data_result = transfer_received_data(received_serial_data, RID, URL, KEY)
            if transfer_received_data_result is not False:
                logging.info('Daten wurden an Webscript übergeben: {0!r}, Status: {1!r}'.format(received_serial_data, transfer_received_data_result))
            else:
                logging.error('Fehler beim übergeben der Daten an das Webscript: {0!r}'.format(received_serial_data))

            previous_received_serial_data = received_serial_data
        else:
            logging.info('Empfangene Daten haben ein ungültiges Format oder wurden bereits empfangen: {0!r}'.format(received_serial_data))

if __name__ == "__main__":
    main()
BlackJack

@neovanmatix: Was sollen denn die ganzen führenden Unterstriche bei den Argumentnamen? Das ist äusserst ungewöhnlich, zumal einige Leute das als Konvention verwenden um *unbenutzte* Namen zu kennzeichnen.

Alternativer Test ob bestimmte Zeichen genau einmal in einer Zeichenkette vorkommen (`Counter` ist aus dem `collections`-Modul):

Code: Alles auswählen

histogram = Counter(received_data)
if all(histogram[character] == 1 for character in 'stv'):
`format_received_data()` ist eigentlich ein Zweizeiler:

Code: Alles auswählen

def format_received_data(rawdata):
    return rawdata.decode('utf-8')[:-2]
Für das aufsplitten des Parameterteils einer URL gibt es in der Standardbibliothek eine Funktion. Die habe ich weiter oben in diesem Thema schon mal benutzt.
neovanmatix
User
Beiträge: 19
Registriert: Samstag 28. Dezember 2013, 20:52

Hallo,

ich wollte mit den Underscore's die Variablen kennzeichnen, die lokal in der Funktion genutzt und übergeben worden sind - bei dem ganzen received_* kam ich durcheinander und wollte verhindern, dass ich doppelte Namen verwende (auch, wenn es ggf. garkeine Auswirkung wg. lokal/global gehabt hätte).

Deine Vorschläge binde ich dann morgen mal ein und melde mich wieder :)
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@neovanmatix: ich würde ja den String einfach parsen und danach testen, ob die nötigen Keys vorkommen:

Code: Alles auswählen

>>> from urllib.parse import parse_qs
>>> query = parse_qs('s=3&t=2000&h=7160&v=4635')
>>> set('svt').issubset(query)
True
Antworten