[GET/POST zwischen ESP32 und Server]Wird async benötigt?

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
Benutzeravatar
Dennis89
User
Beiträge: 1684
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo zusammen,

ich arbeite gerade an einem Projekt und benötige eure Hilfe.
Ein ESP32 liest eine Position eines Ventil's aus, diesen Wert gibt er an einen Server. Der Server speichert den Wert in eine Datenbank und errechnet ein PWM-Signal, das er an den ESP32 sendet und dieser setzt an einem Pin dieses Signal.
Die Kommunikation, vom senden bis zum erhalten des PWM-Wert's soll innerhalb einer Sekunde geschehen.

Ich habe zwei Fragen: Mir ist nichts besseres eingefallen, als das auf dem Server beide `Queue`' s auf Modulebene definiert sind und dass ich das Rechnen und Schreiben in die Datenbank in einem extra Thread mache. Habt ihr eine bessere Idee?
Dann habe ich ein komisches Verhalten, zumindest kommt es mir komisch vor. Mein Testcode sendet jede Zentel Sekunde Werte an den Server und dieser fragt aber nur jede Sekunde die Queue ab und es gehen keine Daten verloren. Ich hätte erwartet, dass da Daten verloren gehen. In die andere Richtung, sende ich jede Sekunde an meinen ESP32, dieser fragt jede Zentel-Sekunde die Daten ab und es gehen hier Daten verloren.

Ich denke, dass das eventuell mit `async` gelöst werden könnte. Zum einen würde ich dieses Verhalten gern verstehen und zum anderen würde ich gerne wissen, wie man so eine Kommunikation richtig aufbaut. Die `sleep`'s in der Schleife, haben bis jetzt noch gar nichts mit der zeitlichen Anforderung zu tun. Das kommt im nächsten Schritt. Ich will erst verstehen, wieso sich das nicht so verhält, wie ich es erwartet habe. Aktuell läuft beides lokal auf einem Rechner, weil das angenehmer ist um den Code anzupassen. Später ist der ESP32 mit Ethernet verbunden.

Server:

Code: Alles auswählen

#!.venv/bin/python
from queue import Empty, Queue
from threading import Thread
from time import sleep

import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()
position = Queue(maxsize=1)
pwm = Queue(maxsize=1)


class Position(BaseModel):
    position: float


@app.post("/")
async def get_position(message: Position):
    response = message.position
    if position.full():
        _ = position.get_nowait()
    position.put_nowait(response)


@app.get("/get_pwm")
async def send_pwm():
    if pwm.empty():
        return {"pwm": 0}
    return {"pwm": pwm.get_nowait()}


class PressureInjection(Thread):
    def run(self):
        pwm_value = 1
        while True:
            if pwm.full():
                _ = pwm.get_nowait()
            pwm.put_nowait(pwm_value)
            try:
                print(f"Aus der Schleife: {position.get_nowait()}")
            except Empty:
                print("leer")
            sleep(1)
            pwm_value += 1


if __name__ == "__main__":
    pressure_injection = PressureInjection()
    pressure_injection.daemon = True
    pressure_injection.start()
    uvicorn.run(app, host="0.0.0.0", port=8000)
ESP32 (läuft aktuell auch lokal)

Code: Alles auswählen

import json
from time import sleep

import requests

SERVER_IP = "localhost"
SERVER_PORT = 8000
SERVER_ADDRESS = f"http://{SERVER_IP}:{SERVER_PORT}/"


def send_to_server(address, data):
    response = requests.post(address, data=data)
    response.close()


def get_from_server(address, target):
    return requests.get(f"{address}/{target}").json()


def main():
    position = 1
    while True:
        send_to_server(SERVER_ADDRESS, json.dumps({"position": position}))
        print(get_from_server(SERVER_ADDRESS, "/get_pwm"))
        position += 0.1
        sleep(0.1)


if __name__ == "__main__":
    main()
Wenn ich beide im Terminal start, dann kommt auf der Serverseite:

Code: Alles auswählen

