From 264256bc186c6a52702f618e77c8d3cb8762804b Mon Sep 17 00:00:00 2001 From: dev0 <> Date: Wed, 15 Mar 2017 11:58:42 +0000 Subject: [PATCH] 98_expandJSON.pm: Initial check in git-svn-id: https://svn.fhem.de/fhem/trunk@13708 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/98_expandJSON.pm | 354 +++++++++++++++++++++++++++++++++++++ fhem/MAINTAINER.txt | 1 + 3 files changed, 356 insertions(+) create mode 100644 fhem/FHEM/98_expandJSON.pm diff --git a/fhem/CHANGED b/fhem/CHANGED index 7ef69a783..7518a3b55 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - new: 98_expandJSON: initial check in - update: 98_DOIFtools: added hints to the derived operands - update: 98_Text2Speech: some small improvements special Audiodevice "none" is now "default due diff --git a/fhem/FHEM/98_expandJSON.pm b/fhem/FHEM/98_expandJSON.pm new file mode 100644 index 000000000..43823bc84 --- /dev/null +++ b/fhem/FHEM/98_expandJSON.pm @@ -0,0 +1,354 @@ +# $Id$ +################################################################################ +# +# Copyright (c) 2017 dev0 +# +# This script is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# This copyright notice MUST APPEAR in all copies of the script! +# +################################################################################ + +my $module_version = "1.10"; + +package main; + +use strict; +use warnings; +use POSIX; + +sub expandJSON_expand($$$$;$$); # Forum #66761 + +sub expandJSON_Initialize($$) { + my ($hash) = @_; + $hash->{DefFn} = "expandJSON_Define"; + $hash->{NotifyFn} = "expandJSON_Notify"; + $hash->{AttrFn} = "expandJSON_Attr"; + $hash->{AttrList} = "addStateEvent:1,0 " + . "addReadingsPrefix:1,0 " + . "disable:1,0 " + . "disabledForIntervals " + . "do_not_notify:1,0"; +} + + +sub expandJSON_Define(@) { + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + my $usg = "\nUse 'define expandJSON " + . " []"; + return "Wrong syntax: $usg" if(int(@a) < 3); + return "ERROR: Perl module JSON is not installed" + if (expandJSON_isPmInstalled($hash,"JSON")); + + my $name = $a[0]; + my $type = $a[1]; + + # source regexp + my $re = $a[2]; + return "Bad regexp: starting with *" if($re =~ m/^\*/); + eval { "test" =~ m/^$re$/ }; + return "Bad regexp $re: $@" if($@); + + $hash->{s_regexp} = $re; + InternalTimer(gettimeofday(), sub(){notifyRegexpChanged($hash, $re);}, $hash); + + # dest regexp + if (defined $a[3]) { + $re = $a[3]; + return "Bad regexp: starting with *" if($re =~ m/^\*/); + eval { "test" =~ m/^$re$/ }; + return "Bad regexp $re: $@" if($@); + $hash->{t_regexp} = $re; + } + else { + $hash->{t_regexp} = ".*"; + } + + my $doTrigger = ($name !~ m/^$re$/); # Forum #34516 + readingsSingleUpdate($hash, "state", "active", $doTrigger); + $hash->{version} = $module_version; + + return undef; +} + + +sub expandJSON_Attr($$) { + my ($cmd,$name,$aName,$aVal) = @_; + my $hash = $defs{$name}; + my $type = $hash->{TYPE}; + my $ret; + + if ($cmd eq "set" && !defined $aVal) { + $ret = "not empty" + } + + elsif ($aName eq "addReadingsPrefix") { + $cmd eq "set" + ? $aVal =~ m/^[01]$/ ? ($hash->{helper}{$aName} = $aVal) : ($ret = "0|1") + : delete $hash->{helper}{$aName} + } + + elsif ($aName eq "do_not_notify") { + $ret = "0|1" if $cmd eq "set" && $aVal !~ m/^[01]$/; + } + + elsif ($aName eq "disable") { + if ($cmd eq "set") { + if ($aVal !~ m/^[01]$/) { $ret = "0|1" } + else { readingsSingleUpdate($hash, "state", $aVal?"disabled":"active", 1) } + } + elsif ($cmd eq "del") { readingsSingleUpdate($hash, "state", "active", 1) } + } + + if ($ret) { + my $v = defined $aVal ? $aVal : ""; + my $msg = "$type: attr $name $aName $v: value must be: "; + Log3 $name, 2, $msg.$ret; + return $msg.$ret; + } + + return undef; +} + + +sub expandJSON_Notify($$) { + my ($hash, $dhash) = @_; + my $name = $hash->{NAME}; + return "" if(IsDisabled($name)); + + my $events = deviceEvents($dhash, AttrVal($name, "addStateEvent", 0)); + return if( !grep { m/{.*}\s*$/s } @{ $events } ); #there is no JSON content + + for (my $i = 0; $i < int(@{$events}); $i++) { + my $event = $events->[$i]; + $event = "" if(!defined($event)); + + my $re = $hash->{s_regexp}; + my $devName = $dhash->{NAME}; + my $found = ($devName =~ m/^$re$/ || "$devName:$event" =~ m/^$re$/s); + if ($found) { + my $type = $hash->{TYPE}; + Log3 $name, 5, "$type $name: Found $devName:$event"; + + my ($reading,$value) = $event =~ m/^\s*{.*}\s*$/s + ? ("state", $event) + : split(": ", $event, 2); + + $hash->{STATE} = $dhash->{NTFY_TRIGGERTIME}; + + if ($value !~ m/^\s*{.*}\s*$/s) { # eg. state with an invalid json + Log3 $name, 5, "$type $name: Invalid JSON: $value"; + return; + } + + Log3 $name, 5, "$type $name: Yield decode: $hash | $devName | $reading " + . "| $value"; + + InternalTimer( + gettimeofday(), + sub(){ expandJSON_decode($hash,$devName,$reading,$value) }, + $hash); + } + } + + return undef; +} + + +sub expandJSON_decode($$$$) { + my ($p) = @_; + my ($hash,$dname,$dreading,$dvalue) = @_; + my ($name,$type) = ($hash->{NAME},$hash->{TYPE}); + my $dhash = $defs{$dname}; + my $h; + eval { $h = decode_json($dvalue); 1; }; + if ( $@ ) { + Log3 $name, 2, "$type $name: Bad JSON: $dname $dreading: $dvalue"; + Log3 $name, 2, "$type $name: $@"; + return undef; + } + + my $sPrefix = $hash->{helper}{addReadingsPrefix} ? $dreading."_" : ""; + readingsBeginUpdate($dhash); + expandJSON_expand($hash,$dhash,$sPrefix,$h); + readingsEndUpdate($dhash, AttrVal($name,"do_not_notify",0) ? 0 : 1); + + return undef; +} + + +sub expandJSON_expand($$$$;$$) { + # thanx to bgewehr for the root position of this recursive snippet + # https://github.com/bgewehr/fhem + my ($hash,$dhash,$sPrefix,$ref,$prefix,$suffix) = @_; + $prefix = "" if( !$prefix ); + $suffix = "" if( !$suffix ); + $suffix = "_$suffix" if( $suffix ); + + if( ref( $ref ) eq "ARRAY" ) { + while( my ($key,$value) = each @{ $ref } ) { + expandJSON_expand($hash,$dhash,$sPrefix,$value, + $prefix.sprintf("%02i",$key+1)."_"); + } + } + elsif( ref( $ref ) eq "HASH" ) { + while( my ($key,$value) = each %{ $ref } ) { + if( ref( $value ) ) { + expandJSON_expand($hash,$dhash,$sPrefix,$value,$prefix.$key.$suffix."_"); + } + else { + # replace illegal characters in reading names + (my $reading = $sPrefix.$prefix.$key.$suffix) =~ s/[^A-Za-z\d_\.\-\/]/_/g; + readingsBulkUpdate($dhash, $reading, $value) + if $reading =~ m/^$hash->{t_regexp}$/; + } + } + } +} + + +sub expandJSON_isPmInstalled($$) +{ + my ($hash,$pm) = @_; + my ($name,$type) = ($hash->{NAME},$hash->{TYPE}); + if (not eval "use $pm;1") + { + Log3 $name, 1, "$type $name: perl modul missing: $pm. Install it, please."; + $hash->{MISSING_MODULES} .= "$pm "; + return "failed: $pm"; + } + + return undef; +} + + +1; + + +=pod +=item helper +=item summary Expand a JSON string from a reading into individual readings +=item summary_DE Expandiert eine JSON Zeichenkette in individuelle Readings +=begin html + + +

