######################################################################################## # $Id$ # # 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 # 1.01.02 bugfix for single-digit NextTimer # 1.01.03 corrections for german Umlaute # 1.01.04 fix statusCheckInterval # 1.01.05 added capability to control plugins (PLUG) # added name to next timer # expicit set for SatIP plugin # handle HELP responses # 1.01.06 strip info from timername, fix stateFormat overwrite # 1.01.07 optimize help text # ######################################################################################## # # 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 = "1.01.07"; my %SVDRP_gets = ( # ); # Raw is not used by now my %SVDRP_defaultsetsRaw = ( "HITK" => "", "LSTT" => ":get", "LSTR" => ":get", "NEXT" => ":get", "STAT" => ":disk", "UPDR" => ":get", "CHAN" => ":+,-", "DELT" => "", "VOLU" => ":+,-,mute", "cleanUp" => ":noArg", "closeDev" => ":noArg", "connect" => ":noArg" ); my %SVDRP_defaultsets = ( "HitKey" => "", "ListTimers" => ":noArg", "NextTimer" => ":noArg", "DiskStatus" => ":noArg", "UpdateRecordings" => ":get", "Channel" => ":+,-", "DeleteTimer" => "", "Volume" => ":+,-,mute", "cleanUp" => ":noArg", "closeDev" => ":noArg", "connect" => ":noArg", "PowerOff" => ":noArg", "ListRecording" => "", "GetAll" => ":noArg", "Plugin" => "", "StreamdevServer" => ":LSTC,DISC", "SatIP" => ":INFO,MODE,LIST,SCAN,STAT,CONT,OPER,ATTA,DETA,TRAC", "Help" => ":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", "ListRecording" => "LSTR", "Plugin" => "PLUG", "StreamdevServer" => "PLUG streamdev-server", "SatIP" => "PLUG satip", "Help" => "HELP" ); my @SVDRP_statusCmds = ("LSTT", "NEXT", "CHAN", "VOLU", "STAT"); my %SVDRP_cmdmap_unused = ( "ListRecordings" => "LSTR" ); my %SVDRP_data = ( # ); my %SVDRP_timers = ( # ); 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 SVDRP []"; } $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"; # set version $hash->{version} = $version; my $name = $hash->{NAME}; # clean up RemoveInternalTimer($hash, "SVDRP_checkConnection"); main::Log3 $name, 5,"[$name]: Define: status timer removed"; 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) = @_; my $name = $hash->{NAME}; RemoveInternalTimer($hash); main::Log3 $name, 5,"[$name]: Undef: status timer removed"; 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); main::Log3 $name, 5,"[$name]: Shutdown: status timer removed"; 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_State; $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 RecordingInfo:short,long 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]; # check if AdditionalSetting is only cmd (e.g. "LSTR") without parameter (e.g. ":1,2,3") # 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"; $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"); main::Log3 $name, 5,"[$name]: Attr: status timer removed"; $hash->{helper}{nextConnectionCheck} = "off"; } else{ RemoveInternalTimer($hash, "SVDRP_checkConnection"); main::Log3 $name, 5,"[$name]: Attr: status timer removed"; $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"); main::Log3 $name, 5,"[$name]: Attr: status timer removed"; $hash->{helper}{nextStatusCheck} = "off"; } else{ RemoveInternalTimer($hash, "SVDRP_checkStatus"); main::Log3 $name, 5,"[$name]: Attr: status timer removed"; $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"); main::Log3 $name, 5,"[$name]: Attr: status timer removed"; 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"); main::Log3 $name, 5,"[$name]: Attr: status timer removed"; 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_State($$$$){ # not needed ... ? 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); #main::Log3 $name, 5,"[$name]: cleanUp: status timer removed"; 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}=""; # reset .sendingcmd to indicate that cmd sending cycle is done readingsSingleUpdate($hash, ".sendingcmd", "0", 1); return ; } sub SVDRP_closeDev { my ($hash) = @_; my $name = $hash->{NAME}; main::Log3 $name, 5,"[$name]: closeDev: closing..."; delete $hash->{DevIoJustClosed} if (defined($hash->{DevIoJustClosed})); DevIo_CloseDev($hash); #$hash->{STATE} = "closed"; $hash->{PARTIAL}=""; } sub SVDRP_Init($){ # default: no action - here we just could initializes connection check my ($hash) = @_; my $name = $hash->{NAME}; main::Log3 $name, 5,"[$name]: Init: DevIo initializing"; #SVDRP_checkStatus($hash); # my $checkInterval = AttrVal( $name, "connectionCheck", "60" ); # #set checkInterval to 60 just for first check; # if ($checkInterval eq "off"){$checkInterval = 60;} #RemoveInternalTimer($hash, "SVDRP_checkConnection"); #main::Log3 $name, 5,"[$name]: Init: status timer removed"; # my $next = gettimeofday() + $checkInterval; # InternalTimer($next , "SVDRP_checkConnection", $hash); # #SVDRP_singleWrite("VDRcontrol|STAT|disk"); return undef; } sub SVDRP_ReInit($){ # no action - just log subroutine call my ($hash) = @_; my $name = $hash->{NAME}; main::Log3 $name, 5,"[$name]: ReInit: DevIo ReInit done"; return undef; } sub SVDRP_Callback($){ # will be executed after connection establishment (see DevIo_OpenDev()) my ($hash, $error) = @_; my $name = $hash->{NAME}; if ($error){ main::Log3 $name, 3, "[$name]: Callback: DevIo callback error: $error"; SVDRP_closeDev($hash); my $offlineMsg = AttrVal( $name, "statusOfflineMsg", "offline" ); my $rv = readingsSingleUpdate($hash, "globalError", $offlineMsg, 1); main::Log3 $name, 5,"[$name]: Callback: [$name] DevIo callback: globalError set to $offlineMsg"; } else{ main::Log3 $name, 3, "[$name]: Callback: DevIo callback with no error"; } # check for disconnected - not required, since we need to close connection after each request # otherwise, no other svdr client can connect! # #my $status = $hash->{STATE}; # #my $status = DevIo_getState($hash); # my $status = ReadingsVal($name, "state", "disconnected"); # my $offlineMsg = AttrVal( $name, "statusOfflineMsg", "offline" ); # if ($status eq "disconnected"){ # # remove timers and pending setValue calls if device is disconnected # main::Log3 $name, 3, "[$name]: Callback: DevIo callback error: STATE is $status"; # my $rv = readingsSingleUpdate($hash, "globalError", $offlineMsg, 1); # RemoveInternalTimer($hash); # main::Log3 $name, 5,"[$name]: Callback: status timer removed"; # 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]: Callback: [$name] DevIo callback: $checkcmd set to $offlineMsg"; # #SVDRP_checkStatus($hash); # main::Log3 $name, 5,"[$name]: Callback: offline, but status timer set"; # 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 = "(unknown)"; my $data; my $rv; my $count = 0; my $output; my $timers = ""; my $parsedmsg = ""; my $code; my $recording = ""; my $plugin = ""; my $text=""; #readingsBeginUpdate($hash); ### now we should analyse which message was received, and put it to the right reading #if ($msg =~ /^22[0|1]/){ if ($msg =~ /^220/){ # format: 220 VDR SVDRP VideoDiskRecorder 2.0.6; Sun Feb 13 17:33:10 2022; UTF-8 $reading = "infoOpen"; (my $code, $msg) = split (/ /, $msg, 2); $rv = readingsSingleUpdate($hash, $reading, $msg, 1); #Log3 $name, 5, "[$name] Parse: updated $reading with '$msg'"; } elsif ($msg =~ /^221/){ # format: 220 VDR SVDRP VideoDiskRecorder 2.0.6; Sun Feb 13 17:33:10 2022; UTF-8 $reading = "infoClose"; (my $code, $msg) = split (/ /, $msg, 2); $rv = readingsSingleUpdate($hash, $reading, $msg, 1); #Log3 $name, 5, "[$name] Parse: updated $reading with '$msg'"; } elsif ($msg =~ /^5\d\d/){ # format: 5xx some error message $reading = "infoError"; (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,2}[0-9]{1,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"; $msg = SVDRP_parseNextTimer($hash, $reading, $msg); # seems that only with newline the sprintf formatting is kept - but I don't like it here ;-) #$msg = $timers."\n".$msg; readingsSingleUpdate($hash, $reading, $msg, 1); } elsif ($msg =~ /^250[ ]\d+[ ][A-Za-z0-9\h\.\-_?!#]+\s$/){ # Channel format: 250 4 RTL Television $reading = "Channel"; (my $code, $msg) = split (/ /, $msg, 2); #$msg = substr $msg, 0, -1; $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 = "HitKeyInfo"; (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}:/){ # 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: 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: updated $reading with $parsedmsg" } elsif ($msg =~ /^250-\d+\h[0-9]{2}\.[0-9]{2}\.[0-9]{2}\h[0-9]{2}:[0-9]{2}\h/){ # Recording List format: # 250-84 26.02.20 16:05v 1:25* Verrückt nach Meer~Staffel 09 $reading = "Recordings"; # check if we got "250-n" if (substr($msg, 3, 1) eq "-"){ ($code, $msg) = split (/-/, $msg, 2); #Log3 $name, 5, "[$name] Parse: substring contains '-'"; } else{ ($code, $msg) = split (/ /, $msg, 2); } $recording = ReadingsVal($name, $reading, ""); $msg = $recording."\n".$msg; $rv = readingsSingleUpdate($hash, $reading, $msg, 1); #Log3 $name, 5, "[$name] Parse: updated $reading with $msg" } elsif ($msg =~ /^215/){ # Recording format: 215-xxxx $reading = "Recordings"; # check if we got "215-n" if (substr($msg, 3, 1) eq "-"){ ($code, $msg) = split (/-/, $msg, 2); #Log3 $name, 5, "[$name] Parse: substring contains '-'"; } else{ ($code, $msg) = split (/ /, $msg, 2); } $recording = ReadingsVal($name, $reading, ""); $msg = SVDRP_parseRecording($name, $msg); if ($msg ne "none"){ $msg = $recording."\n".$msg; $rv = readingsSingleUpdate($hash, $reading, $msg, 1); Log3 $name, 5, "[$name] Parse: updated $reading with '$msg'"; } #$rv = readingsSingleUpdate($hash, $reading, $msg, 1); #Log3 $name, 5, "[$name] Parse: updated $reading with $msg" } elsif ($msg =~ /^214/){ # Helpinfo format: 214-xxxx $reading = "HelpInfo"; # empty HelpInfo reading, if we got a new one my $sendingcmd = ReadingsVal($name,".sendingcmd","0"); if ($sendingcmd eq "1") { readingsBeginUpdate($hash); readingsBulkUpdate($hash, ".sendingcmd", "0", 1); readingsBulkUpdate($hash, $reading, "", 1); readingsEndUpdate($hash, 1); } # check if we got "214-n" if (substr($msg, 3, 1) eq "-"){ ($code, $msg) = split (/-/, $msg, 2); #Log3 $name, 5, "[$name] Parse: HelpInfo: substring contains '-'"; } else{ ($code, $msg) = split (/ /, $msg, 2); } $text = ReadingsVal($name, $reading, ""); $msg = SVDRP_parseHelpinfo($name, $msg); if ($msg ne "none"){ $msg = $text."\n".$msg; $rv = readingsSingleUpdate($hash, $reading, $msg, 1); Log3 $name, 5, "[$name] Parse: HelpInfo: updated $reading with '$msg'"; } #$rv = readingsSingleUpdate($hash, $reading, $msg, 1); #Log3 $name, 5, "[$name] Parse: HelpInfo: updated $reading with $msg" } elsif ($msg =~ /^9[0-9][0-9]/){ # PLUG resonse is like # 900-SAT>IP device: 0 # 900-CardIndex: 0 # 900-Stream: rtsp://10.1.1.9/?src=1&freq=11185&pol=v&ro=0.35&msys=dvbs2&mtype=8psk&sr=22000&fec=23 (Unicast) [stream=1] # 900-Signal: lock=1 strength=67 quality=100 frontend=1 # 900-Stream bitrate: 75 kB/s # 900-Buffer bitrate: 0 kB/s # 900-Buffer usage: 0/2048 kB (0,0%) # 900-Channel: Das Erste HD;ARD:11493:HC23M5O35P0S1:S19.2E:22000:5101=27:5102=deu@3,5103=mis@3,5107=qks@3;5106=deu@106:5104;5105=deu:0:10301:1:1019:0 # 900-Active pids: # 900-Active section filters: # 900-Filter 0: 7 ( 9 kB/s) Pid=0x12 (EIT) # 900-Filter 1: 0 ( 0 kB/s) Pid=0x14 (TDT) # 900-Filter 2: 2 ( 0 kB/s) Pid=0x00 (PAT) # 900-Filter 3: 0 ( 0 kB/s) Pid=0x11 (SDT) # 900-Filter 4: 0 ( 0 kB/s) Pid=0x10 (NIT) # 900 Filter 5: 0 ( 0 kB/s) Pid=0x60 (---) $reading = "PluginInfo"; # check if we got "900-n" if (substr($msg, 3, 1) eq "-"){ ($code, $msg) = split (/-/, $msg, 2); #Log3 $name, 5, "[$name] Parse: substring contains '-'"; } else{ ($code, $msg) = split (/ /, $msg, 2); } $plugin = ReadingsVal($name, $reading, ""); $msg = SVDRP_parsePlugin($name, $msg); if ($msg ne "none"){ $msg = $plugin."\n".$msg; $rv = readingsSingleUpdate($hash, $reading, $msg, 1); Log3 $name, 5, "[$name] Parse: updated $reading with '$msg'"; } #Log3 $name, 5, "[$name] Parse: parsePlugin returned $msg"; #$msg = $plugin."\n".$msg; #$rv = readingsSingleUpdate($hash, $reading, $msg, 1); #Log3 $name, 5, "[$name] Parse: 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) = @_; my $hash = $defs{$name}; #$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"; my $timernameraw = "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, $timernameraw) = split (":", $timerstr, 8); substr ($start, 2, 0) = ":"; substr ($end, 2, 0) = ":"; # strip info from timername $timernameraw =~ s/[:\r]//g; $timername = (split //, $timernameraw, 2)[0]; # store timer ID and Name in hidden setting, to re-use with NextTimer command #$timername =~ s/[:\r\n]//g; #$timername =~ s/[:\r]//g; $SVDRP_timers{$timerid} = $timername; readingsSingleUpdate( $hash, ".Timers", encode_json( \%SVDRP_timers ), 1 ); $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_parseNextTimer { my ($hash, $reading, $msg) = @_; #my $hash = $defs{$name}; my $name = $hash->{NAME}; my $timername = ""; (my $code, $msg) = split (/ /, $msg, 2); # replace double blank by single blank $msg =~ s/ / /g; Log3 $name, 5, "[$name] Parse: NextTimer: $msg"; (my $tid, my $tday, my $tmonth, my $tdate, my $tstart, my $tyear) = split (/ /,$msg); Log3 $name, 5, "[$name] Parse: NextTimer: $tid - $tday - $tmonth - $tdate - $tstart - $tyear"; # get timer name from hidden reading (requires ListTimer to be run) my %myVDRtimers = SVDRP_restoreJson($hash, ".Timers"); if (exists($myVDRtimers{$tid})){ $timername = $myVDRtimers{$tid}; Log3 $name, 5, "[$name] Parse: NextTimer: ID: $tid, name: $timername"; } my $parsedmsg = "ID: ".sprintf("%2s",$tid)." | Day: ".sprintf("%3s",$tday).sprintf("%3s",$tdate).".".sprintf("%3s",$tmonth)." ".sprintf("%4i",$tyear)." | Start: ".$tstart." | Name: ".$timername; Log3 $name, 5, "[$name] Parse: NextTimer: $parsedmsg"; #readingsSingleUpdate($hash, $reading, $parsedmsg, 1); #Log3 $name, 5, "[$name] Parse: updated $reading with $msg"; return $parsedmsg; } sub SVDRP_getTimerNames { my ($name, $msg) = @_; } sub SVDRP_parseRecording { my ($name, $msg) = @_; my $type = "none"; my $recinfo = AttrVal($name,"RecordingInfo","short"); if ($recinfo eq "short") { # #T Löwengrube (Title) #S Tigerbande (Subtitle) #D August 1950 (Description) if (substr($msg, 0, 1) eq "T"){ #$type = "Title: "; $type = "- "; } elsif (substr($msg, 0, 1) eq "S"){ #$type = "Subtitle: "; $type = "- "; } elsif (substr($msg, 0, 1) eq "D"){ #$type = "Description: "; $type = ""; # add newlines after next space after $lf characters #$msg = join ("\n", ( $msg =~ /.{1,80}/gs )); #$msg =~ s/(.{39}[^\s]*)\s+/$1\n/; my $length = length($msg); my $lf = "70"; my $i = "1"; my $count; while ($length > 0){ $count = $i * $lf; $msg =~ s/(.{\Q$count\E}[^\h]*)\s+/$1\n/g; $length = $length - $lf; $i++; } } else{ return "none"; } $msg = $type.(split / /, $msg, 2)[1]; } return $msg; } sub SVDRP_parseHelpinfo { my ($name, $msg) = @_; Log3 $name, 5, "[$name] parseHelpinfo: parsed output is $msg"; return $msg; } sub SVDRP_parsePlugin { my ($name, $msg) = @_; Log3 $name, 5, "[$name] parsePlugin: parsed output is $msg"; return $msg; } 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}; $hash->{version} = $version; # 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, "globalError", "", 1); readingsSingleUpdate($hash, "infoError", "", 1); # set .sendingcmd to indicate that cmd sending cycle is started # needed e.g. to empty HelpInfo as soon as new info is received readingsSingleUpdate($hash, ".sendingcmd", "1", 1); if ($opt eq "cleanUp"){ main::Log3 $name, 5, "[$name]: Set: $name cleanUp"; SVDRP_cleanUp($hash); return; } if ($opt eq "closeDev"){ main::Log3 $name, 5, "[$name]: Set: $name closeDev"; SVDRP_closeDev($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); #$SVDRP_timers{$timerid} = $timername; %SVDRP_timers = (); main::Log3 $name, 5, "[$name]: Set: deleted ListTimers, value is now ".ReadingsVal($name,"ListTimers","none"); } if ($opt eq "LSTR"){ # delete Recordings, will be re-filled completely my $recid; if (!$value){ $recid = "Recording ID: all"; } else{ $recid = "Recording ID: ".$value; } #main::Log3 $name, 5, "[$name]: Set: LastCmd is ".AttrVal($name,"LastCmd","unknown"); #my $recid = "Recording ID: ".((split / /, AttrVal($name,"LastCmd","unknown"), 2)[1] || "all"); readingsSingleUpdate($hash, "Recordings", $recid, 1); main::Log3 $name, 5, "[$name]: Set: deleted Recordings, value is now ".ReadingsVal($name,"Recordings","none"); } if ($opt =~ /^PLUG/){ readingsSingleUpdate($hash, "PluginInfo", "", 1); main::Log3 $name, 5, "[$name]: Set: $name PluginInfo"; } # 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, 1, "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; if ($msg =~ /GetAll/){ my $cmds = join (" ", @SVDRP_statusCmds); $writecmd = $name."|".$cmds."|".$optorg; InternalTimer( $next, "SVDRP_multiWrite", $writecmd); } elsif($msg =~ /NEXT/) { my $cmds = "LSTT NEXT"; $writecmd = $name."|".$cmds."|".$optorg; InternalTimer( $next, "SVDRP_multiWrite", $writecmd); } else{ $writecmd = $name."|".$msg."|".$optorg; 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 { # write single command via DevIo 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_multiWrite { # write multiple commands via DevIo my ($writecmd) = @_; my ( $name, $msg, $optorg ) = split( "\\|", $writecmd ); my $hash = $defs{$name}; my $send; main::Log3 $name, 5, "[$name]: multiWrite: will send: $msg"; my @msgarr = split / /, $msg; #$hash->{helper}{LastCmd} = $optorg; foreach (@msgarr) { if ($_ eq "LSTT"){ # delete ListTimers, will be re-filled completely readingsSingleUpdate($hash, "ListTimers", "", 1); %SVDRP_timers = (); } if ($_ eq "STAT"){ $send = $_." disk\r\n" } else{ $send = "$_\r\n"; } DevIo_SimpleWrite($hash, $send, "2"); #main::Log3 $name, 5, "[$name]: multiWrite: sending $send"; } } sub SVDRP_checkConnection ($) { my ($hash) = @_; my $name = $hash->{NAME}; RemoveInternalTimer($hash, "SVDRP_checkConnection"); main::Log3 $name, 5,"[$name]: checkConnection: status timer removed"; 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 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 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", "DiskStatus" ); my $next; # add LF for better overview main::Log3 $name, 5, "\n"; main::Log3 $name, 5,"[$name]: checkStatus: called with $checkcmd with interval $checkInterval"; 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"; } } sub SVDRP_restoreJson { my ($hash, $reading) = @_; my $name = $hash->{NAME}; my $jsets = ReadingsVal($name, $reading, "{none:none}"); my $decode = decode_json($jsets); # just for logging #my %decode = %$decode; #main::Log3 $name, 5, "[$name]: restore: ". keys(%decode); return %$decode; } ################################################### # 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

