JSON Datensatz in FHEM zusammenstellen und in Datei schreiben?

Begonnen von TubeHead, 03 Juli 2026, 15:56:08

Vorheriges Thema - Nächstes Thema

TubeHead

Hallo in die Runde,

(ich habe es mal unter "Sonstiges" abgelegt, da ich nicht genau wusste, wohin ich es packen soll. Nötigenfalls bitte verschieben...)

Für das Lesen von JSON Daten aus Webseiten u.ä. gibt es ja das Modul "JsonMod".
Aber gibt es auch eine Möglichkeit die ganze Sache umgedreht zu gestalten?

Also z.B. ein DOIF mit Zeitsteuerung, welches periodisch die Daten verschiedenster Geräte einsammelt und dann "JSON-Key:value" zusammenbastelt und in eine physische Datei schreibt?


TomLee

Hallo,

so in etwa und das Ergebnis dann mit FileWrite in eine Datei schreiben:

my @devices = ("Devicename1", "Devicename2", "Devicename3");
my @result;

foreach my $name (@devices) {
    next unless defined($defs{$name});

    push @result, {
        device   => $name,
        status   => ReadingsVal($name, "state", "unknown"),
        occupied => ReadingsVal($name, "occupied", "0"),
        battery  => ReadingsVal($name, "battery", undef),
    };
}

my $json = encode_json(\@result);

Braucht man gar kein DOIF dazu, geht mit einem einfachen at.

Gruss Thomas

TubeHead


rudolfkoenig

encode_json benoetigt "use JSON", was zwar viele Module laden, aber bei weitem nicht alle.

Eine Alternative dazu ist toJSON, was im fhem.pl implementiert ist, z.Bsp. als toJSON($defs{WEB}), oder als toJSON(@result) aus dem obigen Beispiel.

TubeHead


betateilchen

Zitat von: TubeHead am 03 Juli 2026, 17:04:50ufff... jetzt bin ich überfordert  ::)

Warum? Einfach in der Zeile

my $json = encode_json(\@result);
den Funktionsaufruf ersetzen, wie von Rudi vorgeschlagen:

my $json = toJSON(@result);
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

TomLee

#6
Zitatufff... jetzt bin ich überfordert  ::)


Kann dann so aussehen:

##############################################
# $Id: myUtilsTemplate.pm 21509 2020-03-25 11:20:51Z rudolfkoenig $
#
# Save this file as 99_myUtils.pm, and create your own functions in the new
# file. They are then available in every Perl expression.

package main;
use strict;
use warnings;

sub
myUtils_Initialize($$)
{
  my ($hash) = @_;
}

# Enter you functions below _this_ line.


sub DevToJson {
    my @devices = @_;                      # Übergebene Devicenamen aus @_ in ein eigenes Array kopieren
    my @result;                            # Leeres Array anlegen, das später die Hashrefs enthält
    for my $name (@devices) {              # Über jeden übergebenen Devicenamen iterieren
        next if !defined($defs{$name});    # Überspringen, falls das Device in FHEM nicht existiert (kein Eintrag in %defs)
        push @result, {                    # Neue Hashref für dieses Device ans @result-Array anhängen
            device   => $name,             # Devicename ergänzen
            status   => ReadingsVal($name, 'status', 'error')  # Reading "status" auslesen, Default "error" falls nicht vorhanden
        };
    }
    FileWrite("/opt/fhem/fhemdevstates.json", toJSON(\@result));  # JSON direkt erzeugen und in die Datei schreiben (überschreibt den Inhalt)
}
1;


define MeineAktion at +*02:02:00 {DevToJson("Devicename1","Devicename2","Devicename3")}
  • Freuen das alles copy/paste klappt...

TubeHead

Zitat von: TomLee am 04 Juli 2026, 11:25:46Freuen das alles copy/paste klappt...

Das wäre natürlich mehr als optimal  8)  O:-)