leer
INFO:     Started server process [22084]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
leer
leer
leer
leer
INFO:     127.0.0.1:50810 - "POST / HTTP/1.1" 200 OK
Aus der Schleife: 1.0
leer
INFO:     127.0.0.1:50812 - "GET /get_pwm HTTP/1.1" 200 OK
leer
leer
INFO:     127.0.0.1:50814 - "POST / HTTP/1.1" 200 OK
Aus der Schleife: 1.1
leer
INFO:     127.0.0.1:50816 - "GET /get_pwm HTTP/1.1" 200 OK
leer
leer
INFO:     127.0.0.1:50818 - "POST / HTTP/1.1" 200 OK
Aus der Schleife: 1.2000000000000002
leer
INFO:     127.0.0.1:50821 - "GET /get_pwm HTTP/1.1" 200 OK
leer
leer
INFO:     127.0.0.1:50823 - "POST / HTTP/1.1" 200 OK
Aus der Schleife: 1.3000000000000003
leer
INFO:     127.0.0.1:50825 - "GET /get_pwm HTTP/1.1" 200 OK
leer
leer
INFO:     127.0.0.1:50827 - "POST / HTTP/1.1" 200 OK
Aus der Schleife: 1.4000000000000004
leer
INFO:     127.0.0.1:50829 - "GET /get_pwm HTTP/1.1" 200 OK
leer
leer
INFO:     127.0.0.1:50831 - "POST / HTTP/1.1" 200 OK
Aus der Schleife: 1.5000000000000004
leer
INFO:     127.0.0.1:50833 - "GET /get_pwm HTTP/1.1" 200 OK
leer
leer
leer
INFO:     127.0.0.1:50835 - "POST / HTTP/1.1" 200 OK
Aus der Schleife: 1.6000000000000005
leer
INFO:     127.0.0.1:50837 - "GET /get_pwm HTTP/1.1" 200 OK
leer
leer
INFO:     127.0.0.1:50839 - "POST / HTTP/1.1" 200 OK
Aus der Schleife: 1.7000000000000006
leer
INFO:     127.0.0.1:50850 - "GET /get_pwm HTTP/1.1" 200 OK
leer
leer
INFO:     Shutting down
INFO:     Waiting for application shutdown.
INFO:     Application shutdown complete.
INFO:     Finished server process [22084]
Und der simulierte ESP:

Code: Alles auswählen

{'pwm': 7}
{'pwm': 11}
{'pwm': 15}
{'pwm': 19}
{'pwm': 23}
{'pwm': 27}
{'pwm': 32}
{'pwm': 36}
Man sieht schön, dass am ESP Werte verloren gehen und Server alle ankommen.

Vielen Dank vorab.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
sparrow
User
Beiträge: 4635
Registriert: Freitag 17. April 2009, 10:28

Du schreibst, dass du an den ESP sendest. Der pullt doch? Da ist auch die Benennung der Funktion misleading.
Überhaupt ist deine Namensgebung sehr verwirrend. Du nennst den Inhalt eines eingehenden Requests Response.

Dann fällt auf, dass die Threads und async mischt.
Wenn du async verwendet möchtest, solltest du asyncio.Queue verwenden.
Und dein "Injection"-Thread sollte dann auch gar keiner sein.

Dann sind die ganzen Queue-Konstrukte nicht atomar. Du solltest versuchen mit der Queue zu arbeiten und darauf reagieren, wenn es eine Exception gibt. Du weißt wie das geht, denn du tust das in deinem "Injection"-Thread - aber nur um das print herum.

Und ansonsten spiegelt die Ausgabe am Server nicht das wieder, was du in deinem Text beschreibst.
Jedesmal, wenn dort "leer" oder "Aus der Schleife" steht, zählst du den Wert in pwm hoch. Und da das öfter passiert als deine Anfragen siehst du die Werte nicht.
Benutzeravatar
Dennis89
User
Beiträge: 1684
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo und danke für die Antwort.

Ich habe das angepasst, sprich `async` rausgeworfen und jetzt rennt das Ganze gefühlt 10 mal so schnell im Terminal durch. Ist jetzt zwar auf einem anderen Laptop und anderem OS, aber ich denke, dass die Mischung mit Thread und `async` die Ursache ist? Die Ausgabe von `pwm` verliert keine Werte mehr. Dafür zählt die andere Ausgabe nicht ordentlich hoch, sondern verliert Werte. Das ist jetzt so, wie ich es erwarten würde. Entweder habe ich mich missverständlich ausgedrückt oder verstehe deinen letzten Absatz nicht.
Die Ausgabe jetzt:

Code: Alles auswählen

