2014-06-30 11:25:12 +00:00
######################################################
2015-02-14 22:04:45 +00:00
# $Id: 10_SOMFY.pm 6645 2014-10-01 07:55:26Z thomyd $
2014-06-30 11:25:12 +00:00
#
# SOMFY RTS / Simu Hz protocol module for FHEM
# (c) Thomas Dankert <post@thomyd.de>
#
2015-02-14 22:04:45 +00:00
# Needs CULFW V 1.59 or higher (support for "Y" command).
2014-06-30 11:25:12 +00:00
#
# Published under GNU GPL License, v2
2014-09-30 13:56:19 +00:00
#
# 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.
2014-10-01 07:55:26 +00:00
#
# 1.4 thomyd Implemented fallback on/off-for-timer methods and only show warning about stop/go-my
# if the positioning attributes are set.
2015-02-14 22:04:45 +00:00
#
# 1.5 thomyd Bugfix for wrong attribute names when calculating the updatetime (drive-up-...)
2014-09-30 13:56:19 +00:00
2014-06-30 11:25:12 +00:00
######################################################
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"
2014-09-30 13:56:19 +00:00
"80" = > "prog" , # finish pairing
2014-06-30 11:25:12 +00:00
"100" = > "on-for-timer" ,
"101" = > "off-for-timer" ,
2014-09-30 13:56:19 +00:00
"XX" = > "z_custom" , # custom control code
) ;
my % sets = (
"off" = > "" ,
"on" = > "" ,
"stop" = > "" ,
"go-my" = > "" ,
2014-09-30 14:03:46 +00:00
"prog" = > "" ,
2014-09-30 13:56:19 +00:00
"on-for-timer" = > "textField" ,
"off-for-timer" = > "textField" ,
"z_custom" = > "textField" ,
"pos" = > "0,10,20,30,40,50,60,70,80,90,100"
2014-06-30 11:25:12 +00:00
) ;
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)
2014-09-30 13:56:19 +00:00
#############################
sub myUtilsSOMFY_Initialize ($) {
$ modules { SOMFY } { LOADED } = 1 ;
my $ hash = $ modules { SOMFY } ;
SOMFY_Initialize ( $ hash ) ;
} # end sub myUtilsSomfy_initialize
2014-06-30 11:25:12 +00:00
#############################
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 ;
}
2014-09-30 13:56:19 +00:00
# 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"
2014-06-30 11:25:12 +00:00
. " 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" ;
}
2014-09-30 13:56:19 +00:00
#############################
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
2014-06-30 11:25:12 +00:00
#############################
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 "
}
2014-07-04 15:30:58 +00:00
# group devices by their address
my $ name = $ a [ 0 ] ;
2014-06-30 11:25:12 +00:00
my $ address = $ a [ 2 ] ;
2014-07-04 15:30:58 +00:00
2014-06-30 11:25:12 +00:00
$ hash - > { ADDRESS } = uc ( $ address ) ;
2014-07-04 15:30:58 +00:00
my $ tn = TimeNow ( ) ;
2014-06-30 11:25:12 +00:00
# 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) "
}
2014-07-04 15:30:58 +00:00
# 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 ) ;
}
2014-06-30 11:25:12 +00:00
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 "
}
2014-07-04 15:30:58 +00:00
# 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 ) ;
}
2014-06-30 11:25:12 +00:00
}
}
my $ code = uc ( $ address ) ;
my $ ncode = 1 ;
$ hash - > { CODE } { $ ncode + + } = $ code ;
$ modules { SOMFY } { defptr } { $ code } { $ name } = $ hash ;
2014-09-30 13:56:19 +00:00
$ hash - > { move } = 'stop' ;
2014-06-30 11:25:12 +00:00
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 ;
}
#####################################
2014-09-30 13:56:19 +00:00
sub SOMFY_SendCommand ($@)
2014-06-30 11:25:12 +00:00
{
2014-09-30 13:56:19 +00:00
my ( $ hash , @ args ) = @ _ ;
2014-06-30 11:25:12 +00:00
my $ ret = undef ;
2014-09-30 13:56:19 +00:00
my $ cmd = $ args [ 0 ] ;
2014-06-30 11:25:12 +00:00
my $ message ;
2014-09-30 13:56:19 +00:00
my $ name = $ hash - > { NAME } ;
my $ numberOfArgs = int ( @ args ) ;
2014-06-30 11:25:12 +00:00
2014-09-30 13:56:19 +00:00
# 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}$/ ) ) ) ;
2014-06-30 11:25:12 +00:00
2014-09-30 13:56:19 +00:00
my $ command = $ somfy_c2b { $ cmd } ;
# eigentlich überflüssig, da oben schon auf Existenz geprüft wird -> %sets
2014-06-30 11:25:12 +00:00
if ( ! defined ( $ command ) ) {
2014-09-30 13:56:19 +00:00
return "Unknown argument $cmd, choose one of "
2014-06-30 11:25:12 +00:00
. 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" } ) )
{
2014-09-30 13:56:19 +00:00
$ message = "t" . $ attr { $ name } { "symbol-length" } ;
IOWrite ( $ hash , "Y" , $ message ) ;
2014-06-30 11:25:12 +00:00
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" } ) )
{
2014-09-30 13:56:19 +00:00
$ message = "r" . $ attr { $ name } { "repetition" } ;
IOWrite ( $ hash , "Y" , $ message ) ;
2014-06-30 11:25:12 +00:00
Log GetLogLevel ( $ name , 4 ) ,
"SOMFY set repetition: $message for $io->{NAME}" ;
}
my $ value = $ name . " " . join ( " " , @ args ) ;
2014-07-04 15:30:58 +00:00
# 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" } ) ;
}
2014-06-30 11:25:12 +00:00
# message looks like this
# Ys_key_ctrl_cks_rollcode_a0_a1_a2
# Ys ad 20 0ae3 a2 98 42
2014-07-04 15:30:58 +00:00
my $ enckey = uc ( ReadingsVal ( $ name , "enc_key" , "A0" ) ) ;
my $ rollingcode = uc ( ReadingsVal ( $ name , "rolling_code" , "0000" ) ) ;
2014-09-30 13:56:19 +00:00
if ( $ command eq "XX" ) {
# use user-supplied custom command
$ command = $ args [ 1 ] ;
}
$ message = "s"
2014-07-04 15:30:58 +00:00
. $ enckey
2014-06-30 11:25:12 +00:00
. $ command
2014-07-04 15:30:58 +00:00
. $ rollingcode
2014-06-30 11:25:12 +00:00
. 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...
2014-09-30 13:56:19 +00:00
## Send Message to IODev using IOWrite
IOWrite ( $ hash , "Y" , $ message ) ;
2014-06-30 11:25:12 +00:00
# increment encryption key and rolling code
2014-07-04 15:30:58 +00:00
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 ) ) ;
2014-06-30 11:25:12 +00:00
2014-07-04 15:30:58 +00:00
# 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 ) ;
2014-06-30 11:25:12 +00:00
## Do we need to change symbol length back?
if ( defined ( $ attr { $ name } )
&& defined ( $ attr { $ name } { "symbol-length" } ) )
{
2014-09-30 13:56:19 +00:00
$ message = "t" . $ somfy_defsymbolwidth ;
IOWrite ( $ hash , "Y" , $ message ) ;
2014-06-30 11:25:12 +00:00
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" } ) )
{
2014-09-30 13:56:19 +00:00
$ message = "r" . $ somfy_defrepetition ;
IOWrite ( $ hash , "Y" , $ message ) ;
2014-06-30 11:25:12 +00:00
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" ) ) ;
}
}
##########################
2014-07-04 15:30:58 +00:00
# Look for all devices with the same address, and set state, enc-key, rolling-code and timestamp
2014-06-30 11:25:12 +00:00
my $ code = "$hash->{ADDRESS}" ;
my $ tn = TimeNow ( ) ;
foreach my $ n ( keys % { $ modules { SOMFY } { defptr } { $ code } } ) {
my $ lh = $ modules { SOMFY } { defptr } { $ code } { $ n } ;
2014-07-04 15:30:58 +00:00
$ 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 ;
2014-06-30 11:25:12 +00:00
}
return $ ret ;
2014-09-30 13:56:19 +00:00
} # 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 ) ) ;
2015-02-14 22:04:45 +00:00
$ newpos = 100 if ( $ newpos > 100 ) ; # driven only short between close and pos 100!
2014-09-30 13:56:19 +00:00
} 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 ) = @ _ ;
2014-10-01 07:55:26 +00:00
2014-09-30 13:56:19 +00:00
SOMFY_SendCommand ( $ hash , 'stop' ) ;
2014-10-01 07:55:26 +00:00
# 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 )
}
2014-09-30 13:56:19 +00:00
} # 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+$/ ) ;
2014-10-01 07:55:26 +00:00
if ( ( $ cmd eq 'off' ) || ( $ cmd eq 'pos' && $ args [ 1 ] == 0 ) ) {
2014-09-30 13:56:19 +00:00
$ cmd = 'off' ;
$ hash - > { move } = 'up' ;
$ newpos = 0 ;
2015-02-14 22:04:45 +00:00
$ updatetime = ( AttrVal ( $ name , 'drive-up-time-to-open' , 25 ) - AttrVal ( $ name , 'drive-up-time-to-100' , 0 ) ) * $ oldpos / 100 ;
2014-09-30 13:56:19 +00:00
2014-10-01 07:55:26 +00:00
} elsif ( $ cmd eq 'on' ) {
2014-09-30 13:56:19 +00:00
$ 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 ) ;
}
2014-10-01 07:55:26 +00:00
Log3 ( $ name , 3 , "SOMFY_set: cmd $cmd newpos $newpos drivetime $drivetime" ) ;
2014-09-30 13:56:19 +00:00
} elsif ( $ cmd =~ m/stop|go_my/ ) { # assuming stop = pos 100
2014-10-01 07:55:26 +00:00
2014-09-30 13:56:19 +00:00
$ hash - > { move } = 'stop' ;
2014-10-01 07:55:26 +00:00
# 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." ) ;
}
2014-09-30 13:56:19 +00:00
} elsif ( $ cmd eq 'on-for-timer' ) {
$ cmd = 'on' ;
$ hash - > { move } = 'down' ;
$ drivetime = $ args [ 1 ] ;
2014-10-01 07:55:26 +00:00
my $ tclose = AttrVal ( $ name , 'drive-down-time-to-close' , undef ) ;
2014-09-30 13:56:19 +00:00
2014-10-01 07:55:26 +00:00
# only calculate if positioning attributes are set
if ( defined ( $ tclose ) ) {
my $ tmax = ( $ oldpos / 100 ) * $ tclose ;
if ( ( $ tmax + $ drivetime ) > $ tclose ) { # limit ?
$ drivetime = 0 ;
$ updatetime = $ tmax ;
}
2014-09-30 13:56:19 +00:00
}
} elsif ( $ cmd eq 'off-for-timer' ) {
$ cmd = 'off' ;
$ hash - > { move } = 'up' ;
$ drivetime = $ args [ 1 ] ;
2014-10-01 07:55:26 +00:00
my $ topen = AttrVal ( $ name , 'drive-up-time-to-open' , undef ) ;
# only calculate if positioning attributes are set
if ( defined ( $ topen ) ) {
2014-09-30 13:56:19 +00:00
2014-10-01 07:55:26 +00:00
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 ;
}
2014-09-30 13:56:19 +00:00
}
} 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 ;
2014-10-01 07:55:26 +00:00
my $ positioningAttributes = AttrVal ( $ name , 'drive-down-time-to-100' , undef ) ;
2014-09-30 13:56:19 +00:00
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 ) {
2014-10-01 07:55:26 +00:00
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 ) ;
}
2014-09-30 13:56:19 +00:00
} else {
Log3 ( $ name , 1 , "SOMFY_set: Error - drivetime and updatetime = 0" ) ;
}
SOMFY_SendCommand ( $ hash , @ args ) ;
SOMFY_StartTime ( $ hash ) ;
return undef ;
} # end sub SOMFY_setFN
###############################
2014-06-30 11:25:12 +00:00
#############################
sub SOMFY_Parse ($$) {
2014-09-30 13:56:19 +00:00
my ( $ hash , $ msg ) = @ _ ;
# Msg format:
# Ys AB 2C 004B 010010
# address needs bytes 1 and 3 swapped
2014-06-30 11:25:12 +00:00
2014-09-30 13:56:19 +00:00
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" ;
}
2014-06-30 11:25:12 +00:00
}
2014-09-30 13:56:19 +00:00
##############################
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 ) ) ;
2014-06-30 11:25:12 +00:00
2014-09-30 13:56:19 +00:00
} 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 ;
}
2014-06-30 11:25:12 +00:00
#############################
2014-09-30 13:56:19 +00:00
2014-06-30 11:25:12 +00:00
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 .
2014-09-30 13:56:19 +00:00
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 ) .
2014-06-30 11:25:12 +00:00
<br> <br>
< a name = "SOMFYdefine" > </a>
<b> Define </b>
<ul>
<code> define & lt ; name & gt ; SOMFY & lt ; address & gt ; [ & lt ; encryption - key & gt ; ] [ & lt ; rolling - code & gt ; ] </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> & lt ; address & gt ; </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> & lt ; encryption - key & gt ; </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> & lt ; rolling - code & gt ; </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 42 ABCD A5 0 A1C </code> <br>
</ul>
</ul>
<br>
< a name = "SOMFYset" > </a>
<b> Set </b>
<ul>
<code> set & lt ; name & gt ; & lt ; value & gt ; [ & lt ; time & gt ] </code>
<br> <br>
where <code> value </code> is one of: <br>
<pre>
on
off
go - my
stop
2014-09-30 13:56:19 +00:00
pos value ( 0 .. 100 ) # see note
2014-06-30 11:25:12 +00:00
prog # Special, see note
on - for - timer
off - for - timer
2014-09-30 13:56:19 +00:00
</pre>
2014-06-30 11:25:12 +00:00
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>
2014-09-30 13:56:19 +00:00
<code> set rollo_1 pos 50 </code> <br>
2014-06-30 11:25:12 +00:00
</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>
2014-09-30 13:56:19 +00:00
<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>
2014-06-30 11:25:12 +00:00
</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/o ff: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>
2014-09-30 13:56:19 +00:00
< 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>
2014-06-30 11:25:12 +00:00
</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