Auslesen eines Smart Grid Hubs (Webserver)

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
HeiLuRa
User
Beiträge: 8
Registriert: Montag 9. Februar 2015, 18:14

Hallo zusammen,

ich bin neu hier im Forum, klar und warum meldet man sich hier? Man hat Probleme!
Vielleicht könnt Ihr mir helfen.

Zuerst möcht ich kurz mein Projekt beschreiben:
Meine PV-Anlage wird mit zwei dig.Zählern erfasst, auf denen ein sogn. Smat Grid Hub (Fa. efr) installiert ist. Bei den Hubs o. Webservern kann man die aktuellen Daten über ein JSON-Format auslesen. Mein RasPi soll diese Daten alle 15 sek. auslesen, analysieren und darstellen. (Später vielleicht PV-Strom-abhängig Verbraucher ein/ausschalten)

Ich arbeite mit Python 2.7.3 (Erfahrungen habe ich mit ANSI-C) und im allg. läuft das Script gut. Aber zwischendurch kommen Fehler. Zur Fehlerbehandlung sehe ich vor, den voherigen Wert einfach nochmal zurückzugeben. (Nicht elegant, aber beim 15-s-Takt kein Problem)
1.) urlError - Fehler: kann ich erkennen, aber beim nächsten Durchlauf kommt der Fehler wieder.
Kann man diesen Fehler reseten, löschen o.ä.?
2.) ValueError (sehr selten) läuft
3.) "error: [Errno 110] Connection timed out": diese Meldung habe ich noch nicht gelöst. Weder RuntimError noch socket.time erkennt diesen. Gibt es hier eine Lösung?

Nochmals zur Verdeutlichung, einer der Fehler taucht ca. einmal am Tag auf. Da das Script aber anhält (3) oder mit falschen Daten (1) weiterläuft, nervt dieses.

Hier habe ich ein Extrat des Scripts:

Code: Alles auswählen


import urllib2
import socket
import json
import time

def hub_fct(addr):
    global json_old1

    url = ('http://192.168.178.%d/json.txt?LogName=name&LogPSWD=1234' % (addr))
    req = urllib2.Request(url)
    try:
        response = urllib2.urlopen(req)
    except urllib2.URLError, e:
        if hasattr(e, 'reason'):
            print ('URLError(%d): %s' % (addr, e.reason))
        elif hasattr(e, 'code'):
            print ('URLError(%d): %s' % (addr, e.code))
        return json_old1
    except ValueError as e:
        print ('ValueError(%d): %d' % (addr, e.code))
        return json_old1
    except RuntimeError as e:
        print ('RuntimeError(%d): %d' % (addr, e.code))
    except socket.timeout:
        print('TimeOut(%d)')
        return json_old1
    else:
        json_text = response.read()
        json_old1 = json_text
        response.close()
    return json_text
    
i_ges = 4 * 60 * 24         #4 mal pro Std am Tag

while i<i_ges:

  #hier auf 0, 15, 30 u. 45 ste Sek. warten
  #Smart Grid Hubs aufrufen u. auslesen

   data0 = hub_fct(31)
   data1 = hub_fct(32)

 #Datenverarbeitung





Viele Dank jetzt schon mal.
Zuletzt geändert von HeiLuRa am Mittwoch 11. Februar 2015, 15:19, insgesamt 2-mal geändert.
BlackJack

@HeiLuRa: Du gibst ein wenig zu wenig Informationen über die Ausnahmen.

Ad 1.) Man kann keine ”Fehler resetten”. Wenn Du den mehrfach bekommst, dann tritt der halt mehrfach auf. Beseitige die Ursache.

Ad 3.) Welchen Typ hat die Ausnahme denn? Diesen Typ (oder eine Oberklasse davon) müsstest Du behandeln. Und wenn es speziell der Timeout sein soll und es dafür keinen Ausnahmetyp gibt, müsste man wahrscheinlich noch ein Attribut (`errno`!?) von diesem Ausnahmeobjekt prüfen.

`file` beim Codetag ist dazu da als Information einen Dateinamen anzugeben, nicht um damit bei Dir lokal auf dem Rechner liegende Dateien zu referenzieren. Da können *wir* nämlich nicht drauf zugreifen, auf Deinen Rechner. ;-)
HeiLuRa
User
Beiträge: 8
Registriert: Montag 9. Februar 2015, 18:14