leer
INFO:     Started server process [62080]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
leer
leer
INFO:     127.0.0.1:56248 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56264 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56270 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56272 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56278 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56282 - "GET /get_pwm HTTP/1.1" 200 OK
Aus der Schleife: 1.2000000000000002
INFO:     127.0.0.1:56290 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56302 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56306 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56322 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56330 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56346 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56356 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56360 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56366 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56376 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56386 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56402 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56414 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56424 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56436 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56450 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56458 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56472 - "GET /get_pwm HTTP/1.1" 200 OK
Aus der Schleife: 2.100000000000001
INFO:     127.0.0.1:56478 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56484 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56494 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56498 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56514 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56528 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56536 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56548 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56558 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56572 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56580 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56582 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56594 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56610 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56622 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56628 - "GET /get_pwm HTTP/1.1" 200 OK
Aus der Schleife: 2.9000000000000017
INFO:     127.0.0.1:56632 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56640 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56644 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56654 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56658 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56662 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56676 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56684 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56698 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56710 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56718 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56732 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56748 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56756 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56768 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56784 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56786 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56792 - "GET /get_pwm HTTP/1.1" 200 OK
Aus der Schleife: 3.8000000000000025
INFO:     127.0.0.1:56796 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56798 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56812 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56816 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56832 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56842 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56854 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56870 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56884 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56892 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56908 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56914 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56926 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56928 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56942 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56950 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56960 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56974 - "GET /get_pwm HTTP/1.1" 200 OK
Aus der Schleife: 4.7
INFO:     127.0.0.1:56980 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:56982 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:56994 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57008 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57022 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57024 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57036 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57038 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57054 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57056 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57066 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57078 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57090 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57092 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57106 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57120 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57124 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57128 - "GET /get_pwm HTTP/1.1" 200 OK
Aus der Schleife: 5.599999999999997
INFO:     127.0.0.1:57136 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57142 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57144 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57152 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57160 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57172 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57182 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57184 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57192 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57204 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57216 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57230 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57238 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57250 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57258 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57268 - "GET /get_pwm HTTP/1.1" 200 OK
INFO:     127.0.0.1:57272 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:57280 - "GET /get_pwm HTTP/1.1" 200 OK
Aus der Schleife: 6.499999999999994
Und:

Code: Alles auswählen

{'pwm': 3}
{'pwm': 0}
{'pwm': 0}
{'pwm': 4}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 5}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 6}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 7}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 8}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 9}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 0}
{'pwm': 10}
Die anderen Anmerkungen habe ich auch umgesetzt. Ja, das war falsch ausgedrückt von mit, der ESP pullt. Zu `response`, das habe ich so genannt, weil mein Gedanke war, dass das die Antwort auf meine Anfrage ist.
Zu `try`/`except` bei dem `Queue`'s: Ich hatte damit angefangen und dann gemerkt, dass ich damit in `get_Position` zwei mal `position.put_nowait(...)` schreiben müsste. Doppelter Code wollte ich nicht und bin daher auf `if` umgestiegen. War die Anmerkung von dir aus dem Grund, weil ich beides gemischt habe, weil `try`/`except` der übliche Weg ist, das zu machen oder gibt es noch einen weiteren Grund?
Gerade fällt mir noch ein, ob ich noch ein `Lock()` einbauen soll, aber an sich sollte meine Klasse immer die aktuellste Position bekommen und bei `pwm` sehe ich auch keinen Grund. Seht ihr einen?

Der geänderte Code:

Code: Alles auswählen

#!.venv/bin/python
from queue import Empty, Full, Queue
from threading import Thread
from time import sleep

import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()
position = Queue(maxsize=1)
pwm = Queue(maxsize=1)


class Position(BaseModel):
    position: float


@app.post("/")
def get_position(message: Position):
    try:
        position.put_nowait(message.position)
    except Full:
        _ = position.get_nowait()
        position.put_nowait(message.position)



@app.get("/get_pwm")
def send_pwm():
    try:
        return {"pwm": pwm.get_nowait()}
    except Empty:
        return {"pwm": 0}



class PressureInjection(Thread):
    def run(self):
        pwm_value = 1
        while True:
            try:
                pwm.put_nowait(pwm_value)
            except Full:
                _ = pwm.get_nowait()
                pwm.put_nowait(pwm_value)
            try:
                print(f"Aus der Schleife: {position.get_nowait()}")
            except Empty:
                print("leer")
            sleep(1)
            pwm_value += 1


if __name__ == "__main__":
    pressure_injection = PressureInjection()
    pressure_injection.daemon = True
    pressure_injection.start()
    uvicorn.run(app, host="0.0.0.0", port=8000)
und ESP:

Code: Alles auswählen

import json
from time import sleep

import requests

SERVER_IP = "localhost"
SERVER_PORT = 8000
SERVER_ADDRESS = f"http://{SERVER_IP}:{SERVER_PORT}/"


def send_to_server(address, data):
    request = requests.post(address, data=data)
    request.close()


def get_from_server(address, target):
    return requests.get(f"{address}/{target}").json()


def main():
    position = 1
    while True:
        send_to_server(SERVER_ADDRESS, json.dumps({"position": position}))
        print(get_from_server(SERVER_ADDRESS, "/get_pwm"))
        position += 0.1
        sleep(0.1)


if __name__ == "__main__":
    main()
Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten