Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Knollo
User
Beiträge: 63
Registriert: Mittwoch 10. Juni 2020, 14:44

Hallo in die Runde, ich möchte Daten von Thingspeak abrufen. Die Abfrage soll ab einen bestimmten Datum erfolgen:

Code: Alles auswählen

import thingspeak

READ_ID = "2018608"
READ_KEY = "OTXA9V7CA8H6KAOE"

def readData():
    # initialize the connection to thingspeak
    channel = thingspeak.Channel(READ_ID, READ_KEY)
    response = channel.get()
    print(response)

if __name__ == "__main__":
    readData()
dabei wird aber gesamte Channel gelesen.

Code: Alles auswählen

    def get(self, options=None):
        """Get a channel feed.

        `get-a-channel-feed
        <https://mathworks.com/help/thingspeak/get-a-channel-feed.html>`_
        """
verweist auf mathworks, aber ich bekomme es leider nicht gebacken die Daten beispielsweise ab "2024-06-16 13:54:41" zu lesen.

Was läuft da falsch? Kann wer helfen?

Danke
Stefan
Benutzeravatar
sparrow
User
Beiträge: 4528
Registriert: Freitag 17. April 2009, 10:28

Das Modul ist seit 5 Jahren nicht angefasst. Da besteht der Verdacht, dass es tot ist.

Hier verweist die Dokumentation für "valide Parameter" hierher. Ähmliches hast du ja auch schon gefunden. Dann musst du dich jetzt wohl durch die Dokumentation dort wühlen.
Knollo
User
Beiträge: 63
Registriert: Mittwoch 10. Juni 2020, 14:44

Danke, ich werde es mit:

Code: Alles auswählen

import urllib.request, json
versuchen und die URL entsprechend der Thingspeak-Dokumentation für Download im JSON-Format zurechtbasteln.

Stefan
Knollo
User
Beiträge: 63
Registriert: Mittwoch 10. Juni 2020, 14:44

leider muss ich noch mal nachfragen:
Die URL:

Code: Alles auswählen

https://api.thingspeak.com/channels/2018608/feeds.json?start=2024-06-19 17:50:18
ruft alle Daten ab start=2024-06-19 17:50:18 ab - das läuft.

Sobald aber das Datum in das Script gepackt wird meckert der Intepreter:

Code: Alles auswählen

import urllib.request

strDateTime = "2024-06-19 17:50:18"
READ_ID = "2018608"

url = "https://api.thingspeak.com/channels/"+READ_ID+"/feeds.json?start=" + strDateTime
response = urllib.request.urlopen(url)
data = json.loads(response.read())
print(data)

Code: Alles auswählen

http.client.InvalidURL: URL can't contain control characters. '/channels/2018608/feeds.json?start=2024-06-19 17:50:18' (found at least ' ')

Wo werden da noch Steuerzeichen eingefügt? Wie kann ich das verhindern?

Danke - Stefan
Benutzeravatar
__blackjack__
User
Beiträge: 14002
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Knollo: Die URL funktioniert nicht. Da ist, wie die Fehlermeldung ja *deutlich* sagt, mindestens ein Leerzeichen drin. Leerzeichen sind nicht erlaubt in URLs. Du hast die URL vielleicht im Browser ausprobiert — der schickt die aber nicht 1:1 sondern ersetzt für Dich netterweise das Leerzeichen durch "%20". Verwende eine Bibliothek die das auch für Dich macht, oder mach es selbst.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Knollo
User
Beiträge: 63
Registriert: Mittwoch 10. Juni 2020, 14:44

Ups, - wer lesen kann :-)
jetzt läuft es...
Danke
Sirius3
User
Beiträge: 18253
Registriert: Sonntag 21. Oktober 2012, 17:20

Man stückelt keine Strings mit + zusammen, die Parameter müssen richtig kodiert werden.
Dafür gibt es passende Funktionen. Variablennamen schreibt man generell komplett klein und sie enthalten auch keine Datentypen im Namen, Konstanten schreibt man dagegen komplett groß.

Code: Alles auswählen

import urllib
READ_ID = 2018608
FEEDS_URL = f"https://api.thingspeak.com/channels/{READ_ID}/feeds.json"

