Forum: Mikrocontroller und Digitale Elektronik PySerial liest serielle Daten vom uC "meistens"


von Marvin (Gast)


Lesenswert?

Hallo,

ich habe etwas Probleme damit, Daten vom uC (Arduino UNO über USB-RS232) 
mit Pyserial (mit Python3) zu lesen.

Daten sind 4 Temperaturwerte, Format:
"23.45,24.65,18.00,12.36\r\n"

Der Arduino sendet die Daten vernünftig, das habe ich im Terminal 
überprüfen können.

Python-Code:
(Daten sollen eingelesen werden und werden dann mit APscheduler getaktet 
in eine SQL Datenbank geschrieben)
1
import time
2
import serial
3
import pymysql.cursors
4
import pymysql
5
from apscheduler.schedulers.background import BackgroundScheduler
6
7
sched=BackgroundScheduler()
8
9
10
11
ser = serial.Serial(
12
           port='/dev/ttyACM0',
13
           baudrate = 9600,
14
           parity=serial.PARITY_NONE,
15
           stopbits=serial.STOPBITS_ONE,
16
           bytesize=serial.EIGHTBITS,
17
           timeout=5)
18
connection = pymysql.connect(host='localhost',
19
                             user='db_user',
20
                             password='1234',
21
                             db='wetter',
22
                             charset='utf8mb4',
23
                             cursorclass=pymysql.cursors.DictCursor)
24
25
ser.flush()
26
ser.flushInput()
27
28
#######################################################################
29
def get_temp():
30
            with connection.cursor() as cursor:
31
32
                x=ser.readline()
33
                print(x)
34
                line=x.decode()
35
                line = ''.join(line.split()) #whitespaces abtrennen
36
                arr=line.split(',') #am komma trennen
37
                if len(arr)==4:
38
                    print('temp0: ',arr[0], 'temp1: ',arr[1], 'temp2: ',arr[2], 'temp3: ',arr[3])
39
                    sql = "INSERT INTO `data` (`temp0`, `temp1`, `temp2`, `temp3`) VALUES (%s, %s, %s, %s)"
40
                    cursor.execute(sql, (arr[0], arr[1], arr[2], arr[3], ))
41
                    connection.commit()
42
                else:
43
                    print('serial data error!')
44
45
46
#######################################################################
47
48
49
sched.add_job(get_temp, 'interval', seconds=60)
50
sched.start()

Eine beispielhafte Ausgabe:
b'25.50,25.00,27.00,24.44\r\n'
temp0:  25.50 temp1:  25.00 temp2:  27.00 temp3:  24.44
b'25.50,25.00,27.00,24.44\r\n'
temp0:  25.50 temp1:  25.00 temp2:  27.00 temp3:  24.44
b'25.50,25.00,27.00,24.44\r\n'
temp0:  25.50 temp1:  25.00 temp2:  27.00 temp3:  24.44
b'25.50,25.00,27.00,24.44\r\n'
temp0:  25.50 temp1:  25.00 temp2:  27.00 temp3:  24.44
b'25.50,25.00,27.00,24.44\r\n'
temp0:  25.50 temp1:  25.00 temp2:  27.00 temp3:  24.44
b'25.50,25\n'
serial data error!
b'25.50,24.50,26.69,24.12\r\n'
temp0:  25.50 temp1:  24.50 temp2:  26.69 temp3:  24.12
b'25.50,24.50,26.69,24.12\r\n'
temp0:  25.50 temp1:  24.50 temp2:  26.69 temp3:  24.12
b'2\r\n'
serial data error!
b'25.50,24.00,26.69,24.12\r\n'
temp0:  25.50 temp1:  24.00 temp2:  26.69 temp3:  24.12
b'25.50,24.50,26.69,24.12\r\n'
temp0:  25.50 temp1:  24.50 temp2:  26.69 temp3:  24.12
b'25.50,24.50,26.69,24.12\r\n'
temp0:  25.50 temp1:  24.50 temp2:  26.69 temp3:  24.12
b'25.50,24.50,26.69,24.12\r\n'

Ich denke, man kann das Problem ganz gut erkennen. Eine gute Lösung habe 
ich noch nicht gefunden.

Ich habe schon versucht vor dem Lesevorgang mit ser.flushInput() den 
Eingangspuffer zu leeren. Das hat die Fehlerrate aber eher 
verschlechtert.

Danke im Vorraus.
Marvin

von Felix U. (ubfx)


Lesenswert?

In welchen Intervallen werden die Daten gesendet? Könnte mir vorstellen, 
dass der Buffer zwischendurch vollläuft. Benutzt du den Standard Windows 
CDC Treiber oder einen von einem bestimmten Hersteller? Je nach Treiber 
gibt es keinen zirkulären Buffer für die seriellen Daten, sondern er 
läuft einfach voll, wenn du nicht schnell genug ausliest.

