diff --git a/CHANGED b/CHANGED index 4d75599..692f335 100644 --- a/CHANGED +++ b/CHANGED @@ -1,3 +1,17 @@ +2026-02-19 +- transmit sunrise/sunset for UZSU +- make websocket port and message size configurable +- new JSONdata converter + +2023-12-18 +- extended plot durations to allow usage of all smartVISU plot features + +2022-11-19 +- workaround for sporadic fhem crashes + +2022-09-08 +- Adaptions to smartVISU v2.9+ from 2018: thx raman + 2017-12-15 - Modified state handling in received events: thx ddtlabs diff --git a/FHEM/01_fronthem.pm b/FHEM/01_fronthem.pm index 7b35258..2af5415 100644 --- a/FHEM/01_fronthem.pm +++ b/FHEM/01_fronthem.pm @@ -1,17 +1,17 @@ ############################################## # $Id: 01_fronthem.pm 21 2015-02-13 20:25:09Z. herrmannj $ - +# modified: 2018-04-03 00:00:00Z raman #TODO alot ;) #organize loading order #attr cfg file # fixed: # issue with some JSON module at startup -# perl before 5.14 issue +# perl before 5.14 issue # remove debug output # open: -# UTF8 conversation +# UTF8 conversation # num converter with negative values # 99er converter (see reload) @@ -30,21 +30,26 @@ use IO::Select; use fhwebsocket; use JSON; use utf8; +use Encode; use Data::Dumper; +sub fronthem_Notify($$); + sub fronthem_Initialize(@) { my ($hash) = @_; - + $hash->{DefFn} = "fronthem_Define"; $hash->{SetFn} = "fronthem_Set"; $hash->{ReadFn} = "fronthem_Read"; + $hash->{AttrFn} = "fronthem_Attr"; $hash->{UndefFn} = "fronthem_Undef"; + $hash->{NotifyFn} = "fronthem_Notify"; $hash->{ShutdownFn} = "fronthem_Shutdown"; - $hash->{AttrList} = "configFile ".$readingFnAttributes; + $hash->{AttrList} = "configFile maxSendSize port ".$readingFnAttributes; } sub @@ -53,47 +58,28 @@ fronthem_Define($$) my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); my $name = $a[0]; - my $cfg; $hash->{helper}->{COMMANDSET} = 'save'; - #TODO move it to "initialized" - fronthem_ReadCfg($hash, 'fronthem.cfg'); - - my $port = 16384; + $hash->{helper}->{ipcPort} = 16384; # create and register server ipc parent (listener == socket) do { $hash->{helper}->{listener} = IO::Socket::INET->new( LocalHost => 'localhost', - LocalPort => $port, - Listen => SOMAXCONN, + LocalPort => $hash->{helper}->{ipcPort}, + Listen => SOMAXCONN, Reuse => 1 ); - $port++; + $hash->{helper}->{ipcPort} += 1; } until (defined($hash->{helper}->{listener})); - $port -= 1; + $hash->{helper}->{ipcPort} -= 1; my $flags = fcntl($hash->{helper}->{listener}, F_GETFL, 0) or return "error shaping ipc: $!"; fcntl($hash->{helper}->{listener}, F_SETFL, $flags | O_NONBLOCK) or return "error shaping ipc: $!"; - Log3 ($hash, 2, "$hash->{NAME}: ipc listener opened at port $port"); + Log3 ($hash, 2, "$hash->{NAME}: ipc listener opened at port $hash->{helper}->{ipcPort}"); $hash->{TCPDev} = $hash->{helper}->{listener}; $hash->{FD} = $hash->{helper}->{listener}->fileno(); $selectlist{"$name:ipcListener"} = $hash; - $hash->{helper}->{main}->{state} = 'run'; - if ($init_done) - { - # TODO set initial readings - } - - # prepare forking the ws server - $cfg->{hash} = $hash; - $cfg->{id} = 'ws'; - $cfg->{port} = 2121; - $cfg->{ipcPort} = $port; - # preserve - $hash->{helper}->{main}->{state} = 'run'; - fronthem_StartWebsocketServer($cfg); - return undef; } @@ -111,8 +97,8 @@ fronthem_Set(@) } #ipc, accept from forked socket server -sub -fronthem_Read(@) +sub +fronthem_Read(@) { my ($hash) = @_; my $ipcClient = $hash->{helper}->{listener}->accept(); @@ -120,7 +106,7 @@ fronthem_Read(@) fcntl($ipcClient, F_SETFL, $flags | O_NONBLOCK) or return "error shaping ipc client: $!"; # TODO connections from other then localhost possible||usefull ? evaluate the need ... - + my $ipcHash; $ipcHash->{TCPDev} = $ipcClient; $ipcHash->{FD} = $ipcClient->fileno(); @@ -138,11 +124,12 @@ fronthem_Read(@) return undef; } -sub +sub fronthem_Notify($$) { my ($hash, $ntfyDev) = @_; my $ntfyDevName = $ntfyDev->{NAME}; + my $name = $hash->{NAME}; # responde to fhem system events # INITIALIZED|REREADCFG|DEFINED|RENAMED|SHUTDOWN @@ -151,9 +138,22 @@ fronthem_Notify($$) foreach my $event (@{$ntfyDev->{CHANGED}}) { my @e = split(' ', $event); + #Log3 ($hash, 1, "in $e[0]"); #TODO remove if ($e[0] eq 'INITIALIZED') { - # + fronthem_ReadCfg($hash, 'fronthem.cfg'); + $hash->{MAXSENDSIZE} = AttrVal( $name, "maxSendSize", "65536" ); + $hash->{PORT} = AttrVal( $name, "port", "2121" ); + # prepare forking the ws server + my $cfg; + $cfg->{hash} = $hash; + $cfg->{id} = 'ws'; + $cfg->{port} = $hash->{PORT}; + $cfg->{ipcPort} = $hash->{helper}->{ipcPort}; + $cfg->{max_send_size} = $hash->{MAXSENDSIZE}; + # preserve + $hash->{helper}->{main}->{state} = 'run'; + fronthem_StartWebsocketServer($cfg); } elsif ($e[0] eq 'RENAMED') { @@ -165,9 +165,48 @@ fronthem_Notify($$) } } } + elsif ( $ntfyDevName ne $name ) { + return undef + } + + foreach my $change (@{$ntfyDev->{CHANGED}}) { + readingsBeginUpdate($hash); + #Log3 $name, 1, "fronthem $name: change: $change."; + + if ($change =~ /^(.+): (.+)/) { + if($1 eq "ws") { + readingsBulkUpdate($hash, "state", $2); + } + } + } + readingsEndUpdate($hash, 1); return undef; } +sub +fronthem_Attr(@) +{ + my ($type, $devName, $attrName, @param) = @_; + my $hash = $defs{$devName}; + #Log3 $devName, 1, "01_fronthem: $type - $devName - $attrName - $param[0]"; + + if($type eq "set" && $attrName eq "maxSendSize" && $param[0]) { + $hash->{MAXSENDSIZE} = $param[0]; + } + + if($type eq "set" && $attrName eq "port" && $param[0]) { + $hash->{PORT} = $param[0]; + } + + if($type eq "del" && $attrName eq "maxSendSize") { + $hash->{MAXSENDSIZE} = 65536; + } + + if($type eq "del" && $attrName eq "port") { + $hash->{PORT} = 2121; + } +} + sub fronthem_Undef(@) { @@ -188,8 +227,8 @@ fronthem_Shutdown(@) } #ipc, read msg from forked socket server -sub -fronthem_ipcRead($) +sub +fronthem_ipcRead($) { my ($ipcHash) = @_; my $msg = ""; @@ -223,14 +262,15 @@ fronthem_ipcRead($) if (defined($ipcHash->{registered})) { - $id = $ipcHash->{registered}; + $id = $ipcHash->{registered}; # TODO check if a dispatcher is set - eval + eval { + $msg = Encode::encode('UTF-8', $msg); $up = decode_json($msg); - + Log3 ($ipcHash->{PARENT}, $up->{log}->{level}, "ipc $ipcHash->{NAME} ($id): $up->{log}->{text}") if (exists($up->{log}) && (($up->{log}->{cmd} || '') eq 'log')); - #keep global cfg up to date, add new items + #keep global cfg up to date, add new items if (exists($up->{message}) && (($up->{message}->{cmd} || '') eq 'monitor')) { foreach my $item (@{$up->{message}->{items}}) @@ -238,19 +278,41 @@ fronthem_ipcRead($) $ipcHash->{PARENT}->{helper}->{config}->{$item}->{type} = 'item' unless defined($ipcHash->{PARENT}->{helper}->{config}->{$item}->{type}); } } - if (exists($up->{message}) && (($up->{message}->{cmd} || '') eq 'series')) + if (exists($up->{message}) && (($up->{message}->{cmd} || '') eq 'plot')) { - my $item = $up->{message}->{item}; - $ipcHash->{PARENT}->{helper}->{config}->{$item}->{type} = 'plot'; + foreach my $item (@{$up->{message}->{items}}) + { + my $gad = $item->{item}; + $ipcHash->{PARENT}->{helper}->{config}->{$gad}->{type} = 'plot'; + $ipcHash->{PARENT}->{helper}->{config}->{$gad}->{mode} = $item->{mode}; + $ipcHash->{PARENT}->{helper}->{config}->{$gad}->{start} = $item->{start}; + $ipcHash->{PARENT}->{helper}->{config}->{$gad}->{end} = $item->{end}; + $ipcHash->{PARENT}->{helper}->{config}->{$gad}->{count} = $item->{count}; + $ipcHash->{PARENT}->{helper}->{config}->{$gad}->{interval} = $item->{interval}; + $ipcHash->{PARENT}->{helper}->{config}->{$gad}->{updatemode} = $item->{updatemode}; + $ipcHash->{PARENT}->{helper}->{config}->{$gad}->{datamode} = $item->{datamode}; + $ipcHash->{PARENT}->{helper}->{config}->{$gad}->{gad} = $item->{item} if (defined($item->{item})); + $ipcHash->{PARENT}->{helper}->{config}->{$gad}->{gads} = $item->{items} if (defined($item->{items})); + } } + if (exists($up->{message}) && (($up->{message}->{cmd} || '') eq 'log')) + { + foreach my $item (@{$up->{message}->{items}}) + { + my $gad = $item->{item}; + $ipcHash->{PARENT}->{helper}->{config}->{$gad}->{type} = 'log'; + $ipcHash->{PARENT}->{helper}->{config}->{$gad}->{size} = $item->{size}; + } + } fronthem_ProcessDeviceMsg($ipcHash, $up) if (exists($up->{message})); + } or do { + Log3 ($ipcHash->{PARENT}, 2, "ipc $ipcHash->{NAME} ($id): error $@ decoding ipc msg $msg") if ($@); }; - Log3 ($ipcHash->{PARENT}, 2, "ipc $ipcHash->{NAME} ($id): error $@ decoding ipc msg $msg") if ($@); } else { # first incoming msg, must contain id:pid (name) of forked child - # security check, see if we are waiting for. id and pid should be registered in $hash->{helper}->{ipc}->{$id}->{pid} before incoming will be accepted + # security check, see if we are waiting for. id and pid should be registered in $hash->{helper}->{ipc}->{$id}->{pid} before incoming will be accepted if (($msg =~ m/^(\w+):(\d+)$/) && ($ipcHash->{PARENT}->{helper}->{ipc}->{$1}->{pid} eq $2)) { ($id,$pid) = ($1, $2); @@ -275,7 +337,7 @@ fronthem_ipcRead($) # id: eq ws,wss # msg: whats to tell -sub +sub fronthem_ipcWrite(@) { my ($hash, $id, $msg) = @_; @@ -289,19 +351,19 @@ fronthem_ipcWrite(@) my $out = to_json($msg)."\n"; my $lin = length $out; my $result = $hash->{helper}->{ipc}->{$id}->{sock}->{TCPDev}->send($out); - - if (!defined($result)) + + if (!defined($result)) { Log3 ($hash, 1, "$hash->{NAME} send to $id (ipc to child) unkown error"); fronthem_DisconnectClients($hash, $id); return undef; } - if ($result != $lin) + if ($result != $lin) { Log3 ($hash, 1, "$hash->{NAME} send to $id (ipc to child) in $lin send $result"); return undef; } - + return undef; } @@ -319,7 +381,7 @@ fronthem_DisconnectClients(@) return undef; } -# forced disonnect +# forced disonnect sub fronthem_DisconnectClient(@) { @@ -363,10 +425,10 @@ fronthem_ReadCfg(@) my $data; my $filtered->{config} = {}; - + if (length($json_text)) { - eval + eval { my $json = JSON->new->utf8; $data = $json->decode($json_text); @@ -386,6 +448,21 @@ fronthem_ReadCfg(@) $filtered->{config}->{$key}->{reading} = $data->{config}->{$key}->{reading}; $filtered->{config}->{$key}->{converter} = $data->{config}->{$key}->{converter}; $filtered->{config}->{$key}->{set} = $data->{config}->{$key}->{set}; + + if ($data->{config}->{$key}->{type} eq 'plot') + { + $filtered->{config}->{$key}->{start} = $data->{config}->{$key}->{start}; + $filtered->{config}->{$key}->{end} = $data->{config}->{$key}->{end}; + $filtered->{config}->{$key}->{mode} = $data->{config}->{$key}->{mode}; + $filtered->{config}->{$key}->{interval} = $data->{config}->{$key}->{interval}; + $filtered->{config}->{$key}->{updatemode} = $data->{config}->{$key}->{updatemode}; + $filtered->{config}->{$key}->{datamode} = $data->{config}->{$key}->{datamode}; + } + + if ($data->{config}->{$key}->{type} eq 'log') + { + $filtered->{config}->{$key}->{size} = $data->{config}->{$key}->{size}; + } } } @@ -403,7 +480,7 @@ fronthem_WriteCfg(@) $cfgContent->{version} = '1.0'; $cfgContent->{modul} = 'fronthem-server'; - + foreach my $key (keys %{ $hash->{helper}->{config} }) { if ($hash->{helper}->{config}->{$key}->{type} eq 'item') @@ -414,6 +491,30 @@ fronthem_WriteCfg(@) $cfgContent->{config}->{$key}->{converter} = $hash->{helper}->{config}->{$key}->{converter}; $cfgContent->{config}->{$key}->{set} = $hash->{helper}->{config}->{$key}->{set}; } + elsif ($hash->{helper}->{config}->{$key}->{type} eq 'log') + { + $cfgContent->{config}->{$key}->{type} = $hash->{helper}->{config}->{$key}->{type}; + $cfgContent->{config}->{$key}->{size} = $hash->{helper}->{config}->{$key}->{size}; + $cfgContent->{config}->{$key}->{device} = $hash->{helper}->{config}->{$key}->{device}; + $cfgContent->{config}->{$key}->{reading} = $hash->{helper}->{config}->{$key}->{reading}; + $cfgContent->{config}->{$key}->{converter} = $hash->{helper}->{config}->{$key}->{converter}; + $cfgContent->{config}->{$key}->{set} = $hash->{helper}->{config}->{$key}->{set}; + } + elsif ($hash->{helper}->{config}->{$key}->{type} eq 'plot') + { + $cfgContent->{config}->{$key}->{type} = $hash->{helper}->{config}->{$key}->{type}; + $cfgContent->{config}->{$key}->{device} = $hash->{helper}->{config}->{$key}->{device}; + $cfgContent->{config}->{$key}->{reading} = $hash->{helper}->{config}->{$key}->{reading}; + $cfgContent->{config}->{$key}->{converter} = $hash->{helper}->{config}->{$key}->{converter}; + $cfgContent->{config}->{$key}->{set} = $hash->{helper}->{config}->{$key}->{set}; + + $cfgContent->{config}->{$key}->{start} = $hash->{helper}->{config}->{$key}->{start}; + $cfgContent->{config}->{$key}->{end} = $hash->{helper}->{config}->{$key}->{end}; + $cfgContent->{config}->{$key}->{mode} = $hash->{helper}->{config}->{$key}->{mode}; + $cfgContent->{config}->{$key}->{interval} = $hash->{helper}->{config}->{$key}->{interval}; + $cfgContent->{config}->{$key}->{updatemode} = $hash->{helper}->{config}->{$key}->{updatemode}; + $cfgContent->{config}->{$key}->{datamode} = $hash->{helper}->{config}->{$key}->{datamode}; + } } mkdir('./www/fronthem',0777) unless (-d './www/fronthem'); @@ -436,13 +537,19 @@ fronthem_CreateListen(@) { my ($hash) = @_; my $listen; + my $plotlisten; + my $loglisten; foreach my $key (keys %{$hash->{helper}->{config}}) { my $gad = $hash->{helper}->{config}->{$key}; $listen->{$gad->{device}}->{$gad->{reading}}->{$key} = $hash->{helper}->{config}->{$key} if ((defined($gad->{device})) && (defined($gad->{reading}))); + $plotlisten->{$gad->{device}}->{$gad->{reading}}->{$key} = $hash->{helper}->{config}->{$key} if ((defined($gad->{device})) && (defined($gad->{reading})) && $gad->{type} eq 'plot'); + $loglisten->{$gad->{device}}->{$gad->{reading}}->{$key} = $hash->{helper}->{config}->{$key} if ((defined($gad->{device})) && (defined($gad->{reading})) && $gad->{type} eq 'log'); } $hash->{helper}->{listen} = $listen; + $hash->{helper}->{plot} = $plotlisten; + $hash->{helper}->{log} = $loglisten; return undef; } @@ -458,14 +565,14 @@ fronthem_ProcessDeviceMsg(@) { my ($ipcHash, $msg) = @_; - my $hash = $ipcHash->{PARENT}; + my $hash = $ipcHash->{PARENT}; my $connection = $ipcHash->{registered}.':'.$msg->{'connection'}; my $sender = $msg->{'sender'}; my $identity = $msg->{'identity'}; my $message = $msg->{'message'}; - - #TODO: + + #TODO: # check if device with given identity is already connected # if so, reject the connection and give it a hint why @@ -480,7 +587,7 @@ fronthem_ProcessDeviceMsg(@) } else { - #TODO error logging, disconnect + #TODO error logging, disconnect } } elsif((($message->{cmd} || '') eq 'handshake') && ($hash->{helper}->{receiver}->{$connection}->{state} eq 'connecting') ) @@ -493,7 +600,7 @@ fronthem_ProcessDeviceMsg(@) { $hash->{helper}->{receiver}->{$connection}->{device} = $key; $hash->{helper}->{receiver}->{$connection}->{state} = 'connected'; - #build sender + #build sender $hash->{helper}->{sender}->{$key}->{connection} = $ipcHash->{registered}; $hash->{helper}->{sender}->{$key}->{ressource} = $msg->{'connection'}; $hash->{helper}->{sender}->{$key}->{state} = 'connected'; @@ -523,7 +630,7 @@ fronthem_ProcessDeviceMsg(@) { $hash->{helper}->{receiver}->{$connection}->{device} = $key; $hash->{helper}->{receiver}->{$connection}->{state} = 'connected'; - #build sender + #build sender $hash->{helper}->{sender}->{$key}->{connection} = $ipcHash->{registered}; $hash->{helper}->{sender}->{$key}->{ressource} = $msg->{'connection'}; $hash->{helper}->{sender}->{$key}->{state} = 'connected'; @@ -531,8 +638,8 @@ fronthem_ProcessDeviceMsg(@) } } } - - if(($message->{cmd} || '') eq 'disconnect') + + if(($message->{cmd} || '') eq 'disconnect') { my $key = $hash->{helper}->{receiver}->{$connection}->{device}; @@ -541,8 +648,8 @@ fronthem_ProcessDeviceMsg(@) { my $devHash = $defs{$key}; fronthemDevice_fromDriver($devHash, $msg); - delete($hash->{helper}->{sender}->{$key}); - } + delete($hash->{helper}->{sender}->{$key}); + } return undef; } @@ -559,14 +666,14 @@ fronthem_ProcessDeviceMsg(@) #msg is hash from fhem fronthemDevice instance, will be dispatched to forked client, and further to sv client #msg->receiver = speaking name (eg tab) #msg->ressource -#msg->message->cmd +#msg->message->cmd sub fronthem_FromDevice(@) { my ($hash, $device, $msg) = @_; unless (exists($hash->{helper}->{sender}->{$device})) { - Log3 ($hash, 1, "$hash->{NAME} $device want send but isnt a sender"); + Log3 ($hash, 4, "$hash->{NAME} $device want send but isnt a sender"); # TODO device must be disconnected !! fronthem_DisconnectClient($hash, $device); return undef; } @@ -575,7 +682,7 @@ fronthem_FromDevice(@) #ressource within ipc child, leave blank if you want t talk with the process itself $msg->{ressource} = $hash->{helper}->{sender}->{$device}->{ressource}; fronthem_ipcWrite($hash, $connection, $msg); - return undef; + return undef; } ############################################################################### @@ -585,8 +692,9 @@ fronthem_FromDevice(@) sub fronthem_StartWebsocketServer(@) { - my ($cfg) = @_; + my ($cfg) = @_; my $id = $cfg->{id}; + my $name = $cfg->{hash}->{NAME}; my $pid = fork(); return "Error while try to fork $id: $!" unless (defined $pid); @@ -602,10 +710,10 @@ fronthem_StartWebsocketServer(@) setsid(); # close open handles - close STDOUT; + close STDOUT; open STDOUT, '>/dev/null'; close STDIN; - close STDERR; + close STDERR; # open STDERR, '>/dev/null'; open STDERR, '>>', "fronthem.err"; @@ -632,6 +740,7 @@ fronthem_StartWebsocketServer(@) $ws->{'ipc'} = $ipc; $ws->{id} = $id; $ws->{buffer} = ''; + $ws->{max_send_size} = $cfg->{max_send_size}; $ws->watch_readable($ipc->fileno() => \&fronthem_wsIpcRead); fronthem_forkLog3 ($ws->{ipc}, 1, "$ws->{id} could not open port $cfg->{port}") unless $ws->start; @@ -719,7 +828,7 @@ fronthem_wsIpcRead(@) my ($serv, $fh) = @_; my $msg = ''; my $rv; - + $rv = $serv->{'ipc'}->recv($msg, POSIX::BUFSIZ, 0); unless (defined($rv) && length $msg) { $serv -> shutdown(); @@ -729,9 +838,12 @@ fronthem_wsIpcRead(@) while (($serv->{buffer} =~ m/\n/) && (($msg, $serv->{buffer}) = split /\n/, $serv->{buffer}, 2)) { eval { + $msg = Encode::encode('UTF-8', $msg); $msg = decode_json($msg); + } or do { + fronthem_forkLog3 ($serv->{ipc}, 1, "$serv->{id} ipc decoding error $@") if ($@); + $msg = {}; }; - fronthem_forkLog3 ($serv->{ipc}, 1, "$serv->{id} ipc decoding error $@") if ($@); fronthem_wsProcessInboundCmd($serv, $msg); } return undef; @@ -739,7 +851,7 @@ fronthem_wsIpcRead(@) #msg->receiver = speaking name (eg tab) #msg->ressource -#msg->message->cmd +#msg->message->cmd sub fronthem_wsProcessInboundCmd(@) @@ -753,4 +865,70 @@ fronthem_wsProcessInboundCmd(@) return undef; } -1; \ No newline at end of file +1; + +=pod +=item device +=item summary fronthem websocket for FHEM +=item summary_DE fronthem websocket Schnittstelle für FHEM +=begin html + +

