Shelly Pro 3EM auslesen

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
ickweeßnich
User
Beiträge: 4
Registriert: Montag 15. April 2024, 07:57

Hallo,
ich möchte mit einem Python-Script die Daten aus einem Shelly Pro 3EM auslesen. (Der Shelly Pro 3EM ist kleines, hinter dem Stromzähler einzubauendes Gerät, das über http und mqtt Daten wie z.B. Leistung, Spannung etc. dreiphasig über Wlan ausgibt.)

Gebe ich in meinen Browser ein:

Code: Alles auswählen

http://192.168.0.152/rpc/EM.GetStatus?id=0
Erhalte ich als Antwort:

Code: Alles auswählen

id	0
a_current	0.593
a_voltage	230.5
a_act_power	-75.4
a_aprt_power	136.9
a_pf	0.68
a_freq	50
b_current	11.608
b_voltage	228.5
b_act_power	2655.2
b_aprt_power	2656.6
b_pf	1
b_freq	50
c_current	0.058
c_voltage	232.1
c_act_power	2.1
c_aprt_power	13.5
c_pf	0.54
c_freq	50
n_current	null
total_current	12.259
total_act_power	2581.781
total_aprt_power	2806.935
user_calibrated_phase	[]
Hier kann ich sehen, dass mein Balkonkraftwerk gerade 75,4 Watt in Phase a einspeist (a_act_power -75,4), über Phase b gerade 2655,2 Watt verbraucht wird (b_act_power 2655.2); insgesamt ergibt sich ein Verbrauch von 2581,781 Watt (total_act_power 2581.781).

Diesen letzten Wert total_act_power möchte ich in einem Python-Script weiter verwenden.

Die Dokumentation des Herstellers, wie man das macht ist nicht vorhanden, bzw. unverständlich (für mich jedenfalls).
Ich habe deshalb in verschiedenen Foren gesucht, und in einem die Zeilen:

Code: Alles auswählen

# Nimmt Daten von der Shelly 3EM Rest-API und übersetzt sie in ein json-Format
    phase_a     = requests.get(f'http://{shelly_ip}/emeter/0', headers={'Content-Type': 'application/json'}).json()['power']

Das funktioniert mit einem "Shelly 3EM", aber nicht mit dem neueren "Shelly Pro 3EM".

Bei meinem Pro führt diese Zeile auf eine Fehlermeldung (400 - Seite nicht vorhanden)

Weiter als:

Code: Alles auswählen

    phase_a     = requests.get(f'http://{shelly_ip}/rpc/EM.GetStatus?id=0', headers={'a_current': 'application/json'})
    print (phase_a)
Die Antwort auf diese Zeile lautet:

<Response [200]>

komme ich aber nicht. Die Webseite ist zwar vorhanden, aber wie komme ich an die einzelnen Daten?

Kann mir da jemand weiter helfen?
Benutzeravatar
__blackjack__
User
Beiträge: 13227
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Da hilft die Dokumentation von `requests` weiter, da steht was so ein `Response`-Objekt an Attributen und Methoden hat.

Wie kommst Du auf den `headers`-Wert? Klar kann man irgendwelche Header erfinden, aber was soll das?

Edit: Es ist nicht so besonders sinnvoll das `Response`-Objekt `phase_a` zu nennen.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
ickweeßnich
User
Beiträge: 4
Registriert: Montag 15. April 2024, 07:57

@ _blackjack_

Erst mal vielen Dank für die spontane Antwort.

Ich habe bei selbstbau-pv.de ein Script gefunden, das genau das tut, was ich möchte: dass mein Balkonkraftwerk keinen Strom ins öffentliche Netz einspeist, wenn die Sonne es mal zu gut meint. Deshalb soll das Script dafür sorgen, dass in diesem Fall die Leistung des Wechselrichters reduziert wird.

Hier ist der Link zum Script:

https://selbstbau-pv.de/wiki/nulleinspe ... steuerung/

Ich bin überhaupt kein Programmierer, schon gar nicht für objektorientierte Sprachen. Ich kann mit den den Begriffen "headers", "Attributen" und "Methoden" zunächst mal nicht viel anfangen.
Vom Wechselrichter bekomme ich ja auch die Daten, lediglich vom Shelly nicht, weil ich einen etwas neueren Typ habe als der Autor des Scripts.
Die einfachste Lösung wäre, den älteren Typ zu verwenden, leider sind die Dinger nicht ganz billig und einbauen sollte sie ein zugelassener Elektriker. Das muss doch eleganter gehen.
Ich werde mal schauen, ob ich der Dokumentation zu "requests" was finde, das mich weiterbringt, aber ich fürchte, ich bin trotzdem auf Unterstützung angewiesen.
Sirius3
User
Beiträge: 17812
Registriert: Sonntag 21. Oktober 2012, 17:20