def main():
    start_time = "2024-06-19 17:50:18"
    parameters = urllib.parse.urlencode({'start': start_time})
    url = f"{URL}?{parameters}"
    response = urllib.request.urlopen(url)
    data = json.loads(response.read())
    print(data)

if __name__ == "__main__":
    main()
Knollo
User
Beiträge: 63
Registriert: Mittwoch 10. Juni 2020, 14:44

Danke für den Hinweis :-), mit f"{} tue ich mich bischen schwer, aber es sieht optisch übersichtlicher aus...das liegt evtl. auch umständlichen Umgreifen auf der Tastatur...
ich hatte mal irgenwo einen "Knigge" zum guten Code schreiben gefunden, den Link muss ich wieder suchen und besuchen.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1224
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Hab mal wieder ein wenig zu viel gemacht.

Egal, vielleicht nützt es irgendjemanden:

Code: Alles auswählen

import copy
import datetime
import urllib.request
import urllib.parse
import json
import pprint
from decimal import Decimal


BASE_URL = "https://api.thingspeak.com/channels"
FEEDS = "feeds.json"


def get_channel(channel: int | str, since: datetime.datetime | None = None, days: int | None = None) -> dict[str, str]:
   if since and days:
       raise RuntimeError("The arguments `since` and `days` are mutually exclusive")

   if days:
       start = datetime.datetime.now() - datetime.timedelta(days=days)
   elif since:
       start = since
   else:
       raise ValueError("Neither `since` nor `days` are given.")

   channel = str(channel)
   url = "/".join((BASE_URL, channel, FEEDS)) + f"?start={start.isoformat()}"

   with urllib.request.urlopen(url) as fd:
       return json.load(fd)


def to_datetime(dt_str: str) -> datetime.datetime:
    return datetime.datetime.fromisoformat(dt_str.removesuffix("Z"))


def transform_data(data: dict[str, str]) -> dict[str: str | int | float | None]:
    # doing a deepcopy to prevent modification of the original dict given by the argument data
    data_copy = copy.deepcopy(data)
    channel = data_copy["channel"]
    feeds = data_copy["feeds"]

    channel["created_at"] = to_datetime(channel["created_at"])
    channel["updated_at"] = to_datetime(channel["updated_at"])
    channel["id"] = int(channel["id"])
    channel["longitude"] = Decimal(channel["longitude"])
    channel["latitude"] = Decimal(channel["latitude"])


    for index, feed in enumerate(feeds):
        current_feed = feeds[index]
        current_feed["created_at"] = to_datetime(current_feed["created_at"])
        current_feed["entry_id"] = int(current_feed["entry_id"])

        for field in current_feed:
            if field.startswith("field"):
                if current_feed[field] is None:
                    continue

                try:
                    current_feed[field] = int(current_feed[field])
                except ValueError:
                    try:
                        feeds[index][field] = float(feeds[index][field])
                    except ValueError:
                        continue


    return data_copy


result = get_channel(2018608, days=1)
transformed_result = transform_data(result)

# pprint.pprint(result)
pprint.pprint(transformed_result)
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
__blackjack__
User
Beiträge: 14002
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@DeaD_EyE: Die `transform_data()` verwirrt mich ein bisschen. In der ``for index, feed in enumerate(feeds)``-Schleife wird `feed` nirgends verwendet, aber `index` wird verwendet um den Wert von `feed` dann doch noch mal zu ermitteln. Und zwar nur dazu. Man könnte also das `enumerate()` weg lassen und tatsächlich `feed` verwenden. Und das nicht mal über `index` ermitteln und mal über das ebenso ermittelte `current_feed` ansprechen.

Also zum Beispiel so (ungetestet):

Code: Alles auswählen

def transform_data(data):
    #
    # doing a deepcopy to prevent modification of the original dict given by the
    # argument data
    #
    data_copy = copy.deepcopy(data)

    channel = data_copy["channel"]
    channel["created_at"] = to_datetime(channel["created_at"])
    channel["updated_at"] = to_datetime(channel["updated_at"])
    channel["id"] = int(channel["id"])
    channel["longitude"] = Decimal(channel["longitude"])
    channel["latitude"] = Decimal(channel["latitude"])

    for feed in data_copy["feeds"]:
        for name, value in feed.items():
            if name == "created_at":
                feed[name] = to_datetime(value)
            elif name == "entry_id":
                feed[name] = int(value)
            elif name.startswith("field"):
                try:
                    feed[name] = int(value)
                except ValueError:
                    try:
                        feed[name] = float(value)
                    except ValueError:
                        pass

    return data_copy