+ +

+

+ fronthem +

+
+
+ Attributes + + +=end html + +=begin html_DE + +

+ +

+

+ fronthem +

+
+
+ Attribute + + +=end html_DE + +=cut diff --git a/FHEM/31_fronthemDevice.pm b/FHEM/31_fronthemDevice.pm index 407ad31..3e87854 100644 --- a/FHEM/31_fronthemDevice.pm +++ b/FHEM/31_fronthemDevice.pm @@ -1,6 +1,6 @@ ############################################## -# $Id: 31_fronthemDevice.pm 21 2015-02-13 20:25:09Z. herrmannj $ - +# $Id: 31_fronthemDevice.pm 21 2015-02-13 20:25:09Z herrmannj $ +# modified: 2018-04-14 00:0:00Z raman # open: # getAllSets(devName) # num converter with negative values @@ -10,6 +10,7 @@ package main; use strict; use warnings; +use Time::HiRes qw(gettimeofday); use JSON; use utf8; @@ -32,8 +33,8 @@ fronthemDevice_Initialize(@) $hash->{UndefFn} = "fronthemDevice_Undef"; $hash->{ShutdownFn} = "fronthemDevice_Shutdown"; $hash->{FW_detailFn} = "fronthemDevice_fwDetail"; - $hash->{AttrList} = "configFile ". - "whitelist:true,false ".$readingFnAttributes; + $hash->{AttrList} = "configFile " . + "whitelist:true,false ".$readingFnAttributes; $data{FWEXT}{fronthemDevice}{SCRIPT} = "fronthemEditor.js"; } @@ -253,9 +254,10 @@ sub fronthemDevice_ValidateGAD(@) my $result = ''; my $gadItem = $transfer->{item}; + if (!defined($defs{$hash->{helper}->{gateway}}->{helper}->{config}->{$transfer->{item}})) { - Log3 ($hash, 2, "gadModeSelect with unknown GAD $gadItem"); + Log3 ($hash, 2, "gadModeSelect with unknown ITEM $gadItem"); } my $gadAtGateway = $defs{$hash->{helper}->{gateway}}->{helper}->{config}->{$transfer->{item}}; @@ -263,9 +265,9 @@ sub fronthemDevice_ValidateGAD(@) $hash->{helper}->{config}->{$gadItem}->{read} = ($transfer->{config}->{read} eq '1')?1:0; $hash->{helper}->{config}->{$gadItem}->{write} = ($transfer->{config}->{write} eq '1')?1:0; - if ($transfer->{editor} eq 'item') + if ($transfer->{editor} eq 'item' || $transfer->{editor} eq 'log' || $transfer->{editor} eq 'plot') { - $gadAtGateway->{type} = 'item'; + $gadAtGateway->{type} = $transfer->{editor}; #device if ((defined($transfer->{config}->{device})) && length($transfer->{config}->{device})) { @@ -313,45 +315,166 @@ sub fronthemDevice_Notify($$) { my ($hash, $ntfyDev) = @_; + + my $name = $hash->{NAME}; my $ntfyDevName = $ntfyDev->{NAME}; - + # responde to fhem system events # INITIALIZED|REREADCFG|DEFINED|RENAMED|SHUTDOWN if ($ntfyDevName eq "global") { - foreach my $event (@{$ntfyDev->{CHANGED}}) + my @events = deviceEvents($ntfyDev, 1); + foreach my $a (@events) { - my @e = split(' ', $event); - if ($e[0] eq 'INITIALIZED') - { - fronthemDevice_Start($hash); - } - elsif ($e[0] eq 'RENAMED') - { - # TODO see if it is my gateway - my $gateway = fronthemDevice_GetGateway($hash); - if ($gateway && ($gateway->{NAME} eq $e[1])) - { - Log3 ($hash, 4, "$hash->{NAME}:gatway $e[1] renamed to $e[2] - update settings"); - } - } + foreach my $e (@{$a}) { + my @event = split(/\s/, $e); + + if ($event[0] eq 'INITIALIZED') + { + fronthemDevice_Start($hash); + } + elsif ($event[0] eq 'RENAMED') + { + # TODO see if it is my gateway + my $gateway = fronthemDevice_GetGateway($hash); + if ($gateway && ($gateway->{NAME} eq $event[1])) + { + Log3 ($hash, 4, "$hash->{NAME}:gatway $event[1] renamed to $event[2] - update settings"); + } + } + } } } - - return undef if(AttrVal($hash->{NAME}, "disable", 0) == 1); - return undef unless (ReadingsVal($hash->{NAME}, 'state', 'disconnected') eq 'connected'); + + return undef if (AttrVal($name, "disable", 0) == 1); + return undef unless (ReadingsVal($name, 'state', 'disconnected') eq 'connected'); # TODO exit here if gateway is absend - - my $result; + + if (defined($hash->{helper}->{gateway}) && exists($defs{$hash->{helper}->{gateway}}->{helper}->{plot}->{$ntfyDevName}) && InternalVal($name, "TYPE", "") eq "fronthemDevice") + { + my @events = deviceEvents($ntfyDev, 1); + my $listenDevice = $defs{$hash->{helper}->{gateway}}->{helper}->{plot}->{$ntfyDevName}; + + if (@events) { + foreach my $a (@events) + { + foreach my $reading (@{$a}) { + my @actualreading = split(/:\s|\s/, $reading); + my $reading = shift @actualreading; + my $listenDevice = $defs{$hash->{helper}->{gateway}}->{helper}->{plot}->{$ntfyDevName}; + + if (defined($listenDevice->{$reading})) + { + foreach my $gad (keys %{$listenDevice->{$reading}}) + { + my $gadCfg = $defs{$hash->{helper}->{gateway}}->{helper}->{config}->{$gad}; + if($gadCfg->{type} eq 'plot' && $gadCfg->{reading} eq $reading) + { + my $event = $actualreading[0]; + my @converterArgs = split(/\s/, $gadCfg->{converter}); + if (@converterArgs) { + my $converter = shift @converterArgs; + + if(@converterArgs >= 3) { + my $arg = $converterArgs[2]; + if ("@actualreading" =~ /$arg\s([0-9.]+)/) { + $event = $1; + } + } + + my $counter = 1; + my @modes; + if($gadCfg->{datamode} && $gadCfg->{datamode} ne $gadCfg->{mode}) { + if($gadCfg->{datamode} eq "minmaxavg") { + $counter = 3; + @modes = ('min','max','avg'); + } elsif($gadCfg->{datamode} eq "minmax") { + $counter = 2; + @modes = ('min','max'); + } + } else { + @modes = ($gadCfg->{mode}); + } + + for (my $i = 0; $i < $counter; $i++) { + my $param; + $param->{cmd} = 'plot'; + $param->{gad} = $gad; + $param->{device} = $ntfyDevName; + $param->{reading} = $reading; + $param->{mode} = $modes[$i]; + $param->{start} = $gadCfg->{start}; + $param->{end} = $gadCfg->{end}; + $param->{count} = $gadCfg->{count}; + $param->{interval} = $gadCfg->{interval}; + $param->{updatemode} = 'point'; + $param->{datamode} = $gadCfg->{datamode}; + $param->{event} = $event; + @{$param->{events}} = @actualreading; + @{$param->{args}} = @converterArgs; + fronthemDevice_DoConverter($hash, $converter, $param); + if(@converterArgs == 1 && @modes == 1) { + last; + } + } + } + + } + } + } + } + } + } + } + + if (defined($hash->{helper}->{gateway}) && exists($defs{$hash->{helper}->{gateway}}->{helper}->{log}->{$ntfyDevName}) && InternalVal($name, "TYPE", "") eq "fronthemDevice") + { + my @events = deviceEvents($ntfyDev, 1); + my $listenDevice = $defs{$hash->{helper}->{gateway}}->{helper}->{log}->{$ntfyDevName}; + + if (@events) { + foreach my $a (@events) + { + foreach my $reading (@{$a}) { + my @actualreading = split(/:\s|\s/, $reading); + my $reading = shift @actualreading; + my $listenDevice = $defs{$hash->{helper}->{gateway}}->{helper}->{log}->{$ntfyDevName}; + + if (defined($listenDevice->{$reading}) && $reading eq "state") + { + foreach my $gad (keys %{$listenDevice->{$reading}}) + { + my $gadCfg = $defs{$hash->{helper}->{gateway}}->{helper}->{config}->{$gad}; + + if($gadCfg->{type} eq 'log' && $gadCfg->{reading} eq $reading) + { + my ($converter, $p) = split(/\s+/, $gadCfg->{converter}, 2); + my $param; + $param->{cmd} = 'log'; + $param->{gad} = $gad; + $param->{device} = $ntfyDevName; + $param->{reading} = $reading; + $param->{size} = $gadCfg->{size}; + $param->{event} = $ntfyDev->{STATE}; + @{$param->{args}} = split(/\s*,\s*/, $p || ''); + fronthemDevice_DoConverter($hash, $converter, $param); + last; + } + } + } + } + } + } + } #of interest, device is in global (context fronthem parent device) list of known device->reading->gad ? - if (defined($hash->{helper}->{gateway}) && exists($defs{$hash->{helper}->{gateway}}->{helper}->{listen}->{$ntfyDevName})) + if (defined($hash->{helper}->{gateway}) && (exists($defs{$hash->{helper}->{gateway}}->{helper}->{listen}->{$ntfyDevName}) )) { my $max = int(@{$ntfyDev->{CHANGED}}); my $gateway = $hash->{helper}->{gateway}; # TODO getGateway my $listenDevice = $defs{$gateway}->{helper}->{listen}->{$ntfyDevName}; - + for (my $i = 0; $i < $max; $i++) { my $s = $ntfyDev->{CHANGED}[$i]; $s = "state: $s" if (($ntfyDevName ne 'global') && ($s !~ m/.+?:\s.*/)); @@ -360,8 +483,9 @@ fronthemDevice_Notify($$) # step back and see if there is a device-reading equal to $reading[0] # to prevent false interpreting of events like (state) T: 21 H: 50 @reading = ('state', $s) unless ( ($reading[0] eq 'state') || exists($ntfyDev->{READINGS}->{$reading[0]}) ); + if (defined($listenDevice->{$reading[0]})) - { + { #global list of all gad using it foreach my $gad (keys %{$listenDevice->{$reading[0]}}) { @@ -407,6 +531,7 @@ fronthemDevice_DoConverter(@) #check local permissions my $gad = $param->{gad}; my $cmd = $param->{cmd}; + #cmd == get||send: device is allowed to read the via that gad ? if ($cmd =~ /get|send/) { @@ -435,11 +560,37 @@ fronthemDevice_DoConverter(@) # return undef if pad_pin_special_cache_entry != extracted pin from key #} } + if ($cmd eq 'log') + { + if (!$hash->{helper}->{config}->{$gad}->{read} && (AttrVal($hash->{NAME}, 'whitelist', 'true') eq 'true')) + { + Log3 ($hash, 3, "$hash->{NAME} no read permission for $gad"); + return undef; + } + #TODO check pin assignment + #if (defined($hash->{helper}->{config}->{$gad}->{NAME_OF_GAD_THAT_IS_USED_TO_TEST})) + #{ + # return undef if pad_pin_special_cache_entry != extracted pin from key + #} + } + if ($cmd eq 'plot' || $cmd eq 'point') + { + if (!$hash->{helper}->{config}->{$gad}->{read} && (AttrVal($hash->{NAME}, 'whitelist', 'true') eq 'true')) + { + Log3 ($hash, 3, "$hash->{NAME} no read permission for $gad"); + return undef; + } + #TODO check pin assignment + #if (defined($hash->{helper}->{config}->{$gad}->{NAME_OF_GAD_THAT_IS_USED_TO_TEST})) + #{ + # return undef if pad_pin_special_cache_entry != extracted pin from key + #} + } if (eval $convStr) { #if there is a return value from converter, the converter may have chosen thats nothing to do, done by itself or a error is risen return undef if ($result eq 'done'); - return Log3 ($hash, 3, "$hash->{NAME}: $result"); + return Log3 ($hash, 1, "$hash->{NAME}: $result !!!"); } if ($@) { @@ -455,13 +606,14 @@ fronthemDevice_DoConverter(@) fronthemDevice_toDriver($hash, $msg); return undef; } - elsif ($cmd = 'rcv') + elsif ($cmd eq 'rcv') { my $device = $param->{device}; my $set = fronthemDevice_ConfigVal($hash, $gad, 'set'); # exit here if no set is given return undef unless $set; - $set =~ s/^state// if ($set eq 'state'); + #$set =~ s/^state// if ($param->{reading} eq 'state'); + $set =~ s/^state// if ($set eq 'state'); if ($set !~ /.*\$.*/) { fhem "set $device $set $param->{result}"; @@ -472,6 +624,26 @@ fronthemDevice_DoConverter(@) #TODO eval with vars } } + elsif ($cmd eq 'plot' || $cmd eq 'log' || $cmd eq 'url') + { + my $msg; + $msg->{receiver} = $hash->{NAME}; + $msg->{message}->{cmd} = $cmd; +# @{$msg->{message}->{items}} = @{$param->{gads}}?@{$param->{gads}}:($param->{gad}, $param->{gadval}); +# +# Workaround because statement above throws errors if parameters are missing +# + eval { + @{$msg->{message}->{items}} = @{$param->{gads}}?@{$param->{gads}}:($param->{gad}, $param->{gadval}); + } or do { + use Data::Dumper; + print "Unexpected error due to missing gad parameter:"; + print Dumper $msg; + return; + }; + fronthemDevice_toDriver($hash, $msg); + return undef; + } } sub @@ -497,7 +669,7 @@ fronthemDevice_fwDetail(@) my $result = ''; $result = "
\n"; - $result .= "\n"; + $result .= "
\n"; $result .= "\n"; $result .= "
\n"; $result .= "
\n"; @@ -510,7 +682,7 @@ fronthemDevice_fwDetail(@) $result .= "\n"; $result .= "
\n"; $result .= "
"; - $result .= "GAD Edit\n"; + $result .= "Edit Item\n"; $result .= "\n"; $result .= "\n"; $result .= "\n"; $result .= "
\n"; @@ -519,7 +691,7 @@ fronthemDevice_fwDetail(@) $result .= "
\n"; $result .= "
\n"; - + return $result; } @@ -534,7 +706,6 @@ fronthemDevice_ReadCfg(@) my $json_fh; open($json_fh, "<:encoding(UTF-8)", $cfgFile) and do { - #Log3 ($hash, 1, "$hash->{NAME}: Error loading cfg file $!"); local $/; $json_text = <$json_fh>; close $json_fh; @@ -661,7 +832,7 @@ fronthemDevice_fromDriver(@) return undef; } if ($msg->{message}->{cmd} eq 'item') - { + { $hash->{helper}->{cache}->{$msg->{message}->{id}}->{val} = $msg->{message}->{val}; $hash->{helper}->{cache}->{$msg->{message}->{id}}->{time} = gettimeofday(); $hash->{helper}->{cache}->{$msg->{message}->{id}}->{count} += 1; @@ -684,6 +855,48 @@ fronthemDevice_fromDriver(@) } return undef; } + if ($msg->{message}->{cmd} eq 'log') + { + $hash->{helper}->{log} = $msg->{message}->{items}; + foreach my $gad (@{$hash->{helper}->{log}}) + { + if (fronthemDevice_ConfigVal($hash, $gad->{item}, 'converter')) + { + my $param; + $param->{cmd} = 'log'; + $param->{gad} = $gad->{item}; + $param->{device} = fronthemDevice_ConfigVal($hash, $gad->{item}, 'device'); + $param->{reading} = fronthemDevice_ConfigVal($hash, $gad->{item}, 'reading'); + my ($converter, $p) = split(/\s+/, fronthemDevice_ConfigVal($hash, $gad->{item}, 'converter'), 2); + @{$param->{args}} = split(/\s*,\s*/, $p || ''); + fronthemDevice_DoConverter($hash, $converter, $param); + } + } + } + if ($msg->{message}->{cmd} eq 'plot') + { + $hash->{helper}->{plot} = $msg->{message}->{items}; + foreach my $gad (@{$hash->{helper}->{plot}}) + { + if (fronthemDevice_ConfigVal($hash, $gad->{item}, 'converter')) + { + my $param; + $param->{cmd} = 'plot'; + $param->{gad} = $gad->{item}; + $param->{device} = fronthemDevice_ConfigVal($hash, $gad->{item}, 'device'); + $param->{reading} = fronthemDevice_ConfigVal($hash, $gad->{item}, 'reading'); + $param->{mode} = $gad->{mode}; + $param->{start} = $gad->{start}; + $param->{end} = $gad->{end}; + $param->{count} = $gad->{count}; + $param->{interval} = $gad->{interval}; + $param->{updatemode} = $gad->{updatemode}; + my ($converter, $p) = split(/\s+/, fronthemDevice_ConfigVal($hash, $gad->{item}, 'converter'), 2); + @{$param->{args}} = split(/\s/, $p); + fronthemDevice_DoConverter($hash, $converter, $param); + } + } + } return undef; } @@ -697,3 +910,45 @@ fronthemDevice_toDriver(@) 1; + + +=pod +=item device +=item summary The fronthem Device-Connector +=item summary_DE fronthem Device-Connector +=begin html + +