von Marvin (Gast)


Lesenswert?

Ein Datensatz kommt vom Arduino etwa im ein bis zwei Sekunden-Takt.
Das Python Programm läuft auf einem RaspberryPi (OS Raspbian)

von Felix U. (ubfx)


Lesenswert?

Hast du mal probiert, deine gettemp funktion in kürzeren Intervallen 
aufzurufen? In 60 Sekunden können 60 x 25 = 1500 byte auflaufen, das 
könnte auf einem embedded System schon die größe des buffers sein. 
Versuch einfach mal testweise ein Intervall von 1 Sekunde.

von Jim M. (turboj)


Lesenswert?

Wieso wird da überhaupt der Scheduler verwendet? Die ser.readline() 
Funktion müsste doch von sich aus blockieren?

von Marvin (Gast)


Lesenswert?

Ich hab jetzt mal testweise den Scheduler auf eine Sekunde gestellt. 
Bisher gab es damit noch keine Auffälligkeiten. Ich werde es so mal 
etwas laufen lassen.

Jim M. schrieb:
> Wieso wird da überhaupt der Scheduler verwendet? Die
> ser.readline()
> Funktion müsste doch von sich aus blockieren?

Ja, das tut sie, aber ich bekäme dann Messwerte im Takt von ETWA einer 
Sekunde. Ich will die Daten aber im Takt von genau 60 Sekunden in eine 
Datenbak schreiben.

Ich gehe mal davon aus, dass sich der Puffer an einer sinnvollen Stelle 
leeren lässt, damit ich das Messintervall wieder vergrößern kann.

von Felix U. (ubfx)


Lesenswert?

Marvin schrieb:
> Ja, das tut sie, aber ich bekäme dann Messwerte im Takt von ETWA einer
> Sekunde. Ich will die Daten aber im Takt von genau 60 Sekunden in eine
> Datenbak schreiben.
So wie es jetzt ist, ist es aber unvorhersehbar, welchen Messwert du 
kriegst. Du weißt nämlich nicht, wann den Puffer vollgelaufen ist bzw 
wie alt der Messwert, den du liest, ist. Wenn du einen aktuellen Wert 
alle 60 Sekunden willst musst du entweder die Werte in einer while 
Schleife so lesen wie sie reinkommen und dann bei jeder Iteration prüfen 
ob 60 Sekunden vergangen sind und dann den Wert in die DB schreiben. 
Oder du schedulest alle 60 sekunden einen Flush des input buffers und 
wartest dann auf die nächste line.

von Stm M. (stmfresser)


Lesenswert?

ich würde den timeout etwas klein setzen

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Felix U. schrieb:
> Wenn du einen aktuellen Wert alle 60 Sekunden willst musst du entweder
> die Werte in einer while Schleife so lesen wie sie reinkommen und dann
> bei jeder Iteration prüfen ob 60 Sekunden vergangen sind und dann den
> Wert in die DB schreiben.

Damit schreibst Du nur jeden 60sten Wert weg und 59 andere verwirfst Du. 
Wenn man schon jede Sekunde einen Wert bekommt, aber nur jede Minute 
einen Wert protokollieren will, würde ich die Messwerte immer für eine 
Minute mitteln und dann den Mittelwert in die DB schreiben. Dann haben 
die 60 erhaltenen Werte wenigstens einen Sinn.

von Felix U. (ubfx)


Lesenswert?

Frank M. schrieb:
> Damit schreibst Du nur jeden 60sten Wert weg und 59 andere verwirfst Du.

So scheint es vom Fragesteller ja gewollt zu sein, zumindest habe ich 
das aus dem urpsrünglichem Code entnommen.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Felix U. schrieb:
> So scheint es vom Fragesteller ja gewollt zu sein,

Mag ja sein, trotzdem sollte er die gesandten Daten nicht einfach 
ignorieren, sonst greift er willkürlich jede Minute irgendwelche im 
Empfangspuffer befindliche Daten ab.

von Tony (Gast)


Lesenswert?

Darf ich mal fragen, ob Du einen original Arduino Uno mit FTDI benutzt 
oder einen CH340?

Letzterer macht mit mit Chinaclones vermehrt Probleme, da der 
Linuxtreiber (auch auf dem Raspberry) nicht 100% kompatibel mit dem 
Chipsatz ist.

Probiere einfach mal Dein Pythonskript auf einem MAC oder Windows PC 
aus. Wenn Du dort Deine Daten korrekt eingelesen bekommst, dann hast Du 
den Übeltäter. Dann hast Du die Wahl zwischen Treiber oder Arduino mit 
FTDI

Grüße
Tony

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.