Das mit dem Komplettkopieren und verändern finde ich unschön.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Sirius3
User
Beiträge: 18253
Registriert: Sonntag 21. Oktober 2012, 17:20

@DeaD_EyE: Du hast `continue` nach dem Zufallsprinzip in Deine for-Schleife eingebaut? `continue` sollte man nach Möglichkeit vermeiden, in Deinem Fall sind alle überflüssig.
Und wieder einmal ein Beispiel, dass Typannotationen in den meisten Fällen nicht nur überflüssig sind, sondern einfach nur falsch und damit verwirrend.
Datenstrukturen sollte man nicht kopieren und ändern, sondern einfach eine neue anlegen.
Für die Konvertierung wäre eine eigene Funktion gut.

Code: Alles auswählen

def try_int_or_float(value):
    try:
        return int(value)
    except ValueError:
        try:
            return float(value)
        except ValueError:
            return value

def convert_types(data, types, default=lambda x:x):
    return {
        key: types.get(key, default)(value)
        for key, value in data
    }

def transform_data(data):
    return {
        "channel": convert_types(data["channel"], {
            "id": int,
            "longitude": Decimal,
            "latitude": Decimal,
            "created_at": to_datetime,
            "updated_at": to_datetime
        }),
        "feeds": [
            convert_types(feed, {
                "entry_id": int,
                "created_at": to_datetime,
            }, default=try_int_or_float)
            for feed in data["feeds"]
        ]
    }
Benutzeravatar
DeaD_EyE
User
Beiträge: 1224
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

__blackjack__ hat geschrieben: Donnerstag 20. Juni 2024, 16:14 @DeaD_EyE: Die `transform_data()` verwirrt mich ein bisschen. In der ``for index, feed in enumerate(feeds)``-Schleife wird `feed` nirgends verwendet, aber `index` wird verwendet um den Wert von `feed` dann doch noch mal zu ermitteln. Und zwar nur dazu. Man könnte also das `enumerate()` weg lassen und tatsächlich `feed` verwenden. Und das nicht mal über `index` ermitteln und mal über das ebenso ermittelte `current_feed` ansprechen.
Das wollte ich eigentlich auch tun. Ich hab das in 5 Minuten zwischen einer Spielpause geschrieben.

@DeaD_EyE: Du hast `continue` nach dem Zufallsprinzip in Deine for-Schleife eingebaut? `continue` sollte man nach Möglichkeit vermeiden, in Deinem Fall sind alle überflüssig.
Und wieder einmal ein Beispiel, dass Typannotationen in den meisten Fällen nicht nur überflüssig sind, sondern einfach nur falsch und damit verwirrend.
Datenstrukturen sollte man nicht kopieren und ändern, sondern einfach eine neue anlegen.
Für die Konvertierung wäre eine eigene Funktion gut.
Nein, habe ich nicht. Bei den Feldern ist manchmal eine None, welches keine Gleitkommazahl ist, ergo nicht konvertiert werden kann oder sollte, da None meistens ein Hinweis auf fehlende Werte ist. Diese z.B. einfach durch 0.0 zu ersetzen, wäre falsch. So sind die Daten.

Dass die Typen Annotationen falsch sind, habe ich nicht getestet. Bei den 5 Minuten war nicht viel Zeit. Aber ja, sie sind alle falsch. Juckt mich aber nicht die Bohne, da ich in der Regel solchen Code wegschmeiße.
Neu erstellen wäre auch besser als kopieren. Ist mir auch klar. Hätte ich auch so gemacht, wenn ich 6 Minuten Zeit gehabt hätte, dann aber ohne TypenAnnotationen. Falls ich sowas mache, dann teste ich immer mit MyPy.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
Dennis89
User
Beiträge: 1517
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

