From 8fcf72c12651504a3e2a9148fbf666fa0dc71c35 Mon Sep 17 00:00:00 2001 From: thomyd Date: Mon, 30 Jun 2014 11:25:12 +0000 Subject: [PATCH] Implemented Somfy RTS support as FHEM module. This module implements the FHEM side of Somfy support, and enables controlling your Somfy blinds from within FHEM. Commands supported are on/off, stop, go-my (goto "my" position"), program (used for pairing) and off-for-timer/on-for-timer to only partially close the blind. Currently only sending of Somfy commands is supported (like Intertechno). git-svn-id: svn://svn.code.sf.net/p/fhem/code/trunk@6183 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/10_SOMFY.pm | 524 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 524 insertions(+) create mode 100644 fhem/FHEM/10_SOMFY.pm diff --git a/fhem/FHEM/10_SOMFY.pm b/fhem/FHEM/10_SOMFY.pm new file mode 100644 index 000000000..4b7159e0f --- /dev/null +++ b/fhem/FHEM/10_SOMFY.pm @@ -0,0 +1,524 @@ +###################################################### +# $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 " + } + + my $address = $a[2]; + $hash->{ADDRESS} = uc($address); + + # 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 attribute, so it is saved in the statefile + $attr{ $a[0] }{"enc-key"} = lc( $a[3] ); + + 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 + $attr{ $a[0] }{"rolling-code"} = lc( $a[4] ); + } + } + else { + # no values for encryption and rolling code provided, use initial defaults + $attr{ $a[0] }{"enc-key"} = "A0"; + $attr{ $a[0] }{"rolling-code"} = "0000"; + } + + # group devices by their address + my $code = uc($address); + my $ncode = 1; + my $name = $a[0]; + + $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_SetState($$$$) { + my ( $hash, $tim, $vt, $val ) = @_; + + if ( $val =~ m/^(.*) \d+$/ ) { + $val = $1; + } + + if ( !defined( $somfy_c2b{$val} ) ) { + return "Undefined value $val"; + } + + 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); + } +} + +################################### +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); + + # message looks like this + # Ys_key_ctrl_cks_rollcode_a0_a1_a2 + # Ys ad 20 0ae3 a2 98 42 + + $message = "Ys" + . uc( $attr{ $name }{"enc-key"} ) + . $command + . uc( $attr{ $name }{"rolling-code"} ) + . 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 ) ); + + my $enckey = uc($attr{$name}{"enc-key"}); + 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( $attr{ $name }{"enc-key"} ); + my $rolling_code_increment = hex( $attr{ $name }{"rolling-code"} ); + + $attr{ $name }{"enc-key"} = + sprintf( "%02X", ( ++$enc_key_increment & hex("0xAF") ) ); + $attr{ $name }{"rolling-code"} = + sprintf( "%04X", ( ++$rolling_code_increment ) ); + + ## 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, 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; + } + 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