@BlackJack,

Der Fehler mit dem "file" habe ich auch gesehe. Aber irgendwie stehe ich auf dem Schlauch. (vielleicht auch wegen meiner Grippe!)
Was soll ich machen, damit es klappt?

Ich bin ein "Wenig-Foren"-Nutzer, bei dem einem Forum, bei dem ich eine Datei braucht, konnte man sie anhängen. Hier?
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Mach ein paar Leerzeilen zwischen die Code-Tags und kopier deinen Quellcode per Hand da rein. :mrgreen: Willkommen im Forum & gute Besserung!
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
BlackJack

@HeiLuRa: Entweder den Quelltext in den Beitrag kopieren, zwischen die Code-Tags, oder in einem Pastebin oder ähnlichem ablegen und den Link in den Beitrag schreiben. Das Forum hat ein eigenes Pastebin. Link ist oben wo auch die Suche etc. ist. http://www.python-forum.de/pastebin.php
HeiLuRa
User
Beiträge: 8
Registriert: Montag 9. Februar 2015, 18:14

Dank jetzt hat es geklappt! (s.o.)

Ich hoffe, jetzt wird es deutlicher.
BlackJack

`urllib2.URLError()` hat *immer* ein `reason`-Attribut, dafür aber genau wie zum Beispiel `ValueError` *niemals* ein `code`-Attribut. Man sollte seinen Code insbesondere was die Fehlerbehandlung betrifft, testen. Denn sonst fällt der im Ernstfall erst im Fehlerfall auf die Nase, und zwar in einer Art und Weise die man so nicht haben wollte, denn sonst hätte man ja keine Fehlerbehandlung geschrieben.

Wobei die Behandlung hier auch nicht besonders gut ist, weil eigentlich in allen Fällen weniger Informationen ausgegeben werden als vorhanden wären. Ich würde hier das `logging`-Modul empfehlen und damit die Ausnahme(n) ausgeben lassen. Dann muss man auch nicht so viele explizit angeben. Andererseits könnte man die Ausnahmebehandlung in dieser Funktion komplett sein lassen und die ausserhalb machen. Dann kann man sich auch das unschöne ``global`` sparen wenn man *ausserhalb* der Funktion den letzten Wert noch mal verwendet.

Der Code ist sowieso nicht flächendeckend ”überwacht”. Beim `read()`-Aufruf kann ja zum Beispiel auch ein `IOError` oder ein `socket.timeout` auftreten.
HeiLuRa
User
Beiträge: 8
Registriert: Montag 9. Februar 2015, 18:14

Danke BlackJack,
ich werde mich mit dem 'logging'-Modul beschäftigen.

'read()' habe garnicht mehr gesehen... :oops:
thomas.sc
User
Beiträge: 4
Registriert: Sonntag 22. Februar 2015, 13:05

Hallo,

ich habe nun auch so einen SmartGridHub und versuche ihn auszulesen.

Ich bin noch nicht soweit und bin bisher auch etwas anders herangegangen.
Hier mein Code:

Code: Alles auswählen

