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?
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
Lieben Dank! Probiere ich die Tage mal aus...
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.
ufff... jetzt bin ich überfordert ::)
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);
Zitatufff... jetzt bin ich überfordert ::)
- Eine eigene 99 myUtils anlegen: https://wiki.fhem.de/wiki/99_myUtils_anlegen
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;- Eine at-Definition anlegen: https://wiki.fhem.de/wiki/At
define MeineAktion at +*02:02:00 {DevToJson("Devicename1","Devicename2","Devicename3")}- Freuen das alles copy/paste klappt...
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"...
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?
ZitatWo ist da mein (Denk-) Fehler?
Übergeben werden nur die Devicenamen.
Wie sieht die Funktion in deiner myUtils aus?
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?
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
Passt das Ergebnis, wenn Du hier (https://demo-fhem.cooltux.net/fhem?room=TubeHead) 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.
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)
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"}]
Ach so! Du meintest im Link selber? Ich bin davon ausgegangen, das Du meine FHEM-Instanz meinst :))
Und ja: Wenn ich das in Deine Instanz packe, erhalte ich selbiges Ergebnis
Weil die Mühe die ich mir in der Demoinstallation gemacht habe morgen weg sein wird, halt ich meinen weiteren, erratenen Ansatz hier mal fest:
defmod Tele2JSON at +*00:01:00 {DevToJson("/opt/fhem/fhemdevstates.json","TMP:state","HUM:state","wetter:wind","wetter:pressure")}
sub DevToJson {
my ($file, @specs) = @_; # 1. Argument = Dateiname, alle weiteren = "Device:Reading"-Angaben
my (@out, %idx); # @out = spätere JSON-Liste, %idx = Merkhilfe "Device -> zugehörige Hashref"
for (@specs) { # über jede Spec-Angabe iterieren, aktueller Wert steht in $_
my ($d,$r) = split(':',$_,2); # Spec am ersten Doppelpunkt trennen: $d = Device, $r = Reading (2 = max. 2 Teile)
push @out, ($idx{$d} = {device=>$d}) # neue Hashref anlegen, in %idx merken UND (da Zuweisung die Ref zurückgibt) an @out anhängen
if !$idx{$d}; # ... aber nur, wenn dieses Device noch kein Objekt hat
$idx{$d}{$r} = ReadingsVal($d,$r,'error'); # Reading auslesen (Default: error) und im Device-Objekt unter dem Reading-Namen ablegen
}
my $json = toJSON(\@out); # Array-Referenz von FHEM serialisieren lassen -> JSON-String (quotet alles als String)
$json =~ s/:"(-?\d+(?:\.\d+)?)"/:$1/g; # nachträglich die Quotes um reine Zahlenwerte entfernen; führender ":" trifft nur Werte, nie Schlüssel
FileWrite({FileName=>$file}, $json); # JSON-String in die Datei schreiben (überschreibt den bisherigen Inhalt)
}
Ok, vielen Dank für Deine Mühen.
Da ich von Perl keinen blassen Schimmer habe, kann ich dass auch nicht zu einem Ende bringen.
Ich werde es jetzt mal auf einem anderen Weg versuchen und vorher in einem Dummy alles zusammenstellen, was ich für die JSON benötige und dann das ursprüngliche Skript versuchen...
Scheint also, wie anfangs erwähnt, wohl doch nicht so trivial zu sein...
Das letzte Beispiel ist so gebaut, das man beliebig viele Devicename:Readings in der Funktion angeben kann.
Die Funktion selbst müsstest einfach nur copy+paste in der myUtils ergänzen. Keine Änderung und kein Perlwissen nötig.
Die Frage ob das Ergebnis passt oder nicht müsstest halt mal beantworten oder erklären wie deiner Anforderung nach der JSON genau aussehen soll.
Also dann funktioniert es nicht wie erwartet. Ich hatte das genau so kopiert, zur Sicherheit FHEM neu gestartet und in der GUI manuell "execNow" ausgelöst.
Die Datei wird geschrieben, aber außer einem eckigen Klammernpaar ist sie weiterhin leer...
Ich hatte sogar genau die defmod genommen, welche Du auch genutzt hast...
[{"device":"TMP","state":"29"},{"device":"HUM","state":"64"},{"device":"wetter","pressure":"1024","wind":"3.9"}]
So würde passen oder nicht?
ZitatIch hatte sogar genau die defmod genommen, welche Du auch genutzt hast...
Kann nicht sein.
Ich hab dir zum nachvollziehen alles nochmal in der Demoinstallation im Raum TubeHead "gebaut":
https://demo-fhem.cooltux.net/fhem?room=TubeHead
Wenn Du oben in der Befehlszeile
{qx(cat /opt/fhem/fhemdevstates.json)} eingibst dann kommt dieses JSON Array zurück:
[{"device":"TMP","state":39},{"device":"HUM","state":64},{"device":"wetter","pressure":1024,"wind":3.9}]
ZitatKann nicht sein.
... aber naja doch ;o) Wir "meinen" nur was anderes...
+*00:01:00 {DevToJson("/opt/fhem/fhemdevstates.json","TMP:state","HUM:state","wetter:wind","wetter:pressure")}Steht jetzt identisch zu Deiner Testinstallation auch so bei mir. DEF ist also bei Dir und mir identisch.
Das was in 99_myUtils.pm als Code steht, ist ebenfalls per C&P 1:1 identisch mit dem Code, welcher von Dir in Beitrag #16 geschrieben wurde.
{qx(cat /opt/fhem/fhemdevstates.json)}Das funktioniert bei Deiner Testinstallation, aber nicht bei mir. Ich nehme an, das ist OS-Abhängig?
Wie dem auch sei: Das Endergebnis, also der Inhalt der sehr wohl auf die Platte geschriebenen telemetry.json ist hals ein leeres "[]". Warum das bei Deiner Testinstallation funktioniert und bei mir nicht, ist mir ein Rätsel... Fehlt da bei mir irgend ein Paket unter Debian, auf welches Perl hier angewiesen ist?
AUA! Warte mal... ICH DOOF :o
Ich hatte die 99_myUtils.pm als 99:myUtils_Kopie.pm weggesichert. Offensichtlich nimmt FHEM die aber dennoch als gültige Datei; habe ich absolut nicht mit gerechnet ::)
Da habe ich jetzt mal ein .bak von gemacht und siehe da: NU GEIT DAT!!!
So... Die Datei wird auf jeden Fall geschrieben. Problem bleibt nur noch das (für mich) unbrauchbare Format.
Rauskommen soll da sowas wie hier:
{"TMP":25,"HUM":46,"pressure":1018,"wind":18}
Also immer Bezeichner:Wert
Kannst Du mal erklären/zeigen wofür der Json gebraucht wird?
... zur Bereitstellung von Telemetriedaten für MeshCOM (1) vie WebDesk-Server (2) ...
1: https://icssw.org
2: https://github.com/DH1FR/MeshcomWebDesk
Die Funktion in der Demoinstallation spuckt jetzt so einen Json aus wie das Ergebnis hier (https://github.com/DH1FR/MeshcomWebDesk/blob/master/docs/homeassistant-telemetry.md#generated-json-file-example).
Es gibt jetzt einen dritten (edit: optionalen) Teil in der Spec eines Parameters, um den Schlüssel im JSON frei zu benennen.
Cool! Probiere ich aus, sobald ich für meine Holde und mich Futter fertig hab', sonst bekomme ich was an die Ohren :o 8)
Perfekt!
Ich musste allerdings den Timestamp im Code auskommentieren. Der generiert eine Fehlermeldung. Ohne den klappt es aber wie RogerRabbit!
Vielen lieben Dank!
Die Demoinstallation wird jeden 1. im Monat wieder zurückgesetzt und alles ist wieder weg.
Offensichtlich können das auch andere mal brauchen, wäre toll wenn Du Deinen Stand irgendwo festhältst.
Ja, mache ich. Wenn Du nichts dagegen hast, veröffentliche ich das in meinem Blog und bei Git/WebDesk
In erster Linie hier im Forum oder Wiki, meinetwegen hier, wo sonst ist deine Sache.
Ok, im Wiki habe ich keinen Zugriff; wozu auch? Könnte ich eh nie was beitragen...
Dann eben hier. Mache ich nachher oder morgen als kleines HowTo fertig.
Mach einen neuen Thread auf mit aussagekräftigen Titel.
Keine Ahnung welcher Forumbereich der richtige wäre...
Oki... Ich suche mal, ob ich was passendes finde. Die Verbindung Amateurfunk und FHEM ist ja doch schon doch etwas selten...
https://forum.fhem.de/index.php?topic=145261.0