").
": move:$move:");
# do conversions
if ( AttrVal( $name, "positionInverse", 0 ) ) {
$newState = SOMFY_ConvertTo100To0( $newState );
$newExact = $newState;
$rounded = SOMFY_Runden( $newState );
$stateTrans = SOMFY_Translate100To0( $rounded );
} else {
$rounded = SOMFY_Runden( $newState );
$stateTrans = SOMFY_Translate( $rounded );
}
Log3($name,4,"SOMFY_UpdateState: $name after conversions newState:$newState: rounded:$rounded: stateTrans:$stateTrans:");
readingsBulkUpdate($hash,"state",$stateTrans);
$hash->{STATE} = $stateTrans;
readingsBulkUpdate($hash,"position",$rounded);
}
readingsBulkUpdate($hash,"exact",$newExact);
readingsBulkUpdate($hash,$finalPosReading,$newExact) if ( ( defined($finalPosReading) ) && ( $isFinal ) );
if ( defined( $updateState ) ) {
$hash->{updateState} = $updateState;
} else {
delete $hash->{updateState};
}
$hash->{move} = $move;
readingsEndUpdate($hash,$doTrigger);
} # end sub SOMFY_UpdateState
###################################
# Return timingvalues from attr and after correction
sub SOMFY_getTimingValues($) {
my ($hash) = @_;
my $name = $hash->{NAME};
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);
return (undef, undef, undef, undef) if(!defined($t1downclose) || !defined($t1down100) || !defined($t1upopen) || !defined($t1up100));
if ( ( $t1downclose < 0 ) || ( $t1down100 < 0 ) || ( $t1upopen < 0 ) || ( $t1up100 < 0 ) ) {
Log3($name,1,"SOMFY_getTimingValues: $name time values need to be positive values");
return (undef, undef, undef, undef);
}
if ( $t1downclose < $t1down100 ) {
Log3($name,1,"SOMFY_getTimingValues: $name close time needs to be higher or equal than time to pos100");
return (undef, undef, undef, undef);
} elsif ( $t1downclose == $t1down100 ) {
$t1up100 = 0;
}
if ( $t1upopen <= $t1up100 ) {
Log3($name,1,"SOMFY_getTimingValues: $name open time needs to be higher or equal than time to pos100");
return (undef, undef, undef, undef);
}
if ( $t1upopen < 1 ) {
Log3($name,1,"SOMFY_getTimingValues: $name time to open needs to be at least 1 second");
return (undef, undef, undef, undef);
}
if ( $t1downclose < 1 ) {
Log3($name,1,"SOMFY_getTimingValues: $name time to close needs to be at least 1 second");
return (undef, undef, undef, undef);
}
return ($t1down100, $t1downclose, $t1upopen, $t1up100);
}
##############################################################################
##############################################################################
##
## Helper for command parse and send
##
##############################################################################
##############################################################################
#############################
sub SOMFY_RTS_Crypt($$$)
{
my ($operation, $name, $data) = @_;
my $res = substr($data, 0, 2);
my $ref = ($operation eq "e" ? \$res : \$data);
for (my $idx=1; $idx < 7; $idx++)
{
my $high = hex(substr($data, $idx * 2, 2));
my $low = hex(substr(${$ref}, ($idx - 1) * 2, 2));
my $val = $high ^ $low;
$res .= sprintf("%02X", $val);
}
return $res;
}
#############################
sub SOMFY_RTS_Check($$)
{
my ($name, $data) = @_;
my $checkSum = 0;
for (my $idx=0; $idx < 7; $idx++)
{
my $val = hex(substr($data, $idx * 2, 2));
$val &= 0xF0 if ($idx == 1);
$checkSum = $checkSum ^ $val ^ ($val >> 4);
##Log3 $name, 4, "$name: Somfy RTS check: " . sprintf("%02X, %02X", $val, $checkSum);
}
$checkSum &= hex("0x0F");
return sprintf("%X", $checkSum);
}
##############################################################################
##############################################################################
##
## Central Command send routine
##
##############################################################################
##############################################################################
#####################################
sub SOMFY_SendCommand($@)
{
my ($hash, @args) = @_;
my $ret = undef;
my $cmd = $args[0];
my $message;
my $name = $hash->{NAME};
my $numberOfArgs = int(@args);
my $io = $hash->{IODev};
my $ioType = $io->{TYPE};
$ioType = "" if ( ! defined( $ioType ) );
return $ret if(IsIgnored($name));
return $ret if(IsDisabled($name));
Log3($name,4,"SOMFY_sendCommand: $name -> cmd :$cmd: ");
# 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
if ( !defined($command) ) {
return "Unknown argument $cmd, choose one of "
. join( " ", sort keys %somfy_c2b );
}
# CUL specifics
if ($ioType ne "SIGNALduino") {
## 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 );
Log3 $hash, 5, "SOMFY_send 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 );
Log3 $hash, 5, "SOMFY_send set repetition: $message for $io->{NAME}";
}
}
# convert old attribute values to READINGs
my $timestamp = TimeNow();
# 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 = SOMFY_getRollCode( $hash );
if($command eq "XX") {
# use user-supplied custom command
$command = $args[1];
}
# increment encryption key and rolling code
my $new_enc_key = $enckey;
if ( (! AttrVal( $name, "fixed_enckey", 0 ) ) && ( ! SOMFY_isSwitch($hash) ) ) {
my $enc_key_increment = hex( $enckey );
$new_enc_key = sprintf( "%02X", ( ++$enc_key_increment & hex("0xAF") ) );
}
my $rolling_code_increment = hex( $rollingcode );
my $new_rolling_code = sprintf( "%04X", ( ++$rolling_code_increment ) );
$message = "s"
. $new_enc_key
. $command
. $new_rolling_code
. uc( $hash->{ADDRESS} );
## Log that we are going to switch Somfy
Log3 $hash, 4, "SOMFY_send $name " . join(" ", @args) . ": $message";
Log3 $hash, 5, "SOMFY_send $name enc key : ". $new_enc_key." rolling code : ".$new_rolling_code;
## Send Message to IODev using IOWrite
if ($ioType eq "SIGNALduino") {
my $SignalRepeats = AttrVal($name,'repetition', '6');
# swap address, remove leading s
my $decData = substr($message, 1, 8) . substr($message, 13, 2) . substr($message, 11, 2) . substr($message, 9, 2);
my $check = SOMFY_RTS_Check($name, $decData);
my $encData = SOMFY_RTS_Crypt("e", $name, substr($decData, 0, 3) . $check . substr($decData, 4));
$message = 'P43#' . $encData . '#R' . $SignalRepeats;
#Log3 $hash, 4, "$hash->{IODev}->{NAME} SOMFY_sendCommand: $name -> message :$message: ";
IOWrite($hash, 'sendMsg', $message);
} else {
#Log3($name,5,"SOMFY_sendCommand: $name -> message :$message: ");
IOWrite( $hash, "Y", $message );
}
# 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);
# store in uniqueID if requested
SOMFY_storeRollCode( $hash, $new_rolling_code ) if ( AttrVal( $name, "autoStoreRollingCode", 0 ) );
# modify definition of device with actual enc/rc
# change 23.9.2020 - no update def anymore with rolling code (roll code and enc key will be removed)
# SOMFY_updateDef( $hash, $new_enc_key, $new_rolling_code );
# CUL specifics
if ($ioType ne "SIGNALduino") {
## 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 );
Log3 $hash, 5, "SOMFY_send 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 );
Log3 $hash, 5, "SOMFY_send 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;
# change 23.9.2020 - no update def anymore with rolling code (roll code and enc key will be removed)
# SOMFY_updateDef( $lh, $new_enc_key, $new_rolling_code );
}
return $ret;
} # end sub SOMFY_SendCommand
######################################################
######################################################
######################################################
1;
=pod
=item summary supporting devices using the SOMFY RTS protocol - window shades
=item summary_DE für Geräte, die das SOMFY RTS protocol unterstützen - Rolläden
=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.
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 CUL device (which must be defined first).
Reception of Somfy remotes is only supported indirectly through the usage of an FHEMduino
http://www.fhemwiki.de/wiki/FHEMduino
which can then be used to connect to the SOMFY device.
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.
Rolling code and encryption key in the device definition will be always updated on commands sent and can be also changed manually by modifying the original definition (e.g in FHEMWeb - modify).
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
pos value (0..100) # see note
prog # Special, see note
wind_sun_9
wind_only_a
on-for-timer
off-for-timer
manual 0,...,100,200,on,off
Examples:
set rollo_1 on
set rollo_1,rollo_2,rollo_3 on
set rollo_1-rollo_3 on
set rollo_1 off
set rollo_1 pos 50
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.
- pos value
The position is variying between 0 completely open and 100 for covering the full window.
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. See also positionInverse attribute.
- wind_sun_9 and wind_only_a send special commands to the Somfy device that to represent the codes sent from wind and sun detector (with the respective code contained in the set command name)
- manual will only set the position without sending any commands to the somfy device - can be used to correct the position manually
The position reading distinuishes between multiple cases
- Without timing values (see attributes) set only generic values are used for status and position:
open, closed, moving
are used
- With timing values set but drive-down-time-to-close equal to drive-down-time-to-100 and drive-up-time-to-100 equal 0
the device is considered to only vary between 0 and 100 (100 being completely closed)
- With full timing values set the device is considerd a window shutter (Rolladen) with a difference between
covering the full window (position 100) and being completely closed (position 200)
Get
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.
- positionInverse
Inverse operation for positions instead of 0 to 100-200 the positions are ranging from 100 to 10 (down) and then to 0 (closed). The pos set command will point in this case to the reversed pos values. This does NOT reverse the operation of the on/off command, meaning that on always will move the shade down and off will move it up towards the initial position.
- additionalPosReading
Position of the shutter will be stored in the reading pos
as numeric value.
Additionally this attribute might specify a name for an additional reading to be updated with the same value than the pos.
- finalPosReading
This attribute can specify the name of an additional posReading that is only set at the end of a move. Meaning intermediate values are not set.
The name can not be any of the standard readings
- fixed_enckey 1|0
If set to 1 the enc-key is not changed after a command sent to the device. Default is value 0 meaning enc-key is changed normally for the RTS protocol.
- autoStoreRollingCode 1|0
If set to 1 the rolling code is stored automatically in the FHEM uniqueID file (Default is 0 - off). After setting the attribute, the code is first saved after the next change of the rolling code.
- rawDevice [
]
If set this SOMFY device is representing a manual remote, that is used to control a somfy blind. The address of the blind (the physical blind) is specified in the rawdevice attribute to sync position changes in the blind when the remote is used. This requires an iodevice able to receive somfy commands (e.g. signalduino). Multiple physical blinds can be specified separated by space in the attribute.
- 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
- 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.
- drive-down-time-to-100
The time the blind needs to drive down from "open" (pos 0) to pos 100.
In this position, the lower edge touches the window frame, but it is not completely shut.
For a mid-size window this time is about 12 to 15 seconds.
- drive-down-time-to-close
The time the blind needs to drive down from "open" (pos 0) to "close", the end position of the blind.
Note: If set, this value always needs to be higher than drive-down-time-to-100
This is about 3 to 5 seonds more than the "drive-down-time-to-100" value.
- drive-up-time-to-100
The time the blind needs to drive up from "close" (endposition) to "pos 100".
This usually takes about 3 to 5 seconds.
- drive-up-time-to-open
The time the blind needs drive up from "close" (endposition) to "open" (upper endposition).
Note: If set, this value always needs to be higher than drive-down-time-to-100
This value is usually a bit higher than "drive-down-time-to-close", due to the blind's weight.
=end html
=cut