diff --git a/FHEM/10_FS10.pm b/FHEM/10_FS10.pm new file mode 100644 index 000000000..6fc549d1f --- /dev/null +++ b/FHEM/10_FS10.pm @@ -0,0 +1,502 @@ +############################################## +# $Id$ +# +# FS10 basierend auf dem FS20 Modul (FHEM 5.3), elektron-bbs + +package main; + +use strict; +use warnings; + +my %codes = ( + "0" => "off_1", + "2" => "off_2", + "1" => "on_1", + "3" => "on_2", + "8" => "dimdown_1", # 0 | 8 = 8 + "4" => "dimdown_2", + "9" => "dimup_1", # 1 | 8 = 9 + "5" => "dimup_2", +); + + +use vars qw(%fs10_c2b); # Peter would like to access it from outside + +my %models = ( + FS10_ST => 'simple', + FS10_DI => 'dimmer', + FS10_HD => 'dimmer', + FS10_SA => 'timer', + FS10_MS => 'simple', + FS10_S4 => 'remote', + FS10_S8 => 'remote', +); + + +sub +FS10_Initialize($) +{ + my ($hash) = @_; + + foreach my $k (keys %codes) { + $fs10_c2b{$codes{$k}} = $k; + } + + $hash->{Match} = '^P61#[a-fA-F0-9]{8,12}'; + $hash->{SetFn} = "FS10_Set"; + $hash->{DefFn} = "FS10_Define"; + $hash->{UndefFn} = "FS10_Undef"; + $hash->{ParseFn} = "FS10_Parse"; + $hash->{AttrList} = "IODev follow-on-for-timer:1,0 follow-on-timer ". + "do_not_notify:1,0 repetition ". + "ignore:1,0 dummy:1,0 showtime:1,0 ". + "$readingFnAttributes " . + "model:".join(",", sort keys %models); +} + +################################### +sub +FS10_Set($@) +{ + my ($hash, $name, @a) = @_; + + my $ret = undef; + my $na = int(@a); # Anzahl in Array + #Log3 $name, 3, "FS10: na $na"; + + return "no set value specified" if ($na < 1); # if($na < 2 || $na > 3); + return "Dummydevice $hash->{NAME}: will not set data" if(IsDummy($hash->{NAME})); + + my $model = AttrVal($name, "model", "FS10_ST"); + my $modelType = $models{$model}; + + my $list .= "off:noArg on:noArg " if ($modelType ne "remote" ); + + $list .= "dimup dimdown " if ($modelType eq "dimmer" ); + + return SetExtensions($hash, $list, $name, @a) if( $a[0] eq "?" ); + return SetExtensions($hash, $list, $name, @a) if( !grep( $_ =~ /^\Q$a[0]\E($|:)/, split( ' ', $list ) ) ); + + my $setstate = $a[0]; + my $sum = 0; + my $temp = ""; + my $ebeneh = substr($hash->{BTN}, 0, 1); + my $ebenel = substr($hash->{BTN}, 1, 1); + my $housecode = $hash->{HC} - 1; + my $kc; + my $SignalRepeats = AttrVal($name,'repetition', '1'); + my $io = $hash->{IODev}; + my $iNum = 2; + + if ($na > 1 && $setstate =~ m/dim/) { # Anzahl dimup / dimdown + $iNum += $a[1]; + Log3 $name, 3, "$io->{NAME} FS10_set: $name $setstate $a[1]"; + } + else { + Log3 $name, 3, "$io->{NAME} FS10_set: $name $setstate"; + } + Log3 $name, 4, "$io->{NAME} FS10_set: $name: hc=$housecode ebeneHL=$ebeneh $ebenel setstate=$setstate"; + + for my $i (1..$iNum) { + if ($i == 1) { + $kc = $fs10_c2b{$setstate."_1"}; + } + else { + $kc = $fs10_c2b{$setstate."_2"}; + } + $kc = $kc & 7; + if (defined($kc)) { + Log3 $name, 4, "$io->{NAME} FS10_set: $name $i. setstate=$setstate kc=$kc"; + + my $newmsg = "P61#0000000000001"; # 12 Bit Praeambel, 1 Pruefbit + + $newmsg .= dec2nibble($kc); # 1. setstate + $sum += $kc; + + $newmsg .= dec2nibble($ebenel); # 2. Ebene low + $sum += $ebenel; + + $newmsg .= dec2nibble($ebeneh); # 3. Ebene high + $sum += $ebeneh; + + $newmsg .= "10001"; # 4. unused + + $newmsg .= dec2nibble($housecode); # 5. housecode + $sum += $housecode; + + if ($sum >= 11) { # 6. Summe + $temp = 18 - $sum; + } else { + $temp = 10 - $sum; + } + $newmsg .= dec2nibble($temp); + + $newmsg .= "#R" . $SignalRepeats; + + IOWrite($hash, 'sendMsg', $newmsg); + + Log3 $name, 4, "$io->{NAME} FS10_set: $i.sendMsg=$newmsg"; + + #if ($i < $iNum) { + # IOWrite($hash, 'raw', 'SR;R=1;P0=-32000;D=0000;') + #} + } + } + + ########################################### + # Set the state of a device to off if on-for-timer is called + if($modules{FS10}{ldata}{$name}) { + CommandDelete(undef, $name . "_timer"); + delete $modules{FS10}{ldata}{$name}; + } + + #################################### + # following timers + if ($setstate eq "on" && AttrVal($name, "follow-on-for-timer", 0)) { + my $dur = AttrVal($name, "follow-on-timer", 0); + if ($dur > 0) { + my $newState = "off"; + my $to = sprintf("%02d:%02d:%02d", $dur/3600, ($dur%3600)/60, $dur%60); + Log3 $name, 3, "$io->{NAME} FS10_set: $name Set_Follow +$to setstate $newState"; + CommandDefine(undef, $name."_timer at +$to "."setstate $name $newState; trigger $name $newState"); + $modules{FS10}{ldata}{$name} = $to; + } + } + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "state", $setstate); + readingsEndUpdate($hash, 1); # Notify is done by Dispatch + + return $ret; +} + +############################# +sub +FS10_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + my $u = "wrong syntax: define FS10 housecode_button"; + + return $u if(int(@a) < 3); + + my ($housecode, $btncode) = split("_", $a[2], 2); + + return "Define $a[0]: wrong syntax: housecode_button" + if (!defined($housecode) || !defined($btncode)); + + return "Define $a[0]: wrong housecode format: specify a 1 digit value [1-8]" + if ($housecode !~ m/^[1-8]$/i ); + + return "Define $a[0]: wrong button format: specify a 2 digit value [0-7]" + if ($btncode !~ m/^[0-7]{2}$/i ); # Ebene Low, Ebene High + + $hash->{HC} = $housecode; + $hash->{BTN} = $btncode; + + #my $name = $a[0]; + $hash->{CODE} = $a[2]; + #$hash->{lastMSG} = ""; + $modules{FS10}{defptr}{$a[2]} = $hash; + + AssignIoPort($hash); +} + +############################# +sub +FS10_Undef($$) +{ + my ($hash, $name) = @_; + delete($modules{FS10}{defptr}{$hash->{CODE}}) + if(defined($hash->{CODE}) && + defined($modules{FS10}{defptr}{$hash->{CODE}})); + return undef; +} + +sub +FS10_Parse($$) +{ + my ($iohash, $msg) = @_; + my $ioname = $iohash->{NAME}; + my ($protocol,$rawData) = split("#",$msg); + my $err; + my $gesErr; + my $cde; + my $ebenel; + my $ebeneh; + my $u; + my $dev; + my $sum; + my $rsum; + + my $hlen = length($rawData); + my $blen = $hlen * 4; + $protocol=~ s/^[P](\d+)/$1/; # extract protocol + my $bitData = unpack("B$blen", pack("H$hlen", $rawData)); + + Log3 $ioname, 4, "$ioname FS10_Parse: Protocol: $protocol, rawData: $rawData"; + Log3 $ioname, 4, "$ioname FS10_Parse: rawBitData: $bitData ($blen)"; + + my $datastart = 0; + $datastart = index($bitData, "0000001"); + return "" if ($datastart < 0 || $datastart > 10); + + $bitData = substr($bitData, $datastart+6); + $blen = length($bitData); + + Log3 $ioname, 4, "$ioname FS10_Parse: datastart: $datastart, blen: $blen bitData=$bitData ($blen)"; + return "" if ($blen < 30); + + ($err, $cde) = nibble2dec(substr($bitData, 0, 5)); # Command Code + $gesErr = $err; + $sum = $cde; + + ($err, $ebenel) = nibble2dec(substr($bitData, 5, 5)); # EbeneL + $gesErr += $err; + $sum += $ebenel; + + ($err, $ebeneh) = nibble2dec(substr($bitData,10,5)); # EbeneH + $gesErr += $err; + $sum += $ebeneh; + + ($err, $u) = nibble2dec(substr($bitData,15,5)); # unbenutzt, muss 0 sein + if ($u != 0) { + $err = 1; + } + $gesErr += $err; + $sum += $u; + + ($err, $dev) = nibble2dec(substr($bitData,20,5)); # housecode + $gesErr += $err; + $sum += $dev; + + ($err, $rsum) = nibble2dec(substr($bitData,25,5)); # Summe + $gesErr += $err; + + if ($sum > 11) { + $sum = 18 - $sum; + } + else { + $sum = 10 - $sum; + } + $sum = $sum & 7; + if ($sum != $rsum) { + Log3 $ioname, 3, "$ioname FS10_Parse: error sum=$sum rsum=$rsum bitData=$bitData"; + return ""; + } + if ($gesErr > 0) { + Log3 $ioname, 3, "$ioname FS10_Parse: $gesErr errors bitData=$bitData"; + return ""; + } + + $dev++; + my $v = $codes{$cde}; + $v = "unknown_$cde" if(!defined($v)); + my $btn = $ebeneh . $ebenel; + my $deviceCode = $dev . "_" . $btn; + + Log3 $ioname, 4, "$ioname FS10_Parse: cde=$cde $v ebeneHL=$btn u=$u hc=$dev rsum=$rsum"; + + $v =~ s/_[1,2]$//; # _1 oder _2 am Ende abschneiden + + my $def = $modules{FS10}{defptr}{$iohash->{NAME} . "." . $deviceCode}; + $def = $modules{FS10}{defptr}{$deviceCode} if(!$def); + + if(!$def) { + Log3 $ioname, 3, "$ioname FS10_Parse: Unknown device $dev, " . "Button $btn Code $cde ($v), please define it"; + return "UNDEFINED FS10_$deviceCode FS10 $deviceCode"; + } + + my $hash = $def; + my $name = $hash->{NAME}; + return "" if(IsIgnored($name)); + Log3 $name, 4, "$ioname FS10_Parse: $name $v"; + + if ($v eq "on" && AttrVal($name, "follow-on-for-timer", 0)) { + my $dur = AttrVal($name, "follow-on-timer", 0); + if ($dur > 0) { + my $newState = "off"; + my $to = sprintf("%02d:%02d:%02d", $dur/3600, ($dur%3600)/60, $dur%60); + Log3 $name, 4, "$ioname FS10_Parse: $name Set_Follow +$to setstate $newState"; + CommandDefine(undef, $name."_timer at +$to "."setstate $name $newState; trigger $name $newState"); + $modules{FS10}{ldata}{$name} = $to; + } + } + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "state", $v); + readingsEndUpdate($hash, 1); # Notify is done by Dispatch + + return $name; +} + + +####################### +sub nibble2dec { + my $nibble = shift; + my $parity = 1; + my $err; + my $dec = oct("0b" . substr($nibble, 2)); + + for(my $i = 0; $i < 4; $i++) { + $parity += substr($nibble, $i+1, 1); + } + $err = $parity % 2; + if (substr($nibble, 0, 1) eq "0") { # das erste Bit muss 1 sein + $err = 1; + } + return ($err, $dec); +} + +sub dec2nibble { + my $num = shift; + my $parity = 1; # Paritaet ungerade + my $result = ""; + + for(my $i = 0; $i < 3; $i++) { + my $reminder = $num % 2; # Modulo division to get reminder + $result = $reminder . $result; # Concatenation of two numbers + $parity += $reminder; + $num /= 2; # New Value of decimal number to do next set of above operations + } + $result = ($parity % 2) . $result . "1"; # paritybit . bin( num) . checkbit + return $result; +} + +1; + +=pod +=item summary devices communicating using the ELV FS10 protocol +=item summary_DE Anbindung von FS10 Geräten + +=begin html + + +

FS10

+The FS10 module decrypts and sends FS10 messages sent by the SIGNALduino. The following types are supported at the moment: simple, dimmer, timer, remote
+
+ +Define + + +Set + + +Get + + +Attribute + +=end html + +=begin html_DE + + +

FS10

+Das FS10-Modul entschlüsselt und sendet Nachrichten vom Typ FS10, die vom +SIGNALduino verarbeitet werden. Unterstützt werden z.Z. folgende Typen: simple, dimmer, timer, remote
+
+ +Define + + +Set + + +Get + + +Attribute + +=end html_DE +=cut