2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-04 05:16:45 +00:00

CHANGED: 70_ESCVP21net.pm: added cyclicConnect

git-svn-id: https://svn.fhem.de/fhem/trunk@25750 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
hapege 2022-02-27 14:41:04 +00:00
parent d756abdc2e
commit 6a22531cf6
3 changed files with 1375 additions and 170 deletions

View File

@ -1,5 +1,6 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it.
- bugfix: 70_ESCVP21net: added cyclicConnect
- feature: 76_mssDialog: Add option to define dialogues by file
- change: 76_mssDialog: PBP code restructured
- feature: 89_AndroidDB: Added new features

View File

@ -30,7 +30,11 @@
# fixed sporadic not-deleting of RUNNING_PID
# 1.01.10 fix sporadic offline message
# 1.01.11 fix receiving unexpected IMEVENT messages, fixed typos in help
#
# 1.01.12 improved state check
# 1.01.13 improved cleanup, fixed empty val in AdditionalSettings
# 1.01.14 fixed problem with DevIo "No route" sending fhem to 100% CPU
# 1.01.15 add debug options
# 1.01.16 add cyclicConnect to mitigate lost TCP connection issue
#
################################################################################
#
@ -64,7 +68,7 @@ use POSIX;
#use JSON::XS qw (encode_json decode_json);
my $version = "1.01.11";
my $version = "1.01.16";
my $missingModul = "";
eval "use JSON::XS qw (encode_json decode_json);1" or $missingModul .= "JSON::XS ";
@ -72,10 +76,17 @@ eval "use JSON::XS qw (encode_json decode_json);1" or $missingModul .= "JSON::XS
# key(s) in defaultsets and defaultresults will be overwritten,
# if <type>sets/<type>result defines the same key(s)
my %ESCVP21net_debugsets = (
"reRead" => ":noArg",
"encode" => ":noArg",
"decode" => ":noArg",
"PWSTATUS" => ":get"
"reRead" => ":noArg",
"encode" => ":noArg",
"decode" => ":noArg",
"PWSTATUS" => ":get",
"cleanup" => ":noArg",
"connect" => ":noArg",
"removeTimer" => ":noArg",
"closeDevice" => ":noArg",
"deleteNextOpen" => ":noArg",
"openDevice" => ":noArg",
"isOpen" => ":noArg"
);
my %ESCVP21net_defaultsets = (
@ -246,22 +257,22 @@ my %ESCVP21net_Scottyresult = (
# data for sets - needed to tranlate the "nice" commands to raw values
my %ESCVP21net_data = (
"4KENHANCE:off" => "00",
"4KENHANCE:FullHD" => "01",
"ASPECT:Normal" => "00",
"ASPECT:Auto20" => "20",
"ASPECT:Auto" => "30",
"ASPECT:Full" => "40",
"ASPECT:Zoom" => "50",
"AUDIO:Audio1" => "01",
"AUDIO:Audio2" => "02",
"AUDIO:USB" => "03",
"AUTOKEYSTONE:on" => "ON",
"AUTOKEYSTONE:off" => "OFF",
"AVOUT:projection" => "00",
"AVOUT:constantly" => "01",
"BTAUDIO:on" => "01",
"BTAUDIO:off" => "00",
"4KENHANCE:off" => "00",
"4KENHANCE:FullHD" => "01",
"ASPECT:Normal" => "00",
"ASPECT:Auto20" => "20",
"ASPECT:Auto" => "30",
"ASPECT:Full" => "40",
"ASPECT:Zoom" => "50",
"AUDIO:Audio1" => "01",
"AUDIO:Audio2" => "02",
"AUDIO:USB" => "03",
"AUTOKEYSTONE:on" => "ON",
"AUTOKEYSTONE:off" => "OFF",
"AVOUT:projection" => "00",
"AVOUT:constantly" => "01",
"BTAUDIO:on" => "01",
"BTAUDIO:off" => "00",
"CMODE:sRGB" => "01",
"CMODE:Normal" => "02",
"CMODE:Meeting" => "03",
@ -283,128 +294,128 @@ my %ESCVP21net_data = (
"CMODE:3D_Cinema" => "17",
"CMODE:3D_Dynamic" => "18",
"CMODE:DigitalCinema" => "22",
"FREEZE:on" => "ON",
"FREEZE:off" => "OFF",
"HREVERSE:Flip" => "ON",
"HREVERSE:Normal" => "OFF",
"ILLUM:on" => "01",
"ILLUM:off" => "00",
"LUMINANCE:high" => "00",
"LUMINANCE:low" => "01",
"LUMINANCE:normal" => "00",
"LUMINANCE:eco" => "01",
"LUMINANCE:medium" => "02",
"MCFI:off" => "00",
"MCFI:low" => "01",
"MCFI:normal" => "02",
"MCFI:high" => "03",
"MSEL:black" => "00",
"MSEL:blue" => "01",
"MSEL:user" => "02",
"MUTE:on" => "ON",
"MUTE:off" => "OFF",
"OVSCAN:off" => "00",
"OVSCAN:4%" => "02",
"OVSCAN:8%" => "04",
"OVSCAN:auto" => "A0",
"FREEZE:on" => "ON",
"FREEZE:off" => "OFF",
"HREVERSE:Flip" => "ON",
"HREVERSE:Normal" => "OFF",
"ILLUM:on" => "01",
"ILLUM:off" => "00",
"LUMINANCE:high" => "00",
"LUMINANCE:low" => "01",
"LUMINANCE:normal" => "00",
"LUMINANCE:eco" => "01",
"LUMINANCE:medium" => "02",
"MCFI:off" => "00",
"MCFI:low" => "01",
"MCFI:normal" => "02",
"MCFI:high" => "03",
"MSEL:black" => "00",
"MSEL:blue" => "01",
"MSEL:user" => "02",
"MUTE:on" => "ON",
"MUTE:off" => "OFF",
"OVSCAN:off" => "00",
"OVSCAN:4%" => "02",
"OVSCAN:8%" => "04",
"OVSCAN:auto" => "A0",
"PRODUCT:ModelName_off" => "00",
"PRODUCT:ModelName_on" => "01",
"PWR:on" => "ON",
"PWR:off" => "OFF",
"SIGNAL:none" => "00",
"SIGNAL:2D" => "01",
"SIGNAL:3D" => "02",
"SIGNAL:not_supported" => "03",
"SOURCE:HDMI1" => "30",
"SOURCE:HDMI2" => "A0",
"SOURCE:ScreenMirror" => "56",
"SOURCE:PC" => "10",
"SOURCE:Input1" => "10",
"SOURCE:Input2" => "20",
"SOURCE:Video" => "40",
"SOURCE:Video(RCA)" => "41",
"SOURCE:PC1" => "1F",
"SOURCE:PC2" => "2F",
"SOURCE:USB" => "52",
"SOURCE:LAN" => "53",
"SOURCE:Dsub" => "20",
"SOURCE:WirelessHD" => "D0",
"VREVERSE:Flip" => "ON",
"VREVERSE:Normal" => "OFF",
"WLPWR:WLAN_off" => "00",
"WLPWR:WLAN_on" => "01"
"PWR:on" => "ON",
"PWR:off" => "OFF",
"SIGNAL:none" => "00",
"SIGNAL:2D" => "01",
"SIGNAL:3D" => "02",
"SIGNAL:not_supported" => "03",
"SOURCE:HDMI1" => "30",
"SOURCE:HDMI2" => "A0",
"SOURCE:ScreenMirror" => "56",
"SOURCE:PC" => "10",
"SOURCE:Input1" => "10",
"SOURCE:Input2" => "20",
"SOURCE:Video" => "40",
"SOURCE:Video(RCA)" => "41",
"SOURCE:PC1" => "1F",
"SOURCE:PC2" => "2F",
"SOURCE:USB" => "52",
"SOURCE:LAN" => "53",
"SOURCE:Dsub" => "20",
"SOURCE:WirelessHD" => "D0",
"VREVERSE:Flip" => "ON",
"VREVERSE:Normal" => "OFF",
"WLPWR:WLAN_off" => "00",
"WLPWR:WLAN_on" => "01"
);
# hash for results from device, to transtale to nice readings
# e.g answer "POW 04" will be shown als "Standby (Net on)" in GUI
# will be enhanced at runtime with %<type>result
my %ESCVP21net_defaultresults = (
"4KENHANCE:00" => "off",
"4KENHANCE:01" => "FullHD",
"ASPECT:00" => "Normal",
"ASPECT:20" => "Auto20",
"ASPECT:30" => "Auto",
"ASPECT:40" => "Full",
"ASPECT:50" => "Zoom",
"ASPECT:70" => "Wide",
"AUDIO:01" => "Audio1",
"AUDIO:02" => "Audio2",
"AUDIO:03" => "USB",
"AVOUT:00" => "projection",
"AVOUT:01" => "constantly",
"4KENHANCE:00" => "off",
"4KENHANCE:01" => "FullHD",
"ASPECT:00" => "Normal",
"ASPECT:20" => "Auto20",
"ASPECT:30" => "Auto",
"ASPECT:40" => "Full",
"ASPECT:50" => "Zoom",
"ASPECT:70" => "Wide",
"AUDIO:01" => "Audio1",
"AUDIO:02" => "Audio2",
"AUDIO:03" => "USB",
"AVOUT:00" => "projection",
"AVOUT:01" => "constantly",
"AUTOKEYSTONE:ON" => "on",
"AUTOKEYSTONE:OFF" => "off",
"BTAUDIO:01" => "on",
"BTAUDIO:00" => "off",
"FREEZE:ON" => "on",
"FREEZE:OFF" => "off",
"HREVERSE:ON" => "Flip",
"HREVERSE:OFF" => "Normal",
"ILLUM:01" => "on",
"ILLUM:00" => "off",
"MCFI:00" => "off",
"MCFI:01" => "low",
"MCFI:02" => "normal",
"MCFI:03" => "high",
"MSEL:00" => "black",
"MSEL:01" => "blue",
"MSEL:02" => "user",
"MUTE:ON" => "on",
"MUTE:OFF" => "off",
"OVSCAN:00" => "off",
"OVSCAN:02" => "4%",
"OVSCAN:04" => "8%",
"OVSCAN:A0" => "auto",
"PRODUCT:00" => "ModelName_off",
"PRODUCT:01" => "ModelName_on",
"PWR:00" => "Standby (Net off)",
"PWR:01" => "Lamp on",
"PWR:02" => "Warmup",
"PWR:03" => "Cooldown",
"PWR:04" => "Standby (Net on)",
"PWR:05" => "abnormal Standby",
"PWR:07" => "WirelessHD Standby",
"SIGNAL:00" => "none",
"SIGNAL:01" => "2D",
"SIGNAL:02" => "3D",
"SIGNAL:03" => "not_supported",
"SOURCE:10" => "Input1",
"SOURCE:1F" => "PC1",
"SOURCE:20" => "Input2",
"SOURCE:2F" => "PC2",
"SOURCE:30" => "HDMI1",
"SOURCE:40" => "Video",
"SOURCE:41" => "Video(RCA)",
"SOURCE:52" => "USB",
"SOURCE:53" => "LAN",
"SOURCE:56" => "ScreenMirror",
"SOURCE:A0" => "HDMI2",
"SOURCE:D0" => "WirelessHD",
"SOURCE:F0" => "ChangeCyclic",
"VREVERSE:ON" => "Flip",
"VREVERSE:OFF" => "Normal",
"WLPWR:00" => "WLAN_off",
"WLPWR:01" => "WLAN_on"
"BTAUDIO:01" => "on",
"BTAUDIO:00" => "off",
"FREEZE:ON" => "on",
"FREEZE:OFF" => "off",
"HREVERSE:ON" => "Flip",
"HREVERSE:OFF" => "Normal",
"ILLUM:01" => "on",
"ILLUM:00" => "off",
"MCFI:00" => "off",
"MCFI:01" => "low",
"MCFI:02" => "normal",
"MCFI:03" => "high",
"MSEL:00" => "black",
"MSEL:01" => "blue",
"MSEL:02" => "user",
"MUTE:ON" => "on",
"MUTE:OFF" => "off",
"OVSCAN:00" => "off",
"OVSCAN:02" => "4%",
"OVSCAN:04" => "8%",
"OVSCAN:A0" => "auto",
"PRODUCT:00" => "ModelName_off",
"PRODUCT:01" => "ModelName_on",
"PWR:00" => "Standby (Net off)",
"PWR:01" => "Lamp on",
"PWR:02" => "Warmup",
"PWR:03" => "Cooldown",
"PWR:04" => "Standby (Net on)",
"PWR:05" => "abnormal Standby",
"PWR:07" => "WirelessHD Standby",
"SIGNAL:00" => "none",
"SIGNAL:01" => "2D",
"SIGNAL:02" => "3D",
"SIGNAL:03" => "not_supported",
"SOURCE:10" => "Input1",
"SOURCE:1F" => "PC1",
"SOURCE:20" => "Input2",
"SOURCE:2F" => "PC2",
"SOURCE:30" => "HDMI1",
"SOURCE:40" => "Video",
"SOURCE:41" => "Video(RCA)",
"SOURCE:52" => "USB",
"SOURCE:53" => "LAN",
"SOURCE:56" => "ScreenMirror",
"SOURCE:A0" => "HDMI2",
"SOURCE:D0" => "WirelessHD",
"SOURCE:F0" => "ChangeCyclic",
"VREVERSE:ON" => "Flip",
"VREVERSE:OFF" => "Normal",
"WLPWR:00" => "WLAN_off",
"WLPWR:01" => "WLAN_on"
);
# mapping for toggle commands. if PWR is 01, toggle command is off etc
@ -465,12 +476,14 @@ sub ESCVP21net_Define {
$hash->{devioNoSTATE} = 1;
# subscribe only to notify from global and self
$hash->{NOTIFYDEV} = "global,TYPE=ESCVP21net";
# set version
$hash->{version} = $version;
my $name = $hash->{NAME};
# clean up
RemoveInternalTimer($hash, "ESCVP21net_checkConnection");
DevIo_CloseDev($hash);
DevIo_CloseDev($hash) if(DevIo_IsOpen($hash));
# force immediate reconnect
delete $hash->{NEXT_OPEN} if ( defined( $hash->{NEXT_OPEN} ) );
@ -479,10 +492,10 @@ sub ESCVP21net_Define {
# enhance default set hashes with type specific keys
ESCVP21net_setTypeCmds($hash);
# prüfen, ob eine neue Definition angelegt wird
# check if definition is new or existing
if($init_done && !defined($hash->{OLDDEF}))
{
# setzen von stateFormat
# set stateFormat
$attr{$name}{"stateFormat"} = "PWR";
}
main::Log3 $name, 5, "[$name]: Define: device $name defined";
@ -506,7 +519,8 @@ sub ESCVP21net_Shutdown {
BlockingKill( $hash->{helper}{RUNNING_PID} ) if ( defined( $hash->{helper}{RUNNING_PID} ) );
delete $hash->{helper}{nextConnectionCheck} if ( defined( $hash->{helper}{nextConnectionCheck} ) );
delete $hash->{helper}{nextStatusCheck} if ( defined( $hash->{helper}{nextStatusCheck} ) );
main::Log3 $name, 5, "[$name]: deleting timer";
delete $hash->{helper}{nextCyclicConnect} if ( defined( $hash->{helper}{nextCyclicConnect} ) );
main::Log3 $name, 5, "[$name]: Shutdown: deleting timers & RUNNING_PID, close Device";
}
sub ESCVP21net_Initialize {
@ -519,7 +533,7 @@ sub ESCVP21net_Initialize {
$hash->{ReadFn} = \&ESCVP21net_Read;
$hash->{ReadyFn} = \&ESCVP21net_Ready;
$hash->{NotifyFn} = \&ESCVP21net_Notify;
$hash->{StateFn} = \&ESCVP21net_SetState;
#$hash->{StateFn} = \&ESCVP21net_State;
$hash->{ShutdownFn} = \&ESCVP21net_Shutdown;
#$hash->{GetFn} = \&ESCVP21net_Get;
#$hash->{DeleteFn} = \&ESCVP21net_Delete;
@ -527,7 +541,7 @@ sub ESCVP21net_Initialize {
#$hash->{DelayedShutdownFn} = \&ESCVP21net_DelayedShutdown;
$hash->{AttrList} =
"Manufacturer:Epson,other connectionCheck:off,1,15,30,60,120,300,600,3600 AdditionalSettings statusCheckCmd statusCheckInterval:off,1,5,10,15,30,60,300,600,3600 statusOfflineMsg debug:0,1 disable:0,1 "
"Manufacturer:Epson,other connectionCheck:off,1,15,30,60,120,300,600,3600 AdditionalSettings statusCheckCmd statusCheckInterval:off,1,5,10,15,30,60,300,600,3600 statusOfflineMsg debug:0,1 disable:0,1 cyclicConnect:off,10,15,30,60,120,300,600,3600 "
. $readingFnAttributes;
}
@ -574,6 +588,7 @@ sub ESCVP21net_Notify($$) {
if ($checkInterval eq "off"){$checkInterval = 60;}
RemoveInternalTimer($hash, "ESCVP21net_checkConnection");
$next = gettimeofday() + $checkInterval;
$hash->{helper}{nextConnectionCheck} = $next;
InternalTimer($next , "ESCVP21net_checkConnection", $hash);
}
@ -584,15 +599,33 @@ sub ESCVP21net_Notify($$) {
else{
# no timer, so create one for first check
main::Log3 $name, 5, "[$name]: Notify: got @{$events}, no status timer exists, create one";
# force check after 5 seconds; ToDo: rethink, is that a good enough solution?
# force check after 5 seconds
$checkInterval = 5;
RemoveInternalTimer($hash, "ESCVP21net_checkStatus");
$next = gettimeofday() + $checkInterval;
$hash->{helper}{nextStatusCheck} = $next;
InternalTimer($next , "ESCVP21net_checkStatus", $hash);
}
if ( defined( $hash->{helper}{nextCyclicConnect} )){
# i.e. timer exists, do nothing
main::Log3 $name, 5, "[$name]: Notify: got @{$events}, cyclicConnect timer exists, do nothing";
}
else{
# no timer, so create one for first check
main::Log3 $name, 5, "[$name]: Notify: got @{$events}, no cyclicConnect timer exists, create one";
# force check after 5 seconds
$checkInterval = 60;
RemoveInternalTimer($hash, "ESCVP21net_cyclicConnect");
$next = gettimeofday() + $checkInterval;
$hash->{helper}{nextCyclicConnect} = $next;
InternalTimer($next , "ESCVP21net_cyclicConnect", $hash);
}
# force first PWR check after CONNECT
ESCVP21net_Set($hash, $name, "PWR", "get");
}
if($devName eq $name && grep(m/^DISCONNECTED$/, @{$events})){
main::Log3 $name, 5, "[$name]: Notify: got @{$events}, deleting timers";
RemoveInternalTimer($hash);
@ -600,6 +633,7 @@ sub ESCVP21net_Notify($$) {
delete $hash->{helper}{nextStatusCheck} if ( defined( $hash->{helper}{nextStatusCheck} ) );
readingsSingleUpdate( $hash, "PWR", "offline", 1);
main::Log3 $name, 5, "[$name]: Notify: got DISCONNECTED, force PWR to offline";
BlockingKill( $hash->{helper}{RUNNING_PID} ) if ( defined( $hash->{helper}{RUNNING_PID} ) );
}
}
@ -624,12 +658,16 @@ sub ESCVP21net_Attr {
my @valarray = split / /, $attr_value;
my $key;
my $newkey;
my $newkeyval;
my $newkeyval = "";
%VP21addattrs = ();
$hash->{AdditionalSettings} = $attr_value;
foreach $key (@valarray) {
$newkey = (split /:/, $key, 2)[0];
$newkeyval = ":".(split /:/, $key, 2)[1];
# check if AdditionalSetting is only cmd (e.g. "ILLUM") without parameter (e.g. ":0,1")
# otherwise take it as ""
if (defined ((split /:/, $key, 2)[1])){
$newkeyval = ":".(split /:/, $key, 2)[1];
}
main::Log3 $name, 5,"[$name]: Attr: setting $attr_name, key is $newkey, val is $newkeyval";
$VP21addattrs{$newkey} = $newkeyval;
#%ESCVP21net_typesets = (%ESCVP21net_typesets, %VP21addattrs);
@ -673,6 +711,27 @@ sub ESCVP21net_Attr {
main::Log3 $name, 5,"[$name]: Attr: set $attr_name interval to $attr_value";
}
}
elsif ($attr_name eq "cyclicConnect"){
# timer to check status of device
if ($attr_value eq "0") {
# 0 means off
return "0 not allowed for $attr_name!";
}
elsif ($attr_value eq "off"){
RemoveInternalTimer($hash, "ESCVP21net_cyclicConnect");
$hash->{helper}{nextCyclicConnect} = "off";
}
else{
RemoveInternalTimer($hash, "ESCVP21net_cyclicConnect");
$checkInterval = $attr_value;
$next = gettimeofday() + $checkInterval;
$hash->{helper}{nextCyclicConnect} = $next;
InternalTimer( $next, "ESCVP21net_cyclicConnect", $hash);
main::Log3 $name, 5,"[$name]: Attr: set $attr_name interval to $attr_value";
}
}
elsif ($attr_name eq "StatusCheckCmd"){
# ToDo: check for allowed commands
}
@ -703,7 +762,7 @@ sub ESCVP21net_Attr {
}
elsif($attr_name eq "connectionCheck") {
RemoveInternalTimer($hash, "ESCVP21net_checkConnection");
# set default value 600, timer running ech 600s
# set default value 600, timer running each 600s
my $next = gettimeofday() + "600";
$hash->{helper}{nextConnectionCheck} = $next;
InternalTimer( $next, "ESCVP21net_checkConnection", $hash);
@ -711,14 +770,19 @@ sub ESCVP21net_Attr {
}
elsif($attr_name eq "statusCheckInterval") {
RemoveInternalTimer($hash, "ESCVP21net_checkStatus");
# set default value 600, timer running ech 600s
# set default value 600, timer running each 600s
my $next = gettimeofday() + "600";
$hash->{helper}{nextStatusCheck} = $next;
InternalTimer( $next, "ESCVP21net_checkStatus", $hash);
main::Log3 $name, 5,"[$name]: Attr: $attr_name removed, timer set to +600";
}
elsif($attr_name eq "statusCheckInterval") {
# do nothing
elsif($attr_name eq "cyclicConnect") {
RemoveInternalTimer($hash, "ESCVP21net_cyclicConnect");
# set default value 3600, timer running each 3600s
my $next = gettimeofday() + "3600";
$hash->{helper}{nextCyclicConnect} = $next;
InternalTimer( $next, "ESCVP21net_cyclicConnect", $hash);
main::Log3 $name, 5,"[$name]: Attr: $attr_name removed, timer set to +3600";
}
elsif ($attr_name eq "debug"){
delete $hash->{debug};
@ -731,12 +795,18 @@ sub ESCVP21net_Attr {
sub ESCVP21net_Ready($){
my ($hash) = @_;
my $name = $hash->{NAME};
# try to reopen the connection in case the connection is lost
return DevIo_OpenDev($hash, 1, undef );
my $status = DevIo_getState($hash);
if ($status eq "disconnected"){
RemoveInternalTimer($hash, "ESCVP21net_checkConnection");
DevIo_CloseDev($hash) if(DevIo_IsOpen($hash));
DevIo_OpenDev($hash, 1, "ESCVP21net_ReInit", "ESCVP21net_CallbackReady");
}
}
sub ESCVP21net_SetState($$$$){
sub ESCVP21net_State($$$$){
# not really needed
my ($hash, $time, $readingName, $value) = @_;
my $name = $hash->{NAME};
# just logging subroutine call
@ -752,14 +822,28 @@ sub ESCVP21net_Get {
sub ESCVP21net_Init($){
my ($hash) = @_;
my $name = $hash->{NAME};
my $checkInterval;
my $next;
main::Log3 $name, 5,"[$name]: Init: DevIo successful, initialize connectionCheck";
my $checkInterval = AttrVal( $name, "connectionCheck", "60" );
if ($checkInterval eq "off"){$checkInterval = 60;}
# set initial connection check, if "off" it will only run once
$checkInterval = AttrVal( $name, "connectionCheck", "60" );
if ($checkInterval eq "off"){$checkInterval = 60;}
RemoveInternalTimer($hash, "ESCVP21net_checkConnection");
my $next = gettimeofday() + $checkInterval;
InternalTimer($next , "ESCVP21net_checkConnection", $hash);
#delete $hash->{helper}{nextConnectionCheck} if (defined($hash->{helper}{nextConnectionCheck}));
$next = gettimeofday() + $checkInterval;
$hash->{helper}{nextConnectionCheck} = $next;
InternalTimer($next , "ESCVP21net_checkConnection", $hash);
# set initial cyclic check, if "off" it will only run once
$checkInterval = AttrVal( $name, "cyclicConnect", "3600" );
if ($checkInterval eq "off"){$checkInterval = 60;}
RemoveInternalTimer($hash, "ESCVP21net_cyclicConnect");
#delete $hash->{helper}{nextCyclicConnect} if (defined($hash->{helper}{nextCyclicConnect}));
$next = gettimeofday() + $checkInterval;
$hash->{helper}{nextCyclicConnect} = $next;
InternalTimer( $next, "ESCVP21net_cyclicConnect", $hash);
return undef;
}
@ -782,12 +866,12 @@ sub ESCVP21net_Callback($){
main::Log3 $name, 3, "[$name] DevIo callback error: $error";
$offlineMsg = AttrVal($name, "statusOfflineMsg", "offline");
$rv = readingsSingleUpdate($hash, "PWR", $offlineMsg, 1);
main::Log3 $name, 3, "[$name] DevIo callback: force PWR to $offlineMsg";
main::Log3 $name, 3, "[$name] DevIo callback error: force PWR to $offlineMsg";
}
my $status = $hash->{STATE};
my $status = DevIo_getState($hash);
if ($status eq "disconnected"){
# remove timers and pending setValue calls if device is disconnected
main::Log3 $name, 3, "[$name] DevIo callback error: STATE is $status";
main::Log3 $name, 3, "[$name] DevIo callback: STATE is $status, delete timers & RUNNING_PID";
RemoveInternalTimer($hash);
delete $hash->{helper}{nextConnectionCheck}
if (defined($hash->{helper}{nextConnectionCheck}));
@ -805,17 +889,33 @@ sub ESCVP21net_Callback($){
# update reading for $checkcmd with $offlineMsg
#$rv = readingsSingleUpdate($hash, $checkcmd, $offlineMsg, 1);
$rv = readingsSingleUpdate($hash, "PWR", $offlineMsg, 1);
main::Log3 $name, 5,"[$name]: [$name] DevIo callback: $checkcmd set to $offlineMsg";
main::Log3 $name, 5,"[$name]: DevIo callback: $checkcmd set to $offlineMsg";
return ;
}
}
return undef;
}
sub ESCVP21net_CallbackReady() {
my ( $hash, $err ) = @_;
my $name = $hash->{NAME};
main::Log3 $name, 5, "[$name]: CallbackReady error: $err" if ($err);
if ($err && $err =~ /\(113\)/){
# seems that DevIo sends fhem into 100% CPU after TCP timeout
# empirically, this seems to be the case when err is "No route to host (113)"
# (normally, DevIo returns with "timed out")
# so we reconnect then...
ESCVP21net_connect($hash);
}
}
sub ESCVP21net_Read($){
# I could not yet get Read data from DevIo, so I use send/recv socket
my ($hash) = @_;
my $name = $hash->{NAME};
# we dont really expect data here. Its just to gracefully close the device if the connection was closed
my $buf = DevIo_SimpleRead($hash);
main::Log3 $name, 5,"[$name]: Read: received $buf";
return;
}
@ -885,7 +985,7 @@ sub ESCVP21net_Set {
if ($opt eq "decode"){
# restores set commands from .Set
main::Log3 $name, 5, "[$name]: Set: decode: running decode ";
main::Log3 $name, 5, "[$name]: Set: decode: calling decode ";
%ESCVP21net_typesets = ESCVP21net_restoreJson($hash,".Sets");
return undef;
@ -896,7 +996,57 @@ sub ESCVP21net_Set {
#my %decode = %$decode;
#main::Log3 $name, 5, "[$name]: Set: decode: keys(%decode) ";
#return undef;
}
}
if ($opt eq "cleanup"){
# cleanup everything... (like shutdown)
main::Log3 $name, 5, "[$name]: Set: cleanup: calling cleanup ";
ESCVP21net_cleanup($hash);
return undef;
}
if ($opt eq "connect"){
# connecting via Dev_Io
main::Log3 $name, 5, "[$name]: Set: connect: calling connect";
ESCVP21net_connect($hash);
return undef;
}
if ($opt eq "removeTimer"){
# connecting via Dev_Io
main::Log3 $name, 5, "[$name]: Set: removeTimer: calling removeTimer";
ESCVP21net_removeTimer($hash);
return undef;
}
if ($opt eq "closeDevice"){
# connecting via Dev_Io
main::Log3 $name, 5, "[$name]: Set: closeDevice: calling closeDevice";
ESCVP21net_closeDevice($hash);
return undef;
}
if ($opt eq "deleteNextOpen"){
# connecting via Dev_Io
main::Log3 $name, 5, "[$name]: Set: deleteNextOpen: calling deleteNextOpen";
ESCVP21net_deleteNextOpen($hash);
return undef;
}
if ($opt eq "openDevice"){
# connecting via Dev_Io
main::Log3 $name, 5, "[$name]: Set: openDevice: calling openDevice";
ESCVP21net_openDevice($hash);
return undef;
}
if ($opt eq "isOpen"){
# connecting via Dev_Io
#main::Log3 $name, 5, "[$name]: Set: isOpen: calling isOpen";
my $status = DevIo_IsOpen($hash);
main::Log3 $name, 5, "[$name]: Set: isOpen: status is $status, TCPDev: $hash->{TCPDev}";
return undef;
}
##### end of debug options
# everything fine so far, so contruct $arg to pass by blockingFn
@ -1420,7 +1570,6 @@ sub ESCVP21net_checkConnection ($) {
# not connected, DevIo not active, so device won't open again automatically
# should never happen, since we called DevIo_Open above!
# no internal timer needed, but should we ask DevIo again for opening the connection?
#DevIo_OpenDev($hash, 1, "ESCVP21net_Init", "ESCVP21net_Callback");
main::Log3 $name, 5, "[$name]: connectionCheck: no FD, no NEXT_OPEN, should not happen!";
}
elsif ($hash->{FD} && $hash->{NEXT_OPEN}){
@ -1428,14 +1577,12 @@ sub ESCVP21net_checkConnection ($) {
# DevIo tries to connect again at NEXT_OPEN
# should we try to clean up by closing and reopening?
# no internal timer needed
#DevIo_CloseDev($hash);
#DevIo_OpenDev($hash, 1, "ESCVP21net_Init", "ESCVP21net_Callback");
delete $hash->{helper}{nextConnectionCheck}
if ( defined( $hash->{helper}{nextConnectionCheck} ) );
main::Log3 $name, 5, "[$name]: connectionCheck: FD and NEXT_OPEN, Dev_Io will reconnect periodically";
}
elsif ($hash->{FD} && !$hash->{NEXT_OPEN}){
# device is connectd, or seems to be (since broken connection is not detected by DevIo!)
# device is connected, or seems to be (since broken connection is not detected by DevIo!)
# normal state when device is on and reachable
# or when it was on, turned off, but DevIo did not recognize (TCP timeout not reached)
# internal timer makes sense to check, if device is really reachable
@ -1595,17 +1742,88 @@ sub ESCVP21net_restoreJson {
# just for logging
#my %decode = %$decode;
#main::Log3 $name, 5, "[$name]: restore: ". keys(%decode);
return %$decode;
}
sub ESCVP21net_cleanup {
my ($hash) = @_;
#RemoveInternalTimer($hash);
my ($hash) = @_;
my $name = $hash->{NAME};
RemoveInternalTimer($hash);
DevIo_CloseDev($hash);
BlockingKill( $hash->{helper}{RUNNING_PID} ) if ( defined( $hash->{helper}{RUNNING_PID} ) );
#DevIo_CloseDev($hash);
delete $hash->{helper}{nextConnectionCheck} if ( defined( $hash->{helper}{nextConnectionCheck} ) );
delete $hash->{helper}{nextStatusCheck} if ( defined( $hash->{helper}{nextStatusCheck} ) );
main::Log3 $name, 5, "[$name]: cleanup: deleting timers & RUNNING_PID, close Device";
return ;
}
sub ESCVP21net_connect{
my ($hash) = @_;
my $name = $hash->{NAME};
# clean up
RemoveInternalTimer($hash, "ESCVP21net_checkConnection");
DevIo_CloseDev($hash) if(DevIo_IsOpen($hash));
# force immediate reconnect
delete $hash->{NEXT_OPEN} if ( defined( $hash->{NEXT_OPEN} ) );
DevIo_OpenDev($hash, 0, "ESCVP21net_Init", "ESCVP21net_Callback");
main::Log3 $name, 5, "[$name]: connect: opening device via Dev_Io";
return ;
}
sub ESCVP21net_cyclicConnect{
my ($hash) = @_;
my $name = $hash->{NAME};
main::Log3 $name, 5, "[$name]: cyclicConnect: cyclic reconnect...";
RemoveInternalTimer($hash, "ESCVP21net_cyclicConnect");
#delete $hash->{helper}{nextCyclicConnect} if (defined($hash->{helper}{nextCyclicConnect}));
my $checkInterval = AttrVal( $name, "cyclicConnect", "3600" );
if ($checkInterval eq "off"){
$hash->{helper}{nextCyclicConnect} = "off";
return ;
}
ESCVP21net_connect($hash);
my $next = gettimeofday() + $checkInterval; # if checkInterval is off, we won't reach this line
$hash->{helper}{nextCyclicConnect} = $next;
InternalTimer( $next, "ESCVP21net_cyclicConnect", $hash);
}
sub ESCVP21net_removeTimer{
my ($hash) = @_;
my $name = $hash->{NAME};
RemoveInternalTimer($hash, "ESCVP21net_checkConnection");
main::Log3 $name, 5, "[$name]: (debug): removeTimer";
return ;
}
sub ESCVP21net_closeDevice{
my ($hash) = @_;
my $name = $hash->{NAME};
DevIo_CloseDev($hash) if(DevIo_IsOpen($hash));
main::Log3 $name, 5, "[$name]: (debug): closeDevice";
return ;
}
sub ESCVP21net_deleteNextOpen{
my ($hash) = @_;
my $name = $hash->{NAME};
delete $hash->{NEXT_OPEN} if ( defined( $hash->{NEXT_OPEN} ) );
main::Log3 $name, 5, "[$name]: (debug): deleteNextOpen";
return ;
}
sub ESCVP21net_openDevice{
my ($hash) = @_;
my $name = $hash->{NAME};
DevIo_OpenDev($hash, 0, "ESCVP21net_Init", "ESCVP21net_Callback");
main::Log3 $name, 5, "[$name]: (debug): openDevice";
return ;
}
###################################################
# done #
###################################################
@ -1725,9 +1943,15 @@ sub ESCVP21net_cleanup {
<br>Defines the message to set in the Reading related to <i>statusCheckCmd</i> when the device goes offline. Status of device will be checked after each <i>statusCheckIntervall</i> (default: 60s), querying the <i>statusCheckCmd</i> command (default: PWR), and if STATE is <i>disconnected</i> the Reading of <i>statusCheckCmd</i> will be set to this message. Default: offline.
</li>
<br>
<li>cyclicReconnectg
<br><i>off|(value in seconds)</i>
<br><i>value</i> defines the intervall in seconds to perform an periodic reconnect. Each <i>interval</i> we try to re-open the TCP connectionto the projector. Implemented to work around DevIo not recognizing a server-side broken connection, which can lead to a unnecessary, however non-blocking, system load.
<br>Default value is 3600 seconds.
</li>
<br>
<li>debug
<br>You won't need it. But ok, if you insist...
<br>debug will reveal some more set commands, namely <i>encode, decode, reread</i>. They will store the currents sets and results in json format to hidden readings <i>(encode)</i> or restore them <i>(decode)</i>. <i>reread</i> will just restore the available set commands for your projector type in case they got "lost".
<br>debug will reveal some more set commands, namely <i>encode, decode, reread</i>. They will store the currents sets and results in json format to hidden readings <i>(encode)</i> or restore them <i>(decode)</i>. <i>reread</i> will just restore the available set commands for your projector type in case they got "lost". Don't use the other debug commands - unnless you know what you do...
<br>Default is 0, of course.
</li>
</ul>

980
fhem/FHEM/70_SVDRP.pm Executable file
View File

@ -0,0 +1,980 @@
########################################################################################
# $Id$
#
# defmod VDRcontrol SVDRP 192.168.81.20 6419; attr VDRcontrol room Test
#
# SVDRP
#
# control VDR via SVDRP
# refer to http://www.vdr-wiki.de/wiki/index.php/VDR_Optionen
#
# version history
# 1.01.01 first released version for good-willing testers
#
########################################################################################
# defmod VDRcontrol SVDRP 192.168.81.20; attr VDRcontrol room Beamer; attr VDRcontrol verbose 5
########################################################################################
# Useful for learning timer analysis via regexp:
#
# https://www.tutorialspoint.com/execute_perl_online.php
#
##!/usr/bin/perl
#
#$bar = "250 1 Tue Mar 15 09:50:00 2022";
#if ($bar =~ /^250[ ]\d+[ ][A-Za-z]{3}[ ][A-Za-z]{3}[ ][1-9]{2}[ ][0-9]{2}:[0-9]{2}:[0-9]{2}[ ][0-9]{4}$/) {
# print "Found $bar\n";
#} else {
# print "not found\n";
#}
#
#$bar = "250 1760874MB 476308MB 72%";
#if ($bar =~ /^250[ ]\d+MB[ ]\d+MB[ ]\d+%$/) {
# print "Found $bar\n";
#} else {
# print "not found\n";
#}
#
#$bar = "250 4 RTL Tele.visi_on-now45?k#";
#if ($bar =~ /^250[ ]\d+[ ][A-Za-z0-9\h\.\-_?!#]+$/) {
# print "Found $bar\n";
#} else {
# print "not found\n";
#}
#
########################################################################################
#
# This programm is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# The GNU General Public License can be found at
# http://www.gnu.org/copyleft/gpl.html.
# A copy is found in the textfile GPL.txt and important notices to the license
# from the author is found in LICENSE.txt distributed with these scripts.
#
# This script is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
########################################################################################
package main;
use strict;
use warnings;
use Socket; # For constants like AF_INET and SOCK_STREAM
#use Encode qw(encode);
use Blocking;
use Time::HiRes qw(gettimeofday);
use POSIX;
my $version = "0.00.01";
my %SVDRP_gets = (
#
);
my %SVDRP_defaultsetsRaw = (
"HITK" => "",
"LSTT" => ":get",
"LSTR" => ":get",
"NEXT" => ":get",
"STAT" => ":disk",
"UPDR" => ":get",
"CHAN" => ":+,-",
"DELT" => "",
"VOLU" =>":+,-,mute",
"cleanUp" => ":noArg",
"connect" => ":noArg"
);
my %SVDRP_defaultsets = (
"HitKey" => "",
"ListTimers" => ":get",
"NextTimer" => ":get",
"DiskStatus" => ":get",
"UpdateRecordings" => ":get",
"Channel" => ":+,-",
"DeleteTimer" => "",
"Volume" =>":+,-,mute",
"cleanUp" => ":noArg",
"connect" => ":noArg",
"PowerOff" => ":noArg"
);
my %SVDRP_defaultsets_unused = (
"ListRecordings" => ":get"
);
my %SVDRP_cmdmap = (
"HitKey" => "HITK",
"ListTimers" => "LSTT",
"NextTimer" => "NEXT",
"DiskStatus" => "STAT",
"UpdateRecordings" => "UPDR",
"Channel" => "CHAN",
"DeleteTimer" => "DELT",
"Volume" => "VOLU"
);
my %SVDRP_cmdmap_unused = (
"ListRecordings" => "LSTR"
);
my %SVDRP_data = (
#
);
my %SVDRP_result;
my %SVDRPaddattrs;
my %SVDRP_sets = %SVDRP_defaultsets;
sub SVDRP_Define {
my ($hash, $def) = @_;
my @param = split('[ \t]+', $def);
if(int(@param) < 3) {
return "too few parameters: define <name> SVDRP <IP_Address> [<port>]";
}
$hash->{NAME} = $param[0];
$hash->{IP_Address} = $param[2];
if (!$param[3]){
$hash->{port} = "6419";
}
else{
$hash->{port} = $param[3];
}
$hash->{DeviceName} = $param[2].":".$hash->{port};
# prevent "reappeared" messages in loglevel 1
$hash->{devioLoglevel} = 3;
# prevent DevIO from setting "STATE" at connect/disconnect
$hash->{devioNoSTATE} = 1;
# subscribe only to notify from global and self
$hash->{NOTIFYDEV} = "global,TYPE=SVDRP";
my $name = $hash->{NAME};
# clean up
RemoveInternalTimer($hash, "SVDRP_checkConnection");
DevIo_CloseDev($hash);
# force immediate reconnect
delete $hash->{NEXT_OPEN} if ( defined( $hash->{NEXT_OPEN} ) );
# commented to not automatically connect...
#DevIo_OpenDev($hash, 0, "SVDRP_Init", "SVDRP_Callback");
return ;
}
sub SVDRP_Undef {
my ($hash, $arg) = @_;
RemoveInternalTimer($hash);
BlockingKill( $hash->{helper}{RUNNING_PID} ) if ( defined( $hash->{helper}{RUNNING_PID} ) );
DevIo_CloseDev($hash);
return ;
}
sub SVDRP_Shutdown {
my ($hash) = @_;
my $name = $hash->{NAME};
RemoveInternalTimer($hash);
DevIo_CloseDev($hash);
BlockingKill( $hash->{helper}{RUNNING_PID} ) if ( defined( $hash->{helper}{RUNNING_PID} ) );
delete $hash->{helper}{nextConnectionCheck} if ( defined( $hash->{helper}{nextConnectionCheck} ) );
delete $hash->{helper}{nextStatusCheck} if ( defined( $hash->{helper}{nextStatusCheck} ) );
delete $hash->{helper}{RUNNING_PID} if ( defined( $hash->{helper}{RUNNING_PID} ) );
}
sub SVDRP_Initialize {
my ($hash) = @_;
$hash->{DefFn} = \&SVDRP_Define;
$hash->{UndefFn} = \&SVDRP_Undef;
$hash->{SetFn} = \&SVDRP_Set;
$hash->{AttrFn} = \&SVDRP_Attr;
$hash->{ReadFn} = \&SVDRP_Read;
$hash->{ReadyFn} = \&SVDRP_Ready;
$hash->{NotifyFn} = \&SVDRP_Notify;
$hash->{StateFn} = \&SVDRP_SetState;
$hash->{ShutdownFn} = \&SVDRP_Shutdown;
#$hash->{GetFn} = \&SVDRP_Get; # not required
#$hash->{DeleteFn} = \&SVDRP_Delete;
#$hash->{RenameFn} = \&SVDRP_Rename;
#$hash->{DelayedShutdownFn} = \&SVDRP_DelayedShutdown;
$hash->{AttrList} =
"delay:1,2,3,4,5 connectionCheck:off,1,15,30,60,120,300,600,3600 AdditionalSettings statusCheckCmd statusCheckInterval:off,1,5,10,15,30,60,300,600,3600 statusOfflineMsg disable:0,1 "
. $readingFnAttributes;
}
sub SVDRP_Notify($$) {
my ($hash, $devHash) = @_;
my $name = $hash->{NAME}; # own name / hash
my $devName = $devHash->{NAME}; # Device that created the events
my $checkInterval;
my $next;
if(IsDisabled($name)){
main::Log3 $name, 5, "[$name]: Notify: $name is disabled by framework!";
return;
}
my $events = deviceEvents($devHash,1);
#return if( !$events );
# logging of notifies
#main::Log3 $name, 5, "[$name]: running notify from $devName for $name, event is @{$events}";
if($devName eq "global" && grep(m/^INITIALIZED|REREADCFG$/, @{$events})){
if ( defined( $hash->{AdditionalSettings} ))
{
SVDRP_Attr("set",$name,"AdditionalSettings",$hash->{AdditionalSettings});
main::Log3 $name, 5, "adding attrs: $name, ".$hash->{AdditionalSettings};
}
}
return;
}
sub SVDRP_Attr {
my ($cmd,$name,$attr_name,$attr_value) = @_;
my $hash = $defs{$name};
my $checkInterval;
my $next;
main::Log3 $name, 5,"[$name]: Attr: executing $cmd $attr_name to $attr_value";
if($cmd eq "set") {
if ($attr_name eq "AdditionalSettings") {
my @valarray = split / /, $attr_value;
my $key;
my $newkey;
my $newkeyval;
%SVDRPaddattrs = ();
$hash->{AdditionalSettings} = $attr_value;
foreach $key (@valarray) {
#main::Log3 $name, 3,"[$name]: key is $key";
$newkey = (split /:/, $key, 2)[0];
$newkeyval = ":".(split /:/, $key, 2)[1];
main::Log3 $name, 5,"[$name]: Attr: setting $attr_name, key is $newkey, val is $newkeyval";
$SVDRPaddattrs{$newkey} = $newkeyval;
%SVDRP_sets = (%SVDRP_sets, %SVDRPaddattrs);
}
}
elsif ($attr_name eq "connectionCheck"){
if ($attr_value eq "0") {
# avoid 0 timer
return "0 not allowed for $attr_name!";
}
elsif ($attr_value eq "off"){
RemoveInternalTimer($hash, "SVDRP_checkConnection");
$hash->{helper}{nextConnectionCheck} = "off";
}
else{
RemoveInternalTimer($hash, "SVDRP_checkConnection");
$checkInterval = $attr_value;
$next = gettimeofday() + $checkInterval;
$hash->{helper}{nextConnectionCheck} = $next;
InternalTimer( $next, "SVDRP_checkConnection", $hash);
main::Log3 $name, 5,"[$name]: Attr: set $attr_name interval to $attr_value";
}
}
elsif ($attr_name eq "statusCheckInterval"){
# timer to check status of device
if ($attr_value eq "0") {
# 0 means off
return "0 not allowed for $attr_name!";
}
elsif ($attr_value eq "off"){
RemoveInternalTimer($hash, "SVDRP_checkStatus");
$hash->{helper}{nextStatusCheck} = "off";
}
else{
RemoveInternalTimer($hash, "SVDRP_checkStatus");
$checkInterval = $attr_value;
$next = gettimeofday() + $checkInterval;
$hash->{helper}{nextStatusCheck} = $next;
InternalTimer( $next, "SVDRP_checkStatus", $hash);
main::Log3 $name, 5,"[$name]: Attr: set $attr_name interval to $attr_value";
}
}
elsif ($attr_name eq "StatusCheckCmd"){
# decided not to check for allowed commands, user's freedom to define...
}
}
elsif($cmd eq "del"){
if($attr_name eq "AdditionalSettings") {
%SVDRPaddattrs = ();
%SVDRP_sets = %SVDRP_defaultsets;
main::Log3 $name, 5,"[$name]: Attr: deleting $attr_name";
}
elsif($attr_name eq "connectionCheck") {
RemoveInternalTimer($hash, "SVDRP_checkConnection");
delete $hash->{helper}{nextConnectionCheck} if (defined($hash->{helper}{nextConnectionCheck}));
# next 4 lines to set default value 600, timer running ech 600s
#my $next = gettimeofday() + "600";
#$hash->{helper}{nextConnectionCheck} = $next;
#InternalTimer( $next, "SVDRP_checkConnection", $hash);
#main::Log3 $name, 5,"[$name]: Attr: $attr_name removed, timer set to +600";
}
elsif($attr_name eq "statusCheckInterval") {
RemoveInternalTimer($hash, "SVDRP_checkStatus");
delete $hash->{helper}{nextStatusCheck} if (defined($hash->{helper}{nextStatusCheck}));
# next 4 lines to set default value 600, timer running ech 600s
#my $next = gettimeofday() + "600";
#$hash->{helper}{nextStatusCheck} = $next;
#InternalTimer( $next, "SVDRP_checkStatus", $hash);
#main::Log3 $name, 5,"[$name]: Attr: $attr_name removed, timer set to +600";
}
elsif($attr_name eq "statusCheckInterval") {
# do nothing
}
}
return ;
}
sub SVDRP_Ready($){
my ($hash) = @_;
#return DevIo_OpenDev($hash, 1, undef );
}
sub SVDRP_SetState($$$$){
my ($hash, $time, $readingName, $value) = @_;
my $name = $hash->{NAME};
Log3 $name, 5, "[$name] SetState called";
return undef;
}
sub SVDRP_Get {
# return immediately, not required currently
return "none";
}
sub SVDRP_cleanUp {
my ($hash) = @_;
my $name = $hash->{NAME};
main::Log3 $name, 5, "[$name]: cleanup: sending quit, close DevIo";
DevIo_SimpleWrite($hash, "quit\r\n", "2");
RemoveInternalTimer($hash);
BlockingKill( $hash->{helper}{RUNNING_PID} ) if ( defined( $hash->{helper}{RUNNING_PID} ) );
delete $hash->{helper}{RUNNING_PID} if ( defined( $hash->{helper}{RUNNING_PID} ) );
# give VDR 1 s to react before we close connection
#my $next = gettimeofday() + 3;
#InternalTimer( $next, "SVDRP_closeDev", $hash);
DevIo_CloseDev($hash);
$hash->{STATE} = "closed";
$hash->{PARTIAL}="";
return ;
}
sub SVDRP_closeDev{
my ($hash) = @_;
my $name = $hash->{NAME};
main::Log3 $name, 5,"[$name]: closeDev: closing...";
DevIo_CloseDev($hash);
$hash->{STATE} = "closed";
$hash->{PARTIAL}="";
}
sub SVDRP_Init($){
# default: no check - here we just could initializes connection check
my ($hash) = @_;
my $name = $hash->{NAME};
# main::Log3 $name, 5,"[$name]: Init: DevIo successful, initialize connectionCheck";
# my $checkInterval = AttrVal( $name, "connectionCheck", "60" );
# #set checkInterval to 60 just for first check;
# if ($checkInterval eq "off"){$checkInterval = 60;}
RemoveInternalTimer($hash, "SVDRP_checkConnection");
# my $next = gettimeofday() + $checkInterval;
# InternalTimer($next , "SVDRP_checkConnection", $hash);
# #SVDRP_singleWrite("VDRcontrol|STAT|disk");
return undef;
}
sub SVDRP_ReInit($){
my ($hash) = @_;
my $name = $hash->{NAME};
main::Log3 $name, 5,"[$name]: ReInit: DevIo ReInit done";
return undef;
}
sub SVDRP_Callback($){
# will be executed if connection establishment fails (see DevIo_OpenDev())
my ($hash, $error) = @_;
my $name = $hash->{NAME};
main::Log3 $name, 3, "[$name] DevIo callback error: $error" if ($error);
my $status = $hash->{STATE};
if ($status eq "disconnected"){
# remove timers and pending setValue calls if device is disconnected
main::Log3 $name, 3, "[$name] DevIo callback error: STATE is $status";
RemoveInternalTimer($hash);
delete $hash->{helper}{nextConnectionCheck}
if ( defined( $hash->{helper}{nextConnectionCheck} ) );
delete $hash->{helper}{nextStatusCheck}
if ( defined( $hash->{helper}{nextStatusCheck} ) );
BlockingKill( $hash->{helper}{RUNNING_PID} ) if ( defined( $hash->{helper}{RUNNING_PID} ) );
# check if we should update statusCheck
my $checkInterval = AttrVal( $name, "statusCheckInterval", "off" );
my $checkcmd = AttrVal( $name, "statusCheckCmd", "DiskStatus" );
my $offlineMsg = AttrVal( $name, "statusOfflineMsg", "offline" );
if ($checkInterval ne "off"){
my $rv = readingsSingleUpdate($hash, $checkcmd, $offlineMsg, 1);
main::Log3 $name, 5,"[$name]: [$name] DevIo callback: $checkcmd set to $offlineMsg";
return ;
}
}
return undef;
}
sub SVDRP_Read($){
# used by devio
my ($hash) = @_;
my $name = $hash->{NAME};
# read the available data
my $data = DevIo_SimpleRead($hash);
Log3 $name, 5, "[$name] Read function called";
# stop processing if no data is available (device disconnected)
return if(!defined($data)); # connection lost
#Log3 $name, 5, "[$name] Read received: $data";
my $buffer = $hash->{PARTIAL};
#Log3 $name, 3, "[$name] Read: received $data (buffer contains: $buffer)";
# concat received data to $buffer
my $result = $data;
$buffer .= $result;
Log3 $name, 5, "[$name] Read: received: $result";
Log3 $name, 5, "[$name] Read: buffer contains: $buffer";
# as long as the buffer contains newlines (complete datagramm)
my $msg = "none";
while($buffer =~ m/\n/)
{
#my $msg;
# extract the complete message ($msg), everything else is assigned to $buffer
($msg, $buffer) = split("\n", $buffer, 2);
# remove trailing whitespaces
chomp $msg;
# now we could parse the extracted message, not implemented, since I get no data...
SVDRP_parseMessage($hash, $msg);
}
# update $hash->{PARTIAL} with the current buffer content
$hash->{PARTIAL} = $buffer;
Log3 $name, 5, "[$name] Read: after LF check, msg is: $msg";
Log3 $name, 5, "[$name] Read: after LF check, buffer contains: $buffer";
}
sub SVDRP_parseMessage {
# called from Read with $hash, $msg
# $msg contains one complete line - but one only!
my ($hash, $msg) = @_;
my $name = $hash->{NAME};
#my ($input) = @_;
#Log3 "VDR", 5, "[VSR] Parse: input: $input";
#my ($name, $msg) = split "|", $input;
Log3 $name, 5, "[$name] Parse: name: $name, msg: $msg";
#my $hash = $defs{$name};
#$msg = $hash->{PARTIAL};
# strip last "|"
#$msg = substr $msg, 0, -1;
#my @resultarr = split("\\|", $msg);
my $reading = "info";
my $data;
my $rv;
my $count = 0;
my $output;
my $timers = "";
my $parsedmsg = "";
my $code;
readingsBeginUpdate($hash);
### now we should analyse which message was received, and put it to the right reading
if ($msg =~ /^22[0|1]/){
# format: 220 VDR SVDRP VideoDiskRecorder 2.0.6; Sun Feb 13 17:33:10 2022; UTF-8
$reading = "info";
(my $code, $msg) = split (/ /, $msg, 2);
$rv = readingsSingleUpdate($hash, $reading, $msg, 1);
#Log3 $name, 5, "[$name] Parse: updated $reading with '$msg'";
}
if ($msg =~ /^5\d\d/){
# format: 5xx some error message
$reading = "error";
(my $code, $msg) = split (/ /, $msg, 2);
$rv = readingsSingleUpdate($hash, $reading, $msg, 1);
#Log3 $name, 5, "[$name] Parse: updated $reading with '$msg'";
}
elsif ($msg =~ /^250[ ]\d+MB[ ]\d+MB[ ]\d+%\s$/){
# disk status format: 250 1760874MB 476308MB 72%
$reading = "DiskStatus";
#$rv = readingsSingleUpdate($hash, $reading, $msg, 1);
SVDRP_parseDiskStatus($hash, $reading, $msg);
#Log3 $name, 5, "[$name] Parse: updated $reading with '$msg'";
}
elsif ($msg =~ /^250[ ]\d+[ ][A-Za-z]{3}[ ][A-Za-z]{3}[ ][1-9]{2}[ ][0-9]{2}:[0-9]{2}:[0-9]{2}[ ][0-9]{4}\s$/){
# next timer format: 250 1 Tue Mar 15 09:50:00 2022
$reading = "NextTimer";
(my $code, $msg) = split (/ /, $msg, 2);
$rv = readingsSingleUpdate($hash, $reading, $msg, 1);
#Log3 $name, 5, "[$name] Parse: updated $reading with $msg";
}
elsif ($msg =~ /^250[ ]\d+[ ][A-Za-z0-9\h\.\-_?!#]+\s$/){
# Channel format: 250 4 RTL Television
$reading = "Channel";
(my $code, $msg) = split (/ /, $msg, 2);
$rv = readingsSingleUpdate($hash, $reading, $msg, 1);
#Log3 $name, 5, "[$name] Parse: updated $reading with $msg"
}
elsif ($msg =~ /^250[ ]Audio[ ]volume[ ]is[ ][0-9]+|[mute]\s$/){
# Vol format: 250 Audio volume is 245
$reading = "Volume";
(my $code, $msg) = split (/ /, $msg, 2);
$rv = readingsSingleUpdate($hash, $reading, $msg, 1);
#Log3 $name, 5, "[$name] Parse: updated $reading with $msg"
}
elsif ($msg =~ /^250[ ]Key[ ][A-Za-z0-9"]+[ ]accepted\s$/){
# HitKey format: 250 Key "up" accepted
$reading = "HitKey";
(my $code, $msg) = split (/ /, $msg, 2);
$rv = readingsSingleUpdate($hash, $reading, $msg, 1);
#Log3 $name, 5, "[$name] Parse: updated $reading with $msg"
}
elsif ($msg =~ /^250[-|\h]\d+[ ]\d+:\d+:[A-Za-z-]{7}/ ||
$msg =~ /^250[-|\h]\d+[ ]\d+:\d+:\d{4}-\d{2}-\d{2}:\d{4}:\d{4}:\d{2}:\d{2}:[A-Za-z0-9-_!?\.\h]+:\s$/){
# ListTimer formats:
# 250 1 1:1:MTWTF--@2022-03-15:0950:1115:50:99:Verrückt nach Meer (neu):
# 250 2 1:4:2022-02-13:1858:1915:50:99:RTL Aktuell - Das Wetter:
$reading = "ListTimers";
# check if we got "250-n"
if (substr($msg, 3, 1) eq "-"){
($code, $msg) = split (/-/, $msg, 2);
#Log3 $name, 5, "[$name] Parse: v1: substring contains '-'";
}
else{
($code, $msg) = split (/ /, $msg, 2);
}
$timers = ReadingsVal($name, $reading, "");
$msg = SVDRP_parseTimer($name, $msg);
#Log3 $name, 5, "[$name] Parse: parseTimer returned $msg";
$msg = $timers."\n".$msg;
$rv = readingsSingleUpdate($hash, $reading, $msg, 1);
#Log3 $name, 5, "[$name] Parse: v1: updated $reading with $parsedmsg"
}
Log3 $name, 5, "[$name] Parse: updated $reading with '$msg'";
}
sub SVDRP_parseDiskStatus{
my ($hash,$reading,$resultarr) = @_;
my $name = $hash->{NAME};
my ($code, $disksize, $diskfree, $diskspace) = (split (" ", $resultarr,4));
my $sizeunit = "GB";
my $freeunit = "GB";
my $rv;
# strip unit "MB", keep only numbers
$disksize =~ tr/0-9//cd;
$diskfree =~ tr/0-9//cd;
Log3 $name, 5, "[$name] Parse: Disksize: $disksize, Diskfree: $diskfree";
$disksize = $disksize / 1024;
if ($disksize > 1000){
$disksize = sprintf ("%.1f", $disksize / 1024);
$sizeunit = "TB";
}
else{
$disksize = sprintf ("%.1f", $disksize);
}
$diskfree = $diskfree / 1024;
if ($diskfree > 1000){
$diskfree = $diskfree / 1024;
$freeunit = "TB";
}
else{
$diskfree = sprintf ("%.1f", $diskfree);
}
my $returnval = "Size: ".$disksize.$sizeunit." | Free: ".$diskfree.$freeunit." | Used: ".$diskspace;
readingsBeginUpdate($hash);
$rv = readingsBulkUpdate($hash, "DiskUsed", $diskspace, 1);
$rv = readingsBulkUpdate($hash, $reading, $returnval, 1);
readingsEndUpdate($hash, 1);
#$rv = readingsBulkUpdate($hash, $reading, $resultarr[0], 1);
}
sub SVDRP_parseTimer{
my ($name, $msg) = @_;
#$count = 0;
#$output = "";
my $parsedmsg = "none";
my $timerid = "0";
my $timerstr = "none";
my $i1 = "0";
my $i2 = "0",
my $day = "none";
my $start = "0";
my $end = "0";
my $i3 = "0";
my $i4 = "0";
my $timername = "none";
if (!defined($msg)){
$parsedmsg = "error";
}
else{
# format variants:
# 1 1:1:MTWTF--@2022-03-15:0950:1115:50:99:Verrückt nach Meer (neu):
# 2 1:4:2022-02-13:1858:1915:50:99:RTL Aktuell - Das Wetter:
#Log3 $name, 5, "[$name] ParseTimer: reading: $reading, result: $resultarr[$count]";
($timerid, $timerstr) = split (" ", $msg,2);
($i1, $i2, $day, $start, $end, $i3, $i4, $timername) = split (":", $timerstr, 8);
substr ($start, 2, 0) = ":";
substr ($end, 2, 0) = ":";
#$output .= "\n" if ($count > 0); # add LF only if first line is contained
$parsedmsg = "ID: ".sprintf("%2s",$timerid)." | Day: ".sprintf("%-10s",$day)." | Start: ".$start." | Stop: ".$end." | Name: ".$timername;
}
#Log3 $name, 5, "[$name] parseTimer: parsed output is $parsedmsg";
return $parsedmsg;
}
sub SVDRP_Set {
my ($hash, @param) = @_;
return '"set SVDRP" needs at least one argument' if (int(@param) < 2);
my $name = shift @param;
my $opt = shift @param;
my $value = join("", @param);
#my $value = shift @param;
my $msg;
my $msg2;
my $list = "";
my $optorg = $opt;
my $next;
my $writecmd;
$hash = $defs{$name};
# construct set list
my @cList = (keys %SVDRP_sets);
foreach my $key (@cList){
$list = $list.$key.$SVDRP_sets{$key}." ";
}
if (!exists($SVDRP_sets{$opt})){
return "Unknown argument $opt, please choose one of $list";
}
# return if device is disabled
if(IsDisabled($name)){
main::Log3 $name, 5, "[$name]: Set: $name is disabled by framework!";
return;
}
# empty reading error
readingsSingleUpdate($hash, "error", "", 1);
if ($opt eq "cleanUp"){
main::Log3 $name, 5, "[$name]: Set: $name cleanUp";
SVDRP_cleanUp($hash);
return;
}
if ($opt eq "connect"){
main::Log3 $name, 5, "[$name]: Set: $name connect";
DevIo_OpenDev($hash, 0, "SVDRP_Init", "SVDRP_Callback");
return;
}
# $opt is the nice name - read real command from SVDRP_cmdmap
if (exists($SVDRP_cmdmap{$opt})){
$opt = $SVDRP_cmdmap{$opt};
main::Log3 $name, 5, "[$name]: Set: converted command to $opt";
}
# STAT has only one option "disk"
$value = "disk" if ($opt eq "STAT");
if ($opt eq "PowerOff"){
$opt = "HITK";
$value = "Power";
}
if ($opt eq "LSTT"){
# delete ListTimers, will be re-filled completely
readingsSingleUpdate($hash, "ListTimers", "", 1);
main::Log3 $name, 5, "[$name]: Set: deleted ListTimers, value is now ".ReadingsVal($name,"ListTimers","none");
}
# get or no value will sent send $msg to the given command $opt
if ($value eq "get" || !$value){
$msg = "$opt\r\n";
$msg2 = $msg;
}
# construct command with value
else {
$msg = "$opt $value\r\n";
$msg2 = $opt."|".$value."\r\n";
}
#delete $hash->{helper}{LastCmd};
$hash->{STATE} = "query...";
DevIo_OpenDev($hash, 0, "SVDRP_Init", "SVDRP_Callback");
# Open connection returns welcome string like
# "220 VDR SVDRP VideoDiskRecorder 2.0.6; Sun Feb 6 21:16:36 2022; UTF-8"
# Read stores received data in $hash->{PARTIAL}
my $delay = AttrVal( $name, "delay", "1" );
# give VDR "delay" s to react before we send command
$writecmd = $name."|".$msg."|".$optorg;
$next = gettimeofday() + $delay;
InternalTimer( $next, "SVDRP_singleWrite", $writecmd);
$msg =~ s/[\r\n]//g;
readingsSingleUpdate($hash, "LastCmd", $msg, 1);
# give VDR 1 s to react before we close connection
$next = gettimeofday() + (2 * $delay);
InternalTimer( $next, "SVDRP_cleanUp", $hash);
return;
}
sub SVDRP_singleWrite {
my ($writecmd) = @_;
my ( $name, $msg, $optorg ) = split( "\\|", $writecmd );
my $hash = $defs{$name};
#$hash->{helper}{LastCmd} = $optorg;
DevIo_SimpleWrite($hash, $msg, "2");
main::Log3 $name, 5, "[$name]: singleWrite: sending $msg";
}
sub SVDRP_checkConnection ($) {
my ($hash) = @_;
my $name = $hash->{NAME};
RemoveInternalTimer($hash, "SVDRP_checkConnection");
my $checkInterval = AttrVal( $name, "connectionCheck", "off" );
if ($checkInterval eq "off"){
return ;
}
# my $status = DevIo_IsOpen($hash); # would just tell if FD exists
# let's try to reopen the connection. If successful, FD is kept or created.
# if not successful, NEXT_OPEN is created.
# $status is always undef, since callback fn is given
my $status = DevIo_OpenDev($hash, 1, "SVDRP_ReInit", "SVDRP_Callback");
#delete $hash->{NEXT_OPEN} if ( defined( $hash->{NEXT_OPEN} ) );
#delete $hash->{helper}{nextConnectionCheck} if ( defined( $hash->{helper}{nextConnectionCheck} ) );
if (!($hash->{FD}) && $hash->{NEXT_OPEN}) {
# device was connected, but TCP timeout reached
# DevIo tries to re-open after NEXT_OPEN
# no internal timer needed
delete $hash->{helper}{nextConnectionCheck}
if ( defined( $hash->{helper}{nextConnectionCheck} ) );
main::Log3 $name, 3, "[$name]: DevIo_Open has no FD, NEXT_OPEN is $hash->{NEXT_OPEN}, no timer set";
}
elsif (!($hash->{FD}) && !$hash->{NEXT_OPEN}){
# not connected, DevIo not active, so device won't open again automatically
# should never happen, since we called DevIo_Open above!
# no internal timer needed, but should we ask DevIo again for opening the connection?
#DevIo_OpenDev($hash, 1, "SVDRP_Init", "SVDRP_Callback");
main::Log3 $name, 3, "[$name]: DevIo_Open has no FD, no NEXT_OPEN, should not happen!";
}
elsif ($hash->{FD} && $hash->{NEXT_OPEN}){
# not connected - device was connected, but is not reachable currently
# DevIo tries to connect again at NEXT_OPEN
# should we try to clean up by closing and reopening?
# no internal timer needed
#DevIo_CloseDev($hash);
#DevIo_OpenDev($hash, 1, "SVDRP_Init", "SVDRP_Callback");
delete $hash->{helper}{nextConnectionCheck}
if ( defined( $hash->{helper}{nextConnectionCheck} ) );
main::Log3 $name, 3, "[$name]: DevIo_Open has FD and NEXT_OPEN, try to reconnect periodically";
}
elsif ($hash->{FD} && !$hash->{NEXT_OPEN}){
# device is connectd, or seems to be (since broken connection is not detected by DevIo!)
# normal state when device is on and reachable
# or when it was on, turned off, but DevIo did not recognize (TCP timeout not reached)
# internal timer makes sense to check, if device is really reachable
my $next = gettimeofday() + $checkInterval; # if checkInterval is off, we won't reach this line
$hash->{helper}{nextConnectionCheck} = $next;
InternalTimer( $next, "SVDRP_checkConnection", $hash);
main::Log3 $name, 3, "[$name]: DevIo_Open has FD but no NEXT_OPEN, next timer set";
}
}
sub SVDRP_checkStatus ($){
my ($hash) = @_;
my $name = $hash->{NAME};
my $checkInterval = AttrVal( $name, "statusCheckInterval", "off" );
my $checkcmd = AttrVal( $name, "statusCheckCmd", "PWR" );
my $next;
if ($checkInterval eq "off"){
RemoveInternalTimer($hash, "SVDRP_checkStatus");
main::Log3 $name, 5,"[$name]: checkStatus: status timer removed";
return ;
}
else{
my $value = "get";
SVDRP_Set($hash, $name, $checkcmd, $value);
$next = gettimeofday() + $checkInterval;
$hash->{helper}{nextStatusCheck} = $next;
InternalTimer( $next, "SVDRP_checkStatus", $hash);
main::Log3 $name, 5,"[$name]: checkStatus: next status timer set";
}
}
###################################################
# end #
###################################################
1;
=pod
=item summary control VDR by SVDRP via (W)Lan
=item summary_DE Steuerung von VDR mittels SVDRP über (W)Lan
=begin html
<a id="SVDRP"></a>
<h3>SVDRP</h3>
<ul>
<i>SVDRP</i> implements SVDRP to control VDR via (W)Lan.
<br><br>
<a id="SVDRP-define"></a>
<b>Define</b>
<ul>
<code>define &lt;name&gt; SVDRP &lt;IP_Address&gt [&lt;port&gt;]</code>
<br>
<br>This module helps you for basic control of your VDR.
<br>Only a reasonable subset of SVDRP commands in implemented.
<br>E.g. it does not make sense to set timers via fhem - vdradmin is much more convenient.
<br><br>
<ul>
<li><b>IP_Address</b> - the IP Address of your VDR
</li>
<li><b>port</b> - ... guess? Yes, the port. If not given, VDR standard port 6419 is used.
</li>
<li>Example: <code>define VDRcontrol SVDRP 10.10.0.1 6419</code>
</li>
</ul>
</ul>
<br>
<a id="SVDRP-set"></a>
<b>Set</b>
<br>
<ul>
<br>Available <b>set</b> commands are taken from http://www.vdr-wiki.de/wiki/index.php/SVDRP.
<br>For the predefined "raw" commands, "nice" names will be shown for the readings, e.g. <b>DiskStatus</b> instead of <b>STAT disk</b>.
<br>Default set commands are
<br><br>
<li>Channel
<br>set value can be <i>"+"</i> or <i>"-"</i> or any channel number you want to switch to.
<br><i>set &lt;name&gt; Channel</i> will get you the channel VDR is currently tuned to.
</li>
<br>
<li>DeleteTimer
<br><i>set &lt;name&gt; DeleteTimer &lt;number&gt;</i> will delete ... hm, guess?
<br>(you can get the timer numbers via <i>ListTimers</i>)
</li>
<br>
<li>DiskStatus
<br>no value or <i>get</i> will display the current disk usage in <i>DiskStatus</i>
<br>Additionally, the reading <i>DiskUsed</i> will be set to the disk fill level.
</li>
<br>
<li>HitKey
<br>Enables you to send any Key defined by http://www.vdr-wiki.de/wiki/index.php/SVDRP
<br>E.g.<i>set &lt;name&gt; HitKey Power</i> will cleanly power off VDR.
</li>
<br>
<li>PowerOff
<br>A shortcut to cleanly power off VDR, same as <i>set &lt;name&gt; HitKey Power</i>
</li>
<br>
<li>ListTimers
<br>no value or <i>get</i> will query all timers from VDR.
<br>raw answer from VDR will be parsed into a little bit nicer format.
</li>
<li>NextTimer
<br>no value or <i>get</i> will exactly get what it says.
</li>
<li>UpdateRecordings
<br>no value or <i>get</i> will trigger VDR to re-read the recordings.
<br>(No output to fhem - no sense to show all recordings here)
</li>
<li>Volume
<br>set value can be <i>"+"</i> or <i>"-"</i> or <i>mute</i> or any Volume (0-255) you want to set.
<br><i>set &lt;name&gt; Volume</i> will get you VDR's current Volume setting.
</li>
<li>connect
<br>just connects to VDR, no further action.
<br>Reading "info" will be updated.
<br>Attention: As long as connection to VDR is open, no other SVDRP client can connect!
<br>You might want to use "cleanup" to be able to reconnect other clients.
</li>
<li>cleanup
<br>closes connection to VDR, no further action.
<br>Reading "info" will be updated.
</li>
</ul>
<br>
<a id="SVDRP-attr"></a>
<b>Attributes</b>
<br>
<ul>
<li>AdditionalSettings
<br><i>cmd1:val_1,...,val_n cmd2:val_1,...,val_n</i>
<br>You can specify own set commands here, they will be added to the <b>set</b> list.
<br>Multiple own sets can be specified, separated by a blank.
<br>command and values are separated by <b>":"</b>, values are separated by <b>","</b>.
<br>Example: <i>HITK:up,down,Power MESG</i>
</li>
<br>
<li>connectionCheck
<br><i>off|(value in seconds)</i>
<br><i>value</i> defines the intervall in seconds to perform an connection check.
<br>Normally you won't need that. Use at your own risk...
<br>Default value is "off".
</li>
<br>
<li>statusCheckIntervall
<br><i>off|(value in seconds)</i>
<br><i>value</i> defines the intervall in seconds to perform an status check.
<br>Each <i>interval</i> the VDR is queried with the command defined by <i>statusCheckCmd</i> (default: DiskStatus).
<br>Default value is off.
</li>
<br>
<li>statusCheckCmd
<br><i>(any command(s) you set)</i>
<br>Defines the command(s) used by statusCheckIntervall.
</li>
<br>
<li>statusOfflineMsg
<br><i>(any message text you set)</i>
<br>Defines the message to set in the Reading related to <i>statusCheckCmd</i> when the device goes offline.
<br>Status of device will be checked after each <i>statusCheckIntervall</i> (default: off), querying the <i>statusCheckCmd</i> command (default: DiskStatus), and if STATE is <i>disconnected</i> the Reading of <i>statusCheckCmd</i> will be set to this message. Default: closed.
</li>
<li>delay
<br><i>delay time in seconds</i>
<br>Depending on the answering speed of your VDR, it might be necessary to grant a certain delay beween opening the connection (and getting the initial answer shown in reading "info"), sending a command, receiving the result and closing the connection.
<br>Default: 1.
</li>
<br>
</ul>
</ul>
=end html
=cut