mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-03-04 11:26:55 +00:00
901 lines
28 KiB
Perl
901 lines
28 KiB
Perl
######################################################
|
|
# $Id$
|
|
#
|
|
# SOMFY RTS / Simu Hz protocol module for FHEM
|
|
# (c) Thomas Dankert <post@thomyd.de>
|
|
#
|
|
# This will only work if you flashed your CUL with
|
|
# the newest culfw (support for "Y" command).
|
|
#
|
|
# Published under GNU GPL License, v2
|
|
#
|
|
# History:
|
|
# 1.0 thomyd initial implementation
|
|
#
|
|
# 1.1 Elektrolurch state changed to open,close,pos <x>
|
|
# for using "set device pos <value> the attributes
|
|
# drive-down-time-to-100, drive-down-time-to-close,
|
|
# drive-up-time-to-100 and drive-up-time-to-open must be set
|
|
# Hardware section seperated to SOMFY_SetCommand
|
|
#
|
|
# 1.2 Elektrolurch state is now set after reaching the position of the blind
|
|
# preparation for receiving signals of Somfy remotes signals,
|
|
# associated with the blind
|
|
#
|
|
# 1.3 thomyd Basic implementation of "parse" function, requires updated CULFW
|
|
# Removed open/close as the same functionality can be achieved with an eventMap.
|
|
#
|
|
# 1.4 thomyd Implemented fallback on/off-for-timer methods and only show warning about stop/go-my
|
|
# if the positioning attributes are set.
|
|
|
|
######################################################
|
|
|
|
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", # finish pairing
|
|
"100" => "on-for-timer",
|
|
"101" => "off-for-timer",
|
|
"XX" => "z_custom", # custom control code
|
|
);
|
|
|
|
my %sets = (
|
|
"off" => "",
|
|
"on" => "",
|
|
"stop" => "",
|
|
"go-my" => "",
|
|
"prog" => "",
|
|
"on-for-timer" => "textField",
|
|
"off-for-timer" => "textField",
|
|
"z_custom" => "textField",
|
|
"pos" => "0,10,20,30,40,50,60,70,80,90,100"
|
|
);
|
|
|
|
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 myUtilsSOMFY_Initialize($) {
|
|
$modules{SOMFY}{LOADED} = 1;
|
|
my $hash = $modules{SOMFY};
|
|
|
|
SOMFY_Initialize($hash);
|
|
} # end sub myUtilsSomfy_initialize
|
|
|
|
#############################
|
|
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->{AttrFn} = "SOMFY_Attr";
|
|
|
|
$hash->{AttrList} = " drive-down-time-to-100"
|
|
. " drive-down-time-to-close"
|
|
. " drive-up-time-to-100"
|
|
. " drive-up-time-to-open "
|
|
. " 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_StartTime($) {
|
|
my ($d) = @_;
|
|
|
|
my ($s, $ms) = gettimeofday();
|
|
|
|
my $t = $s + ($ms / 1000000); # 10 msec
|
|
my $t1 = 0;
|
|
$t1 = $d->{'starttime'} if(exists($d->{'starttime'} ));
|
|
$d->{'starttime'} = $t;
|
|
my $dt = sprintf("%.2f", $t - $t1);
|
|
|
|
return $dt;
|
|
} # end sub SOMFY_StartTime
|
|
|
|
#############################
|
|
sub SOMFY_Define($$) {
|
|
my ( $hash, $def ) = @_;
|
|
my @a = split( "[ \t][ \t]*", $def );
|
|
|
|
my $u = "wrong syntax: define <name> 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;
|
|
$hash->{move} = 'stop';
|
|
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_SendCommand($@)
|
|
{
|
|
my ($hash, @args) = @_;
|
|
my $ret = undef;
|
|
my $cmd = $args[0];
|
|
my $message;
|
|
my $name = $hash->{NAME};
|
|
my $numberOfArgs = int(@args);
|
|
|
|
|
|
# custom control needs 2 digit hex code
|
|
return "Bad custom control code, use 2 digit hex codes only" if($args[0] eq "z_custom"
|
|
&& ($numberOfArgs == 1
|
|
|| ($numberOfArgs == 2 && $args[1] !~ m/^[a-fA-F0-9]{2}$/)));
|
|
|
|
my $command = $somfy_c2b{ $cmd };
|
|
# eigentlich überflüssig, da oben schon auf Existenz geprüft wird -> %sets
|
|
if ( !defined($command) ) {
|
|
|
|
return "Unknown argument $cmd, 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 = "t" . $attr{ $name }{"symbol-length"};
|
|
IOWrite( $hash, "Y", $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 = "r" . $attr{ $name }{"repetition"};
|
|
IOWrite( $hash, "Y", $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"));
|
|
|
|
if($command eq "XX") {
|
|
# use user-supplied custom command
|
|
$command = $args[1];
|
|
}
|
|
|
|
$message = "s"
|
|
. $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 using IOWrite
|
|
IOWrite( $hash, "Y", $message );
|
|
|
|
# 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 = "t" . $somfy_defsymbolwidth;
|
|
IOWrite( $hash, "Y", $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 = "r" . $somfy_defrepetition;
|
|
IOWrite( $hash, "Y", $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->{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;
|
|
} # end sub SOMFY_SendCommand
|
|
|
|
###################################
|
|
sub SOMFY_CalcNewPos($) {
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $dt = SOMFY_StartTime($hash);
|
|
my $move = $hash->{move};
|
|
my $oldpos = $hash->{oldpos};
|
|
my $newpos = ($move eq 'up')?0:100; # it works anyway
|
|
my $timestamp = TimeNow();
|
|
|
|
|
|
# Attributes for calulation
|
|
my $t1down100 = AttrVal($name,'drive-down-time-to-100',undef);
|
|
my $t1downclose = AttrVal($name,'drive-down-time-to-close',undef);
|
|
my $t1upopen = AttrVal($name,'drive-up-time-to-open',undef);
|
|
my $t1up100 = AttrVal($name,'drive-up-time-to-100',undef);
|
|
|
|
if(defined($t1down100) && defined($t1downclose) && defined($t1up100) && defined($t1upopen)) {
|
|
# attributes are set
|
|
if($move eq 'down') {
|
|
$newpos = $oldpos + (100 * $dt / $t1down100);
|
|
|
|
} elsif($move eq 'up') {
|
|
if($oldpos > 100) {
|
|
$dt = $dt - $t1up100;
|
|
$newpos = $oldpos - (100 * $dt / ($t1upopen - $t1up100));
|
|
$newpos = 100 if($newpos > 100), # driven only short between close and pos 100!
|
|
|
|
} else {
|
|
$newpos = $oldpos - (100 * $dt / ($t1upopen - $t1up100));
|
|
|
|
}
|
|
$newpos = 0 if($newpos < 0);
|
|
|
|
} else {
|
|
Log3($name,1,"SOMFY_CalcNewPos: $name move wrong $move");
|
|
} # error
|
|
} else {
|
|
# no attributes set
|
|
Log3($name,1,"SOMFY_CalcNewPos $name drive-down-time... attributes not set");
|
|
}
|
|
|
|
# update state
|
|
my $value;
|
|
if($newpos == 0) {
|
|
$value = 'off';
|
|
|
|
} elsif($newpos > 100) {
|
|
$value = 'on';
|
|
|
|
} else {
|
|
$value = 'pos '.SOMFY_Runden($newpos); # for using icons in state
|
|
}
|
|
|
|
$hash->{CHANGED}[0] = $value;
|
|
$hash->{STATE} = $value;
|
|
$hash->{READINGS}{state}{TIME} = $timestamp;
|
|
$hash->{READINGS}{state}{VAL} = $value;
|
|
setReadingsVal($hash,'position',$newpos,$timestamp);
|
|
|
|
# finish move
|
|
$hash->{move} = 'stop';
|
|
return undef;
|
|
} # end sub SOMFY_CalcNewPos
|
|
|
|
###################################
|
|
sub SOMFY_SendStop($) {
|
|
my ($hash) = @_;
|
|
|
|
SOMFY_SendCommand($hash,'stop');
|
|
|
|
# only calculate new position if the attributes are set
|
|
my $positioningAttributes = AttrVal($hash->{NAME},'drive-down-time-to-100',undef);
|
|
if (defined($positioningAttributes)) {
|
|
SOMFY_CalcNewPos($hash)
|
|
}
|
|
} # end sub SOMFY_SendStop
|
|
|
|
###################################
|
|
sub SOMFY_Runden($) {
|
|
my ($v) = @_;
|
|
return sprintf("%d", ($v + 5) /10) * 10;
|
|
} # end sub SOMFY_Runden
|
|
|
|
###################################
|
|
sub SOMFY_Set($@) {
|
|
my ( $hash, $name, @args ) = @_;
|
|
my $numberOfArgs = int(@args);
|
|
|
|
if ( $numberOfArgs < 1 ) {
|
|
return "no set value specified" ;
|
|
}
|
|
|
|
my $cmd = lc($args[0]);
|
|
my $drivetime = 0; # on/off-for-timer and pos <value> -> move by time
|
|
my $updatetime = 0; # move to endpos or go-my / stop -> assume stop = pos 100
|
|
|
|
my $oldpos = ReadingsVal($name,'position',0);
|
|
$hash->{oldpos} = $oldpos; # store it for later recalculation
|
|
my $newpos = $args[1];
|
|
|
|
return "Bad time spec" if($cmd =~m/(on|off)-for-timer/ && $numberOfArgs == 2 && $args[1] !~ m/^\d*\.?\d+$/);
|
|
|
|
if(($cmd eq 'off') || ($cmd eq 'pos' && $args[1] == 0)) {
|
|
$cmd = 'off';
|
|
$hash->{move} = 'up';
|
|
$newpos = 0;
|
|
$updatetime = (AttrVal($name,'drive-up-time-open',25) - AttrVal($name,'drive-up-time-100',0)) * $oldpos / 100;
|
|
|
|
} elsif ($cmd eq 'on') {
|
|
$hash->{move} = 'down';
|
|
|
|
my $t1 = AttrVal($name,'drive-down-time-to-100',100);
|
|
my $t2 = AttrVal($name,'drive-down-time-to-close',100);
|
|
$newpos = sprintf("%d",100 * $t2/$t1);
|
|
$updatetime = $t1* (1 - ($oldpos / 100)) + ($t2 - $t1);
|
|
|
|
} elsif($cmd eq 'pos') {
|
|
return "bad pos specification" if(!defined($newpos));
|
|
return "SOMFY_set: oldpos eq newpos ($newpos" if($newpos == $oldpos);
|
|
return "SOMFY_set: $newpos must be > 0 and < 100" if($newpos < 0 || $newpos > 100);
|
|
|
|
my $t1down = AttrVal($name,'drive-down-time-to-100',undef);
|
|
my $t1upopen = AttrVal($name,'drive-up-time-to-open',undef);
|
|
my $t1up100 = AttrVal($name,'drive-up-time-to-100',undef);
|
|
return "Please set attr drive-down-time-to-100, drive-down-time-to-close, "
|
|
. "drive-up-time-to-100 and drive-up-time-to-open before using the pos <value> extension" if(!defined($t1down) || !defined($t1upopen) || !defined($t1up100));
|
|
|
|
if($newpos > $oldpos) { # down
|
|
$cmd = 'on';
|
|
$hash->{move} = 'down';
|
|
$drivetime = ($t1down * ($newpos - $oldpos) / 100);
|
|
|
|
} else { # up
|
|
$cmd = 'off';
|
|
$hash->{move} = 'up';
|
|
my $t1 = $t1upopen - $t1up100;
|
|
$drivetime = ($t1 * ($oldpos - $newpos) / 100);
|
|
}
|
|
Log3($name,3,"SOMFY_set: cmd $cmd newpos $newpos drivetime $drivetime");
|
|
|
|
} elsif($cmd =~m/stop|go_my/) { # assuming stop = pos 100
|
|
|
|
$hash->{move} = 'stop';
|
|
|
|
# only warn about stop/go-my if the attributes are set
|
|
my $positioningAttributes = AttrVal($name,'drive-down-time-to-100',undef);
|
|
|
|
if (defined($positioningAttributes)) {
|
|
$newpos = 100;
|
|
$hash->{READINGS}{position}{VAL} = 100;
|
|
|
|
Log3($name,3,"SOMFY_set: Warning: go-my/stop will mess up correct positioning! Please use pos <value> instead.");
|
|
}
|
|
|
|
} elsif($cmd eq 'on-for-timer') {
|
|
$cmd = 'on';
|
|
$hash->{move} = 'down';
|
|
$drivetime = $args[1];
|
|
my $tclose = AttrVal($name,'drive-down-time-to-close',undef);
|
|
|
|
# only calculate if positioning attributes are set
|
|
if (defined($tclose)) {
|
|
my $tmax = ($oldpos / 100) * $tclose;
|
|
|
|
if(($tmax + $drivetime) > $tclose) { # limit ?
|
|
$drivetime = 0;
|
|
$updatetime = $tmax;
|
|
}
|
|
}
|
|
} elsif($cmd eq 'off-for-timer') {
|
|
$cmd = 'off';
|
|
$hash->{move} = 'up';
|
|
$drivetime = $args[1];
|
|
my $topen = AttrVal($name,'drive-up-time-to-open',undef);
|
|
|
|
# only calculate if positioning attributes are set
|
|
if (defined($topen)) {
|
|
|
|
my $t100 = AttrVal($name,'drive-up-time-to-100',0);
|
|
my $tpos = $topen * ($topen / ($topen - $t100)) - ($oldpos / 100);
|
|
|
|
if(($tpos + $drivetime) > $topen) { # limit ?
|
|
$drivetime = 0;
|
|
$updatetime = $tpos;
|
|
}
|
|
}
|
|
} elsif(!exists($sets{$cmd})) {
|
|
my @cList;
|
|
foreach my $k (sort keys %sets) {
|
|
my $opts = undef;
|
|
$opts = $sets{$k};
|
|
|
|
if (defined($opts)) {
|
|
push(@cList,$k . ':' . $opts);
|
|
} else {
|
|
push (@cList,$k);
|
|
}
|
|
} # end foreach
|
|
|
|
return "Unknown argument $cmd, choose one of " . join(" ", @cList);
|
|
} # error and ? handling
|
|
|
|
$args[0] = $cmd;
|
|
|
|
my $positioningAttributes = AttrVal($name,'drive-down-time-to-100',undef);
|
|
|
|
if($drivetime > 0) {
|
|
# timer fuer stop starten
|
|
RemoveInternalTimer($hash);
|
|
Log3($name,3,"SOMFY_set: $name -> stopping in $drivetime sec");
|
|
InternalTimer(gettimeofday()+$drivetime,"SOMFY_SendStop",$hash,0);
|
|
|
|
} elsif($updatetime > 0) {
|
|
if(defined($positioningAttributes)) {
|
|
# timer fuer Update state starten
|
|
RemoveInternalTimer($hash);
|
|
Log3($name,3,"SOMFY_set: $name -> state update in $updatetime sec");
|
|
InternalTimer(gettimeofday()+$updatetime,"SOMFY_CalcNewPos",$hash,0);
|
|
}
|
|
} else {
|
|
Log3($name,1,"SOMFY_set: Error - drivetime and updatetime = 0");
|
|
}
|
|
|
|
SOMFY_SendCommand($hash,@args);
|
|
SOMFY_StartTime($hash);
|
|
|
|
return undef;
|
|
} # end sub SOMFY_setFN
|
|
###############################
|
|
|
|
|
|
#############################
|
|
sub SOMFY_Parse($$) {
|
|
my ($hash, $msg) = @_;
|
|
|
|
# Msg format:
|
|
# Ys AB 2C 004B 010010
|
|
# address needs bytes 1 and 3 swapped
|
|
|
|
if (substr($msg, 0, 2) eq "Yr" || substr($msg, 0, 2) eq "Yt") {
|
|
# changed time or repetition, just return the name
|
|
return $hash->{NAME};
|
|
}
|
|
|
|
# get address
|
|
my $address = uc(substr($msg, 14, 2).substr($msg, 12, 2).substr($msg, 10, 2));
|
|
|
|
# get command and set new state
|
|
my $cmd = sprintf("%X", hex(substr($msg, 4, 2)) & 0xF0);
|
|
if ($cmd eq "10") {
|
|
$cmd = "11"; # use "stop" instead of "go-my"
|
|
}
|
|
|
|
my $newstate = $codes{ $cmd };
|
|
|
|
my $def = $modules{SOMFY}{defptr}{$address};
|
|
|
|
if($def) {
|
|
my @list;
|
|
foreach my $name (keys %{ $def }) {
|
|
my $lh = $def->{$name};
|
|
$name = $lh->{NAME}; # It may be renamed
|
|
|
|
return "" if(IsIgnored($name)); # Little strange.
|
|
|
|
# update the state and log it
|
|
readingsSingleUpdate($lh, "state", $newstate, 1);
|
|
|
|
Log3 $name, 4, "SOMFY $name $newstate";
|
|
|
|
push(@list, $name);
|
|
}
|
|
# return list of affected devices
|
|
return @list;
|
|
|
|
} else {
|
|
Log3 $hash, 3, "SOMFY Unknown device $address, please define it";
|
|
return "UNDEFINED SOMFY_$address SOMFY $address";
|
|
}
|
|
}
|
|
##############################
|
|
sub SOMFY_Attr(@) {
|
|
my ($cmd,$name,$aName,$aVal) = @_;
|
|
my $hash = $defs{$name};
|
|
|
|
return "\"SOMFY Attr: \" $name does not exist" if (!defined($hash));
|
|
|
|
# $cmd can be "del" or "set"
|
|
# $name is device name
|
|
# aName and aVal are Attribute name and value
|
|
if ($cmd eq "set") {
|
|
if ($aName =~/drive-(down|up)-time-to.*/) {
|
|
# check name and value
|
|
return "SOMFY_attr: value must be >0 and <= 100" if($aVal <= 0 || $aVal > 100);
|
|
}
|
|
|
|
if ($aName eq 'drive-down-time-to-100') {
|
|
$attr{$name}{'drive-down-time-to-100'} = $aVal;
|
|
$attr{$name}{'drive-down-time-to-close'} = $aVal if(!defined($attr{$name}{'drive-down-time-to-close'}) || ($attr{$name}{'drive-down-time-to-close'} < $aVal));
|
|
|
|
} elsif($aName eq 'drive-down-time-to-close') {
|
|
$attr{$name}{'drive-down-time-to-close'} = $aVal;
|
|
$attr{$name}{'drive-down-time-to-100'} = $aVal if(!defined($attr{$name}{'drive-down-time-to-100'}) || ($attr{$name}{'drive-down-time-to-100'} > $aVal));
|
|
|
|
} elsif($aName eq 'drive-up-time-to-100') {
|
|
$attr{$name}{'drive-up-time-to-100'} = $aVal;
|
|
|
|
} elsif($aName eq 'drive-up-time-to-open') {
|
|
$attr{$name}{'drive-up-time-to-open'} = $aVal;
|
|
$attr{$name}{'drive-up-time-to-100'} = 0 if(!defined($attr{$name}{'drive-up-time-to-100'}) || ($attr{$name}{'drive-up-time-to-100'} > $aVal));
|
|
}
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
#############################
|
|
|
|
1;
|
|
|
|
|
|
=pod
|
|
=begin html
|
|
|
|
<a name="SOMFY"></a>
|
|
<h3>SOMFY - Somfy RTS / Simu Hz protocol</h3>
|
|
<ul>
|
|
The Somfy RTS (identical to Simu Hz) protocol is used by a wide range of devices,
|
|
which are either senders or receivers/actuators.
|
|
Right now only SENDING of Somfy commands is implemented in the CULFW, so this module currently only
|
|
supports devices like blinds, dimmers, etc. through a <a href="#CUL">CUL</a> device (which must be defined first).
|
|
|
|
<br><br>
|
|
|
|
<a name="SOMFYdefine"></a>
|
|
<b>Define</b>
|
|
<ul>
|
|
<code>define <name> SOMFY <address> [<encryption-key>] [<rolling-code>] </code>
|
|
<br><br>
|
|
|
|
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.
|
|
<br>
|
|
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.
|
|
<br>
|
|
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.
|
|
<br>
|
|
You are now able to control this blind from FHEM, the receiver thinks it is just another remote control.
|
|
|
|
<ul>
|
|
<li><code><address></code> is a 6 digit hex number that uniquely identifies FHEM as a new remote control channel.
|
|
<br>You should use a different one for each device definition, and group them using a structure.
|
|
</li>
|
|
<li>The optional <code><encryption-key></code> is a 2 digit hex number (first letter should always be A)
|
|
that can be set to clone an existing remote control channel.</li>
|
|
<li>The optional <code><rolling-code></code> is a 4 digit hex number that can be set
|
|
to clone an existing remote control channel.<br>
|
|
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,<br>
|
|
if you used FHEM to clone an existing remote.
|
|
<br>
|
|
This is because the code is original remote's codes are out of sync.</li>
|
|
</ul>
|
|
<br>
|
|
|
|
Examples:
|
|
<ul>
|
|
<code>define rollo_1 SOMFY 000001</code><br>
|
|
<code>define rollo_2 SOMFY 000002</code><br>
|
|
<code>define rollo_3_original SOMFY 42ABCD A5 0A1C</code><br>
|
|
</ul>
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="SOMFYset"></a>
|
|
<b>Set </b>
|
|
<ul>
|
|
<code>set <name> <value> [<time>]</code>
|
|
<br><br>
|
|
where <code>value</code> is one of:<br>
|
|
<pre>
|
|
on
|
|
off
|
|
go-my
|
|
stop
|
|
pos value (0..100) # see note
|
|
prog # Special, see note
|
|
on-for-timer
|
|
off-for-timer
|
|
</pre>
|
|
Examples:
|
|
<ul>
|
|
<code>set rollo_1 on</code><br>
|
|
<code>set rollo_1,rollo_2,rollo_3 on</code><br>
|
|
<code>set rollo_1-rollo_3 on</code><br>
|
|
<code>set rollo_1 off</code><br>
|
|
<code>set rollo_1 pos 50</code><br>
|
|
</ul>
|
|
<br>
|
|
Notes:
|
|
<ul>
|
|
<li>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.<br>
|
|
The blind will move up and down shortly to indicate success.
|
|
</li>
|
|
<li>on-for-timer and off-for-timer send a stop command after the specified time,
|
|
instead of reversing the blind.<br>
|
|
This can be used to go to a specific position by measuring the time it takes to close the blind completely.
|
|
</li>
|
|
<li>pos value<br>
|
|
The position must be between 0 and 100 and the appropriate
|
|
attributes drive-down-time-to-100, drive-down-time-to-close,
|
|
drive-up-time-to-100 and drive-up-time-to-open must be set.<br>
|
|
pos 100 means the blind covers the window (but is not completely shut), 0 means it is completely open.
|
|
</li>
|
|
</ul>
|
|
</ul>
|
|
<br>
|
|
|
|
<b>Get</b> <ul>N/A</ul><br>
|
|
|
|
<a name="SOMFYattr"></a>
|
|
<b>Attributes</b>
|
|
<ul>
|
|
<a name="IODev"></a>
|
|
<li>IODev<br>
|
|
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.<br>
|
|
Note: The IODev has to be set, otherwise no commands will be sent!<br>
|
|
If you have both a CUL868 and CUL433, use the CUL433 as IODev for increased range.
|
|
</li><br>
|
|
|
|
<a name="eventMap"></a>
|
|
<li>eventMap<br>
|
|
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:<ul><code>
|
|
attr store eventMap on:open off:closed<br>
|
|
attr store eventMap /on-for-timer 10:open/off:closed/<br>
|
|
set store open
|
|
</code></ul>
|
|
</li><br>
|
|
|
|
<li><a href="#do_not_notify">do_not_notify</a></li><br>
|
|
<a name="attrdummy"></a>
|
|
<li>dummy<br>
|
|
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.
|
|
</li><br>
|
|
|
|
<li><a href="#loglevel">loglevel</a></li><br>
|
|
|
|
<li><a href="#showtime">showtime</a></li><br>
|
|
|
|
<a name="model"></a>
|
|
<li>model<br>
|
|
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.).<br>
|
|
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
|
|
<code>a-z 0-9</code> and <code>-</code> (dash),
|
|
other characters should be ommited.<br>
|
|
Here is a list of "official" devices:<br>
|
|
<b>Receiver/Actor</b>: somfyblinds<br>
|
|
</li><br>
|
|
|
|
|
|
<a name="ignore"></a>
|
|
<li>ignore<br>
|
|
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 <a
|
|
href="#attrdummy">dummy</a> 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 <a href="#devspec">devspec</a>). You still get them with the
|
|
"ignored=1" special devspec.
|
|
</li><br>
|
|
|
|
<a name="drive-down-time-to-100"></a>
|
|
<li>drive-down-time-to-100<br>
|
|
The time the blind needs to drive down from "open" (pos 0) to pos 100.<br>
|
|
In this position, the lower edge touches the window frame, but it is not completely shut.<br>
|
|
For a mid-size window this time is about 12 to 15 seconds.
|
|
</li><br>
|
|
|
|
<a name="drive-down-time-to-close"></a>
|
|
<li>drive-down-time-to-close<br>
|
|
The time the blind needs to drive down from "open" (pos 0) to "close", the end position of the blind.<br>
|
|
This is about 3 to 5 seonds more than the "drive-down-time-to-100" value.
|
|
</li><br>
|
|
|
|
<a name="drive-up-time-to-100"></a>
|
|
<li>drive-up-time-to-100<br>
|
|
The time the blind needs to drive up from "close" (endposition) to "pos 100".<br>
|
|
This usually takes about 3 to 5 seconds.
|
|
</li><br>
|
|
|
|
<a name="drive-up-time-to-open"></a>
|
|
<li>drive-up-time-to-open<br>
|
|
The time the blind needs drive up from "close" (endposition) to "open" (upper endposition).<br>
|
|
This value is usually a bit higher than "drive-down-time-to-close", due to the blind's weight.
|
|
</li><br>
|
|
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="SOMFYevents"></a>
|
|
<b>Generated events:</b>
|
|
<ul>
|
|
From a Somfy device you can receive one of the following events.
|
|
<li>on</li>
|
|
<li>off</li>
|
|
<li>stop</li>
|
|
<li>go-my<br></li>
|
|
Which event is sent is device dependent and can sometimes be configured on
|
|
the device.
|
|
</ul>
|
|
</ul>
|
|
|
|
|
|
|
|
=end html
|
|
=cut
|