Beim ersten `phase_a` machst Du es ja fast richtig, wobei `phase_a` für `power` auch ein komischer Name ist.
Besser wäre:

Code: Alles auswählen

# Nimmt Daten von der Shelly 3EM Rest-API und übersetzt sie in ein json-Format
response = requests.get(f'http://{shelly_ip}/emeter/0', headers={'Content-Type': 'application/json'})
response.raise_for_status()
power = response.json()['power']
Das auf den zweiten Fall zu übertragen, sollte also nicht allzu kompliziert sein.
Benutzeravatar
__blackjack__
User
Beiträge: 13227
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ickweeßnich: Das Programm ist nicht so besonders toll, also eigentlich fehlerhaft, weil da bei Ausnahmen an zwei Stellen einfach weiter gemacht wird als hätte es kein Problem gegeben. Was entweder zu einem Folgefehler führt weil Namen undefiniert sind, oder der Rest der Schleife mit mindestens teilweise alten Werten durchgeführt wird.

Man schreibt auch keine nackten ``except:`` ohne konkrete Ausnahmen. Ausnahmen sind wenn man die Ausnahme wieder auslöst und sich an anderer Stelle sinnvoll darum kümmert, oder die Ausnahme komplett mit Traceback protokolliert, damit man eine Chance bei der Fehlersuche hat.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
ickweeßnich
User
Beiträge: 4
Registriert: Montag 15. April 2024, 07:57

Ok, nochmals vielen Dank an alle.
Mag ja sein, dass das Programm nicht wirklich toll ist und ungewöhnliche Variablennamen verwendet, es stört mich nicht besonders; ich weiß auch nicht, was den Autor veranlasst hat es so zu schreiben. Hauptsache, es tut was es soll.

Ich habe ein bisschen rumprobiert und bin auf die Lösung gestoßen, die Zeilen des Originals:

Code: Alles auswählen

        phase_a     = requests.get(f'http://{shelly_ip}/emeter/0', headers={'Content-Type': 'application/json'}).json()['power']
        phase_b     = requests.get(f'http://{shelly_ip}/emeter/1', headers={'Content-Type': 'application/json'}).json()['power']
        phase_c     = requests.get(f'http://{shelly_ip}/emeter/2', headers={'Content-Type': 'application/json'}).json()['power']
        grid_sum    = phase_a + phase_b + phase_c # Aktueller Bezug - rechnet alle Phasen zusammen

habe ich ersetzt durch:

Code: Alles auswählen

grid_sum = requests.get(f'http://{shelly_ip}/rpc/EM.GetStatus?id=0', headers={'Content-Type': 'application/json'}).json()['total_act_power']
Damit habe ich erst mal das erreicht, was ich wollte, um den Rest des Scriptes muss ich mich etwas später kümmern.

Vielen Dank für eure Mühe
Benutzeravatar
__blackjack__
User
Beiträge: 13227
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ickweeßnich: Es tut halt nicht das was es soll. Es macht bei Fehlern einfach weiter als wäre kein Fehler aufgetreten und arbeitet dann mit falschen Werten, was entweder zu Folgefehlern führt oder das mit den falschen Werten weitergemacht wird. Bist Du sicher, dass das keine grösseren Probleme nach sich ziehen kann, oder reicht Dir einfach das bisher noch nichts ausserhalb dessen was für Dich akzeptabel ist, passiert ist?
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
ickweeßnich
User
Beiträge: 4
Registriert: Montag 15. April 2024, 07:57

Du meinst die beiden excepts, die zu einer banalen Fehlermeldung führen.
Das Ganze ist Teil einer Endlosschleife, die alle ein oder zwei Sekunden durchlaufen wird. Wenn einmal ein Fehler auftritt, sollte der beim nächsten Durchlauf wahrscheinlich nicht gleich wieder auftreten. Das muss jetzt die Praxis zeigen, welche Fehler auftreten und wie häufig.
Der schlimmste "Schaden" der passieren kann, ist die Abschaltung des Wechselrichters, also keine Ernte von Solarstrom mehr. Das ist der Zustand, den wir alle vor der Existenz von Balkonkraftwerken hatten.
Ich wüsste auch gar nicht, wie ich auf Fehler reagieren sollte, ausser das Script neu zu starten. Das Script soll mal auf einem Raspi laufen. Kann man in Python so etwas wie einen watchdog-timer programmieren, der dann den Raspi rebootet? Wie gesagt bin ich kein Programmierer; ich fürchte, ich würde mit einer Fehlerbehandlungsroutine nur noch mehr Fehler einbauen...
Antworten