+
    + The Somfy RTS (identical to Simu Hz) protocol is used by a wide range of devices, + which are either senders or receivers/actuators. + As we right now are only able to SEND Somfy commands, but CAN'T receive them, this module currently only + supports devices like blinds, dimmers, etc. through a CUL device, so this must be defined first. + +

    + + + Define +
      + define <name> SOMFY <address> [<encryption-key>] [<rolling-code>] +

      + + The address is a 6-digit hex code, that uniquely identifies a single remote control channel. + It is used to pair the remote to the blind or dimmer it should control. +
      + Pairing is done by setting the blind in programming mode, either by disconnecting/reconnecting the power, + or by pressing the program button on an already associated remote. +
      + Once the blind is in programming mode, send the "prog" command from within FHEM to complete the pairing. + The blind will move up and down shortly to indicate completion. +
      + You are now able to control this blind from FHEM, the receiver thinks it is just another remote control. + +
        +
      • <address> is a 6 digit hex number that uniquely identifies FHEM as a new remote control channel. +
        You should use a different one for each device definition, and group them using a structure. +
      • +
      • The optional <encryption-key> is a 2 digit hex number (first letter should always be A) + that can be set to clone an existing remote control channel.
      • +
      • The optional <rolling-code> is a 4 digit hex number that can be set + to clone an existing remote control channel.
        + If you set one of them, you need to pick the same address as an existing remote. + Be aware that the receiver might not accept commands from the remote any longer,
        + if you used FHEM to clone an existing remote. +
        + This is because the code is original remote's codes are out of sync.
      • +
      +
      + + Examples: +
        + define rollo_1 SOMFY 000001
        + define rollo_2 SOMFY 000002
        + define rollo_3_original SOMFY 42ABCD A5 0A1C
        +
      +
    +
    + + + Set +
      + set <name> <value> [<time>] +

      + where value is one of:
      +
      +    on
      +    off
      +    go-my
      +    stop
      +    prog  # Special, see note
      +    on-for-timer
      +    off-for-timer
      +
      + Examples: +
        + set rollo_1 on
        + set rollo_1,rollo_2,rollo_3 on
        + set rollo_1-rollo_3 on
        + set rollo_1 off
        +
      +
      + Notes: +
        +
      • prog is a special command used to pair the receiver to FHEM: + Set the receiver in programming mode (eg. by pressing the program-button on the original remote) + and send the "prog" command from FHEM to finish pairing.
        + The blind will move up and down shortly to indicate success. +
      • +
      • on-for-timer and off-for-timer send a stop command after the specified time, + instead of reversing the blind.
        + This can be used to go to a specific position by measuring the time it takes to close the blind completely. +
      • +
      +
    +
    + + Get
      N/A

    + + + Attributes +
      + +
    • IODev
      + Set the IO or physical device which should be used for sending signals + for this "logical" device. An example for the physical device is a CUL.
      + Note: The IODev has to be set, otherwise no commands will be sent!
      + If you have both a CUL868 and CUL433, use the CUL433 as IODev for increased range. +

    • + + +
    • eventMap
      + Replace event names and set arguments. The value of this attribute + consists of a list of space separated values, each value is a colon + separated pair. The first part specifies the "old" value, the second + the new/desired value. If the first character is slash(/) or comma(,) + then split not by space but by this character, enabling to embed spaces. + Examples:
        + attr store eventMap on:open off:closed
        + attr store eventMap /on-for-timer 10:open/off:closed/
        + set store open +
      +

    • + +
    • do_not_notify

    • + +
    • dummy
      + Set the device attribute dummy to define devices which should not + output any radio signals. Associated notifys will be executed if + the signal is received. Used e.g. to react to a code from a sender, but + it will not emit radio signal if triggered in the web frontend. +

    • + +
    • loglevel

    • + +
    • showtime

    • + + +
    • model
      + The model attribute denotes the model type of the device. + The attributes will (currently) not be used by the fhem.pl directly. + It can be used by e.g. external programs or web interfaces to + distinguish classes of devices and send the appropriate commands + (e.g. "on" or "off" to a switch, "dim..%" to dimmers etc.).
      + The spelling of the model names are as quoted on the printed + documentation which comes which each device. This name is used + without blanks in all lower-case letters. Valid characters should be + a-z 0-9 and - (dash), + other characters should be ommited.
      + Here is a list of "official" devices:
      + Receiver/Actor: somfyblinds
      +

    • + + + +
    • ignore
      + Ignore this device, e.g. if it belongs to your neighbour. The device + won't trigger any FileLogs/notifys, issued commands will silently + ignored (no RF signal will be sent out, just like for the dummy attribute). The device won't appear in the + list command (only if it is explicitely asked for it), nor will it + appear in commands which use some wildcard/attribute as name specifiers + (see devspec). You still get them with the + "ignored=1" special devspec. +

    • + +
    +
    + + + Generated events: +
      + From a Somfy device you can receive one of the following events. +
    • on
    • +
    • off
    • +
    • stop
    • +
    • go-my
    • + Which event is sent is device dependent and can sometimes be configured on + the device. +
    +
+ + + +=end html +=cut