###################################################### # $Id$ # # SOMFY RTS / Simu Hz protocol module for FHEM # (c) Thomas Dankert # # This will only work if you flashed your CUL with # the newest culfw (support for "Y" command). # # Published under GNU GPL License, v2 ###################################################### package main; use strict; use warnings; my %codes = ( "10" => "go-my", # goto "my" position "11" => "stop", # stop the current movement "20" => "off", # go "up" "40" => "on", # go "down" "80" => "prog", # enter programming mode "100" => "on-for-timer", "101" => "off-for-timer", ); my %somfy_c2b; my $somfy_defsymbolwidth = 1240; # Default Somfy frame symbol width my $somfy_defrepetition = 6; # Default Somfy frame repeat counter my %models = ( somfyblinds => 'blinds', ); # supported models (blinds only, as of now) ############################# sub SOMFY_Initialize($) { my ($hash) = @_; # map commands from web interface to codes used in Somfy RTS foreach my $k ( keys %codes ) { $somfy_c2b{ $codes{$k} } = $k; } # YsKKC0RRRRAAAAAA # $hash->{Match} = "^YsA..0..........\$"; $hash->{SetFn} = "SOMFY_Set"; #$hash->{StateFn} = "SOMFY_SetState"; $hash->{DefFn} = "SOMFY_Define"; $hash->{UndefFn} = "SOMFY_Undef"; # $hash->{ParseFn} = "SOMFY_Parse"; $hash->{AttrList} = "IODev" . " symbol-length" . " enc-key" . " rolling-code" . " repetition" . " switch_rfmode:1,0" . " do_not_notify:1,0" . " ignore:0,1" . " dummy:1,0" . " model:somfyblinds" . " loglevel:0,1,2,3,4,5,6"; } ############################# sub SOMFY_Define($$) { my ( $hash, $def ) = @_; my @a = split( "[ \t][ \t]*", $def ); my $u = "wrong syntax: define SOMFY address " . "[encryption-key] [rolling-code]"; # fail early and display syntax help if ( int(@a) < 3 ) { return $u; } # check address format (6 hex digits) if ( ( $a[2] !~ m/^[a-fA-F0-9]{6}$/i ) ) { return "Define $a[0]: wrong address format: specify a 6 digit hex value " } # group devices by their address my $name = $a[0]; my $address = $a[2]; $hash->{ADDRESS} = uc($address); my $tn = TimeNow(); # check optional arguments for device definition if ( int(@a) > 3 ) { # check encryption key (2 hex digits, first must be "A") if ( ( $a[3] !~ m/^[aA][a-fA-F0-9]{1}$/i ) ) { return "Define $a[0]: wrong encryption key format:" . "specify a 2 digits hex value (first nibble = A) " } # store it as reading, so it is saved in the statefile # only store it, if the reading does not exist yet my $old_enc_key = uc(ReadingsVal($name, "enc_key", "invalid")); if($old_enc_key eq "invalid") { setReadingsVal($hash, "enc_key", uc($a[3]), $tn); } if ( int(@a) == 5 ) { # check rolling code (4 hex digits) if ( ( $a[4] !~ m/^[a-fA-F0-9]{4}$/i ) ) { return "Define $a[0]: wrong rolling code format:" . "specify a 4 digits hex value " } # store it, if old reading does not exist yet my $old_rolling_code = uc(ReadingsVal($name, "rolling_code", "invalid")); if($old_rolling_code eq "invalid") { setReadingsVal($hash, "rolling_code", uc($a[4]), $tn); } } } my $code = uc($address); my $ncode = 1; $hash->{CODE}{ $ncode++ } = $code; $modules{SOMFY}{defptr}{$code}{$name} = $hash; AssignIoPort($hash); } ############################# sub SOMFY_Undef($$) { my ( $hash, $name ) = @_; foreach my $c ( keys %{ $hash->{CODE} } ) { $c = $hash->{CODE}{$c}; # As after a rename the $name my be different from the $defptr{$c}{$n} # we look for the hash. foreach my $dname ( keys %{ $modules{SOMFY}{defptr}{$c} } ) { if ( $modules{SOMFY}{defptr}{$c}{$dname} == $hash ) { delete( $modules{SOMFY}{defptr}{$c}{$dname} ); } } } return undef; } ##################################### sub SOMFY_Extension_Fn($) { my (undef, $name, $cmd) = split(" ", shift, 3); return if(!defined($defs{$name})); if($cmd eq "on-for-timer") { DoSet($name, "stop"); # send the stop-command } elsif($cmd eq "off-for-timer") { DoSet($name, "stop"); } } ############################# sub SOMFY_Do_For_Timer($@) { my ($hash, $name, $cmd, $param) = @_; my $cmd1 = ($cmd =~ m/on.*/ ? "on" : "off"); RemoveInternalTimer("SOMFY $name $cmd"); return "$cmd requires a number as argument" if($param !~ m/^\d*\.?\d*$/); if($param) { # send the on/off command first DoSet($name, $cmd1); # schedule the stop command for later InternalTimer(gettimeofday()+$param,"SOMFY_Extension_Fn","SOMFY $name $cmd",0); } return } ################################### sub SOMFY_Set($@) { my ( $hash, $name, @args ) = @_; my $ret = undef; my $numberOfArgs = int(@args); my $message; if ( $numberOfArgs < 1 ) { return "no set value specified" ; } return SOMFY_Do_For_Timer($hash, $name, @args) if($args[0] =~ m/[on|off]-for-timer$/); return "Bad time spec" if($numberOfArgs == 2 && $args[1] !~ m/^\d*\.?\d+$/); my $command = $somfy_c2b{ $args[0] }; if ( !defined($command) ) { return "Unknown argument $args[0], choose one of " . join( " ", sort keys %somfy_c2b ); } my $io = $hash->{IODev}; ## Do we need to change RFMode to SlowRF? if ( defined( $attr{ $name } ) && defined( $attr{ $name }{"switch_rfmode"} ) ) { if ( $attr{ $name }{"switch_rfmode"} eq "1" ) { # do we need to change RFMode of IODev my $ret = CallFn( $io->{NAME}, "AttrFn", "set", ( $io->{NAME}, "rfmode", "SlowRF" ) ); } } ## Do we need to change symbol length? if ( defined( $attr{ $name } ) && defined( $attr{ $name }{"symbol-length"} ) ) { $message = "Yt" . $attr{ $name }{"symbol-length"}; CUL_SimpleWrite( $io, $message ); Log GetLogLevel( $name, 4 ), "SOMFY set symbol-length: $message for $io->{NAME}"; } ## Do we need to change frame repetition? if ( defined( $attr{ $name } ) && defined( $attr{ $name }{"repetition"} ) ) { $message = "Yr" . $attr{ $name }{"repetition"}; CUL_SimpleWrite( $io, $message ); Log GetLogLevel( $name, 4 ), "SOMFY set repetition: $message for $io->{NAME}"; } my $value = $name ." ". join(" ", @args); # convert old attribute values to READINGs my $timestamp = TimeNow(); if(defined($attr{$name}{"enc-key"} && defined($attr{$name}{"rolling-code"}))) { setReadingsVal($hash, "enc_key", $attr{$name}{"enc-key"}, $timestamp); setReadingsVal($hash, "rolling_code", $attr{$name}{"rolling-code"}, $timestamp); # delete old attribute delete($attr{$name}{"enc-key"}); delete($attr{$name}{"rolling-code"}); } # message looks like this # Ys_key_ctrl_cks_rollcode_a0_a1_a2 # Ys ad 20 0ae3 a2 98 42 my $enckey = uc(ReadingsVal($name, "enc_key", "A0")); my $rollingcode = uc(ReadingsVal($name, "rolling_code", "0000")); $message = "Ys" . $enckey . $command . $rollingcode . uc( $hash->{ADDRESS} ); ## Log that we are going to switch Somfy Log GetLogLevel( $name, 2 ), "SOMFY set $value: $message"; ( undef, $value ) = split( " ", $value, 2 ); # Not interested in the name... ## Send Message to IODev and wait for correct answer my $msg = CallFn( $io->{NAME}, "GetFn", $io, ( " ", "raw", $message ) ); if ( $msg =~ m/raw => Ys$enckey.*/ ) { Log 4, "Answer from $io->{NAME}: $msg"; } else { Log 2, "SOMFY IODev device didn't answer Ys command correctly: $msg"; } # increment encryption key and rolling code my $enc_key_increment = hex( $enckey ); my $rolling_code_increment = hex( $rollingcode ); my $new_enc_key = sprintf( "%02X", ( ++$enc_key_increment & hex("0xAF") ) ); my $new_rolling_code = sprintf( "%04X", ( ++$rolling_code_increment ) ); # update the readings, but do not generate an event setReadingsVal($hash, "enc_key", $new_enc_key, $timestamp); setReadingsVal($hash, "rolling_code", $new_rolling_code, $timestamp); ## Do we need to change symbol length back? if ( defined( $attr{ $name } ) && defined( $attr{ $name }{"symbol-length"} ) ) { $message = "Yt" . $somfy_defsymbolwidth; CUL_SimpleWrite( $io, $message ); Log GetLogLevel( $name, 4 ), "SOMFY set symbol-length back: $message for $io->{NAME}"; } ## Do we need to change repetition back? if ( defined( $attr{ $name } ) && defined( $attr{ $name }{"repetition"} ) ) { $message = "Yr" . $somfy_defrepetition; CUL_SimpleWrite( $io, $message ); Log GetLogLevel( $name, 4 ), "SOMFY set repetition back: $message for $io->{NAME}"; } ## Do we need to change RFMode back to HomeMatic?? if ( defined( $attr{ $name } ) && defined( $attr{ $name }{"switch_rfmode"} ) ) { if ( $attr{ $name }{"switch_rfmode"} eq "1" ) { # do we need to change RFMode of IODev? my $ret = CallFn( $io->{NAME}, "AttrFn", "set", ( $io->{NAME}, "rfmode", "HomeMatic" ) ); } } ########################## # Look for all devices with the same address, and set state, enc-key, rolling-code and timestamp my $code = "$hash->{ADDRESS}"; my $tn = TimeNow(); foreach my $n ( keys %{ $modules{SOMFY}{defptr}{$code} } ) { my $lh = $modules{SOMFY}{defptr}{$code}{$n}; $lh->{CHANGED}[0] = $value; $lh->{STATE} = $value; $lh->{READINGS}{state}{TIME} = $tn; $lh->{READINGS}{state}{VAL} = $value; $lh->{READINGS}{enc_key}{TIME} = $tn; $lh->{READINGS}{enc_key}{VAL} = $new_enc_key; $lh->{READINGS}{rolling_code}{TIME} = $tn; $lh->{READINGS}{rolling_code}{VAL} = $new_rolling_code; } return $ret; } ############################# sub SOMFY_Parse($$) { # not implemented yet, since we only support SENDING of somfy commands } ############################# 1; =pod =begin html

SOMFY - Somfy RTS / Simu Hz protocol

=end html =cut