import urllib.request
wp = urllib.request ("http://192.168.178.%d/json.txt?LogName=name&LogPSWD=1234")
pw = wp.read()
print(pw) 
Hier werden dann die Daten auch ausgegeben, nun möchte ich sie gerne weiterbearbeiten und komme nicht weiter :-(
Ich habe dann Versucht:

Code: Alles auswählen

Leistung = wp[331:357]
und bekomme dann leider den Fehler:
TypeError: 'HTTPResponse' object is not subscriptable

wie kann ich die Variable pw in eine Stringvariable ändern?

Ich habe schon versucht mit

Code: Alles auswählen

daten = str(wp)
bekomme dann allerdings
<http.client.HTTPResponse objet at 0x0000000003A159E8>

und nicht die Daten.

Wie muss die richtige Typumwandlung aussehen?

Viele Grüße

thomas.sc
HeiLuRa
User
Beiträge: 8
Registriert: Montag 9. Februar 2015, 18:14

Hallo thomas.sc,

nach dem Auslesen musst Du erst in utf8 encodieren. Danach hast Du ein JSON-Format. Aus diesem Format kannst Du Dir dann die Werte ziehen. (s. Anleitung vom SmatGridHub).
Mein Routine:

Code: Alles auswählen


def zaehler(arg, txt, l_old):             #txt = daten aus Hub
    length = len(txt)
    if (arg == 31):
        if (length < (l_old - 10)):
            err_txt = ('Länge(%d): %d, %d' % (arg, length, l_old))
            print err_txt
            save_error(err_txt)
            return data_old1

    pos1 = txt.find('[', 0, length)         #Länge zw. den ersten []
    pos2 = txt.find(']', 0, length)
    pos3 = txt.find('[', pos2, length)      #zw. den zweiten [] stehen die WErte
    pos4 = txt.find(']', pos3, length)
    data = json.loads(txt[pos3:pos4+1])     #interessanter Teil
    return data;
txt ist json_text aus der eigentl. Ausleseroutine.

Hoffentlich hilft es weiter.
Hast Du eigentlich auch Probleme mit dem Auslesen? Bei mir stimmt sproradisch die Länge nicht, daher bekommen ich dadurch Probleme.

Güße
Lutz
HeiLuRa
User
Beiträge: 8
Registriert: Montag 9. Februar 2015, 18:14

Hier noch die Zeile zum Encodieren:

text = json_txt.decode('utf8')
BlackJack

@HeiLuRa: Zum *En*kodieren verwendet man `*de*code()`? ;-)
thomas.sc
User
Beiträge: 4
Registriert: Sonntag 22. Februar 2015, 13:05

Hallo HeiLuRa,

ich habe es mit dem hiermit probiert:

Code: Alles auswählen

pw = pw.encode('utf-8')
print(pw)
und bekomme dann die Meldung:
AttributeError: 'bytes`object has no attribute 'encode'

Mit Deinem Code habe ich es auch probiert, da scheinen einige Variablen nicht definiert zu sein, z.B. i.

Viele Grüße

Thomas.sc
BlackJack

@thomas.sc: Vom Webserver kommen Bytes, die muss man zu einer Zeichenkette *de*kodieren.

Dein gezeigter Code kann nicht funktionieren weil `urllib.request` ein Modul ist und Module kann man nicht aufrufen.

Dann hast Du da `wp` was laut Fehlermeldung ein `HTTPResponse`-Objekt ist, von dem Du mit `read()` Bytes liest und an den Namen `pw` bindest. Und dann versuchst Du eine Zeichenkettenoperation auf dem ersten Objekt. Schönes Beispiel warum man vernünftige Namen verwenden sollte und auf keinen Fall nichtssagende Abkürzungen als Namen die in zwei aufeinanderfolgenden Zeilen definiert werden man es aber trotzdem schafft prompt den falschen weiterzuverwenden.

Wobei es ziemlich unsinnig ist, und das geht an beide Fragesteller, ein serialisiertes JSON-Dokument mit Zeichenkettenoperationen zu bearbeiten, also Slicing und `find()` auf Klammern, statt das Dokument zu parsen (parsen zu lassen) und dann wie vorgesehen zu verwenden. Feste Indexwerte oder das Zählen von Klammern (und dann auch noch mit `find()` und ohne auf -1 zu prüfen) sind prima Wege sich in den Fuss zu schiessen wenn das Ergebnis mal nicht so aussieht wie man das erwartet hat. Da verwendet das Gerät anscheinend schon ein strukturiertes Standarddatenformat und ihr murkst da auf Zeichenkettenebene drauf herum. m)
HeiLuRa
User
Beiträge: 8
Registriert: Montag 9. Februar 2015, 18:14

Hallo BlackJack,

vielen Dank für den Hinweis.
Ich habe gleich meine find()-Routine geändert. Jetzt "finde" ich die entsprechenden Werte wesentlich eleganter.
Der Code sieht jetzt so aus, und funktioniert sehr gut (@thomas.sc)

Code: Alles auswählen


import urllib2
import json

url = 'http://192.168.178.31/json.txt?LogName=user&LogPSWD=user'
req = urllib2.Request(url)
response = urllib2.urlopen(req)
json_text = response.read()
response.close()
j_text = json_text.decode('utf8')
data = json.loads(j_text)
Wert = data['billingData:']['values'][2]['value']
print Wert

