Den aktuellsten publizierten Datensatz oder Einzelwert eines bestimmten Datenkanals abrufen

Ich suche gerade eine Möglichkeit wie ein Microcontroller übers Netz an (nicht selbst gemessene) einzelne Sensordaten kommt, die bei uns auf swarm oder weather in der Datenbank liegen.

Das zugehörige Szenario (in Planung, noch nicht realisiert) aus dem ClimArt-Projekt: An einem Baum haben wir im Erdreich einen Sensor, der die Bodenfeuchte misst und per Funk an weather.hiveeyes.org schickt. Im Baum hängt ein Bewegungs-Sensor mit MP3-Ausgabe, aka TreeAware, der bei Mensch im Anflug losquatscht und zwar unterschiedliche Inhalte, je nach Bodenfeuchte: “Is aber trocken heute, bitte gießen” – “Alles paletti!” – “Überschwemmung, ich ertrinke” D.h. ich bräuchte den letzten Sensorwert der Bodenfeuchte in der Datenbank.

Die aktuell implementierte Datenexportschnittstelle wird bei Imkerliche Daten und Umweltdaten exportieren beschrieben, daran knüpfen sich folgende Überlegungen bzw. erweiterte Anforderungen:

Bei der aktuellen Lösung ist vermutlich JSON am ehesten geeignet von einem Microcontroller übers Netz heruntergeladen und ausgelesen zu werden, z.B. mit https://swarm.hiveeyes.org/api/hiveeyes/zku/paxcounter/bauschilderung/data.json?from=now-5m&to=now allerdings wird dann, je nach Intervall, mehr als ein Datensatz übertragen und man muss das empfangene JSON parsen. Ist das “the way to go” oder gibt es noch einfachere Möglichkeiten, etwa HTTP mit nur einem Rückgabewert? Oder ist das zu selbstgestrickt und “das macht man nicht”?

1 Like

Hi Clemens,

die kompaktesten Darstellungen werden momentan durch folgende Parameter geboten:

Deinen Wunsch für einen limit parameter, und unter Umständen einem zusätzlichen flatten oder project, um Dir pax oder soil-humidity als “allerneuesten Einzelwert” rauszugeben, realisiere ich gerne.

Unabhängig davon wäre aber unbedingt zu überlegen, MQTT zu verwenden? Das würde sich ja hervorragend anbieten. Endlich hätten wir die Gelegenheit, mal subscribe() auf einem Sensorknoten zu testen.

Viele Grüße,
Andreas.

1 Like

limit als Parameter würde z.B. nur einen Datensatz (den letzten) ausgeben? Was würde flatten oder project machen?

Parallel dazu stelle ich mir gerade die Frage, welche Bibliothek(en) (auf dem node) ich dafür verwenden würde. MQTT ist sicher eine Möglichkeit. Der Datenupload in meinem für hier recycelten OpenHive-sketch kann allerdings schon HTTP und kann auch Rückgabewerte entgegennehmen, daher wäre HTTP the first trial, ggf. mit einer Bibliothek, die das JSON-handling etwas erleichtert, falls wir das brauchen und keine Einzelwerte als Rückgabe bekommen.

Ich weiß, das ganze ist recht neu als Anforderung, da wir bisher nur Daten vom node zum Server geschickt haben und die andere Richtung noch nicht benötigt wurde. Durch die schönen Vorarbeiten von dir mit den CSV-Exporten, die mir das Debuggen schon oft sehr erleichtern (kommt überhaupt was an?, werden die Daten richtig in der DB gespeichert?) Ist ggf. eine Lösung gar nicht so weit entfernt.

limit würde die Anzahl der Ergebnisse beschränken, per Zählung statt per Zeitraum, wie bisher exklusiv möglich.

Die Anforderung ist super. Irgendwo hab ich mir das bestimmt schonmal notiert gehabt.

Mglw. ist auch pluck das richtige Wort dafür.

jq

Hier eine Implementierungvariante klassisch mit jq.