+ +

+

+ fronthemDevice +

+
    + Define +
      + define <name> fronthemDevice <ip-address>
      +

    +
+ +=end html + +=begin html_DE + +

+ +

+

+ fronthemDevice +

+
    + Define +
      + define <name> fronthemDevice <ip-address>
      +

    +
+ +=end html_DE + +=cut + diff --git a/FHEM/99_fronthemUtils.pm b/FHEM/99_fronthemUtils.pm index 9371e4e..febc75a 100644 --- a/FHEM/99_fronthemUtils.pm +++ b/FHEM/99_fronthemUtils.pm @@ -1,10 +1,13 @@ ############################################## # $Id: 99_fronthemUtils.pm 0 2015-11-10 08:00:00Z herrmannj $ +# modified: 2018-04-14 00:00:00Z raman +# modified: 2022-11-27 11:00:00Z alkazaa and wvhn package main; use strict; use warnings; use JSON; +use Time::HiRes qw(gettimeofday); sub fronthemUtils_Initialize($$) @@ -14,54 +17,409 @@ fronthemUtils_Initialize($$) sub fronthem_decodejson($) { return decode_json($_[0]); -} +} sub fronthem_encodejson($) { return encode_json($_[0]); -} +} + +sub +fronthem_ActualTimeStamp($) +{ + my ( $time ) = @_; + $time = $time * 1000; + return sprintf("%.0f", $time) * 1; +} +sub +fronthem_TimeStamp($) +{ + my ($date) = @_; + my ($year,$mon,$mday,$hour,$min,$sec) = split(/[\s_:-]+/, $date); + my $time = timelocal($sec,$min,$hour,$mday,$mon-1,$year); + return $time * 1000; +} + +# evaluates smartVISU duration format with up to 4 digits (instead of 2) +# loops through the parameter with more terms e.g. "1y 3m 5d 10h" and sums the results up +# Bonus: a leading term with 0 like e.g. "0w" will not contribute to the total time but it can +# be used to select the aggregation period for aggregation modes 'avg', 'min', 'sum' etc. in sub fronthem_Duration +sub +fronthem_Time($$) +{ + my ($time, $period) = @_; + + # allow Unix timestamp in milliseconds as well as "now" + if ($period =~ /^(.*?)\s*(\d{13})/) { + return int($2/1000 + 0.5); + } + if ($period eq "now") { + return $time; + } + my @periods = split(' ', $period); # split parameters like "1y 3m 5d 10h" into an array like ("1y","3m","5d","10h") + foreach my $period (@periods) # and loop over the array elements + { + if ($period =~ /^([-+]?\d{1,4})(s|i|h|d|w|m|y)/) + { + my $newTime = 0; + if ($2 eq "s") + { + $newTime = $1; + } + elsif ($2 eq "i") + { + $newTime = $1 * 60; + } + elsif ($2 eq "h") + { + $newTime = $1 * 3600; + } + elsif ($2 eq "d") + { + $newTime = $1 * 3600 * 24; + } + elsif ($2 eq "w") + { + $newTime = $1 * 3600 * 24 * 7; + } + elsif ($2 eq "m") + { + $newTime = $1 * 3600 * 24 * 30; + } + elsif ($2 eq "y") + { + $newTime = $1 * 3600 * 24 * 365; + } + $time -= $newTime; + } + } + return $time; +} + +# select the database evaluation mode from first term of smartVISU duration, e.g. "0d" is daystats but "1d" is hourstats +# available: timerange hourstats daystats weekstats monthstats yearstats +sub +fronthem_Duration($) +{ + my ($duration) = @_; + if ($duration =~ /^(\d{1,4})(s|i|h|d|w|m|y)/) + { + if ($2 eq "s" || $2 eq "i") + { + return "timerange"; + } + elsif ($2 eq "h") + { + if ($1 eq "1") { + return "timerange"; + } + return "hourstats"; + } + elsif ($2 eq "d") + { + if ($1 eq "1") { + return "hourstats"; + } + return "daystats"; + } + elsif ($2 eq "w") + { + if ($1 eq "1") { + return "daystats"; + } + return "weekstats"; + } + elsif ($2 eq "m") + { + if ($1 eq "1") { + return "weekstats"; + } + return "monthstats"; + } + elsif ($2 eq "y") + { + if ($1 eq "1") { + return "monthstats"; + } + return "yearstats"; + } + } + return "timerange"; +} + +sub fronthem_sunrise($) { + my ($hour,$min,$sec) = split(/:/, sunrise_abs($_[0])); + return $hour . ':' . $min; +} + +sub fronthem_sunset($) { + my ($hour,$min,$sec) = split(/:/, sunset_abs($_[0])); + return $hour . ':' . $min; +} ############################################################################### # # Umsetzen der UZSU-Settings für ein device # ############################################################################### -sub UZSU_execute($$) -{ - my ($device, $uzsu) = @_; - - $uzsu = decode_json($uzsu); - - my $weekdays_part = " "; - for(my $i=0; $i < @{$uzsu->{list}}; $i++) { - my $weekdays = $uzsu->{list}[$i]->{rrule}; - $weekdays = substr($weekdays,18,50); - if (($uzsu->{list}[$i]->{active})) { - if ($uzsu->{list}[$i]->{event} eq 'time'){ - $weekdays_part = $weekdays_part.' '.$weekdays.'|'.$uzsu->{list}[$i]->{time}.'|'.$uzsu->{list}[$i]->{value}; - } - else { - # Bugfix below: because sunset_abs from 99_sunrise_el does not work if max-time = "" - if ($uzsu->{list}[$i]->{timeMin} ne '' and $uzsu->{list}[$i]->{timeMax} ne '') { - $weekdays_part = $weekdays_part.' '.$weekdays.'|{'.$uzsu->{list}[$i]->{event}.'_abs("REAL",'.$uzsu->{list}[$i]->{timeOffset} * 60 .',"'.$uzsu->{list}[$i]->{timeMin}.'","'.$uzsu->{list}[$i]->{timeMax}.'")}|'.$uzsu->{list}[$i]->{value}; - } - else { - $weekdays_part = $weekdays_part.' '.$weekdays.'|{'.$uzsu->{list}[$i]->{event}.'_abs("REAL",'.$uzsu->{list}[$i]->{timeOffset} * 60 .',,)}|'.$uzsu->{list}[$i]->{value}; - } - } - } - } - fhem('defmod wdt_uzsu_'.$device.' WeekdayTimer '.$device.' en '.$weekdays_part); - if ($uzsu->{active}){ - fhem('attr wdt_uzsu_'.$device.' disable 0'); -} else { - fhem('attr wdt_uzsu_'.$device.' disable 1'); -} - fhem('attr wdt_uzsu_'.$device.' room UZSU'); - #fhem('save'); # use only if you want to save WDT settings immediately. - +# +# Nicht vergessen! In FHEM notify definieren! +# +# define UZSU notify .*:uzsu:.* { UZSU_execute($NAME, $EVTPART1) } +# +# oder wenn Einstellungen gespeichert werden sollen: +# +# define UZSU notify .*:uzsu:.* { UZSU_execute($NAME, $EVTPART1, 'save') } +# +# Damit die Einstellungen gespeichert werden können, folgendes Attribut setzen: +# attr global autosave 1 +# +# +sub UZSU_execute($$;$) +{ + my ($device, $uzsu, $save) = @_; + $uzsu = decode_json($uzsu); + + fhem('delete wdt_uzsu_'.$device.'.*'); + + for (my $i = 0; $i < @{$uzsu->{list}}; $i++) { + if ($uzsu->{list}[$i]->{active}) { + my %rrule = UZSU_getRrules($uzsu->{list}[$i]{rrule}); + my $holiday = $uzsu->{list}[$i]{holiday}{weekend} && $uzsu->{list}[$i]{holiday}{workday} ? '' : $uzsu->{list}[$i]{holiday}{weekend} ? $rrule{'BYDAY'} ne '' ? ',$we' : '$we' : $uzsu->{list}[$i]{holiday}{workday} ? $rrule{'BYDAY'} ne '' ? ',!$we' : '!$we' : ''; + my $time = $uzsu->{list}[$i]{event} eq "time" ? $uzsu->{list}[$i]{time} : '{'.$uzsu->{list}[$i]->{event} . '_abs("REAL"' . ($uzsu->{list}[$i]->{timeOffset} ne '' ? ',' . $uzsu->{list}[$i]->{timeOffset} * 60 : '') . ($uzsu->{list}[$i]->{timeMin} ne '' ? ', "' . $uzsu->{list}[$i]->{timeMin} . '"' : '') . ($uzsu->{list}[$i]->{timeMax} ne '' ? ', "' . $uzsu->{list}[$i]->{timeMax} . '"' : '') . ')}'; + my $condition = UZSU_getCommand($uzsu->{list}[$i]{condition}); + + my $weekdayTimer = $rrule{'BYDAY'} . $holiday . ($rrule{'BYDAY'} ne '' || $holiday ne '' ? "|" : '') . $time . "|" . $uzsu->{list}[$i]{value}; + my $delayedExec = UZSU_getCommand($uzsu->{list}[$i]{delayedExec}); + + fhem('defmod wdt_uzsu_' . $device . '_' . $i . ' WeekdayTimer ' . $device . ' en ' . $weekdayTimer . $condition); + fhem('attr wdt_uzsu_' . $device . '_' . $i . ' room UZSU'); + fhem('attr wdt_uzsu_' . $device . '_' . $i . ' group ' . $device); + fhem('setreading wdt_uzsu_' . $device . '_' . $i . ' weekdays ' . $weekdayTimer); + fhem('defmod rg_uzsu_' . $device . ' readingsGroup wdt_uzsu_' . $device . '.*'); + fhem('attr rg_uzsu_' . $device . ' room UZSU'); + if ($delayedExec) { + fhem('attr wdt_uzsu_' . $device . '_' . $i . ' delayedExecutionCond ' . $delayedExec); + } + } + } + if ($uzsu->{active}) { + fhem('attr NAME=wdt_uzsu_' . $device . '_.*' . ' disable 0'); + } + else { + fhem('attr NAME=wdt_uzsu_' . $device . '_.*' . ' disable 1'); + } + fhem('save', 1) if ($save eq 'save'); +} + +############################################################################### +# +# UZSU-Hilfsfunktionen +# +############################################################################### +sub UZSU_getRrules($) +{ + my ($split) = @_; + my @a = split(/;/, $split); + my %hash; + foreach (@a){ + my ($key,$val) = split(/=/, $_); + $hash{$key} = $val; + } + + if (exists($hash{'BYDAY'})) + { + return %hash; + } + else { + $hash{'BYDAY'} = ''; + return %hash; + } +} + + +sub UZSU_getCommand($) +{ + my ($command) = @_; + + if($command->{active} && $command->{type} ne "String") + { + if($command->{deviceString} =~ /^AttrVal|InternalVal|ReadingsVal\("\S+"\s?,\s?"\S+"\s?,\s?"\S*"\)$/) + { + return ' (' . $command->{deviceString} . ' ' . $command->{type} . ' "' . $command->{value} . '")'; + } + elsif($command->{deviceString} =~ /^Value\("\S+"\)$/) + { + return ' (' . $command->{deviceString} . ' ' . $command->{type} . ' "' . $command->{value} . '")'; + } + } + elsif($command->{active} && $command->{type} eq "String" && $command->{deviceString} ne '') + { + if($command->{deviceString} =~ /^fhem ".+"( if\(.+\))?$/) + { + return ' {' . $command->{deviceString} . '}'; + } + else + { + return ' (' . $command->{deviceString} . ')'; + } + } + return ''; +} + + +############################################################################### +# +# Sammeln der Log-Daten in einem Dummy-Device +# (Dummy-Device wir automatisch erstellt, wenn für zu überwachende Devices +# svReadins gesetzt wird - Erklärung siehe unten!) +# +# +############################################################################### +# +# Nicht vergessen! In FHEM notify definieren! +# +# define Log notify .* { Log_SetList($NAME,$EVENT) } +# +# und folgende Attribute in "attr global userattr" hinzufügen: +# +# svEvents svReadins svRegex +# +# svReadins: Für das Device das Reading setzen, das überwacht werden soll. +# z.B. wenn state überwacht werden soll: +# +# attr svReadins state battery:Batterie +# +# z.B. wenn state und battery überwacht und +# battery in Batterie geändert werden soll: +# +# attr svReadins state battery:Batterie +# +# Zur Übersicht der überwachten Devices kann eine readingsGroup +# angelegt werden: +# +# define rg_svLog readingsGroup , .*:?svReadins,? +# attr rg_svLog nameStyle style="color:red;;font-weight:bold" +# +# svEvents: Wird automatisch für das Dummy-Device (Log-Liste) angelegt, kann aber +# an die jeweiligen Bedürdnisse angepasst werden. +# +# svRegex: Hier kann für das Dummy-Device (Log-Liste) eine Liste mit Events, +# die ersetzt werden sollen, angegeben werden. +# +# z.B. attr svRegex low:schwach opened:geöffnet closed:geschlossen connected:verbunden disconnected:getrennt +# +sub +Log_SetList($$) +{ + my ($device, $event) = @_; + my $reading = ""; + my $message = ""; + + if ($event =~ qr/(.*?):\s+(.*)/p) { + $reading = $1; + $message = $2; + } else { + $reading = "state"; + $message = $event; + } + + my @info = split(" ", AttrVal($device, "svReadins", "")); + my $list = ""; + my $newReading = ""; + foreach (@info) { + if ($_ =~ qr/(.*):(.+)/p) { + $list = $1; + $newReading = $2; + } else { + $list = $_; + } + + if ($list eq $reading) + { + fhem ("set" . " svLog_" . $list . " block"); + + if ($newReading ne "") { + $reading = $newReading; + } + + if(!$defs{"svLog_" . $list}) { + fhem ("define" . " svLog_" . $list . " dummy"); + fhem("attr" . " svLog_" . $list . " svEvents info:alive,ok,online,[O|o]pened,[C|c]onnected,[C|c]losed,[G|g]eschlossen warning:low,[O|o]pen,[O|o]ffen,offline,overload,unreachable error:dead,[D|d]isconnected,unknown,IOerr"); + } + + my @state = split(" ", AttrVal("svLog_" . $list, "svEvents", "")); + foreach (@state) { + if ($_ =~ qr/(.*):(.+)/p) { + my $z = $2; + $z =~ s/,/|/g; + + if($reading eq "state") { + fhem ("setreading" . " svLog_" . $list . " " . $device . " " . $message); + } elsif ($message =~ /.*:?\s?($z)/) { + fhem ("setreading" . " svLog_" . $list . " " . $device . " " . $reading . " " . $1); + } else { + fhem ("setreading" . " svLog_" . $list . " " . $device . " " . $reading . " " . $message); + } + } + } + fhem ("set" . " svLog_" . $list . " send"); + } + } +} + +############################################################################### +# +# Log-Hilfsfunktionen +# +############################################################################### +sub +Log_GetList($) +{ + my ($name) = @_; + my $list = ""; + my $level = "info"; # info = green, error = red, warning = yellow (levels for smartvisu status.log) + + my @info = split(" ", AttrVal($name, "svRegex", "")); + my @state = split(" ", AttrVal($name, "svEvents", "")); + + my $i = 1; + foreach my $key (sort { $b cmp $a } keys %{$defs{$name}{READINGS}}) + { + my $reading = $defs{$name}{READINGS}{$key}{VAL}; + + foreach (@state) { + if ($_ =~ qr/(.*):(.+)/p) { + my $newLevel = $1; + my $states = $2; + $states =~ s/,/|/g; + + if($reading =~ /.*:?\s?($states)/) + { + $level = $newLevel; + + foreach (@info) { + if ($_ =~ qr/(.*):(.+)/p) { + my $a = $1; + my $b = $2; + $reading =~ s/$a/$b/g; + } + } + } + } + } + + my $device = AttrVal( $key, "alias", $key ); + my $timestring = $defs{$name}{READINGS}{$key}{TIME}; + $timestring =~ s/\s/T/g; # Compatibility for older Browser "2018-03-10T18:34:53" + $list .= '{"message":"'. $device . " - " . $reading . '","time":"' . $timestring . '","level":"' . $level . '"}' . ($i == keys(%{$defs{$name}{READINGS}}) ? "" : ",") if ($key ne "state"); + + $i++; + } + return $list; } + package fronthem; use strict; use warnings; @@ -69,7 +427,7 @@ use warnings; ############################################################################### # For use with UZSU-Widget in SV and UZSU-notify in fhem # Setreading a device reading using JSON conversion (gadval => reading=decode_json() => setval => encode_json(reading) ) -# the reading ("uzsu") must be created manually for each UZSU-enabled device in fhem using "setreading uzsu {}" +# the reading ("uzsu") must be created manually for each UZSU-enabled device in fhem using "setreading uzsu {"active":false,"list":[]} # in the fhem commandline ############################################################################### @@ -83,7 +441,7 @@ sub UZSU(@) my $device = $param->{device}; my $reading = $param->{reading}; my $event = $param->{event}; - + my @args = @{$param->{args}}; my $cache = $param->{cache}; @@ -94,16 +452,21 @@ sub UZSU(@) if ($param->{cmd} eq 'send') { $param->{gad} = $gad; - $param->{gadval} = main::fronthem_decodejson(main::ReadingsVal($device, $reading, '')); - $param->{gads} = []; + # we could initialize the JSON with the default but for security reasons the user should do this willingly + # $param->{gadval} = main::fronthem_decodejson(main::ReadingsVal($device, $reading, '{"active": false, "list": []}')); + $param->{gadval} = main::fronthem_decodejson(main::ReadingsVal($device, $reading, '{}')); + $param->{gads} = []; + $param->{gadval}->{sunrise} = main::fronthem_sunrise("REAL"); + $param->{gadval}->{sunset} = main::fronthem_sunset("REAL"); + return undef; } elsif ($param->{cmd} eq 'rcv') { - $gadval = main::fronthem_encodejson($gadval); - $gadval =~ s/;/;;/ig; - $param->{result} = main::fhem("setreading $device $reading $gadval"); - $param->{results} = []; + $gadval = main::fronthem_encodejson($gadval); + $gadval =~ s/;/;;/ig; + $param->{result} = main::fhem("setreading $device $reading $gadval"); + $param->{results} = []; return 'done'; } elsif ($param->{cmd} eq '?') @@ -113,6 +476,54 @@ sub UZSU(@) return undef; } +############################################################################### +# +# connect fhem device with JSON data +# +############################################################################### + +sub JSONdata(@) +{ + my ($param) = @_; + my $cmd = $param->{cmd}; + my $gad = $param->{gad}; + my $gadval = $param->{gadval}; + + my $device = $param->{device}; + my $reading = $param->{reading}; + my $event = $param->{event}; + + my @args = @{$param->{args}}; + my $cache = $param->{cache}; + + if ($param->{cmd} eq 'get') + { + $param->{cmd} = 'send'; + } + if ($param->{cmd} eq 'send') + { + $param->{gad} = $gad; + $param->{gadval} = main::fronthem_decodejson(main::ReadingsVal($device, $reading, '{}')); + $param->{gads} = []; + + return undef; + } + elsif ($param->{cmd} eq 'rcv') + { + + $gadval = main::fronthem_encodejson($gadval); + $gadval =~ s/;/;;/ig; + $param->{result} = main::fhem("setreading $device $reading $gadval"); + $param->{results} = []; + return 'done'; + } + elsif ($param->{cmd} eq '?') + { + return 'usage: JSON'; + } + return undef; +} + ############################################################################### # # connect fhem device with on|off state to switch @@ -128,7 +539,7 @@ sub AnAus(@) my $device = $param->{device}; my $reading = $param->{reading}; my $event = $param->{event}; - + my @args = @{$param->{args}}; my $cache = $param->{cache}; @@ -140,14 +551,14 @@ sub AnAus(@) if ($param->{cmd} eq 'send') { $param->{gad} = $gad; - $param->{gadval} = (lc($event) eq 'an')?'1':'0'; - $param->{gads} = []; + $param->{gadval} = (lc($event) eq 'an')?'1':'0'; + $param->{gads} = []; return undef; } elsif ($param->{cmd} eq 'rcv') { - $param->{result} = ($gadval)?'an':'aus'; - $param->{results} = []; + $param->{result} = ($gadval)?'an':'aus'; + $param->{results} = []; return undef; } elsif ($param->{cmd} eq '?') @@ -157,16 +568,389 @@ sub AnAus(@) return undef; } +############################################################################### +# +# direct invert numerical values +# @param max +# +############################################################################### +sub NumInvert(@) +{ + my ($param) = @_; + my $cmd = $param->{cmd}; + my $gad = $param->{gad}; + my $gadval = $param->{gadval}; + + my $device = $param->{device}; + my $reading = $param->{reading}; + my $event = $param->{event}; + + my @args = @{$param->{args}}; + my $cache = $param->{cache}; + my $max = 100; + if (defined($args[0])) + { + $max = $args[0]; + } + + if ($param->{cmd} eq 'get') + { + $event = ($reading eq 'state')?main::Value($device):main::ReadingsVal($device, $reading, ''); + $param->{cmd} = 'send'; + } + if ($param->{cmd} eq 'send') + { + return "NumInvert converter got [$event] from $device, $reading but cant interpret it as a number" unless $event =~ /\D*([+-]{0,1}\d+[.]{0,1}\d*).*?/; + $event = $max - $1; + $param->{gad} = $gad; + $param->{gadval} = $event; + $param->{gads} = []; + return undef; + } + elsif ($param->{cmd} eq 'rcv') + { + return "NumInvert converter received [$gadval] but cant interpret it as a number" unless $gadval =~ /\D*([+-]{0,1}\d+[.]{0,1}\d*).*?/; + $gadval = $max - $1; + $param->{result} = $gadval; + $param->{results} = []; + return undef; + } + elsif ($param->{cmd} eq '?') + { + return 'usage: NumInvert'; + } + return undef; +} + +############################################################################### +# +# Send readings collected in dummy device as status.log +# +############################################################################### +sub Log(@) +{ + my ($param) = @_; + my $cmd = $param->{cmd}; + my $gad = $param->{gad}; + my $gadval = $param->{gadval}; + my $device = $param->{device}; + my $reading = $param->{reading}; + my $event = $param->{event}; + + my @args = @{$param->{args}}; + my $cache = $param->{cache}; + + if ($param->{cmd} eq 'get') + { + $param->{cmd} = 'send'; + } + if ($param->{cmd} eq 'log') + { + $param->{cmd} = 'send'; + } + if ($param->{cmd} eq 'send') + { + use Encode qw(decode encode); + my $list = main::Log_GetList($device); + $list = encode("utf8", '[' . $list . ']'); + $param->{gad} = $gad; + $param->{gadval} = main::fronthem_decodejson($list); + $param->{gads} = []; + + return undef; + } + elsif ($param->{cmd} eq 'rcv') + { + $param->{result} = $gadval; + $param->{results} = []; + return undef; + } + elsif ($param->{cmd} eq '?') + { + return 'usage: log'; + } + return undef; +} + +############################################################################### +# +# Plot data from fhem database +# @param +# +############################################################################### +sub Plot(@) +{ + my ($param) = @_; + my $cmd = $param->{cmd}; + my $gad = $param->{gad}; + my $gadval = $param->{gadval}; + + my $device = $param->{device}; + my $reading = $param->{reading}; + my $event = $param->{event}; + + my $mode = $param->{mode}; + my $start = $param->{start}; + my $end = $param->{end}; + my $count = $param->{count}; + my $interval = $param->{interval}; + my $updatemode = $param->{updatemode}; + + my @args = @{$param->{args}}; + my $cache = $param->{cache}; + + return "error $gad: converter syntax: missing paramter: name of database" if (@args != 1); + + if ($param->{cmd} eq 'get') { + $param->{cmd} = 'send'; + } + if ($param->{cmd} eq 'plot') { + $param->{cmd} = 'send'; + } + if ($param->{cmd} eq 'send') { + my @data = { + "item" => $gad . '.' . $mode . '.' . $start . '.' . $end . '.' . $count, + "updatemode" => $updatemode, + "plotdata" => [], + }; + + if ($updatemode eq 'point') { + if ($mode eq "raw") { + push(@{$data[0]->{plotdata}[0]}, main::fronthem_ActualTimeStamp(main::gettimeofday())); + push(@{$data[0]->{plotdata}[0]}, sprintf("%#.4f", $event) * 1); + } + elsif ($mode eq "avg") { + + } + elsif ($mode eq "sum") { + + } + elsif ($mode eq "min") { + + } + elsif ($mode eq "max") { + + } + } + else { + my $from = main::FmtDateTime(main::fronthem_Time(time(), $start)); + $from =~ s/ /_/ig; + my $to = main::FmtDateTime(main::fronthem_Time(time(), $end)); + $to =~ s/ /_/ig; + + my $duration = "timerange"; + if ($mode ne "raw") { + $duration = main::fronthem_Duration($start); + } + + my $string = main::CommandGet(undef, $args[0] . ' - webchart ' . $from . ' ' . $to . ' ' . $device . ' ' . $duration . ' TIMESTAMP ' . $reading); + my @response = main::fronthem_decodejson($string); + + foreach my $data (@response) { + my $i = 0; + foreach my $row (@{$data->{data}}) { + if ($mode eq "raw") { # [TIMESTAMP,VALUE] + push(@{$data[0]->{plotdata}[$i]}, main::fronthem_TimeStamp($row->{TIMESTAMP})); + push(@{$data[0]->{plotdata}[$i]}, sprintf("%#.4f", $row->{VALUE}) * 1); + } + elsif ($mode eq "avg") { # [TIMESTAMP,AVG] + push(@{$data[0]->{plotdata}[$i]}, main::fronthem_TimeStamp($row->{TIMESTAMP})); + push(@{$data[0]->{plotdata}[$i]}, $duration eq "timerange" ? sprintf("%#.4f", $row->{VALUE}) * 1 : sprintf("%#.4f", $row->{AVG}) * 1); + } + elsif ($mode eq "sum") { # [TIMESTAMP,SUM] + push(@{$data[0]->{plotdata}[$i]}, main::fronthem_TimeStamp($row->{TIMESTAMP})); + push(@{$data[0]->{plotdata}[$i]}, $duration eq "timerange" ? sprintf("%#.4f", $row->{VALUE}) * 1 : sprintf("%#.4f", $row->{SUM}) * 1); + } + elsif ($mode eq "min") { # [TIMESTAMP,MIN] + push(@{$data[0]->{plotdata}[$i]}, main::fronthem_TimeStamp($row->{TIMESTAMP})); + push(@{$data[0]->{plotdata}[$i]}, $duration eq "timerange" ? sprintf("%#.4f", $row->{VALUE}) * 1 : sprintf("%#.4f", $row->{MIN}) * 1); + } + elsif ($mode eq "max") { # [TIMESTAMP,MAX] + push(@{$data[0]->{plotdata}[$i]}, main::fronthem_TimeStamp($row->{TIMESTAMP})); + push(@{$data[0]->{plotdata}[$i]}, $duration eq "timerange" ? sprintf("%#.4f", $row->{VALUE}) * 1 : sprintf("%#.4f", $row->{MAX}) * 1); + } + $i++; + } + } + } + $param->{gads} = [@data]; + return undef; + } + elsif ($param->{cmd} eq 'rcv') { + $param->{result} = $gadval; + $param->{results} = []; + return undef; + } + elsif ($param->{cmd} eq '?') { + return 'usage: plot'; + } + return undef; +} + +############################################################################### +# +# Plot data from fhem filelog +# @param +# +############################################################################### +sub Plotfile(@) +{ + my ($param) = @_; + my $cmd = $param->{cmd}; + my $gad = $param->{gad}; + my $gadval = $param->{gadval}; + + my $device = $param->{device}; + my $reading = $param->{reading}; + my $event = $param->{event}; + + my $mode = $param->{mode}; + my $start = $param->{start}; + my $end = $param->{end}; + my $count = $param->{count}; + my $interval = $param->{interval}; + my $updatemode = $param->{updatemode}; + + my @args = @{$param->{args}}; + my $cache = $param->{cache}; + + return "error $gad: converter syntax: missing paramter: name of database" if (@args < 1 || @args > 4); + + if ($param->{cmd} eq 'get') { + $param->{cmd} = 'send'; + } + if ($param->{cmd} eq 'plot') { + $param->{cmd} = 'send'; + } + if ($param->{cmd} eq 'send') { + my @data = { + "item" => $gad . '.' . $mode . '.' . $start . '.' . $end . '.' . $count, + "updatemode" => $updatemode, + "plotdata" => [], + }; + + my $column = 3; + if ($reading eq "state") { + $reading = ""; + } elsif($reading ne "state" && @args == 1) { + $column = 4; + } elsif(@args > 1) { + $column = $args[1] * 1; + if ($args[2]) { + $reading = $args[2]; + } + } + my $regex = $args[3] ? $args[3] : ""; + + if ($updatemode eq 'point') { + if (!$args[3]) { + push(@{$data[0]->{plotdata}[0]}, main::fronthem_ActualTimeStamp(main::gettimeofday())); + push(@{$data[0]->{plotdata}[0]}, sprintf("%#.4f", $event) * 1); + } + } + else { + my $from = main::FmtDateTime(main::fronthem_Time(time(), $start)); + $from =~ s/ /_/ig; + my $to = main::FmtDateTime(main::fronthem_Time(time(), $end)); + $to =~ s/ /_/ig; + + my $string = main::fhem("get " . $args[0] . " - - " . $from . " " . $to ." " . $column .":" . $reading . ":0:" . $regex, 1); + my @response = split("\n", $string); + pop @response; + + for (my $i = 0; $i < @response; $i++) { + $response[$i] =~ /([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}:[0-9]{2}:[0-9]{2})\s+([0-9\.-]+)/; + push(@{$data[0]->{plotdata}[$i]}, main::fronthem_TimeStamp($1)); + push(@{$data[0]->{plotdata}[$i]}, sprintf("%#.4f", $2) * 1); + } + } + $param->{gads} = [@data]; + return undef; + } + elsif ($param->{cmd} eq 'rcv') { + $param->{result} = $gadval; + $param->{results} = []; + return undef; + } + elsif ($param->{cmd} eq '?') { + return 'usage: plotfile'; + } + return undef; +} 1; =pod +=item helper +=item summary fronthem utility functions +=item summary_DE fronthem Hilfsfunktionen =begin html - -