Vielen Dank nochmal.
Lutz
thomas.sc
User
Beiträge: 4
Registriert: Sonntag 22. Februar 2015, 13:05

Hallo BlackJack,
hallo Lutz,

vielen Dank für die Unterstützung und die Hinweise/Hilfe.

@Lutz das ist echt genial, ich habe den Code mal angepasst, damit kann ich jetzt weitermachen:

Code: Alles auswählen

import urllib2
import json
 
url = 'http://192.168.178.31/json.txt?LogName=name&LogPSWD=1234'
req = urllib2.Request(url)
response = urllib2.urlopen(req)
json_text = response.read()
response.close()
j_text = json_text.decode('utf8')
data = json.loads(j_text)
T0 = data['billingData:']['values'][0]['value']
T1 = data['billingData:']['values'][1]['value']
Gesamtleistung = data['billingData:']['values'][2]['value']
LeistungL1 = data['billingData:']['values'][3]['value']
LeistungL2 = data['billingData:']['values'][4]['value']
LeistungL3 = data['billingData:']['values'][5]['value']
SpannungL1 = data['billingData:']['values'][6]['value']
SpannungL2 = data['billingData:']['values'][7]['value']
SpannungL3 = data['billingData:']['values'][8]['value']
Frequenz = data['billingData:']['values'][9]['value']
FrequenzUnit = data['billingData:']['values'][9]['unit']

Kundennummer = data['billingData:']['assignment'][4]['value']
Zeitangabe = data['billingData:']['assignment'][5]['value']
print("Arbeit:")
print(T0)
print(T1)
print("Leistung:")
print(Gesamtleistung)
print(LeistungL1)
print(LeistungL2)
print(LeistungL3)
print("Spannung:")
print(SpannungL1)
print(SpannungL2)
print(SpannungL3)
print("Frequenz:")
print(Frequenz)
print(FrequenzUnit)
print(Kundennummer)
print(Zeitangabe)
Viele Grüße

Thomas
BlackJack

@thomas.sc: Also für meinen Geschmack werden dort viel zu oft gleiche Teilausdrücke ausgewertet und zu viele magische Indexwerte verwendet.
thomas.sc
User
Beiträge: 4
Registriert: Sonntag 22. Februar 2015, 13:05

Hallo BlackJack,

die Daten werden vom dem SmartGrid Hub, der an den Stromzähler ansgeschlossen ist im json-Format bereitgestellt. Um diese Daten "auswerten" zu können müssen Sie zyklisch abgerufen werden, dann in eine Datenbank geschrieben um daraus dann eine Verbrauchsgrafik (Lastprofil) zu erstellen. Welche Werte nun genau weiterverarbeitet werden, will ich mal sehen. Grundsätzlich ist der Erste Schritt, das abrufen und aufbereiten der Daten, erfolgreich abgeschlossen.

Viele Grüße

Thomas
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Das meinte BlackJack nicht. Das Problem an deinem Code ist, dass da sehr viel identischer Kram drin steht, dass solltest du irgendwie zusammenfassen. Sei es nun durch einen Zwischenschritt oder durch eine Funktion. Die magischen Indexwerte beziehen sich auf die Indizes, welche da so frei in deinem Code schwirren. Da weißt du in wenigen Tagen selbst nicht mehr so genau, was die denn nun Bedeuten. Statt der "3" im Index bei "LeistungL1" solltest du dir also einen vernünftigen Namen für die drei einfallen lassen und dann daraus eine Konstante machen. Dann verrät dir der Name der Konstante auch noch in einem Jahr, was du da eigentlich gemeint hast.
Das Leben ist wie ein Tennisball.
BlackJack

Ergänzend zu EyDu: Ich habe den hoffentlich berechtigten Verdacht das man da gar keine festen Indexerte verwenden muss sondern das in den Wörterbüchern zu den einzelnen Werten nicht nur der 'value' steht, sondern auch was der bedeutet. Die Einheit für den jeweiligen Wert scheint da ja auch gespeichert zu sein. Sich am ”magischen” Index zu orientieren erscheint mehr wenig robust zu sein.
Antworten