# $Id$ # # Siro module for FHEM # Thanks for templates/coding from SIGNALduino team and Jarnsen_darkmission_ralf9 # # Needs SIGNALduino >= V3.3.1-dev (10.03.2017). # Published under GNU GPL License, v2 # History: # 0.01 2017-05-24 Smag Decoding/sniffing signals, Binary-keycode-decoding for on, off, stop, favourite and p2 (pairing). # 0.02 2017-05-25 Smag Tests with CUL/COC and Signalduino. Successful signalrepeat to device via Signalduino. # 0.03 2017-07-23 Smag initial template # 0.04 2017-07-24 Smag Changed binary device-define to much more easier hex-code (28bit+channel). # 0.10 2017-07-30 Smag Siro-Parse implemented. First alpha-version went out for Byte09 and det # 0.11 2017-07-30 Smag, Byte09 updated module # 0.12 2017-08-02 Byte Subroutine X_internaltset komplett entfernt, Zusammenlegung mit X_set # Byte Variablen bereinigt # Byte Code bereinigt # Byte Einführung "readings", "lastmsg" und "aktMsg" inkl. Unixtime # 0.13 2017-08-03 Byte Senden eines doppelten Stoppbefehls abgefangen, keine Hardwaremittelanfahrt durch zweimaliges Stopp mehr möglich # 0.14 2017-08-04 Byte Attr SignalLongStopRepeats eingeführt. Anzahl der Repeats für Longstop (Favourite). undef = 15 # 0.15 2017-08-05 Byte Attr "timer" eingeführt. Enthält die Zeit zwischen zwei ausgeführten Befehlen zur Positionsberechnung # 0.16 2017-08-06 Byte Berechnung der aktuellen Position nach Stop-Kommando - Darstellung im state bzw. im Slider # 0.17 2017-08-09 Byte Fehler bei der Positionsfahrt aus der Fav-Position behoben # 0.18 2017-08-10 Byte Änderung des State bei Empfang von FB integriert # 0.19 2017-08-12 Byte Attr 'position_adjust' abgeschafft # 0.20 2017-08-12 Attr 'operation_mode' eingeführt # 0 Normaler Modus : Keine 'position_adjust' - Fahrt über 0 # 1 Normaler Modus : Positionsanfahrt immer -> 'position_adjust' - Fahrt über 0 # 2 Repeater Modus : Modul empfängt die FB und leitet es an den Motor weiter. Motor empfängt die FB nicht direkt. # Kanalanpassung notwendig. Zuverlässigster Betrieb! # 0.21 2017-08-13 Smag: Code-Cleanup. Spellcheck. Documentation # 0.22 2017-08-18 Byte: Kompletten Code überarbeitet. Laufzeitoptimierung. Reaktionszeit verbessert. Unnötige Readings in die Internals verlagert. Fehler bei direktem umschalten behoben. # Operation_mode 1 komplett entfernt, om 2 ist nun om 1. Operationmode 1 bis Fertigstellung deaktiviert # 0.23 Beta Byte V0.22 - > V1.0 Beta # 0.24 2017-08-20 Byte Positionsanfahrt über Alexa möglich - "schalte DEVICE XX%" # Operation_mode 1 eingeführt. ( Repeatermodus ) # 0.25 2017-08-26 Byte diverse Korrekturen, Codebereinigung, Anfahrt der HW_Favorit Position über FB im Mode 1 möglich # 0.26 2017-08-26 Byte Commandref Deutsch hinzugefügt # 0.27 2017-08-29 Byte Define von Devices, die eine Kanal nutzen der bereits von einem Device genutzt wird (channel / send_channel_mode1) wird unterbunden # Debug_Attr (0/1) eingefügt - es werden diverse redings angelegt und kein Befehl physisch anden Rollo gesendet - nur Fehlersuche # 0.28 2017-09-02 ByteFehler bei Stateaktualisierung in Zusammenhang mit Stop bei Favoritenanfahrt behoben # 0.29 2017-08-29 Byte Define von Devices, die einen Kanal nutzen, der bereits von einem Device genutzt wird (channel / send_channel_mode1) wird unterbunden # Debug_Attr (0/1) eingefügt - es werden diverse redings angelegt und kein Befehl physisch anden Rollo gesendet - nur Fehlersuche # Set favorite und Attr prog_fav_sequence eingeführt - programmierung derHardware_Favorite_Position # Codebereinigung # Allgemeine Fehler behoben # 0.30 2017-09-09 Byte Betrieb in eingeschränkter Funktion ohne 'time'- Attribute möglich # 0.31 2017-09-10 Byte Commandref ergänzt Deutsch/Englisch # 0.32 2017-09-16 Byte Fehlerkorrekturen # 0.34 2017-09-17 Invers Dokumentation, Byte Korrekturen Log # 0.35 2017-09-24 Byte Fehlerkorrekturen , Einbau Device mit Kanal 0 als Gruppendevice ( noch gesperrt ) . Attribut "channel" enfernt , Kanalwahl nur noch über das Device möglich . # 0.36 2017-09-24 Byte Device0 Favoritenanfahrt und Positionsanfahrt durch FHEM möglich # 0.37 2017-09-25 SMag Prerelease-Vorbereitungen. Codeformatierung, Fehlerkorrekturen, Textkorrekturen. # 0.38 2017-09-27 optimierung sub Siro_Setgroup($) -> laufzeitverbesserung # # 0.39 2017-10-14 Byte Log überarbeitet / Parse überarbeitet / Define überarbeitet / interne Datenstruktur geändert / Internals überarbeitet / Groupdevice ( Kanal 0 ) möglich . Fehlerkorrekturen / attribut down_for_timer und up_for_timer eingebaut # 0.40 2017-10-15 Byte Code bereinigt # 0.41 2017-10-17 Byte anpassung der %Sets je nach device ( groupdevice ) # 0.42 2017-10-18 Byte attr "down_auto_stop" eingefügt - beendet runterfahrt durch on/close/fb bei ATTR weiterfahrt durch nochmalige cmd . Comandref ergänzt # 0.43 2017-10-19 Byte attr "invers_position[0/1]" eingefügt. Invertiert positionsanzeige und anfahrt 0 -> 100% = rollo geschlossen - 1 -> 0% =rollo geschlossen # 0.44 2017-10-19 Byte bugfix -> set favorite. Unterscheidung ob "time_down_to_favorite" gesetzt oder nicht. ( interpretation :favorite programmiert oder nicht ) - entsprechende anpassung des kommandos ( erst löschen -> dann speichern ) # 0.45 2017-10-28 Byte fehler bei erneutem Kommando während Positionsanfahrten behoben # 0.46 2017-05-11 Byte fehler bei fhem-neustart behoben # 0.47 2017-11-11 Byte attr Disable zugefügt. ################################################################################################################ # Todo's: # - # - ############################################################################################################### package main; use strict; use warnings; my $version = "V 0.47"; my %codes = ( "55" => "stop", # Stop the current movement or move to custom position "11" => "off", # Move "up" "33" => "on", # Move "down" "CC" => "prog", # Programming-Mode (Remote-control-key: P2) ); my %sets = ( "open" => "noArg", "close" => "noArg", "off" => "noArg", "stop" => "noArg", "on" => "noArg", "fav" => "noArg", "prog" => "noArg", "prog_stop" => "noArg", "position" => "slider,0,1,100", # Wird nur bei vorhandenen time_to attributen gesetzt "state" => "noArg" , "set_favorite" => "noArg", "down_for_timer" => "textField", "up_for_timer" => "textField" ); my %sendCommands = ( "off" => "off", "stop" => "stop", "on" => "on", "open" => "off", "close" => "on", "fav" => "fav", "prog" => "prog", "set_favorite" => "setfav" ); my %siro_c2b; my %models = ( siroblinds => 'blinds', siroshutter => 'shutter' ); ################################################################# sub Siro_Initialize($) { my ($hash) = @_; # Map commands from web interface to codes used in Siro foreach my $k ( keys %codes ) { $siro_c2b{ $codes{$k} } = $k; } $hash->{SetFn} = "Siro_Set"; $hash->{NotifyFn} = "Siro_Notify"; $hash->{ShutdownFn} = "Siro_Shutdown"; #$hash->{StateFn} = "Siro_SetState"; #change $hash->{DefFn} = "Siro_Define"; $hash->{UndefFn} = "Siro_Undef"; $hash->{DeleteFn} = "Siro_Delete"; $hash->{ParseFn} = "Siro_Parse"; $hash->{AttrFn} = "Siro_Attr"; $hash->{Match} = "^P72#[A-Fa-f0-9]+"; $hash->{AttrList} = " IODev" . " disable:0,1" . " SignalRepeats:1,2,3,4,5,6,7,8,9" . " SignalLongStopRepeats:10,15,20" . " channel_send_mode_1:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15" . " $readingFnAttributes" . " setList" . " ignore:0,1" . " dummy:1,0" # . " model:siroblinds,siroshutter" . " time_to_open" . " time_to_close" . " time_down_to_favorite" . " hash" . " operation_mode:0,1" . " debug_mode:0,1" . " down_limit_mode_1:slider,0,1,100" . " down_auto_stop:slider,0,1,100" . " invers_position:0,1" . " prog_fav_sequence"; $hash->{AutoCreate} = { "Siro.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", autocreateThreshold => "2:10" } }; $hash->{NOTIFYDEV} = "global"; Siro_LoadHelper($hash) if($init_done); } ################################################################# sub Siro_Define($$) { my ( $hash, $def ) = @_; my @a = split( "[ \t][ \t]*", $def ); my $u = "Wrong syntax: define Siro id "; my $askedchannel; # Angefragter kanal # Fail early and display syntax help if ( int(@a) < 3 ) { return $u; } if ( $a[2] =~ m/^[A-Fa-f0-9]{8}$/i ){ $hash->{ID} = uc(substr($a[2], 0, 7)); $hash->{CHANNEL} = sprintf( "%d", hex(substr($a[2], 7, 1)) ); $askedchannel=sprintf( "%d", hex(substr($a[2], 7, 1)) );; } else { return "Define $a[0]: wrong address format: specify a 8 char hex value (id=7 chars, channel=1 char) . Example A23B7C51. The last hexchar identifies the channel. -> ID=A23B7C5, Channel=1. " } my $test; my $device; my $chanm1; my $chan; my @channels = ('','0','1','2','3','4','5','6','7','8','9'); my @testchannels; # Enthält alle Kanäle die in Benutzung sind my @keys; my @values; my $testname; $hash->{Version} = $version; my $name = $a[0]; # Log3($name, 0, "Siro_define: $hash->{helper}{position} "); # if ($hash->{helper}{position} eq '') # { # $hash->{helper}{position}="0"; # $hash->{helper}{aktMsg} = "stop".' '.'0'.' '.gettimeofday(); # $hash->{helper}{lastMsg} = "stop".' '.'0'.' '.gettimeofday(); # $hash->{helper}{lastProg} ="0"; # $hash->{helper}{lastparse_stop} ="stop".' '.gettimeofday(); # $hash->{helper}{lastparse} =""; # $hash->{helper}{parse_aborted} ="0"; # } my $tn = TimeNow(); #Wird wohl nicht benötigt?!?! down_limit_mode_1 if ($askedchannel ne "0") { #Setzen der vordefinierten Attribute $attr{$name}{devStateIcon} ="{return '.*:fts_shutter_1w_'.(int(\x{24}state/10)*10)}" if(!defined ($attr{$name}{devStateIcon})); #TMP_Byte09 !defined $attr{$name}{webCmd} ="stop:on:off:fav:position" if (!defined ($attr{$name}{webCmd})); $attr{$name}{down_limit_mode_1} ="100" if (!defined ($attr{$name}{down_limit_mode_1})); $attr{$name}{prog_fav_sequence} ="prog,2,stop,2,stop" if (!defined ($attr{$name}{prog_fav_sequence})); #$attr{$name}{room} ="Siro" if (!defined ($attr{$name}{genericDeviceType})); $attr{$name}{SignalLongStopRepeats} ="15" if (!defined ($attr{$name}{SignalLongStopRepeats})); $attr{$name}{SignalRepeats} ="8" if (!defined ($attr{$name}{SignalRepeats})); $attr{$name}{operation_mode} ="0" if (!defined ($attr{$name}{operation_mode})); $attr{$name}{down_auto_stop} ="100" if (!defined ($attr{$name}{down_auto_stop})); $attr{$name}{invers_position} ="0" if (!defined ($attr{$name}{invers_position})); } else { $attr{$name}{devStateIcon} ="{return '.*:fts_shutter_1w_'.(int(\x{24}state/10)*10)}" if(!defined ($attr{$name}{devStateIcon})); #TMP_Byte09 !defined $attr{$name}{webCmd} ="stop:on:off:fav:position" if (!defined ($attr{$name}{webCmd})); #$attr{$name}{room} ="Siro" if (!defined ($attr{$name}{genericDeviceType})); $attr{$name}{SignalLongStopRepeats} ="15" if (!defined ($attr{$name}{SignalLongStopRepeats})); $attr{$name}{SignalRepeats} ="8" if (!defined ($attr{$name}{SignalRepeats})); #$attr{$name}{SignalRepeats} ="2"; $attr{$name}{down_auto_stop} ="100" if (!defined ($attr{$name}{down_auto_stop})); $attr{$name}{invers_position} ="0" if (!defined ($attr{$name}{invers_position})); } my $code = uc($a[2]); my $ncode = 1; my $devpointer = $hash->{ID}.$hash->{CHANNEL}; $hash->{CODE}{ $ncode++ } = $code; $modules{Siro}{defptr}{$devpointer} = $hash; Log3($name, 5, "Siro_define: angelegtes Device - code -> $code name -> $name hash -> $hash "); AssignIoPort($hash); } ################################################################# sub Siro_Undef($$) { my ( $hash, $name) = @_; delete( $modules{Siro}{defptr}{$hash} ); return undef; } ################################################################# sub Siro_Shutdown($) { my ( $hash ) = @_; my $name = $hash->{NAME}; readingsSingleUpdate($hash, ".aktMsg",$hash->{helper}{aktMsg}, 0); readingsSingleUpdate($hash, ".lastMsg",$hash->{helper}{lastMsg}, 0); readingsSingleUpdate($hash, ".lastProg",$hash->{helper}{lastProg}, 0); readingsSingleUpdate($hash, ".lastparse",$hash->{helper}{lastparse}, 0); readingsSingleUpdate($hash, ".lastparse_stop",$hash->{helper}{lastparse_stop}, 0); readingsSingleUpdate($hash, ".parse_aborted",$hash->{helper}{parse_aborted}, 0); readingsSingleUpdate($hash, ".positionsave",$hash->{helper}{position}, 0); readingsSingleUpdate($hash, ".positiontimer",$hash->{helper}{positiontimer}, 0); return; } ################################################################# sub Siro_LoadHelper($) { my ( $hash ) = @_; my $name = $hash->{NAME}; my $save = ReadingsVal($name, '.aktMsg', 'undef'); if ($save eq 'undef') { $hash->{helper}{position}="0"; $hash->{helper}{aktMsg} = "stop".' '.'0'.' '.gettimeofday(); $hash->{helper}{lastMsg} = "stop".' '.'0'.' '.gettimeofday(); $hash->{helper}{lastProg} ="0"; $hash->{helper}{lastparse_stop} ="stop".' '.gettimeofday(); $hash->{helper}{lastparse} =""; $hash->{helper}{parse_aborted} ="0"; } else { $hash->{helper}{aktMsg} = ReadingsVal($name, '.aktMsg', ''); $hash->{helper}{lastMsg} = ReadingsVal($name, '.lastMsg', ''); $hash->{helper}{lastparse} = ReadingsVal($name, '.lastparse', ''); $hash->{helper}{lastProg} = ReadingsVal($name, '.lastProg', ''); $hash->{helper}{lastparse_stop} = ReadingsVal($name, '.lastparse_stop', ''); $hash->{helper}{parse_aborted} = ReadingsVal($name, '.parse_aborted', ''); $hash->{helper}{position} = ReadingsVal($name, '.positionsave', ''); $hash->{helper}{positiontimer} = ReadingsVal($name, '.positiontimer', ''); } Log3($name, 5, "Siro: Helper initialisiert"); return; } ################################################################# sub Siro_Notify($$) { my ($own_hash, $dev_hash) = @_; my $ownName = $own_hash->{NAME}; # own name / hash my $devName = $dev_hash->{NAME}; # Device that created the events my $events = deviceEvents($dev_hash, 1); if($devName eq "global" && grep(m/^INITIALIZED|REREADCFG$/, @{$events})) { Siro_LoadHelper($own_hash); } return; } ################################################################# sub Siro_Delete($$) { my ( $hash, $name ) = @_; return undef; } ################################################################# sub Siro_SendCommand($@) { my ($hash, @args) = @_; my $ret = undef; my $cmd = $args[0]; # Command as text (on, off, stop, prog) my $message; # IO-Message (full) my $chan; # Channel my $binChannel; # Binary channel my $SignalRepeats; # my $name = $hash->{NAME}; my $binHash; my $bin; # Full binary IO-Message my $binCommand; my $numberOfArgs = int(@args); my $command = $siro_c2b{ $cmd }; my $io = $hash->{IODev}; # IO-Device (SIGNALduino) my $debug = AttrVal($name,'debug_mode', '0'); Log3($name, 5, "Siro_sendCommand: hash -> $hash - $name -> cmd :$cmd: - args -> @args"); if ($args[2] eq "longstop") { $SignalRepeats =AttrVal($name,'SignalLongStopRepeats', '15') } else { $SignalRepeats = AttrVal($name,'SignalRepeats', '10'); } my $operationmode = AttrVal($name,'operation_mode', 'on'); Log3($name, 5, "Siro_sendCommand: operationmode -> $operationmode"); if ($operationmode eq '1') { $chan = AttrVal($name,'channel_send_mode_1', $hash->{CHANNEL}); Log3($name, 5, "Siro_sendCommand: channel für OM1 -> $chan"); } else { $chan = AttrVal($name,'channel', undef); if (!defined($chan)) { $chan = $hash->{CHANNEL}; } } if ($chan eq "0") { Log3($name, 5, "Siro_sendCommand: Aborted not sent on a channel 0 request "); return; } $binChannel = sprintf("%04b",$chan); Log3($name, 5, "Siro set channel: $chan ($binChannel) for $io->{NAME}"); my $value = $name ." ". join(" ", @args); $binHash = sprintf( "%028b", hex( $hash->{ID} ) ); Log3 $io, 5, "Siro_sendCommand: BinHash: = $binHash"; $binCommand = sprintf( "%08b", hex( $command ) ); Log3 $io, 5, "Siro_sendCommand: BinCommand: = $binCommand"; $bin = $binHash . $binChannel . $binCommand; # Binary code to send Log3 $io, 5, "Siro_sendCommand: Siro set value = $value"; $message = 'P72#' . $bin . '#R' . $SignalRepeats; if ($debug eq "1") { readingsSingleUpdate($hash, "DEBUG_SEND","$name -> message :$message: ", 1); } else { Log3 $io, 5, "Siro_sendCommand: Siro_sendCommand: $name -> message :$message: "; IOWrite($hash, 'sendMsg', $message); } my $devicename =$hash->{IODev}; Log3($name, 5, "Siro_sendCommand: name -> $name command -> $cmd "); Log3($name, 3, "Siro_sendCommand: execute comand $cmd - sendMsg to $devicename channel $chan -> $message "); return $ret; } ################################################################# sub Siro_Parse($$) { my $debug=''; my @args; my ($hash, $msg) = @_; my $doubelmsgtime = 2; # zeit in sek in der doppelte nachrichten blockiert werden my $favcheck =$doubelmsgtime+1; # zeit in der ein zweiter stop kommen muss/darf für fav my $testid = substr($msg, 4, 8); my $testcmd = substr($msg, 12, 2); my $timediff; my $name = $hash->{NAME}; return "" if(IsDisabled($name)); if(my $lh = $modules{Siro}{defptr}{$testid}) { my $name = $lh->{NAME}; Log3 $hash, 3, "Siro_Parse: Incomming msg from IODevice $testid - $name device is defined"; if (defined ($name) && $testcmd ne "54" ) # prüfe auf doppele msg falls gerät vorhanden und cmd nicht stop { Log3 $lh, 5, "Siro_Parse: Incomming msg $msg from IODevice name/DEF $testid - Hash -> $lh"; my $testparsetime = gettimeofday(); my $lastparse = $lh->{helper}{lastparse}; my @lastparsearray =split(/ /,$lastparse); if (!defined($lastparsearray[1])){$lastparsearray[1] = 0}; if (!defined($lastparsearray[0])){$lastparsearray[0] = ""}; $timediff = $testparsetime-$lastparsearray[1]; my $abort ="false"; Log3 $lh, 5, "Siro_Parse: test doublemsg "; Log3 $lh, 5, "Siro_Parse: lastparsearray[0] -> $lastparsearray[0] "; Log3 $lh, 5, "Siro_Parse: lastparsearray[1] -> $lastparsearray[1] "; Log3 $lh, 5, "Siro_Parse: testparsetime -> $testparsetime "; Log3 $lh, 5, "Siro_Parse: timediff -> $timediff "; if ($msg eq $lastparsearray[0]) { if ($timediff < $doubelmsgtime ) { $abort ="true"; } } $lh->{helper}{lastparse} = "$msg $testparsetime"; if ($abort eq "true") { Log3 $lh, 4, "Siro_Parse: aborted , doublemsg "; return $name; } Log3 $lh, 4, "Siro_Parse: not aborted , no doublemsg "; } my (undef ,$rawData) = split("#",$msg); my $hlen = length($rawData); my $blen = $hlen * 4; my $bitData = unpack("B$blen", pack("H$hlen", $rawData)); Log3 $hash, 5, "Siro_Parse: msg = $rawData length: $msg"; Log3 $hash, 5, "Siro_Parse: rawData = $rawData length: $hlen"; Log3 $hash, 5, "Siro_Parse: converted to bits: $bitData"; my $id = substr($rawData, 0, 7); # The first 7 hexcodes are the ID my $BitChannel = substr($bitData, 28, 4); # Not needed atm my $channel = sprintf( "%d", hex(substr($rawData, 7, 1)) ); # The last hexcode-char defines the channel my $channelhex = substr($rawData, 7, 1) ; # tmp my $cmd = sprintf( "%d", hex(substr($rawData, 8, 1)) ); my $newstate = $codes{ $cmd . $cmd}; # Set new state my $deviceCode = $id . $channelhex; # Tmp change channel -> channelhex. The device-code is a combination of id and channel $debug=$debug."id-".$id." "; Log3 $hash, 5, "Siro_Parse: device ID: $id"; Log3 $hash, 5, "Siro_Parse: Channel: $channel"; Log3 $hash, 5, "Siro_Parse: Cmd: $cmd Newstate: $newstate"; Log3 $hash, 5, "Siro_Parse: deviceCode: $deviceCode"; if (defined ($name) && $testcmd eq "54" ) # prüfe auf doppele msg falls gerät vorhanden und cmd stop { # Log3 $lh, 5, "Siro_Parse: prüfung auf douplestop "; my $testparsetime = gettimeofday(); my $lastparsestop = $lh->{helper}{lastparse_stop}; my $parseaborted = $lh->{helper}{parse_aborted}; my @lastparsestoparray =split(/ /,$lastparsestop); my $timediff = $testparsetime-$lastparsestoparray[1]; my $abort ="false"; $parseaborted=0 if (!defined ($parseaborted)); Log3 $lh, 5, "Siro_Parse: test doublestop "; Log3 $lh, 5, "Siro_Parse: lastparsearray[0] -> $lastparsestoparray[0] "; Log3 $lh, 5, "Siro_Parse: lastparsearray[1] -> $lastparsestoparray[1] "; Log3 $lh, 5, "Siro_Parse: testparsetime -> $testparsetime "; Log3 $lh, 5, "Siro_Parse: timediff -> $timediff "; Log3 $lh, 5, "Siro_Parse: parseaborted -> $parseaborted "; if ($newstate eq $lastparsestoparray[0]) { if ($timediff < 3 ) { $abort ="true"; $parseaborted++; } } if ($abort eq "true" && $parseaborted < 8 ) { $lh->{helper}{parse_aborted}=$parseaborted; Log3 $lh, 5, "Siro_Parse: aborted , doublestop "; return $name; } $lh->{helper}{lastparse_stop} = "$newstate $testparsetime"; if ( $parseaborted >= 7 ) { $parseaborted = 0; $lh->{helper}{parse_aborted}=$parseaborted; $testparsetime = gettimeofday(); $lh->{helper}{lastparse_stop} = "$newstate $testparsetime"; if ($newstate eq "stop") { Log3 $lh, 3, "Siro_Parse: double_msg signal_favoritenanfahrt erkannt "; $newstate="fav"; $args[0]="fav"; } } } if (defined ($name)) { $args[0]=$newstate; my $parseaborted = 0; $lh->{helper}{parse_aborted}=$parseaborted; $debug=$debug.' '.$name; my $operationmode = AttrVal($name,'operation_mode', '0'); my $debugmode = AttrVal($name,'debug_mode', '0'); my $chan; if ($operationmode eq '0') { $chan = AttrVal($name,'channel', undef); if (!defined($chan)) { $chan = $lh->{CHANNEL}; } } if ($operationmode eq '1') { $chan = AttrVal($name,'channel', undef); if (!defined($chan)) { $chan = $lh->{CHANNEL}; } } my $aktMsg = $lh->{helper}{aktMsg} ; my @last_action_array=split(/ /,$aktMsg); my $lastaction = $last_action_array[0]; my $lastaction_position = $last_action_array[1]; my $lastaction_time = $last_action_array[2]; readingsSingleUpdate($lh, "parsestate", $newstate, 1); Log3 $lh, 5, "Siro_Parse: $name $newstate"; Log3 $lh, 5, "Siro_Parse: operationmode -> $operationmode"; if ($operationmode ne '1' || $chan eq "0") { Log3 $lh, 5, "Siro_Parse: set mode to physical"; $lh->{helper}{MODE} = "physical"; $debug=$debug.' physical'; } else { $lh->{helper}{MODE} = "repeater"; $debug=$debug.' repeater'; } if ($chan eq "0") { $args[1]="physical"; $debug=$debug.' physical'; } Log3 $lh, 2, "Siro_Parse -> Siro_Set: $lh, $name, @args"; Siro_Set($lh, $name, @args) ; $debug=$debug.' '.$lh; if($debugmode eq "1") { readingsSingleUpdate($lh, "DEBUG_PARSE",$debug, 1); } ############################################## # Return list of affected devices #my ( $hash, $name, @args ) = @_; ############################################## return $name; } } else { my (undef ,$rawData) = split("#",$msg); my $hlen = length($rawData); my $blen = $hlen * 4; my $bitData = unpack("B$blen", pack("H$hlen", $rawData)); Log3 $hash, 5, "Siro_Parse: msg = $rawData length: $msg"; Log3 $hash, 5, "Siro_Parse: rawData = $rawData length: $hlen"; Log3 $hash, 5, "Siro_Parse: converted to bits: $bitData"; my $id = substr($rawData, 0, 7); # The first 7 hexcodes are the ID my $BitChannel = substr($bitData, 28, 4); # Not needed atm my $channel = sprintf( "%d", hex(substr($rawData, 7, 1)) ); # The last hexcode-char defines the channel my $channelhex = substr($rawData, 7, 1) ; # tmp my $cmd = sprintf( "%d", hex(substr($rawData, 8, 1)) ); my $newstate = $codes{ $cmd . $cmd}; # Set new state my $deviceCode = $id . $channelhex; # Tmp change channel -> channelhex. The device-code is a combination of id and channel $debug=$debug."id-".$id." "; Log3 $hash, 5, "Siro_Parse: device ID: $id"; Log3 $hash, 5, "Siro_Parse: Channel: $channel"; Log3 $hash, 5, "Siro_Parse: Cmd: $cmd Newstate: $newstate"; Log3 $hash, 5, "Siro_Parse: deviceCode: $deviceCode"; Log3 $hash, 2, "Siro unknown device $deviceCode, please define it"; return "UNDEFINED Siro_$deviceCode Siro $deviceCode"; } } ############################################################# sub Siro_Attr(@) { my ($cmd,$name,$aName,$aVal) = @_; my $hash = $defs{$name}; return "\"Siro Attr: \" $name does not exist" if (!defined($hash)); my $channel = ($hash->{CHANNEL}); if ($channel eq "0") { my @notallowed = ("prog_fav_sequence", "time_to_open", "time_to_close", "operation_mode", "channel_send_mode_1", "time_down_to_favorite"); foreach my $test (@notallowed) { if ($test eq $aName ){return "\"Siro Attr: \" $name is a group device, the attribute $aName $aVal is not allowed here.";} } } return undef if (!defined($name)); return undef if (!defined($aName)); #return undef if (!defined($aVal)); if ($cmd eq "set") { if ($aName eq "debug_mode" && $aVal eq "0") { Log3 $hash, 5, "debug_mode: reading deleted"; delete ($hash->{READINGS}{DEBUG_SEND}); delete ($hash->{READINGS}{DEBUG_PARSE}); delete ($hash->{READINGS}{DEBUG_SET}); } if ($aName eq "debug_mode" && $aVal eq "1") { readingsSingleUpdate($hash, "DEBUG_SEND","aktiv", 1); readingsSingleUpdate($hash, "DEBUG_PARSE","aktiv", 1); readingsSingleUpdate($hash, "DEBUG_SET","aktiv", 1); Log3 $hash, 5, "debug_mode: create reading"; } if ($aName eq "invers_position") { if ( $aVal eq "0" ) { my $oldicon = "{return '.*:fts_shutter_1w_'.(100-(int(\$state/10)*10))}"; Log3 $hash, 3, "Siro_Attr: change attr old -> $attr{$name}{devStateIcon} $oldicon"; if ($attr{$name}{devStateIcon} eq $oldicon) { Log3 $hash, 3, "Siro_Attr: ersetzt"; $attr{$name}{devStateIcon} ="{return '.*:fts_shutter_1w_'.(int(\$state/10)*10)}" ; } if (defined ($attr{$name}{down_auto_stop})) { my $autostop = AttrVal($name,'down_auto_stop', 'undef'); $autostop = 100 - $autostop; $attr{$name}{down_auto_stop} =$autostop; } my $oldstate =$hash->{helper}{position}; my $newState = $oldstate; my $updateState; Log3 $hash, 3, "Siro_Attr: state old -> $oldstate new -> $newState"; Siro_UpdateState( $hash, $newState, '', $updateState, 1 ); } if ( $aVal eq "1" ) { my $oldicon = "{return '.*:fts_shutter_1w_'.(int(\$state/10)*10)}"; Log3 $hash, 3, "Siro_Attr: change attr old -> $attr{$name}{devStateIcon} $oldicon"; if ($attr{$name}{devStateIcon} eq $oldicon) { Log3 $hash, 3, "Siro_Attr: ersetzt"; $attr{$name}{devStateIcon} ="{return '.*:fts_shutter_1w_'.(100-(int(\$state/10)*10))}" ; } if (defined ($attr{$name}{down_auto_stop})) { my $autostop = AttrVal($name,'down_auto_stop', 'undef'); $autostop = 100 - $autostop; $attr{$name}{down_auto_stop} =$autostop; } my $oldstate =$hash->{helper}{position}; my $newState = 100 - $oldstate; my $updateState; Log3 $hash, 3, "Siro_Attr: state old -> $oldstate new -> $newState"; Siro_UpdateState( $hash, $newState, '', $updateState, 1 ); } } } if ($cmd eq "del") { if ($aName eq "invers_position") { my $oldicon = "{return '.*:fts_shutter_1w_'.(100-(int(\$state/10)*10))}"; Log3 $hash, 3, "Siro_Attr delete invers: change attr old -> $attr{$name}{devStateIcon} $oldicon"; if ($attr{$name}{devStateIcon} eq $oldicon) { Log3 $hash, 3, "Siro_Attr: ersetzt"; $attr{$name}{devStateIcon} ="{return '.*:fts_shutter_1w_'.(int(\$state/10)*10)}" ; } my $oldstate =$hash->{helper}{position}; my $newState = $oldstate; my $updateState; Log3 $hash, 3, "Siro_Attr: state old -> $oldstate new -> $newState"; Siro_UpdateState( $hash, $newState, '', $updateState, 1 ); } } return undef; } ################################################################# # Call with hash, name, virtual/send, set-args sub Siro_Set($@) { my $testtimestart = gettimeofday(); my $debug; my ( $hash, $name, @args ) = @_; return "" if(IsDisabled($name)); # Set without argument #parseonly my $numberOfArgs = int(@args); my $nodrive = "false"; $args[2]="0"; my $action ="no action"; return "Siro_set: No set value specified" if ( $numberOfArgs < 1 ); my $cmd = $args[0]; my $invers = AttrVal($name,'invers_position', '0'); my $runningaction = ReadingsVal($name, 'action', 'no action'); my $runningtime = 0; # teste auf hilfsvariablen my $save = ReadingsVal($name, '.aktMsg', 'undef'); if ($save eq 'undef') { $hash->{helper}{position}="0"; $hash->{helper}{aktMsg} = "stop".' '.'0'.' '.gettimeofday(); $hash->{helper}{lastMsg} = "stop".' '.'0'.' '.gettimeofday(); $hash->{helper}{lastProg} ="0"; $hash->{helper}{lastparse_stop} ="stop".' '.gettimeofday(); $hash->{helper}{lastparse} =""; $hash->{helper}{parse_aborted} ="0"; readingsSingleUpdate($hash, ".aktMsg",$hash->{helper}{aktMsg}, 0); readingsSingleUpdate($hash, ".lastMsg",$hash->{helper}{lastMsg}, 0); readingsSingleUpdate($hash, ".lastProg",$hash->{helper}{lastProg}, 0); readingsSingleUpdate($hash, ".lastparse",$hash->{helper}{lastparse}, 0); readingsSingleUpdate($hash, ".lastparse_stop",$hash->{helper}{lastparse_stop}, 0); readingsSingleUpdate($hash, ".parse_aborted",$hash->{helper}{parse_aborted}, 0); readingsSingleUpdate($hash, ".positionsave",$hash->{helper}{position}, 0); readingsSingleUpdate($hash, ".positiontimer",$hash->{helper}{positiontimer}, 0); } ######################################### für ECHO if ( $cmd =~ /^\d+$/) { if ($cmd >= 0 && $cmd <= 100) { $args[0] ="position"; $args[1] =$cmd; $cmd = "position"; } } ######################################### #diverse befehle bei laufender aktion abfangen if (($cmd eq "position" || $cmd eq 'up_for_timer' || $cmd eq 'on_for_timer') && $runningaction ne 'no action') { my $restartmsg = $name.' '.$args[0].' '.$args[1]; Log3($name,3,"Siro_Set: laufende action gefunden - restart msg -> $restartmsg "); InternalTimer(gettimeofday()+0,"Siro_Restartcmd",$restartmsg); $args[0] ="stop"; $args[1] =''; $cmd = "stop"; } ######################################### # invertiere position if ($cmd eq "position" && $invers eq "1") { my $noinvers = $args[1]; my $inversposition = 100 - $args[1]; $args[1] = $inversposition; Log3($name,3,"Siro_Set: invertiere Position - input -> $noinvers - inverted -> $inversposition "); } ######################################### ######################################### if (!defined $args[2]) { $args[2]=''; } if (!defined $args[1]) { $args[2]='0'; $args[1]=''; } if ($args[1] eq "physical") { $hash->{helper}{MODE} = "physical"; } if (!defined($hash)){return;} if (!defined($name)){return;} $debug = "$hash, $name, @args"; if ($cmd ne "?") { Log3($name,5,"Siro_Set: aufgerufen -> cmd -> $cmd args -> @args "); } ######################################### # up/down for timer mappen auf on/off und timer für stop setzen if ($cmd eq 'up_for_timer') { Log3($name,5,"Siro_Set: up_for_timer @args $args[1]"); $cmd ="off"; InternalTimer($testtimestart+$args[1], "Siro_Stop", $hash, 0); # State auf Stopp $args[0] =$cmd; $action = 'up for timer '.$args[1]; readingsSingleUpdate($hash, "action",$action, 1); #tmp } if ($cmd eq 'down_for_timer') { Log3($name,3,"Siro_Set: down_for_timer @args $args[1]"); $cmd ="on"; readingsSingleUpdate($hash, "action",'down for timer '.$args[1], 1); #tmp InternalTimer($testtimestart+$args[1], "Siro_Stop", $hash, 0); # State auf Stopp $args[0] =$cmd; $action = 'down for timer '.$args[1]; readingsSingleUpdate($hash, "action",$action, 1); #tmp } ######################################### # diverse befehle auf bekannte befehle mappen if ($cmd eq 'open') { $cmd ="off"; $args[0] =$cmd; } if ($cmd eq 'close') { $cmd ="on"; $args[0] =$cmd; } ######################################### $hash->{Version} = $version; # diverse attr einlesen my $debugmode = AttrVal($name,'debug_mode', '0'); my $testchannel = $hash->{CHANNEL}; my $timetoopen = AttrVal($name,'time_to_open', 'undef'); # fahrzeit komplett runter my $timetoclose = AttrVal($name,'time_to_close', 'undef'); # fahrzeit komplett hoch my $operationmode = AttrVal($name,'operation_mode', '0'); # mode 0 normal / 1 repeater my $limitedmode="off"; my $downlimit = AttrVal($name,'down_limit_mode_1', '100'); # maximale runterfahrt im mode 1 my $autostop = AttrVal($name,'down_auto_stop', '100'); # automatischer stop bei runterfahrt ######################################### # sets an device anpassen if ($testchannel ne "0") { %sets = ( "open" => "noArg", "close" => "noArg", "off" => "noArg", "stop" => "noArg", "on" => "noArg", "fav" => "noArg", "prog" => "noArg", "prog_stop" => "noArg", "position" => "slider,0,1,100", # Wird nur bei vorhandenen time_to attributen gesetzt "state" => "noArg" , "set_favorite" => "noArg", "down_for_timer" => "textField", "up_for_timer" => "textField" # Ggf. entfernen (Alexa) ); if($timetoopen eq 'undef' || $timetoclose eq 'undef') { if ($attr{$name}{webCmd} eq "stop:on:off:fav:position") { $attr{$name}{webCmd} ="stop:on:off:fav"; } $limitedmode="on"; $hash->{INFO} = "limited function without ATTR time_to_open / time_to_close / time_down_to_favorite"; } else { if ($attr{$name}{webCmd} eq "stop:on:off:fav") { $attr{$name}{webCmd} ="stop:on:off:fav:position"; } delete($hash->{INFO}); } } else # this block is for groupdevicec ( channel 0 ) { Log3($name,5,"Siro_Set: Groupdevice erkannt "); %sets = ( "open" => "noArg", "close" => "noArg", "off" => "noArg", "stop" => "noArg", "on" => "noArg", "fav" => "noArg", #"prog" => "noArg", #"prog_stop" => "noArg", "position" => "slider,0,1,100", # Wird nur bei vorhandenen time_to attributen gesetzt "state" => "noArg" , #"set_favorite" => "noArg", "down_for_timer" => "textField", "up_for_timer" => "textField" ); if ($cmd ne "?") #change { my $timetoopen = "15"; my $timetoclose = "15"; my $testhashtmp =""; my $testhashtmp1 =""; my $namex =""; my $testid =""; my $id = $hash->{ID}; my @grouphash; my @groupnames; @groupnames = Siro_Testgroup($hash,$id); $hash->{INFO} = "This is a group Device with limited functions affected the following devices:\n @groupnames"; my $groupcommand = $cmd.",".$args[0].",".$args[1].",".$hash; $hash->{helper}{groupcommand} = $groupcommand; InternalTimer(gettimeofday()+0,"Siro_Setgroup",$hash,1); } $hash->{helper}{MODE}="physical"; } ######################################### # Check for unknown arguments if(!exists($sets{$cmd})) { my @cList; # Overwrite %sets with setList my $atts = AttrVal($name,'setList',""); my %setlist = split("[: ][ ]*", $atts); foreach my $k (sort keys %sets) { my $opts = undef; $opts = $sets{$k}; $opts = $setlist{$k} if(exists($setlist{$k})); if (defined($opts)) { push(@cList,$k . ':' . $opts); } else { push (@cList,$k); } } # end foreach return "Siro_set: Unknown argument $cmd, choose one of " . join(" ", @cList); } ######################################### # undefinierte attr time to open/close abfangen if(($timetoopen eq 'undef' || $timetoclose eq 'undef') && $testchannel ne "0") { Log3($name,1,"Siro_Set:limited function without definition of time_to_close and time_to_open. Please define this attributes."); $nodrive ="true"; $timetoopen=1; $timetoclose=1; } ######################################### # progmodus setzen if ($cmd eq "prog") { $hash->{helper}{lastProg} = gettimeofday()+180; # 3 min programmiermodus } if ($cmd eq "prog_stop") { $hash->{helper}{lastProg} = gettimeofday(); return; } ######################################### # favoritenposition speichern if ($cmd eq "set_favorite") { if($debugmode eq "1") { readingsSingleUpdate($hash, "DEBUG_SET",'setze Favorite', 1); } my $sequence; my $sequenceraw = AttrVal($name,'prog_fav_sequence', ''); if (defined ($attr{$name}{time_down_to_favorite})) { $sequence = $sequenceraw.',2,'.$sequenceraw; Log3($name,0,"Siro_Set: delete and set favorit -> $sequence"); } else { $sequence = $sequenceraw ; Log3($name,0,"Siro_Set: set favorit -> $sequence"); } $hash->{sequence} = $sequence; # 3 Min Programmiermodus InternalTimer(gettimeofday()+1,"Siro_Sequence",$hash,0); # State auf stop setzen nach Erreichen der Fahrdauer Log3($name,1,"Siro_Set:setting new favorite"); return; } ######################################### # variablenberechnung für folgende funktionen my $check='check'; my $bmode = $hash->{helper}{aktMsg}; if ($bmode eq "repeater") { $check ='nocheck'; } my $ondirekttime = $timetoclose/100; # Zeit für 1 Prozent Runterfahrt my $offdirekttime = $timetoopen/100; # Zeit für 1 Prozent Hochfahrt #my $runningtime; my $timetorun; my $newposstate; if (defined ($args[1])) { if ( $args[1]=~ /^\d+$/) { $newposstate = $args[1];# Anzufahrende Position in Prozent Log3($name,5,"Siro_Set:newposstate -> $newposstate"); $timetorun = $timetoclose/100*$newposstate; # Laufzeit von 0 prozent }bis zur anzufahrenden Position } } my $virtual; # Kontrollvariable der Positionsanfahrt my $newState; my $updateState; my $positiondrive; my $state = $hash->{STATE}; my $aktMsg = $hash->{helper}{aktMsg} ; my @last_action_array=split(/ /,$aktMsg); my $lastaction = $last_action_array[0]; my $lastaction_position = $last_action_array[1]; my $lastaction_time = $last_action_array[2]; my $befMsg = $hash->{helper}{lastMsg}; my @before_action_array=split(/ /,$befMsg); my $beforeaction_position = $before_action_array[1]; my $timebetweenmsg = $testtimestart-$last_action_array[2];# Zeit zwischen dem aktuellen und letzten Befehl $timebetweenmsg = (int($timebetweenmsg*10)/10); my $oldposition; # Alter Stand in Prozent my $newposition ; # Errechnende Positionsänderung in Prozent - > bei on plus alten Stand my $finalposition;# Erreichter Rollostand in Prozent für state; my $time_to_favorite = AttrVal($name,'time_down_to_favorite', 'undef'); my $favorit_position; my $mode = $hash->{helper}{MODE};# Betriebsmodus virtual, physicalFP my $lastprogmode = $hash->{helper}{lastProg}; my $testprogmode = $testtimestart; my $testprog = int($testprogmode) - int($lastprogmode); my $oldstate = ReadingsVal($name, 'state', 100); Log3($name,5,"Siro_set: test auf double stop"); Log3($name,5,"Siro_set: testprogmode -> $testprogmode"); Log3($name,5,"Siro_set: lastprogmode -> $lastprogmode"); Log3($name,5,"Siro_set: lastaction -> $lastaction"); Log3($name,5,"Siro_set: cmd -> $cmd"); # verhindern eines doppelten stoppbefehls ausser progmodus 1 if ($testprogmode > $lastprogmode) # Doppelten Stoppbefehl verhindern, ausser progmodus aktiv { if ($lastaction eq 'stop' && $cmd eq 'stop' && $check ne 'nocheck') { Log3($name,5,"Siro_set: double stop, action aborted"); readingsSingleUpdate($hash, "prog_mode", "inaktiv " , 1); if($debugmode eq "1") { $debug = "Siro_set: double stop, action aborted"; readingsSingleUpdate($hash, "DEBUG_SET",$debug, 1); } return; } } else { $testprog = $testprog*-1; $virtual = "virtual"; readingsSingleUpdate($hash, "prog_mode", "$testprog" , 1); } ######################################### # invertierung position if ( $invers eq "1") { $oldstate = 100 - $oldstate; $autostop = 100 - $autostop; } ######################################### Log3($name,5,"Siro_Set: teste autostop: $autostop < 100 $oldstate < $autostop - $cmd"); # erkennung autostopfunktiuon if ($cmd eq "on" && $autostop < 100 && $oldstate < $autostop ) { $cmd="position"; $args[1]=$autostop; $newposstate = $args[1]; # position autostop $timetorun = $timetoclose/100*$newposstate; Log3($name,1,"Siro_Set: autostop gefunden mapping auf position erfolgt: $newposstate "); } ######################################### # erkennung downlimit funktion if ($downlimit < 100 && $operationmode eq "1") { if ($cmd eq 'position' && $downlimit < $newposstate) { $args[1]=$downlimit; $newposstate = $args[1]; # Anzufahrende Position in Prozent $timetorun = $timetoclose/100*$newposstate; Log3($name,1,"Siro_Set: drive down limit reached: $newposstate "); } if ($cmd eq 'on') { $cmd="position"; $args[1]=$downlimit; $newposstate = $args[1]; # Anzufahrende Position in Prozent $timetorun = $timetoclose/100*$newposstate; Log3($name,1,"Siro_Set: drive down limit reached: $newposstate "); } } ######################################### # on/off Umschaltung ohne zwischenzeitliches Anhalten if ($cmd eq 'on' && $timebetweenmsg < $timetoopen && $lastaction eq 'off') # Prüfe auf direkte Umschaltung on - off { $oldposition = $beforeaction_position; $newposition = $timebetweenmsg/$offdirekttime; $finalposition = $oldposition-$newposition; if ($limitedmode eq "on") { $finalposition ="50"; } $hash->{helper}{lastMsg} = $aktMsg; $hash->{helper}{aktMsg} = "stop".' '.int($finalposition).' '.gettimeofday(); $hash->{helper}{positiontimer} = $timebetweenmsg; $hash->{helper}{position} = int($finalposition); if ($mode ne "physical") { Siro_SendCommand($hash, 'stop'); } $aktMsg = $hash->{helper}{aktMsg} ; @last_action_array=split(/ /,$aktMsg); $lastaction = $last_action_array[0]; $lastaction_position = $last_action_array[1]; $lastaction_time = $last_action_array[2]; $befMsg = $hash->{helper}{lastMsg}; @before_action_array=split(/ /,$befMsg); $beforeaction_position = $before_action_array[1]; $timebetweenmsg = $testtimestart-$last_action_array[2]; # Zeit zwischen dem aktuellen und letzten Befehl $timebetweenmsg = (int($timebetweenmsg*10)/10); } ######################################### # off/on Umschaltung ohne zwischenzeitliches Anhalten if ($cmd eq 'off' && $timebetweenmsg < $timetoclose && $lastaction eq 'on') { $oldposition = $beforeaction_position; $newposition = $timebetweenmsg/$ondirekttime; $finalposition = $oldposition+$newposition; if ($limitedmode eq "on") { $finalposition ="50"; } $hash->{helper}{lastMsg} = $aktMsg; $hash->{helper}{aktMsg} = "stop".' '.int($finalposition).' '.gettimeofday(); $hash->{helper}{positiontimer} = $timebetweenmsg; if ($mode ne "physical") { Siro_SendCommand($hash, 'stop'); } $aktMsg = $hash->{helper}{aktMsg} ; @last_action_array=split(/ /,$aktMsg); $lastaction = $last_action_array[0]; $lastaction_position = $last_action_array[1]; $lastaction_time = $last_action_array[2]; $befMsg = $hash->{helper}{lastMsg}; @before_action_array=split(/ /,$befMsg); $beforeaction_position = $before_action_array[1]; $timebetweenmsg = gettimeofday()-$last_action_array[2]; # Zeit zwischen dem aktuellen und letzten Befehl $timebetweenmsg = (int($timebetweenmsg*10)/10); } ######################################### # Positionsberechnung bei einem Stopp-Befehl if ($cmd eq 'stop') { # lösche interne timer - setze reading action auf no action readingsSingleUpdate($hash, "action",'no action', 1); RemoveInternalTimer($hash); Log3($name,5,"Siro_Set: cmd stop timebetweenmsg -> $timebetweenmsg ondirekttime -> $ondirekttime offdirekttime -> $offdirekttime "); $oldposition = $beforeaction_position; if ($ondirekttime eq "0" || $offdirekttime eq "0") # Fehler division durch 0 abfanken bei ungesetzten attributen { Log3($name,5,"Siro_Set: cmd stop -> Positionserrechnung ohne gesetzte Attribute , Finalposition wird auf 50 gesetzt "); $finalposition ="50"; $args[1] = $finalposition; } else { Log3($name,5,"Siro_Set: stop - Lastaction -> $lastaction $lastaction_position $oldposition"); if ($lastaction eq 'position') { if ( $lastaction_position > $oldposition) { $lastaction="on"; } else { $lastaction="off"; } } if ($lastaction eq 'on')# Letzte Fahrt runter (on) { $newposition = $timebetweenmsg/$ondirekttime; $finalposition = $oldposition+$newposition; } elsif ($lastaction eq 'off')# Letzte Fahrt hoch (off) { $newposition = $timebetweenmsg/$offdirekttime; $finalposition = $oldposition-$newposition; } elsif ($lastaction eq 'fav')# Letzte Fahrt unbekannt { #Fahrtrichtung ermitteln - dafür Position von lastmsg nehmen $beforeaction_position $favorit_position =$time_to_favorite/$ondirekttime; Log3($name,5,"Siro_Set: drive to position aborted (target position:$favorit_position %) : (begin possition $beforeaction_position %) "); if ($favorit_position < $beforeaction_position)# Fahrt hoch { $newposition = $timebetweenmsg/$offdirekttime; $finalposition = $oldposition-$newposition; } if ($favorit_position > $beforeaction_position)# Fahrt runter { $newposition = $timebetweenmsg/$ondirekttime; $finalposition = $oldposition+$newposition; } Log3($name,5,"Siro_Set position: $finalposition "); } if ($finalposition < 0){$finalposition = 0;} if ($finalposition > 100){$finalposition = 100;} if ($limitedmode eq "on"){$finalposition ="50";} $finalposition = int($finalposition); # abrunden $args[1] = $finalposition; } } ######################################### # Hardware-Favorit anfahren if ($cmd eq 'fav') { Log3($name,5,"Siro_Set fav: $cmd "); if (!defined $time_to_favorite) { $time_to_favorite=5; } # Tmp ggf. ändern # if ( $time_to_favorite eq "undef") # { # $time_to_favorite=5; # #return; # } if ($ondirekttime eq "0" || $offdirekttime eq "0") # Fehler division durch 0 abfanken bei ungesetzten attributen { Log3($name,1,"Siro_Set: set cmd fav -> Favoritberechnung ohne gesetzte Attribute , aktion nicht möglich"); return; } $favorit_position =$time_to_favorite/$ondirekttime; # Errechnet nur die Position, die von oben angefahren wurde. pos $args[0] = $cmd; $args[1] = int($favorit_position); if($time_to_favorite eq 'undef') { Log3($name,1,"Siro_Set: function position limited without attr time_down_to_favorite"); $time_to_favorite ="1"; $args[1] ="50"; # new if ($mode ne "physical") { $args[2] = 'longstop'; $args[0] = 'stop'; Siro_SendCommand($hash, @args); readingsSingleUpdate($hash, "position",'50', 1); readingsSingleUpdate($hash, "state",'50', 1); } return; } $args[2] = 'longstop'; $hash->{helper}{lastMsg} = $aktMsg; $hash->{helper}{aktMsg} = $args[0].' '.$args[1].' '.gettimeofday(); $hash->{helper}{positiontimer} = $timebetweenmsg; $cmd ='stop'; $args[0] = $cmd; if ($mode ne "physical") { Siro_SendCommand($hash, @args); } my $position = $hash->{helper}{position}; # Position für die Zeitberechnung speichern $hash->{helper}{position}=int($favorit_position); Siro_UpdateState( $hash, int($favorit_position), '', '', 1 ); #Fahrtrichtung ermitteln - dafür Position von lastmsg nehmen $beforeaction_position $runningtime = 0; if ($favorit_position < $position)# Fahrt hoch { readingsSingleUpdate($hash, "action",'up to favorite', 1); #tmp my $change = $position - $favorit_position; # änderung in % $runningtime = $change*$offdirekttime; } if ($favorit_position > $position)# Fahrt runter { readingsSingleUpdate($hash, "action",'down to favorite', 1); #tmp my $change = $favorit_position - $position ; # Änderung in % $runningtime = $change*$ondirekttime; } InternalTimer($testtimestart+$runningtime,"Siro_Position_fav",$hash,0); # state auf Stopp setzen nach Erreichen der Fahrtdauer return; } ######################################### # Teste auf Position '0' oder '100' -> Mappen auf 'on' oder 'off' if ($cmd eq 'on'){$args[1] = "100";} if ($cmd eq 'off'){$args[1] = "0";} ######################################### #Aktualisierung des Timers (Zeit zwischen den Befehlen, lastmsg und aktmsg) $hash->{helper}{lastMsg} = $hash->{helper}{aktMsg}; $hash->{helper}{aktMsg} = $args[0].' '.$args[1].' '.gettimeofday(); $hash->{helper}{positiontimer} = $timebetweenmsg; ######################################### if ( defined($newposstate) ) { if($newposstate eq "0") { $cmd="off"; Log3($name,5,"recognized position 0 "); } elsif ($newposstate eq "100") { $cmd="on"; Log3($name,5,"recognized position 100 "); } } # Direkte Positionsanfahrt if ($cmd eq 'position') { Log3($name,5,"Siro_Set: nodrive -> $nodrive"); if (($ondirekttime eq "0" || $offdirekttime eq "0" || $nodrive eq "true") && $testchannel ne "0" ) # Fehler division durch 0 abfanken bei ungesetzten attributen { Log3($name,1,"Siro_Set: Positionsanfahrt ohne gesetzte Attribute , aktion nicht möglich -> abbruch"); return "Positionsanfahrt ohne gesetzte Attribute time_to_open und time_to_close nicht moeglich"; } my $aktposition=$hash->{helper}{position}; # Fahrt nach oben if ($newposstate < $aktposition) { if ($action eq "no action") { $action = 'up to position '.$newposstate; readingsSingleUpdate($hash, "action",$action, 1); #tmp } $cmd='off'; my $percenttorun = $aktposition-$newposstate; $runningtime = $percenttorun*$offdirekttime; $timetorun=$runningtime; Log3($name,5,"Siro_Set: direkt positiondrive: -> timing:($runningtime = $percenttorun*$offdirekttime) -> open runningtime:$runningtime - modification in % :$percenttorun"); } #Fahrt nach unten if ($newposstate > $aktposition) { if ($action eq "no action") { $action = 'down to position '.$newposstate; readingsSingleUpdate($hash, "action",$action, 1); #tmp } $cmd='on'; my $percenttorun = $newposstate-$aktposition; $runningtime = $percenttorun*$ondirekttime; $timetorun=$runningtime; Log3($name,5,"Siro_Set: direkt positiondrive: -> timing: ($runningtime = $percenttorun*$ondirekttime) -> close runningtime:$runningtime - modification in % :$percenttorun"); } $virtual = "virtual"; # keine Stateänderung Log3($name,5,"Siro_Set: direkt positiondrive: -> setting timer to $runningtime"); InternalTimer($testtimestart+$runningtime,"Siro_Position_down_stop",$hash,0); } ######################################### # abarbeitung weiterer befehle if($cmd eq 'on') { if ($action eq "no action") { $action = 'down '; readingsSingleUpdate($hash, "action",$action, 1); #tmp # voraussichtliche fahrzeit berechnen bis 100% my $aktposition=$hash->{helper}{position}; my $percenttorun = 100 - $aktposition; $runningtime = $percenttorun*$ondirekttime; $timetorun=$runningtime; Log3($name,4,"Siro_Set: aktposition -> $aktposition - percenttorun -> $percenttorun - ondirekttime -> $ondirekttime"); Log3($name,4,"Siro_Set: voraussichtliche fahrdauer bis 100%: -> $runningtime"); InternalTimer(gettimeofday()+$timetorun+1, "Siro_Stopaction", $hash, 0); # action auf Stopp } $newState = '100'; $positiondrive = 100; } elsif($cmd eq 'off') { if ($action eq "no action") { $action = 'up '; readingsSingleUpdate($hash, "action",$action, 1); #tmp # voraussichtliche fahrzeit berechnen bis 0% my $aktposition=$hash->{helper}{position}; my $percenttorun = $aktposition; $runningtime = $percenttorun*$offdirekttime; $timetorun=$runningtime; Log3($name,4,"Siro_Set: aktposition -> $aktposition - percenttorun -> $percenttorun - offdirekttime -> $offdirekttime"); Log3($name,4,"Siro_Set: voraussichtliche fahrdauer bis 0%: -> $runningtime"); InternalTimer(gettimeofday()+$timetorun+1, "Siro_Stopaction", $hash, 0); # action auf Stopp } $newState = '0'; $positiondrive = 0; } elsif($cmd eq 'stop') { $newState = $finalposition; $positiondrive = $finalposition; } elsif($cmd eq 'fav') { $newState = $favorit_position; $positiondrive = $favorit_position; } else { $newState = $state; #todo: Was mache ich mit der Positiondrive? } ######################################### if (!defined($virtual)){$virtual = "";} if (!defined($newposstate)){$newposstate = 0;} if (!defined($newposstate)){$newposstate = 0;} if ($virtual ne "virtual") # Kein Stateupdate beim Anfahren einer Position { if ($newposstate < 0){$newposstate = 0;} if ($newposstate > 100){$newposstate = 100;} $hash->{helper}{position}=$positiondrive; Log3($name,5,"Siro_Set: stateupdate erfolgt -> $positiondrive"); ######################################### # invertiere position if ( $invers eq "1") { my $noinvers = $newState; my $inversposition = 100 - $newState; $newState = $inversposition; Log3($name,3,"Siro_Set: invertiere Position vor setstate - berechnet -> $noinvers - inverted -> $newState "); } ######################################### Siro_UpdateState( $hash, $newState, '', $updateState, 1 ); } else { Log3($name,5,"Siro_Set: kein stateupdate erfolgt"); #Setze readings positiondrive und positiontime if ($newposstate < 0){$newposstate = 0;} if ($newposstate > 100){$newposstate = 100;} $hash->{helper}{position}=$newposstate; } $args[0] = $cmd; if (!defined($mode)){$mode ="virtual"}; if($debugmode eq "1") { readingsSingleUpdate($hash, "DEBUG_SET",$debug, 1); } if ($mode ne "physical") { Log3($name,3,"Siro_set: handing over to Siro_Send_Command with following arguments: @args"); Siro_SendCommand($hash, @args); } $hash->{helper}{MODE} = "virtual"; $hash->{helper}{LastMODE} = $mode; my $testtimeend = gettimeofday(); $runningtime =$testtimeend - $testtimestart; Log3($name,5,"Siro_set: runningtime -> $runningtime"); return ; } ########################################################################### sub Siro_UpdateState($$$$$) { my ($hash, $newState, $move, $updateState, $doTrigger) = @_; readingsBeginUpdate($hash); if ($newState < 0){$newState = 0;} if ($newState > 100){$newState = 100;} readingsBulkUpdate($hash,"state",$newState); readingsBulkUpdate($hash,"position",$newState); $hash->{state} = $newState; #$hash->{helper}{position} = $newState; readingsEndUpdate($hash, $doTrigger); } ################################################################# sub Siro_Position_down_start($) { my ($hash) = @_; my $name = $hash->{NAME}; #my $timetoopen = AttrVal($name,'time_to_open', 'undef'); #tmp #my $timetoclose = AttrVal($name,'time_to_close', 'undef');#tmp my @args; $args[0] = 'on'; my $virtual='virtual'; Siro_SendCommand($hash, @args,, $virtual); Log3 $name, 5, "Siro_Position_down_start: completed"; return; } ################################################################# sub Siro_Position_down_stop($) { my ($hash) = @_; my $name = $hash->{NAME}; my @args; $args[0] = 'stop'; if (!defined($args[1])){$args[1]="";} my $virtual='virtual'; Siro_SendCommand($hash, @args, $virtual); my $positiondrive=$hash->{helper}{position}; my $aktMsg = $hash->{helper}{aktMsg}; $hash->{helper}{lastMsg} = $aktMsg; #$hash->{helper}{aktMsg} = $args[0].' '.$args[1].' '.gettimeofday(); $hash->{helper}{aktMsg} = $args[0].' '.$positiondrive.' '.gettimeofday(); # invertiere position my $invers = AttrVal($name,'invers_position', '0'); if ( $invers eq "1") { my $noinvers = $positiondrive; my $inversposition = 100 - $positiondrive; $positiondrive = $inversposition; Log3($name,3,"Siro_Set: invertiere Position vor setstate - berechnet -> $noinvers - inverted -> $positiondrive "); } readingsSingleUpdate($hash, "action",'no action', 1); #tmp Siro_UpdateState( $hash, $positiondrive, '', '', 1 ); Log3 $name, 5, "Siro_Position_down_stop: completed -> state:$positiondrive "; return; } ################################################################# sub Siro_Position_fav($) { my ($hash) = @_; my $name = $hash->{NAME}; my @args; my $aktMsg = $hash->{helper}{aktMsg}; $hash->{helper}{lastMsg} = $aktMsg; my @last_action_array=split(/ /,$aktMsg); $hash->{helper}{aktMsg} = 'stop'.' '.$last_action_array[1].' '.gettimeofday(); readingsSingleUpdate($hash, "action",'no action', 1); #tmp Log3 $name, 5, "Siro_Position_fav: completed"; return; } ################################################################# sub Siro_Sequence($) { my $debug; my ($hash) = @_; my $name = $hash->{NAME}; Log3($name,1,"Siro_Sequence:START"); my @args; my @sequence = split(/,/,$hash->{sequence}); my $debugmode = AttrVal($name,'debug_mode', '0'); my $cmd = shift @sequence; my $timer = shift @sequence; $debug=$debug.' '.$cmd.' '.$timer.' '.@sequence; $hash->{sequence} = join(",",@sequence); # 3 min Programmiermodus $args[0]=$cmd; Siro_SendCommand($hash, @args, 'virtual'); if ( defined($timer) ) { InternalTimer(gettimeofday()+$timer, "Siro_Sequence", $hash, 0); $debug=$debug.'- Erneute Aufrufsequenz'; } else { $debug=$debug.'- Sequenz beendet, ATTR time_to _fav neu berechnet und gesetzt, progmode beendet'; readingsSingleUpdate($hash, "prog_mode", "inaktiv " , 1); delete($hash->{sequence}); my $timetoclose = AttrVal($name,'time_to_close', '10'); my $ondirekttime = $timetoclose/100; # Zeit für 1 Prozent Runterfahrt my $position = $hash->{helper}{position}; my $newfav = $ondirekttime * $position; $attr{$name}{time_down_to_favorite} = $newfav; } if($debugmode eq "1") { readingsSingleUpdate($hash, "DEBUG_SEQUENCE",$debug, 1); } Log3 $name, 5, "Siro_Sequence: completed"; return; } ############################################################### sub Siro_Setgroup($) { my ($hash) = @_; my $name = $hash->{NAME}; my $grouphashraw=$hash->{helper}{affected_devices_h}; my @grouphash=split(/,/,$grouphashraw); my $groupnamesraw=$hash->{helper}{affected_devices_n}; my @groupnames=split(/,/,$groupnamesraw); my $groupcommandraw=$hash->{helper}{groupcommand}; my @groupcommand=split(/,/,$groupcommandraw); Log3($name,5,"Siro_Setgroup : @groupnames -> $groupcommandraw "); my $count =0; foreach my $senddevice(@groupnames) { my @args; #Log3($name,5,"----------------------------"); Log3($name,5,"Siro_Setgroup: count -> $count "); Log3($name,5,"Siro_Setgroup: senddevice -> $senddevice "); Log3($name,5,"Siro_Setgroup: testhash -> $grouphash[$count] "); Log3($name,5,"Siro_Setgroup: command -> $groupcommand[1] $groupcommand[2] "); #Log3($name,5,"----------------------------"); $args[0]=$groupcommand[1]; $args[1]=$groupcommand[2]; Log3($name,5,"Siro_Setgroup: aufruf -> $grouphash[$count],$senddevice, @args "); Log3($name,5,"Siro_Setgroup: set $senddevice $groupcommand[1] $groupcommand[2]"); #my $cs = "set Siro_5B417081 on"; my $cs ="set $senddevice $groupcommand[0] $groupcommand[2]"; my $client_hash = $grouphash[$count]; Log3($name,5,"Siro_Setgroup: command -> ".$cs); my $errors = AnalyzeCommandChain(undef, $cs);; $count++; } return; } ############################################################### sub Siro_Stop($) { my ($hash) = @_; my $name = $hash->{NAME}; my @args; $args[0] = "stop"; #my ( $hash, $name, @args ) = @_; readingsSingleUpdate($hash, "action",'no action', 1); #tmp Log3($name,0,"Siro_Stop: x-for-timer stop -> @args "); Siro_Set($hash, $name, @args); return; } ############################################################### sub Siro_Restartcmd($) { my $incomming = $_[0]; my @msgarray=split(/ /,$incomming); my $name = $msgarray[1]; #my $hash = $msgarray[1]; #my $name = $hash->{NAME}; my $cs ="set $incomming"; Log3($name,3,"Siro_Restartcmd: incomming -> $incomming "); Log3($name,3,"Siro_Restartcmd: command -> ".$cs); my $errors = AnalyzeCommandChain(undef, $cs); return; } ############################################################### sub Siro_Stopaction($) { my ($hash) = @_; my $name = $hash->{NAME}; Log3($name,5,"Siro_Stopaction: setze no action "); readingsSingleUpdate($hash, "action",'no action', 1); #tmp return; } ###############################################################readingsSingleUpdate($hash, "action",$action, 1); #tmp sub Siro_Testgroup($$) { my ( $hash, $id ) = @_; my $name = $hash->{NAME}; my $testid; my $testidchan; my @groupnames; my @grouphash; foreach my $testdevices(keys %{$modules{Siro}{defptr}})# { $testid = substr($testdevices, 0, 7); $testidchan = substr($testdevices, 7, 1); Log3($name,5,"Siro_Testgroup: groupdevice search device $testid -> test device -> $testdevices-$testidchan "); if ($id eq $testid ) { my $lh = $modules{Siro}{defptr}{$testdevices}; #def my $namex = $lh->{NAME}; my $channelx = $lh->{CHANNEL}; Log3($name,5,"Siro_Testgroup: device for group found -> $namex lh -$lh"); if ($channelx ne "0") { Log3($name,5,"Siro_Testgroup: device for group found -> $namex hash -> $lh"); push(@groupnames,$namex); push(@grouphash,$lh); # betreffendes hash zur gruppe zufügen } } } my $hashstring; foreach my $target (@grouphash) { $hashstring=$hashstring.$target.","; } chop($hashstring); $hash->{helper}{affected_devices_h} = "$hashstring"; my $devicestring; foreach my $target (@groupnames) { $devicestring=$devicestring.$target.","; } chop($devicestring); $hash->{helper}{affected_devices_n} = $devicestring; return @groupnames; } ############################################# sub Siro_icon($) { #return 'fts_shutter_1w_'.(int($state/10)*10); my ($id) = @_; my $lh = $modules{Siro}{defptr}{$id}; my $position =$lh->{helper}{position}; my $xsvg =" Created by potrace 1.8, written by Peter Selinger 2001-2007 image/svg+xml "; return $xsvg; } 1; =pod =item summary Supports rf shutters from Siro =item summary_DE Unterstützt Siro Rollo-Funkmotoren =begin html

Siro protocol

=end html =begin html_DE

Siro protocol

=end html_DE =cut