SVDRP

    SVDRP implements SVDRP to control VDR via (W)Lan.

    Define
      define <name> SVDRP <IP_Address> [<port>]

      70_SVDRP.pm provides basic control of your VDR.
      Only a reasonable subset of SVDRP commands in implemented, since it e.g. does not make sense to set timers via fhem - vdradmin is a much more convenient GUI for that.

      • IP_Address - the IP Address of your VDR
      • port - ... guess? Yes, the port. If not given, VDR standard port 6419 is used.
      • Example: define VDRcontrol SVDRP 10.10.0.1 6419

    Set

      Available set commands are taken from http://www.vdr-wiki.de/wiki/index.php/SVDRP.
      For the predefined "raw" commands, "nice" names will be shown for the readings, e.g. DiskStatus instead of STAT disk.
      Default set commands are

    • Channel
      set value can be "+" or "-" or any channel number you want to switch to.
      set <name> Channel will get you the channel VDR is currently tuned to.

    • DeleteTimer
      set <name> DeleteTimer <number> will delete ... hm, guess?
      (you can get the timer numbers via ListTimers)

    • DiskStatus
      no value or get will display the current disk usage in DiskStatus
      Additionally, the reading DiskUsed will be set to the disk fill level.

    • GetAll
      no value or get will query several SVDRP settings:
      "LSTT", "NEXT", "CHAN", "VOLU", "STAT"
      (i.e. ListTimers, NextTimer, Channel, Volume, DiskStatus)

    • Help
      gets the avaialble SVDRP commands from VDR and stores them in reading "HelpInfo"
    • HitKey
      Enables you to send any Key defined by http://www.vdr-wiki.de/wiki/index.php/SVDRP
      E.g.set <name> HitKey Power will cleanly power off VDR.

    • ListRecording
      set value should be an existing recording ID. Depending on the attribute RecordingInfo either all available info will be shown, or a reasonable subset.
      If no value is given, all available recordings will be read and shown.
      Attention: Depending on the number of number of recordings, this might take a while! fhem might show "timeout", and a screen refresh might be necessary. Use with care...

    • ListTimers
      no value or get will query all timers from VDR.
      raw answer from VDR will be parsed into a little bit nicer format.

    • NextTimer
      no value or get will exactly get what it says.
      (to get the timer name, ListTimers will be called before)

    • Plugin
      calls SVDRP with PLUG - you can enter any Plugin's SVDRP commands here to control the plugin
      calling without parameter gets the list of avaialble plugins and stroes them in reading "HelpInfo"
      I cannot test this with any plugin, but the Plugin's answer should go to the reading "PluginInfo" or "InfoError" (if Plugin gives an error message)

    • PowerOff
      A shortcut to cleanly power off VDR, same as set <name> HitKey Power

    • SatIP
      send control commands to your SatIP plugin

    • StreamdevServer
      sends the corresponding SVDRP command to the streamdev-Plugin (LSTC,DISC)

    • UpdateRecordings
      no value or get will trigger VDR to re-read the recordings.
      (No output to fhem - no sense to show all recordings here)

    • Volume
      set value can be "+" or "-" or mute or any Volume (0-255) you want to set.
      set <name> Volume will get you VDR's current Volume setting.

    • connect
      just connects to VDR, no further action.
      Reading "info" will be updated.
      Attention: As long as connection to VDR is open, no other SVDRP client can connect!
      You might want to use "cleanup" to be able to reconnect other clients.

    • cleanUp
      closes connection to VDR, no further action.
      Reading "info" will be updated.

    • closeDev
      subset of cleanup. Just closes DevIo connection.
      If you don't know what that means, you don't need it ;-)

    Attributes
    • AdditionalSettings
      cmd1:val_1,...,val_n cmd2:val_1,...,val_n
      You can specify own set commands here, they will be added to the set list.
      Multiple own sets can be specified, separated by a blank.
      command and values are separated by ":", values are separated by ",".
      Example: HITK:up,down,Power MESG

    • RecordingInfo
      short|long
      defines the amount of information shown on ListRecording
      short will display recording iD, title, subtitle, Description
      long will show all available information of the requested Recording
      Default value is "short"

    • connectionCheck
      off|(value in seconds)
      value defines the intervall in seconds to perform an connection check.
      Normally you won't need that. Use at your own risk...
      Default value is "off".

    • statusCheckInterval
      off|(value in seconds)
      value defines the interval in seconds to perform an status check.
      Each interval the VDR is queried with the command defined by statusCheckCmd (default: DiskStatus).
      Default value is off.

    • statusCheckCmd
      (any command(s) you set)
      Defines the command(s) used by statusCheckInterval.

    • statusOfflineMsg
      (any message text you set)
      Defines the message to set in the Reading related to statusCheckCmd when the device goes offline.
      Status of device will be checked after each statusCheckInterval (default: off), querying the statusCheckCmd command (default: DiskStatus), and if STATE is disconnected the Reading of statusCheckCmd will be set to this message. Default: closed.

    • delay
      delay time in seconds
      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.
      Default: 1.

=end html =cut