Abfrage von luftdaten.info mit Python

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
eagleflight
User
Beiträge: 28
Registriert: Dienstag 12. Februar 2019, 19:45

Der Noob lässt mal wieder grüßen :)
Diesmal mit einen JSON Problem

Über ein Nodemcu werden Daten eines Feinstaubsensors and die Website luftdaten.info übertragen und können dort mittels request wieder abgeholt werden. Die Daten werden als Json String bereitgestellt. Für die Daten gibt es zwei unterschiedliche Adressen. (siehe Skript).
Die Abfrage stammt aus dem Netz und wurde von mir angepasst.

Nachdem mein Skript seit ca. 3 Wochen permanent in einer 10 Min Schleife läuft, habe ich festgestellt, dass es öfter unregelmässig zu Falschauswertungen im Json.load kommt. Hier werden Werte vertauscht so daß der String nicht mehr sauber aufgelöst werden kann. Am Ende sind die Positionen von Temperatur und Humidity vertauscht. :/ Laut Betreiber der Website wäre der Json String immer gleich.

Gibt es eine Möglichkeit die Werte direkt aus den Json-String bzw. dem JSON Dictionary auszulesen und sich die Sucherrei im geparsten String zu ersparen ? Gestern lief es ohne Probleme, heute sind wieder die letzten Positionen vertauscht, damit kommt nur Müll raus :/.
Gibt es vielleicht so etwas wie einen Pointer o.ä. den man bei json.loads auf Anfang stellen muss ?

Hier der lauffähige Code Auszug mit den URL's.

Code: Alles auswählen


#!/usr/bin/python
# -*- coding: utf-8 -*-
# depends: python-requests
import time
import requests
import json

def pick_luftdaten_values(sensor):
    # Sensordaten für SDS011 und DHT11 abfragen
    # dazu die api von luftdaten.info nutzen
    
    r = requests.get(sensor)
    json_string = r.text
    parsed_json = json.loads(json_string)
 
    # print( json.dumps(parsed_json, sort_keys=True, indent=4, separators=(',',':')))
    
    l = len(parsed_json)-1
    a = len(parsed_json[l]['sensordatavalues'])
         
    if a == 1:
        result=(parsed_json[l]['sensordatavalues'][0]['value_type'])+": "+(parsed_json[l]['sensordatavalues'][0]['value'])
    if a == 2:
        result=(parsed_json[l]['sensordatavalues'][0]['value_type'])+": "+(parsed_json[l]['sensordatavalues'][0]['value'])
        result=result+" "+(parsed_json[l]['sensordatavalues'][1]['value_type'])+": "+(parsed_json[l]['sensordatavalues'][1]['value'])
    return(result)


#  Sensor Nummer 22451 und 22452
def luftdatenabfrage():
    try:
        url = 'http://api.luftdaten.info/static/v1/sensor/22451/'
        tweet = pick_luftdaten_values(url)
        
        #print(tweet)

        url = 'http://api.luftdaten.info/static/v1/sensor/22452/'
        tweet = tweet + " " + pick_luftdaten_values(url)
        
        #print(tweet)
        
        p1 = float(tweet[tweet.find('P1:') +3 : tweet.find("P2:")-1])
        p2 = float(tweet[tweet.find('P2:') +3 : tweet.find("humidity")-1])
        h1 = float(tweet[tweet.find('humidity:') + 10 : tweet.find("temperature")-1])
        t1 = float(tweet[tweet.find('temperature:') +13 : len(tweet)])               
        
    except:
        p1 = 0
        p2 = 0
        h1 = 0
        t1 = 0
        tweet = "no data available"
    
    finally:
        return(tweet, p1, p2, h1, t1)    
    

tweet, p1, p2, h1, t1 = luftdatenabfrage()

print(tweet)
print(p1)
print(p2)
print(h1)
print(t1)

Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@eagleflight: Könntest Du das Problem etwas genauer beschreiben? Was passiert genau und was erwartest Du stattdessen? Und warum verbastelst Du das erst so völlig unübersichtlich in eine Zeichenkette um danach dann wieder irgendwelche Werte aus dieser Zeichenkette heraus zu kratzen?

