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 + +
+define <name> fronthemdefine <name> fronthem| \n";
$result .= "\n";
@@ -510,7 +682,7 @@ fronthemDevice_fwDetail(@)
$result .= "\n";
$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
+
+
+ "; - $result .= "GAD Edit\n"; + $result .= "Edit Item\n"; $result .= "
+ fronthemDevice ++ + +=end html + +=begin html_DE + + ++ fronthemDevice ++ + +=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: +# +# attrfronthemUtils-
+ fronthemUtils ++
+ + + Defined converter functions +
+ + + + + + + + fronthemUtils ++
+ + + Converter-Functionen +
+ + + + + + +
| |||||||||||||||||||||||||||||