Demo

Mit diesem JSON Payload

$ jo pax=42
{"pax":42}

würde es jenes anstellen

$ jo pax=42 | jq .pax
42

so dass nur der skalare Wert rauskommt.

Live data

http "https://swarm.hiveeyes.org/api/hiveeyes/zku/paxcounter/bauschilderung/data.json?from=now-5m" \
  | jq 'sort_by(.time) | reverse | .[0].pax'
2

Verwendete Programme

https://github.com/jpmens/jo
GitHub - jqlang/jq: Command-line JSON processor
https://github.com/httpie/httpie

2 Likes

JMESPath

JMESPath is a query language for JSON.

Wir haben das Thema bei mqttwarn auch schon länger im Auge, siehe Improve accessing nested elements in JSON payloads · Issue #303 · mqtt-tools/mqttwarn · GitHub. Dort habe ich neulich über JMESPath berichtet, das sehr solide aussieht.

Apparently, it is involved in the Mars Helicopter mission in some way or another. :100:

Such- bzw. Transformationsausdruck

Select most recent pax measurement values from ESP32-Paxcounter devices recording data using the Kotori data historian.

reverse(sort_by([], &time)) | [0].pax

Das finde ich zu aufwändig, Dir das in Arduino/C++ anzutun.

Python Implementierung

Dies hier wäre ein Entwurf für eine Implementierung im Kotori, mit dem entsprechenden Python Paket https://github.com/jmespath/jmespath.py.

Habe gerade den im aktuellen Zeitraum bisher höchsten Wert gesampled. ;]

wget https://gist.github.com/amotl/21d7f8647920388358fc504ee819ce58/raw/example_jmespath_paxcounter_kotori.py
python example_jmespath_paxcounter_kotori.py "https://swarm.hiveeyes.org/api/hiveeyes/zku/paxcounter/bauschilderung/data.json?from=now-5m"
3.0

HTTP export API: Sortierung, Limit, und “Scalar” Transformation

Die Implementierung für Kotori schaut nun doch anders aus, an der entsprechenden Stelle läuft eh alles über pandas.

Kotori can do it natively now, using the new HTTP export API query parameters sort, direction, limit, and scalar.

[export] Improve export transformation capabilities by amotl · Pull Request #146 · daq-tools/kotori · GitHub

Beispiel

# Select most recent `pax` measurement values from ESP32-Paxcounter
# devices recording data using the Kotori data historian.
http "https://swarm.hiveeyes.org/api/hiveeyes/zku/paxcounter/bauschilderung/data.json?from=now-5m&sort=time&direction=desc&limit=1&scalar=pax"

https://swarm.hiveeyes.org/api/hiveeyes/zku/paxcounter/bauschilderung/data.json?from=now-5m&sort=time&direction=desc&limit=1&scalar=pax

2.0
1 Like

Mit Zeitstempel?

An dieser Stelle wäre vielleicht nett, wenn noch der Zeitstempel dabei stünde? Also so in etwa?

<time>,2.0

Oder lieber als weiteres HTTP Header Feld, und weiterhin der Wert Solo im Body?

Target-Time: <time>

2.0
1 Like

Cool, solo im body wäre für mich am leichtesten weiterzuverarbeiten. :)

Zeitstempel als Option könnte sinnvoll sein.

1 Like

Aber besteht auch die Möglichkeit, ggf. auf den Header zuzugreifen, um sich von dort den Zeitstempel zu holen, z.B. um “staleness” anzeigen zu können.

Ich frage nur prinzipiell, weil das offensichtlich bei Anwendungen wichtig ist, wo es eben wichtig ist. Daher würde ich das gerne gut realisiert sehen.