Sorry wegen der späten Rückmeldung. Ich bin noch nicht dazu gekommen, da einer unserer Hunde massive gesundheitliche Probleme hat und unters Messer musste. Und da die Tiere zur Familie gehören und Familie immer an erster Stelle steht, auch wenn das vorher s.g. "Wanderpokale" waren, mit denen niemand fertig geworden ist, ändert es nichts daran. Da habe ich dann für anderes keine Kopf...
Mit etwas Glück kann ich den Hund aber heute Abend wieder nach Hause holen und komme dann die Tage mal zum Testen, ggf. sogar zum "Verstehen"...



TubeHead

So... Endlich mal dazu gekommen. (Notiz an mich: Weniger offene Baustellen ^^)

Die Funktion scheint genau das zu tun, was sie soll. Sie schreibt zumindest die angegebene Datei ins FHEM-Root.
Nur leider enthält die Date lediglich ein eckiges Klammerpaar [] ohne Inhalt  :(

Da ich ein DOIF'ler und kein AT'ler bin, bin ich mir nicht sicher, ob ich das "at" korrekt definiert habe.

Hier mal der Trum, wie er momentan ausschaut. Zu Testzwecken erstmal auf Minuten-Intervall gesetzt. Geschickter wäre es m.E. später, die Datei bei einem Event eines der eingetragenen Geräte neu zu schreiben... Aber erst mal muss es überhaupt funktionieren...

defmod Tele2JSON at +*00:01:00 {DevToJson("TMP:state","HUM:state","CreateTeleRain:a","windrichtung:dir","wetter:wind","wetter:pressure")}
attr Tele2JSON group MQTT
attr Tele2JSON room 980 - Umwelt
#   CFGFN      /opt/fhem/_INC/980_Global_Umwelt.cfg
#   COMMAND    {DevToJson("TMP:state","HUM:state","CreateTeleRain:a","windrichtung:dir","wetter:wind","wetter:pressure")}
#   DEF        +*00:01:00 {DevToJson("TMP:state","HUM:state","CreateTeleRain:a","windrichtung:dir","wetter:wind","wetter:pressure")}
#   FUUID      6a6c82cc-f33f-7e68-f712-fab2a21e0c86f464
#   NAME       Tele2JSON
#   NR         1219
#   NTM        13:33:09
#   PERIODIC   yes
#   RELATIVE   yes
#   REP        -1
#   STATE      Next: 13:33:09
#   TIMESPEC   00:01:00
#   TRIGGERTIME 1785497589.74015
#   TRIGGERTIME_FMT 2026-07-31 13:33:09
#   TYPE       at
#   eventCount 17
#   READINGS:
#     2026-07-31 13:32:09   state           Next: 13:33:09
#
setstate Tele2JSON Next: 13:33:09
setstate Tele2JSON 2026-07-31 13:32:09 state Next: 13:33:09


Wo ist da mein (Denk-) Fehler?


TomLee

ZitatWo ist da mein (Denk-) Fehler?

Übergeben werden nur die Devicenamen.

Wie sieht die Funktion in deiner myUtils aus?

TubeHead

#10
ZitatÜbergeben werden nur die Devicenamen.
Öhhh... Und wie weiß die Funktion dann, welche Daten ich im JSON aus dem device haben möchte? Ich denke da nur an das Device Wetter (Proplant) mit seinen extrem vielen Readings...

EDIT:
Habe mal NUR die Gerätenamen gelassen. Dann erhalte ich im JSON folgendes:
[{"device":"TMP","status":"error"},{"device":"HUM","status":"error"},{"device":"CreateTeleRain","status":"error"},{"device":"windrichtung","status":"error"},{"device":"wetter","status":"error"}]

Hier die MyUtils:

package main;
use strict;
use warnings;

sub
myUtils_Initialize($$)
{
  my ($hash) = @_;
}

# Enter you functions below _this_ line.

#########################################################
##  Wandelt die Kompassdaten der Wetterstation
##  in Klartext Himmelsrichtung um
#########################################################
sub myCompass($) {
  my ($azimuth) = @_;

  my $compassPoint = "-?-";

  if ($azimuth      < 11.25) {
    $compassPoint = "_N_";
  } elsif ($azimuth < 33.75)  {
    $compassPoint = "NNO";
  } elsif ($azimuth < 56.25) {
    $compassPoint = "N_O";
  } elsif ($azimuth < 78.75)  {
    $compassPoint = "ONO";
  } elsif ($azimuth < 101.25){
    $compassPoint = "_O_";
  } elsif ($azimuth < 123.75)  {
    $compassPoint = "OSO";
  } elsif ($azimuth < 146.25){
    $compassPoint = "S_O";
  } elsif ($azimuth < 168.75)  {
    $compassPoint = "SSO";
  } elsif ($azimuth < 191.25){
    $compassPoint = "_S_";
  } elsif ($azimuth < 213.75)  {
    $compassPoint = "SSW";
  } elsif ($azimuth < 236.25){
    $compassPoint = "S_W";
  } elsif ($azimuth < 258.75)  {
    $compassPoint = "WSW";
  } elsif ($azimuth < 281.25){
    $compassPoint = "_W_";
  } elsif ($azimuth < 303.75)  {
    $compassPoint = "WNW";
  } elsif ($azimuth < 326.25){
    $compassPoint = "N_W";
  } elsif ($azimuth <= 348.75)  {
    $compassPoint = "NNW";
  } elsif ($azimuth <= 361)  {
    $compassPoint = "_N_";
  }
  return $compassPoint;
}
1;


# JSON für Telemetrie zusammenbauen
sub DevToJson {
    my @devices = @_;                      # Übergebene Devicenamen aus @_ in ein eigenes Array kopieren
    my @result;                            # Leeres Array anlegen, das später die Hashrefs enthält
    for my $name (@devices) {              # Über jeden übergebenen Devicenamen iterieren
        next if !defined($defs{$name});    # Überspringen, falls das Device in FHEM nicht existiert (kein Eintrag in %defs)
        push @result, {                    # Neue Hashref für dieses Device ans @result-Array anhängen
            device  => $name,            # Devicename ergänzen
            status  => ReadingsVal($name, 'status', 'error')  # Reading "status" auslesen, Default "error" falls nicht vorhanden
        };
    }
    FileWrite("/opt/fhem/telemetry.json", toJSON(\@result));  # JSON direkt erzeugen und in die Datei schreiben (überschreibt den Inhalt)
}
1;

Noch eine Frage zum Verständnis: Für was ist die "1" am Ende der Funktion gut?

TomLee

ZitatÖhhh... Und wie weiß die Funktion dann, welche Daten ich im JSON aus dem device haben möchte? Ich denke da nur an das Device Wetter (Proplant) mit seinen extrem vielen Readings...

Das war oben halt noch nicht klar, ich hab mit dem Beispiel einfach unterstellt das es Readings sein sollen die in jedem Device vorkommen.

Ich überleg mir was, wenn sich kein anderer meldet.


ZitatNoch eine Frage zum Verständnis: Für was ist die "1" am Ende der Funktion gut?

https://perldoc.perl.org/functions/require

TomLee

Passt das Ergebnis, wenn Du hier oben in die Befehlszeile folgendes eingibst:

{qx(cat /opt/fhem/fhemdevstates.json)}
Wenn ja, aufpassen das der Dateipfad jetzt als Parameter mitgegeben wird in dem at.

TubeHead

Wenn ich Deine Befehlszeile so wie sie ist in Kommandozeile von FHEM kopiere und absende, passiert schlicht ... nichts ...

Ist das evtl. OS-abhängig? Fhem selbst läuft auf einem Debian, gewerkelt wird aber von einem WIN10 aus, CHrome-basierter Browser (Vivaldi)

TomLee

Wenn ich auf den Link klicke und dort oben in der Befehlszeile den cat Befehl eingebe, dann wird mir in FHEMWEB folgendes ausgegeben:


[{"device":"TMP","state":"29"},{"device":"HUM","state":"64"},{"device":"wetter","pressure":"1024","wind":"3.9"}]