fronthemUtils

-
    -
+

+ +

+

+ fronthemUtils +

+
    + This is a collection of converter functions that can be used with + fronthemDevice
    +
    +
    + Defined converter functions

    +
      +
    • AnAus
      invert state values an|aus to 0|1

    • +
    • JSON
      send / receive readings as JSON objects

    • +
    • Log
      send readings collected in dummy device as status.log

    • +
    • NumInvert
      direct invert of numerical values

    • +
    • Plot
      Plot data from fhem database
      + parameter for converter: Plot <name of database>
      + For MySQL databases the averaging mode allows time-weighted averaging.
      + The averaging time is specified by the tmin parameter in plot.period.
      + For details see here and other contribution in that thread +

    • +
    • Plotfile
      Plot data from fhem filelog
      + parameter for converter: Plotfile <column> <regex> +

    • +
    • UZSU
      for the control objects of the UZSU-Widget in smartVISU
      + attr global autosave must be set to 1 ) +

    • +
    +
+ =end html + +=begin html_DE + +

+ +

+

+ fronthemUtils +

+
    + Sammlung an Converter-Functionen, die mit + fronthemDevice eingesetzt werden können
    +
    +
    + Converter-Functionen

    +
      +
    • AnAus
      wandelt die Werte an|aus in 0|1 um und umgekehrt

    • +
    • JSON
      sendet / empfängt Readings als JSON-Objekte

    • +
    • Log
      Sendet Readings, die in einem Dummy-Device gesammelt werden als status.log

    • +
    • NumInvert
      wandelt numerische Werte direkt um

    • +
    • Plot
      Plot-Daten aus der FHEM-Datenbank
      + Parameter für converter: Plot <name of database> +

    • +
    • Plotfile
      Plot-Daten aus filelog von FHEM
      + Parameter für converter: Plotfile <column> <regex> +

    • +
    • UZSU
      für Schaltzeiten-Objekte des UZSU-Widgets in smartVISU
      + attr global autosave muss dafür auf den Wert 1 gesetzt werden) +

    • +
    +