Ich stelle mir gerade folgendes Szenario vor. Ein “Sensor-node”, der die Daten liefert, ist seit Wochen ausgefallen. Nun wird per limit oder ähnlich der letzte Wert abgerufen, der natürlich veraltet ist. Der “Ausgabe-node” geht nun davon aus, dass es weil “last” aktuelle Daten sind. Vielleicht dafür auch so was möglich: Zeitraum – wie jetzt auch schon – eingrenzen und zusätzlich als Option limit=1 oder last. Wenn ich, für obigen Fall, nun die letzte Stunde angebe und nur einen Datensatz anfordern würde keine zurückgehen und damit auch nix veraltetes.

Ja. So sollte die Anfrage von Dir gestellt werden. Wie oben in meinem Beispiel zu sehen.

Hier das Beispiel für “staleness”, am gleichen Kanal. Zur Simulation einfach auf now-1s gehen. Du siehst, dass Du auf jeden Fall den HTTP Status Code auswerten solltest, den Body ergo nur bei Status=200 lesen und auswerten.

https://swarm.hiveeyes.org/api/hiveeyes/zku/paxcounter/bauschilderung/data.json?from=now-1s&sort=time&direction=desc&limit=1&scalar=pax

1 Like

Das ist ganz wunderbar! Mit den zusätzlich eingeführten Parametern sort und direction ist es nun auch möglich ganz einfach Maxima direction=desc und Minima direction=asc auszugeben!

Documentation for sort, direction, limit and scalar

  • sort specify a variable you like to sort by, e.g. sort=temperature
  • direction use direction=desc for sorting descending or direction=asc for ascending
    [direction=asc seems to be default in case you omit the direction parameter]
  • limit reduce the amout of outputed datasets by setting a number
  • scalar outputs only one “naked” value without any brackets or variable header. You have to specify the variable e.g. scalar=humidity. In case you are using scalar the option limit is automatically set to 1. [see discussion below for deleting]

Example

Time sorted descending (newest reading first):

https://swarm.hiveeyes.org/api/hiveeyes/zku/paxcounter/bauschilderung/data.json?from=2023-06-09T09:15&to=2023-06-09T09:24&sort=time&direction=desc
[
  {"time":"2023-06-09T09:23:39.746Z","ble":0.0,"pax":0.0,"timesync_seqno":null,"wifi":0.0}, 
  {"time":"2023-06-09T09:22:39.770Z","ble":0.0,"pax":1.0,"timesync_seqno":null,"wifi":1.0}, 
  {"time":"2023-06-09T09:21:39.766Z","ble":0.0,"pax":1.0,"timesync_seqno":null,"wifi":1.0},
  {"time":"2023-06-09T09:20:39.749Z","ble":0.0,"pax":0.0,"timesync_seqno":null,"wifi":0.0},
  {"time":"2023-06-09T09:19:39.768Z","ble":0.0,"pax":2.0,"timesync_seqno":null,"wifi":2.0},
  {"time":"2023-06-09T09:18:39.755Z","ble":0.0,"pax":1.0,"timesync_seqno":null,"wifi":1.0},
  {"time":"2023-06-09T09:17:39.750Z","ble":0.0,"pax":1.0,"timesync_seqno":null,"wifi":1.0},
  {"time":"2023-06-09T09:16:39.761Z","ble":0.0,"pax":1.0,"timesync_seqno":null,"wifi":1.0},
  {"time":"2023-06-09T09:15:39.768Z","ble":0.0,"pax":2.0,"timesync_seqno":null,"wifi":2.0}
]

Last (newest) datapoint in a time frame

First (oldest) datapoint in a time frame

Maximun in a time frame

Minimum a time frame

So scalar outputs always the first item in a given array.

Mit Zeitstempel?

Noch eine (zu späte?) Überlegung, weil ich gerade (wieder) gesehen habe, dass man Felder bei der Ausgabe auch excluden kann:

Wir könnten auch bei scalar default den timestamp mit ausgeben und bei &exclude=time nicht, sondern so wie bisher, passt aber eigentlich nicht ganz zusammen, da Spezialfall für scalar bei zusätzlicher Verwendung von exclude und da auch nur wenn time als Option angegeben wird.

2 Likes

:100:

Ja, damit geht jetzt einiges mehr.