expandJSON

+ +
    +

    Expand a JSON string from a reading into individual readings

    + +
      +
    • Requirement: perl module JSON
      + Use "cpan install JSON" or operating system's package manager to install + Perl JSON Modul. Depending on your os the required package is named: + libjson-perl or perl-JSON. +
    • +

    + + + Define

    + +
      + define <name> expandJSON <source_regex> + [<target_regex>]

      + +
    • + <name>
      + A name of your choice.

    • + +
    • + <source_regex>
      + Regexp that must match your devices, readings and values that contain + the JSON strings. Regexp syntax is the same as used by notify and must not + contain spaces.
      +

    • + +
    • + <target_regex>
      + Optional: This regexp is used to determine whether the target reading is + converted or not at all. If not set then all readings will be used. If set + then only matching readings will be used. Regexp syntax is the same as + used by notify and must not contain spaces.
      +

    • + +
    • + Examples:
      +
      + Source reading:
      + + device:{.*} #state without attribute addStateEvent
      + device:state:.{.*} #state with attribute addStateEvent
      + device:reading:.{.*}
      + Sonoff.*:ENERGY.*:.{.*}
      + .*wifiIOT.*:.*sensor.*:.{.*}
      + (?i)dev.*:(sensor1|sensor2|teleme.*):.{.*}
      + (devX:{.*}|devY.*:jsonY:.{.*Wifi.*{.*SSID.*}.*}) +

      +
      + + Target reading:
      + .*power.*
      + (Power|Current|Voltage|.*day)

      + + Complete definitions:
      + define ej1 expandJSON device:sourceReading:.{.*} targetReading +
      + define ej2 expandJSON Sonoff.*:ENERGY.*:.{.*} (Power|.*day) +
      + define ej3 expandJSON (?i).*_sensordev_.*:.*:.{.*} +

      + +

    • +
    + + + Set

    +
      + N/A

      +
    + + + Get

    +
      + N/A

      +
    + + + Attributes

    +
      +
    • addReadingsPrefix
      + Add source reading as prefix to new generated readings. Useful if you have + more than one reading with a JSON string that should be converted. +

    • + +
    • disable
      + Used to disable this device. +

    • + +
    • do_not_notify
      + Do not generate events for converted readings at all. Think twice before + using this attribute. In most cases, it is more appropriate to use + event-on-change-reading in target devices. +

    • + +
    • disabledForIntervals
    • +
    • addStateEvent
    • +
    +
+ +=end html +=cut diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index f0753e8ee..f9e9a7e8c 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -404,6 +404,7 @@ FHEM/98_CustomReadings.pm HCS http://forum.fhem.de Unterstue FHEM/98_dewpoint.pm Joachim http://forum.fhem.de Automatisierung FHEM/98_Dooya.pm Jarnsen/ralf9/darkmission http://forum.fhem.de Sonstige Systeme FHEM/98_dummy.pm rudolfkoenig http://forum.fhem.de Automatisierung +FHEM/98_expandJSON.pm dev0 http://forum.fhem.de Unterstuetzende Dienste FHEM/98_fheminfo.pm mfr69bs/rudolfkoenig http://forum.fhem.de Sonstiges FHEM/98_fhemdebug.pm rudolfkoenig http://forum.fhem.de Sonstiges FHEM/98_GoogleAuth.pm betateilchen http://forum.fhem.de Unterstuetzende Dienste