+ +=end html_DE =cut diff --git a/FHEM/fhwebsocket.pm b/FHEM/fhwebsocket.pm index c9588b6..f5ec406 100644 --- a/FHEM/fhwebsocket.pm +++ b/FHEM/fhwebsocket.pm @@ -63,7 +63,7 @@ sub start { if ($fh == $self->{listen}) { my $sock = $self->{listen}->accept; next unless $sock; - my $conn = new fronthem::WebSocket::Server::Connection(socket => $sock, server => $self); + my $conn = new fronthem::WebSocket::Server::Connection(socket => $sock, server => $self, max_send_size => $self->{max_send_size}); $self->{conns}{$sock} = {conn=>$conn, lastrecv=>time}; $self->{select_readable}->add($sock); $self->{on_connect}($self, $conn); diff --git a/README.md b/README.md index 020e8b7..af277ca 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,11 @@ # fronthem -fronthem websocket Schnittstelle für FHEM +fronthem websocket Schnittstelle für FHEM zur Verbindung mit smartVISU + +# Wiki +https://wiki.fhem.de/wiki/Kategorie:Fronthem/smartVISU + +# Support Forum +https://forum.fhem.de/index.php/board,72.0.html + +# Update in FHEM +update force https://raw.githubusercontent.com/wvhn/fronthem/master/controls_fronthem.txt diff --git a/controls_fronthem.txt b/controls_fronthem.txt index 2b77c35..00cca95 100644 --- a/controls_fronthem.txt +++ b/controls_fronthem.txt @@ -1,8 +1,9 @@ -UPD 2021-03-21_10:06:23 21901 FHEM/01_fronthem.pm -UPD 2021-03-21_10:06:09 21015 FHEM/31_fronthemDevice.pm -UPD 2015-02-13_21:23:35 7435 FHEM/fhwebsocket.pm +UPD 2026-02-24_15:02:24 29329 FHEM/01_fronthem.pm +UPD 2022-11-24_15:04:07 28957 FHEM/31_fronthemDevice.pm +UPD 2026-02-24_15:04:18 29439 FHEM/99_fronthemUtils.pm +UPD 2026-02-24_15:04:48 7476 FHEM/fhwebsocket.pm UPD 2019-01-03_11:22:09 9811 FHEM/fhconverter.pm -UPD 2017-03-16_14:28:59 13825 www/pgm2/fronthemEditor.js +UPD 2022-09-08_18:55:13 14158 www/pgm2/fronthemEditor.js UPD 2015-01-16_11:51:34 462 www/images/default/arrow-down.svg UPD 2015-01-16_11:51:34 464 www/images/default/arrow-up.svg UPD 2015-01-16_11:51:34 706 www/images/default/desktop.svg diff --git a/www/pgm2/fronthemEditor.js b/www/pgm2/fronthemEditor.js index 1581cc1..1afac2e 100644 --- a/www/pgm2/fronthemEditor.js +++ b/www/pgm2/fronthemEditor.js @@ -64,7 +64,7 @@ function sveRefreshGADList(device, gadList) { keys.sort(); insert.push(''); insert.push(''); - insert.push(''); + insert.push(''); //$.each(gad, function(i, item) { // insert.push(''); //}); @@ -149,6 +149,16 @@ function sveShowGADEditor(device, gadName, gadItem) { $('#gadeditcontainer').finish(); sveGADEditorItem(device, gadName, gad); break; + case 'log': + console.log('log'); + $('#gadeditcontainer').finish(); + sveGADEditorItem(device, gadName, gad); + break; + case 'plot': + console.log('plot'); + $('#gadeditcontainer').finish(); + sveGADEditorItem(device, gadName, gad); + break; default: sveGADEditorTypeSelect(device, gadName, gad); break; @@ -179,8 +189,8 @@ function sveGADEditorItem(device, gadName, gad) { $('#gadeditor').append(''); $('#gadeditor').append(''); $('#gadeditor').append(''); - $('#gadeditor').append(''); - $('#gadeditor').append(''); + $('#gadeditor').append(''); + $('#gadeditor').append(''); if (gad.whitelist === 'false') $('.permission').hide(); @@ -316,6 +326,7 @@ function sveGADEdtorAddTypeSelect(device, gadName, gad) { $('#gadeditor').append(''); $('
gaddevicerw
itemdevicerw
' + i + 'nnn
' + 'cmd set' + '
  
permission for ' + device + '
read PIN GAD:
write PIN GAD:
read PIN Item:
write PIN Item:
' + 'mode' + '