Wow, danke für die Doku!

Könnten wir. Ob “einfach so”, oder “anderswie”, weiß ich auch nicht genau.

Ja, scalar= ist definitiv eine Spezialoperation, daher habe ich doch nicht die generischeren project, pluck, oder pick Operationen für die Namensgebung verwendet, weil diese meist auf Vektoren und Maps arbeiten, und solche auch zurückgeben – und eben keine Einzelwerte!

Momentan wertet scalar= noch keine Liste von Werten aus – in diesem Fall sind es die Spaltennamen. Vielleicht wäre das eine Möglichkeit.

Das stimmt nicht ganz, aber es läuft wohl auf das gleiche hinaus, ja.

Du kannst gerne das posting verbessern oder das korrekt(er) beschreiben, hatte beim Testen nur limit=2 in Verbindung mit scalar verwendet und in diesem Fall wird limit ignoriert ← besseres wording?

in case you are using scalar the option limit is ignored.

Ja, mach ich dann eh beim »ins Reine schreiben«, und versuche mein Bestes. Danke.

Streng gesehen stimmt das auch nicht. Es wird in der Tat angewendet, aber ändert nichts am Ergebnis – das ist der Unterschied zwischen Implementierung und beobachtetem Verhalten.

Das ist nun aber auch wirklich Haarspalterei – ich weiß nicht ob sich die Dokumentation dieses Verhaltens lohnt, ohne sich dabei zu versteigen. Ich würde den Teil einfach weglassen.

Dass Du alle Kombis durchprobieren würdest, und das garantiert auch andere tun werden – ist ja völlig in Ordnung. Aber ich würde nichts dokumentieren, was sich auch wieder ändern könnte.


xkcd: Workflow

3 Likes

… der Definition eines skalaren Datentyps folgend.

Im Endeffekt ist es ja auch nur so ohne weiteres gut am Arduino Client handhabbar. Sobald wir dort mehrere Werte stehen hätten, bräuchte man eine Formatkonvention, die mindestens das Trennzeichen beschreibt [1].

Daher bliebe uns nur, das anders zu benennen, wenn wir wirklich Zeit und Wert in einer Zeile haben wollen – das ginge aber vmtl. schon über include/exclude!? – oder aber wir stecken den eineindeutigen Zeitstempel bei scalar=anything stattdessen in den HTTP Header.


  1. Vielleicht böte sich eine Möglichkeit “CSV without Header” als praktikabelste Mikrovariante an? Also so, mit header=false: https://swarm.hiveeyes.org/api/hiveeyes/zku/paxcounter/bauschilderung/data.txt?from=now-5m&include=pax&limit=1&header=false. Das käme dem Client insofern entgegen, als dass er nicht auch noch Firstline-Skipping implementieren müsste, sondern nur ein einziges String().split() – das gäbe die Arduino API vielleicht easy her und wäre auch nicht übermäßig raumgreifend yet robust? ↩︎

Die aktuelle Implementierung ist die folgende. Das ist nur eine handvoll Code, er passt auf eine A4 Seite.

Kotori channel data export » DataFrame manipulation

Ich sehe dort auch gerade, dass limit=0 derzeit ebenfalls noch nicht DWIM macht. Gewünscht wäre wohl eher “gar keine Werte ausgeben, limit=0 eben”.

1 Like

Ich glaube das oben geht momentan nur auf swarm, können wir das auch auf weather implementieren, ich versuche gerade das abzurufen, es gibt aber keinen Einzelwert aus:

https://weather.hiveeyes.org/api/climart/zku/weatherstation/main/data.json?from=now-5h&to=now&sort=time&direction=desc&scalar=temp

Hi Clemens,

ich habe den Patch gerade ad hoc eingespielt. Mit diesem Link klappt es hoffentlich wie gewünscht.

https://weather.hiveeyes.org/api/climart/zku/weatherstation/main/data.json?sort=time&direction=desc&scalar=temp

Viele Grüße,
Andreas.