Seite 1 von 1

Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Sonntag 16. Juni 2024, 20:24
von Knollo
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

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Sonntag 16. Juni 2024, 20:43
von sparrow
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.

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Sonntag 16. Juni 2024, 20:59
von Knollo
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

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Mittwoch 19. Juni 2024, 22:11
von Knollo
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

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Mittwoch 19. Juni 2024, 22:33
von __blackjack__
@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.

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Mittwoch 19. Juni 2024, 22:42
von Knollo
Ups, - wer lesen kann :-)
jetzt läuft es...
Danke

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Mittwoch 19. Juni 2024, 22:53
von Sirius3
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()

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Mittwoch 19. Juni 2024, 23:01
von Knollo
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.

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Donnerstag 20. Juni 2024, 15:43
von DeaD_EyE
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)

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Donnerstag 20. Juni 2024, 16:14
von __blackjack__
@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.

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Donnerstag 20. Juni 2024, 18:23
von Sirius3
@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"]
        ]
    }

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Freitag 21. Juni 2024, 11:35
von DeaD_EyE
__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.

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Samstag 22. Juni 2024, 17:09
von Dennis89
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

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Samstag 22. Juni 2024, 17:34
von Sirius3
@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.

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Samstag 22. Juni 2024, 17:44
von Dennis89
Okay das habe ich verstanden, danke für die Erklärung.


Grüße
Dennis

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Samstag 22. Juni 2024, 17:51
von __blackjack__
@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. 😎

Re: Thingspeak Read Data mit thingspeak (Datum eingrenzen)

Verfasst: Samstag 22. Juni 2024, 19:42
von Dennis89
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