ich möchte mal kurz Zwischenfragen.
Sirius3 hat geschrieben: Donnerstag 20. Juni 2024, 18:23 Datenstrukturen sollte man nicht kopieren und ändern, sondern einfach eine neue anlegen.
In diesem Zusammenhang fällt mir ein Problem ein. Man iteriert zum Beispiel über eine Liste und will dann in der Schleife ein Objekt aus der Liste löschen. Dass das Probleme mit sich bringt verstehe ich.
Mich würde interessieren, wann wäre es denn okay eine Kopie zu erstellen? Was können denn für Probleme auftreten, bei der hier gezeigten Kopie und der Änderungen der Werte?

Ich frage deshalb, weil ich von einem Code bei uns in der Firma weis, der genau so aufgebaut ist. In diesem Fall wird eine Klassen-Instanz kopiert und dann Attribute geändert. Gemacht wird das, weil am Ende die Werte der "originalen" Instanz auch benötigt werden. Da der Code nicht von mir ist, werde ich mich da auch nicht einmischen, aber ich will für mich natürlich die Hintergründe verstehen um selbst nicht mal in Probleme zu laufen.


Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 18253
Registriert: Sonntag 21. Oktober 2012, 17:20

@Dennis89: das Problem sind weniger die Kopien, sondern das Verändern von Objekten. Weil man nie Objekte ändern sollte, weil man nicht weiß, ob das Objekt auch noch woanders benutzt wird.
Wenn man aber keine Funktionen hat, die Objekte verändern, sondern immer neue Objekte zurückgeben, dann braucht man auch keine Kopien anlegen.
Und nun die Frage, warum sollte ich eine Liste kopieren, um dann jedes Element durch ein verändertes Element zu ersetzen? Oder eine Liste kopieren, um danach einige Elemente wieder zu löschen?
Es ist viel einfacher zu verstehen, eine neue veränderte Liste zu erstellen.

Natürlich gibt es auch Ausnahmen: wenn man Probleme mit Speicher oder Geschwindigkeit bekommt.
Benutzeravatar
Dennis89
User
Beiträge: 1517
Registriert: Freitag 11. Dezember 2020, 15:13

Okay das habe ich verstanden, danke für die Erklärung.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 14002
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dennis89: Und auch das kopieren und dann ändern ist problematisch weil ausser in den einfachsten Fällen oft nicht so klar ist bis zu welcher Tiefe man kopieren muss. Nicht selten greifen die Leute dann um auf Nummer sicher zu gehen auf `copy.deepcopy()` zurück, womit dann der letzte Punkt von Sirius3 natürlich auch nicht mehr stimmt, der für das verändern statt neu anlegen spricht.

Wie ist denn der Code aufgebaut der Objekte kopiert und verändert? Würde das ausserhalb der Methode oder Funktion denn auffallen ob neue Objekte erstellt werden, oder vorhandene kopiert und verändert werden? Falls das ausserhalb nicht bemerkbar ist, könnte man das als Implementierungsdetail ansehen. Vielleicht um den Code zu vereinfachen. Weil beispielsweise nur wenige von vielen Attributen verändert werden, und auch nur unter bestimmten Umständen.

Falls `attrs` verwendet wird, sollte man für solche Fälle die `evolve()`-Funktion aus dem Modul kennen. 😎
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
Dennis89
User
Beiträge: 1517
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die weitere Erklärung.

`deepcopy()` wird so weit ich mich erinnern kann benutzt.
Sicher weis ich nur, das zwei Attribute verwendet werden und das kein `attrs` verwendet wird. Es wurde von einem Programmierer gemacht, also keine Hobby-Tippse wie ich, allerdings ohne größere Python-Erfahrung.
Ich bin mir über den Ablauf im Detail nicht mehr sicher. Weil das für mich kein flüssiger Ablauf ist bzw. das Programm hat so viele Klassen, teilweise nur um Werte zu speichern, da wird auch teilweise auf Objekte zugegriffen, die nicht expliziet übergeben worden sind. Ich benötige jedes mal, wenn es um das Programm geht, eine etwas längere Anlaufphase, bis ich durchsteige. Am Montag habe ich Zugriff darauf, dann schaue ich mir den Teil an. Es gibt auf jeden Fall, einen Fall und wenn der eintritt, dann wird kopiert.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten