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:
parent
d756abdc2e
commit
6a22531cf6
@ -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
|
||||
|
@ -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
980
fhem/FHEM/70_SVDRP.pm
Executable 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 <name> SVDRP <IP_Address> [<port>]</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 <name> Channel</i> will get you the channel VDR is currently tuned to.
|
||||
</li>
|
||||
<br>
|
||||
<li>DeleteTimer
|
||||
<br><i>set <name> DeleteTimer <number></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 <name> HitKey Power</i> will cleanly power off VDR.
|
||||
</li>
|
||||
<br>
|
||||
<li>PowerOff
|
||||
<br>A shortcut to cleanly power off VDR, same as <i>set <name> 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 <name> 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
|
Loading…
x
Reference in New Issue
Block a user