Falls die Elemente zu 'sensordatavalues' nicht immer die gleiche Reihenfolge haben sollten: Das macht ja nichts weil die durch den 'valuetype'-Wert ja identifiziert werden können.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
sparrow
User
Beiträge: 4164
Registriert: Freitag 17. April 2009, 10:28

Der Code schießt sich selbst in dem Fuß.
Ist das irgendwelcher umgebauter Beispielcode für einen Twitter-Client? Oder warum heißen da Variablen "tweet"?

Wenn doch schon json zurück kommt, dann sollte man das auch sauber verarbeiten.

Hier mal die Ausgabe der Website formatiert. Das hilft ja manchmal den Überblick zu behalten:

Code: Alles auswählen

[  
   {  
      "id":3166869294,
      "sensordatavalues":[  
         {  
            "value":"59.70",
            "id":6721847928,
            "value_type":"humidity"
         },
         {  
            "value":"9.50",
            "id":6721847927,
            "value_type":"temperature"
         }
      ],
      "timestamp":"2019-03-22 06:59:44",
      "sensor":{  
         "id":22452,
         "pin":"7",
         "sensor_type":{  
            "manufacturer":"various",
            "name":"DHT22",
            "id":9
         }
      },
      "sampling_rate":null,
      "location":{  
         "altitude":"135.2",
         "id":11393,
         "country":"DE",
         "longitude":"8.8600",
         "latitude":"49.9480"
      }
   },
   {  
      "id":3166883398,
      "sensordatavalues":[  
         {  
            "value":"60.30",
            "id":6721877603,
            "value_type":"humidity"
         },
         {  
            "value":"9.60",
            "id":6721877602,
            "value_type":"temperature"
         }
      ],
      "timestamp":"2019-03-22 07:02:11",
      "sensor":{  
         "id":22452,
         "pin":"7",
         "sensor_type":{  
            "manufacturer":"various",
            "name":"DHT22",
            "id":9
         }
      },
      "sampling_rate":null,
      "location":{  
         "altitude":"135.2",
         "id":11393,
         "country":"DE",
         "longitude":"8.8600",
         "latitude":"49.9480"
      }
   }
]
eagleflight
User
Beiträge: 28
Registriert: Dienstag 12. Februar 2019, 19:45

Guten Morgen
Es gibt 2 URL#s die abgefragt werden müssen. Einmal die Feinstaubwerte und zum Zweiten die Temperatur und Feuchtewerte. Die setzte ich beide zusammen und daraus entsteht folgender Json String aus dem die Sektionen "sensordatavalues - P1, P2" und "sensordatavalues - humidity und temperature" herausgeholt werden müssen. Aber irgendwie scheint sich bei meiner Version der Sting zu ändern, jedenfalls ging es gestern Abend nicht und heute früh kommt wieder das richtige Ergebnis :roll: Ich suche jetzt den Weg wie man direkt auf die Values zugreifen kann ohne diese Stringfummelei.

Code: Alles auswählen

[
    {
        "id":3166925901,
        "location":{
            "altitude":"135.2",
            "country":"DE",
            "id":11393,
            "latitude":"49.9480",
            "longitude":"8.8600"
        },
        "sampling_rate":null,
        "sensor":{
            "id":22451,
            "pin":"1",
            "sensor_type":{
                "id":14,
                "manufacturer":"Nova Fitness",
                "name":"SDS011"
            }
        },
        "sensordatavalues":[
            {
                "id":6721966975,
                "value":"27.80",
                "value_type":"P1"
            },
            {
                "id":6721966977,
                "value":"4.62",
                "value_type":"P2"
            }
        ],
        "timestamp":"2019-03-22 07:09:35"
    },
    {
        "id":3166940390,
        "location":{
            "altitude":"135.2",
            "country":"DE",
            "id":11393,
            "latitude":"49.9480",
            "longitude":"8.8600"
        },
        "sampling_rate":null,
        "sensor":{
            "id":22451,
            "pin":"1",
            "sensor_type":{
                "id":14,
                "manufacturer":"Nova Fitness",
                "name":"SDS011"
            }
        },
        "sensordatavalues":[
            {
                "id":6721997451,
                "value":"19.98",
                "value_type":"P1"
            },
            {
                "id":6721997452,
                "value":"3.72",
                "value_type":"P2"
            }
        ],
        "timestamp":"2019-03-22 07:12:07"
    }
]
[
    {
        "id":3166926200,
        "location":{
            "altitude":"135.2",
            "country":"DE",
            "id":11393,
            "latitude":"49.9480",
            "longitude":"8.8600"
        },
        "sampling_rate":null,
        "sensor":{
            "id":22452,
            "pin":"7",
            "sensor_type":{
                "id":9,
                "manufacturer":"various",
                "name":"DHT22"
            }
        },
        "sensordatavalues":[
            {
                "id":6721967612,
                "value":"57.60",
                "value_type":"humidity"
            },
            {
                "id":6721967611,
                "value":"10.80",
                "value_type":"temperature"
            }
        ],
        "timestamp":"2019-03-22 07:09:39"
    },
    {
        "id":3166940454,
        "location":{
            "altitude":"135.2",
            "country":"DE",
            "id":11393,
            "latitude":"49.9480",
            "longitude":"8.8600"
        },
        "sampling_rate":null,
        "sensor":{
            "id":22452,
            "pin":"7",
            "sensor_type":{
                "id":9,
                "manufacturer":"various",
                "name":"DHT22"
            }
        },
        "sensordatavalues":[
            {
                "id":6721997587,
                "value":"57.00",
                "value_type":"humidity"
            },
            {
                "id":6721997586,
                "value":"11.40",
                "value_type":"temperature"
            }
        ],
        "timestamp":"2019-03-22 07:12:08"
    }
]
P1: 19.98 P2: 3.72 humidity: 57.00 temperature: 11.40
19.98
3.72
57.0
11.4
eagleflight
User
Beiträge: 28
Registriert: Dienstag 12. Februar 2019, 19:45

Ja, der Code kommt von einer Website die einen Tweet für Twitter erzeugt. Ich habe den einfach übernommen, weil er auf Anhieb ein brauchbares Ergebnis geliefert hat. Jetzt, nach einiger Zeit stellt sich aber heraus, das wohl irgendwo ein Bug drinnen ist. Ab und zu sind am Ende des Strings die Werte temperature und humidity vertauscht.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Was heißt vertauscht? Ist nur die Ausgabe vertauscht oder sind die Werte selbst vertauscht? Interpretierst Du die Daten als String oder liest Du sie (hoffentlich) per json.loads ein?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@eagleflight: requests kann auch direkt json als Ergebnis liefern.
`l` und `a` sind schlechte Variablennamen. Wenn man das letzte Element einer Liste will, kann man auch mit negativen Indices arbeiten.
Es ist schlecht, innerhalbe eines Programms mit formatierten Strings zu arbeiten (wobei wenn es gebraucht wird format besser ist als Strings mit + zusammenzustückeln). Das einfachste wäre, ein Wörterbuch zu benutzen.
`return` ist keine Funktion, die Klammern also überflüssig.

Nakte `except` sollte man nicht benutzten, weil die auch viele Programmierfehler abfangen. Immer so konkret wie möglich die Exception angeben, und auch nur dann, wenn der Fehler sinnvoll behandelt werden kann. Eine Exception in einen String "no data available" umzuwandeln, ist z.B. hier nicht sinnvoll.

Wenn Du mit Wörterbüchern arbeitest, brauchst Du auch keine Stringsuchfunktionen.
Du benutzt Python2 (besser auf Python3 umsteigen!) und dort ist `print` keine Funktion, die Klammern gehören also weg.

Code: Alles auswählen

import requests

SENSOR_URL = "http://api.luftdaten.info/static/v1/sensor/{}"

def pick_luftdaten_values(sensor_id):
    # Sensordaten für SDS011 und DHT11 abfragen
    # dazu die api von luftdaten.info nutzen
    result = requests.get(SENSOR_URL.format(sensor_id))
    data = result.json()
    return {d['value_type']: d['value'] for d in data[-1]['sensordatavalues']}


#  Sensor Nummer 22451 und 22452
def luftdatenabfrage():
    sensor_p = pick_luftdaten_values(22451)
    sensor_h = pick_luftdaten_values(22452)
    return sensor_p['P1'], snesor_p['P2'], sensor_h['humidity'], sensor_h['temperature']

p1, p2, h1, t1 = luftdatenabfrage()
print p1
print p2
print h1
print t1
Benutzeravatar
sparrow
User
Beiträge: 4164
Registriert: Freitag 17. April 2009, 10:28

Wenn ich das richtig sehe, befindet sich in den Daten ja auch mehr als eine Messung.
Vielleicht wäre es auch sinnvoll zu schauen, ob die Daten bereits vorliegen.
eagleflight
User
Beiträge: 28
Registriert: Dienstag 12. Februar 2019, 19:45

@ Sirius

Du iritierst mich etwas mit Deinen Aussagen bezgl. print :shock:
Ich benutze sicher Python3 und bekomme einen Fehler, wenn ich print ohne Klammer benutze :?

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/pi/python/feinstaubsensor_abfragen_02.py", line 10
    print sys.version_info[0]
            ^
SyntaxError: Missing parentheses in call to 'print'
mit Klammer geht es und liefert brav Version 3
eagleflight
User
Beiträge: 28
Registriert: Dienstag 12. Februar 2019, 19:45

So geht es, Danke.
War noch ein fehlender "/" in der url und in sensor_h ein Buchstabendreher, also nichts tragisches.

Die Klammer für print brauche ich leider immer noch :/

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: utf-8 -*-
# depends: python-requests
import requests
import json

SENSOR_URL = "http://api.luftdaten.info/static/v1/sensor/{}/"

def pick_luftdaten_values(sensor_id):
    # Sensordaten für Luftdaten (p1/p2=Feinstaubwerte, DHT für Temp und Luftfeuchte) abfragen
    # dazu die api von luftdaten.info nutzen
    result = requests.get(SENSOR_URL.format(sensor_id))
    data = result.json()
    return {d['value_type']: d['value'] for d in data[-1]['sensordatavalues']}

#  Sensor Nummer 22451 und 22452
def luftdatenabfrage():
    sensor_p = pick_luftdaten_values(22451)
    sensor_h = pick_luftdaten_values(22452)
    return sensor_p['P1'], sensor_p['P2'], sensor_h['humidity'], sensor_h['temperature']

p1, p2, h1, t1 = luftdatenabfrage()
print (p1)
print (p2)
print (h1)
print (t1)
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich bin mir nicht sicher warum Sirius3 auf Python 2 kam, aber er scheint sich geirrt zu haben, womit die Klammer um das print auch voellig ok geht. Und er das ja eh empfohlen hat.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__deets__: Das scheint ein Raspi zu sein, und in der ersten Zeile steht ganz deutlich dass das mit Python 2 ausgeführt werden soll. Also sind entweder die Klammern bei `print` falsch oder die erste Zeile. Und auch die zweite Zeile deutet eher auf Python 2 hin, weil sie bei 3 überflüssig wäre.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

@__blackjack__ das mag drauf hindeuten, die erzwungene Klammern deuten in die andere Richtung und sind ein bisschen deutlicher beim deuten ;)
Und da encoding-tags ja etwas sind, dass aehnlich wie sudo und Salz reichlich verwandt wird in der Hoffnung es hilft etwas, ist auch das nicht aussagekraeftig.

Mir ging es ja nur darum dem TE klar zu machen, dass es kein Fehler ist, wenn er Klammern braucht.
Antworten