10_SOMFY.pm 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717
  1. ##############################################################################
  2. #
  3. # 10_SOMFY.pm
  4. #
  5. # This file is part of Fhem.
  6. #
  7. # Fhem is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # Fhem is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with Fhem. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. ##############################################################################
  21. #
  22. # $Id: 10_SOMFY.pm 12918 2016-12-31 10:10:47Z viegener $
  23. #
  24. # SOMFY RTS / Simu Hz protocol module for FHEM
  25. # (c) Thomas Dankert <post@thomyd.de>
  26. # (c) Johannes Viegener / https://github.com/viegener/Telegram-fhem/tree/master/Somfy
  27. #
  28. # Discussed in FHEM Forum: https://forum.fhem.de/index.php/topic,53319.msg450080.html#msg450080
  29. #
  30. ##############################################################################
  31. # History:
  32. # 1.0 thomyd initial implementation
  33. #
  34. # 1.1 Elektrolurch state changed to open,close,pos <x>
  35. # for using "set device pos <value> the attributes
  36. # drive-down-time-to-100, drive-down-time-to-close,
  37. # drive-up-time-to-100 and drive-up-time-to-open must be set
  38. # Hardware section seperated to SOMFY_SetCommand
  39. #
  40. # 1.2 Elektrolurch state is now set after reaching the position of the blind
  41. # preparation for receiving signals of Somfy remotes signals,
  42. # associated with the blind
  43. #
  44. # 1.3 thomyd Basic implementation of "parse" function, requires updated CULFW
  45. # Removed open/close as the same functionality can be achieved with an eventMap.
  46. #
  47. # 1.4 thomyd Implemented fallback on/off-for-timer methods and only show warning about stop/go-my
  48. # if the positioning attributes are set.
  49. #
  50. # 1.5 thomyd Bugfix for wrong attribute names when calculating the updatetime (drive-up-...)
  51. #
  52. # 1.6 viegener New state and action handling (trying to stay compatible also adding virtual receiver capabilities)
  53. #
  54. # Further refined:
  55. # 2015-04-30 - state/position are now regularly updated during longer moves (as specified in somfy_updateFreq in seconds)
  56. # 2015-04-30 - For blinds normalize on pos 0 to 100 (max) (meaning if drive-down-time-to-close == drive-down-time-to-100 and drive-up-time-to-100 == 0)
  57. # 2015-04-30 - new reading exact position called 'exact' also used for further pos calculations
  58. # 2015-07-03 additionalPosReading <name> for allowing to specify an additional reading to contain position for shutter
  59. # 2015-07-03 Cleanup of reading update routine
  60. #
  61. # 2015-07-06 viegener - Timing improvement for position calculation / timestamp used before extensive calculations
  62. # 2015-07-06 viegener - send stop command only when real movement needs to be stopped (to avoid conflict with my-pos for stopped shutters)
  63. # 2015-07-09 viegener - FIX: typo in set go-my (was incorrectly spelled: go_my)
  64. # 2015-07-09 viegener - FIX: log and set command helper corrections
  65. # 2015-08-05 viegener - Remove setList (obsolete) and could be rather surprising for the module
  66. ######################################################
  67. #
  68. # 2016-05-03 viegener - Support readingFnAttributes also for Somfy
  69. # 2016-05-11 viegener - Handover SOMFY from thdankert/thomyd
  70. # 2016-05-11 viegener - Cleanup Todolist
  71. # 2016-05-11 viegener - Some additions to documentation (commandref)
  72. # 2016-05-13 habichvergessen - Extend SOMFY module to use Signalduino as iodev
  73. # 2016-05-13 viegener - Fix for CUL-SCC
  74. # 2016-05-16 habichvergessen - Fixes - on newly (autocreated entries)
  75. # 2016-05-16 habichvergessen - add rolling code / enckey for autocreate
  76. # 2016-05-16 viegener - Minor cleanup on code
  77. # 2016-05-16 viegener - Fix Issue#5 - autocreate preparation (code in modules pointer for address also checked for empty hash)
  78. # 2016-05-16 viegener - Ensure Ys message length correct before analyzing
  79. # 2016-05-29 viegener - Correct define for readingsval on rollingcode etc
  80. # 2016-05-29 viegener - Some cleanup - translations reduced
  81. # 2016-05-29 viegener - remove internals exact/position use only readings
  82. # 2016-05-29 viegener - Fix value in exact not being numeric (Forum449583)
  83. # 2016-10-06 viegener - add summary for fhem commandref
  84. # 2016-10-06 viegener - positionInverse for inverse operation 100 open 10 down 0 closed
  85. # 2016-10-17 viegener - positionInverse test and fixes
  86. # 2016-10-18 viegener - positionInverse documentation and complettion (no change to set on/off logic)
  87. # 2016-10-25 viegener - drive-Attribute - correct syntax check - add note in commandref
  88. # 2016-10-30 viegener - FIX: remove wrong attribute up-time-to-close - typo in attr setter
  89. # 2016-10-14 viegener - FIX: Use of uninitialized value $updateState in concatenation
  90. #
  91. # 2016-12-30 viegener - New sets / code-commands 9 / a - wind_sun_9 / wind_only_a
  92. #
  93. #
  94. ###############################################################################
  95. #
  96. ### Known Issue - if timer is running and last command equals new command (only for open / close) - considered minor/but still relevant
  97. #
  98. ###############################################################################
  99. ###############################################################################
  100. # Somfy Modul - OPEN
  101. ###############################################################################
  102. #
  103. # -
  104. # - Autocreate
  105. # - Complete shutter / blind as different model
  106. # - Make better distinction between different IoTypes - CUL+SCC / Signalduino
  107. # -
  108. # -
  109. #
  110. ###############################################################################
  111. package main;
  112. use strict;
  113. use warnings;
  114. #use List::Util qw(first max maxstr min minstr reduce shuffle sum);
  115. my %codes = (
  116. "10" => "go-my", # goto "my" position
  117. "11" => "stop", # stop the current movement
  118. "20" => "off", # go "up"
  119. "40" => "on", # go "down"
  120. "80" => "prog", # finish pairing
  121. "90" => "wind_sun_9", # wind and sun (sun + flag)
  122. "A0" => "wind_only_a", # wind only (flag)
  123. "100" => "on-for-timer",
  124. "101" => "off-for-timer",
  125. "XX" => "z_custom", # custom control code
  126. );
  127. my %sets = (
  128. "off" => "noArg",
  129. "on" => "noArg",
  130. "stop" => "noArg",
  131. "go-my" => "noArg",
  132. "prog" => "noArg",
  133. "on-for-timer" => "textField",
  134. "off-for-timer" => "textField",
  135. "z_custom" => "textField",
  136. "wind_sun_9" => "noArg",
  137. "wind_only_a" => "noArg",
  138. "pos" => "0,10,20,30,40,50,60,70,80,90,100"
  139. );
  140. my %sendCommands = (
  141. "off" => "off",
  142. "open" => "off",
  143. "on" => "on",
  144. "close" => "on",
  145. "prog" => "prog",
  146. "stop" => "stop",
  147. "wind_sun_9" => "wind_sun_9",
  148. "wind_only_a" => "wind_only_a",
  149. );
  150. my %inverseCommands = (
  151. "off" => "on",
  152. "on" => "off",
  153. "on-for-timer" => "off-for-timer",
  154. "off-for-timer" => "on-for-timer"
  155. );
  156. my %somfy_c2b;
  157. my $somfy_defsymbolwidth = 1240; # Default Somfy frame symbol width
  158. my $somfy_defrepetition = 6; # Default Somfy frame repeat counter
  159. my $somfy_updateFreq = 3; # Interval for State update
  160. my %models = ( somfyblinds => 'blinds', somfyshutter => 'shutter', ); # supported models (blinds and shutters)
  161. ######################################################
  162. ######################################################
  163. ##################################################
  164. # new globals for new set
  165. #
  166. my $somfy_posAccuracy = 2;
  167. my $somfy_maxRuntime = 50;
  168. my %positions = (
  169. "moving" => "50",
  170. "go-my" => "50",
  171. "open" => "0",
  172. "off" => "0",
  173. "down" => "150",
  174. "closed" => "200",
  175. "on" => "200"
  176. );
  177. my %translations = (
  178. "0" => "open",
  179. "150" => "down",
  180. "200" => "closed"
  181. );
  182. my %translations100To0 = (
  183. "100" => "open",
  184. "10" => "down",
  185. "0" => "closed"
  186. );
  187. ##################################################
  188. # Forward declarations
  189. #
  190. sub SOMFY_CalcCurrentPos($$$$);
  191. ######################################################
  192. ######################################################
  193. #############################
  194. sub myUtilsSOMFY_Initialize($) {
  195. $modules{SOMFY}{LOADED} = 1;
  196. my $hash = $modules{SOMFY};
  197. SOMFY_Initialize($hash);
  198. } # end sub myUtilsSomfy_initialize
  199. #############################
  200. sub SOMFY_Initialize($) {
  201. my ($hash) = @_;
  202. # map commands from web interface to codes used in Somfy RTS
  203. foreach my $k ( keys %codes ) {
  204. $somfy_c2b{ $codes{$k} } = $k;
  205. }
  206. # YsKKC0RRRRAAAAAA
  207. # $hash->{Match} = "^YsA..0..........\$";
  208. $hash->{SetFn} = "SOMFY_Set";
  209. #$hash->{StateFn} = "SOMFY_SetState";
  210. $hash->{DefFn} = "SOMFY_Define";
  211. $hash->{UndefFn} = "SOMFY_Undef";
  212. $hash->{ParseFn} = "SOMFY_Parse";
  213. $hash->{AttrFn} = "SOMFY_Attr";
  214. $hash->{AttrList} = " drive-down-time-to-100"
  215. . " drive-down-time-to-close"
  216. . " drive-up-time-to-100"
  217. . " drive-up-time-to-open "
  218. . " additionalPosReading "
  219. . " positionInverse:1,0 "
  220. . " IODev"
  221. . " symbol-length"
  222. . " enc-key"
  223. . " rolling-code"
  224. . " repetition"
  225. . " switch_rfmode:1,0"
  226. . " do_not_notify:1,0"
  227. . " ignore:0,1"
  228. . " dummy:1,0"
  229. . " model:somfyblinds,somfyshutter"
  230. . " loglevel:0,1,2,3,4,5,6"
  231. . " $readingFnAttributes";
  232. }
  233. #############################
  234. sub SOMFY_StartTime($) {
  235. my ($d) = @_;
  236. my ($s, $ms) = gettimeofday();
  237. my $t = $s + ($ms / 1000000); # 10 msec
  238. my $t1 = 0;
  239. $t1 = $d->{'starttime'} if(exists($d->{'starttime'} ));
  240. $d->{'starttime'} = $t;
  241. my $dt = sprintf("%.2f", $t - $t1);
  242. return $dt;
  243. } # end sub SOMFY_StartTime
  244. #############################
  245. sub SOMFY_Define($$) {
  246. my ( $hash, $def ) = @_;
  247. my @a = split( "[ \t][ \t]*", $def );
  248. my $u = "wrong syntax: define <name> SOMFY address "
  249. . "[encryption-key] [rolling-code]";
  250. # fail early and display syntax help
  251. if ( int(@a) < 3 ) {
  252. return $u;
  253. }
  254. # check address format (6 hex digits)
  255. if ( ( $a[2] !~ m/^[a-fA-F0-9]{6}$/i ) ) {
  256. return "Define $a[0]: wrong address format: specify a 6 digit hex value "
  257. }
  258. # group devices by their address
  259. my $name = $a[0];
  260. my $address = $a[2];
  261. $hash->{ADDRESS} = uc($address);
  262. # check optional arguments for device definition
  263. if ( int(@a) > 3 ) {
  264. # check encryption key (2 hex digits, first must be "A")
  265. if ( ( $a[3] !~ m/^[aA][a-fA-F0-9]{1}$/i ) ) {
  266. return "Define $a[0]: wrong encryption key format:"
  267. . "specify a 2 digits hex value (first nibble = A) "
  268. }
  269. # reset reading time on def to 0 seconds (1970)
  270. my $tzero = FmtDateTime(0);
  271. # store it as reading, so it is saved in the statefile
  272. # only store it, if the reading does not exist yet
  273. if(! defined( ReadingsVal($name, "enc_key", undef) )) {
  274. setReadingsVal($hash, "enc_key", uc($a[3]), $tzero);
  275. }
  276. if ( int(@a) == 5 ) {
  277. # check rolling code (4 hex digits)
  278. if ( ( $a[4] !~ m/^[a-fA-F0-9]{4}$/i ) ) {
  279. return "Define $a[0]: wrong rolling code format:"
  280. . "specify a 4 digits hex value "
  281. }
  282. # store it, if old reading does not exist yet
  283. if(! defined( ReadingsVal($name, "rolling_code", undef) )) {
  284. setReadingsVal($hash, "rolling_code", uc($a[4]), $tzero);
  285. }
  286. }
  287. }
  288. my $code = uc($address);
  289. my $ncode = 1;
  290. $hash->{CODE}{ $ncode++ } = $code;
  291. $modules{SOMFY}{defptr}{$code}{$name} = $hash;
  292. $hash->{move} = 'stop';
  293. AssignIoPort($hash);
  294. }
  295. #############################
  296. sub SOMFY_Undef($$) {
  297. my ( $hash, $name ) = @_;
  298. foreach my $c ( keys %{ $hash->{CODE} } ) {
  299. $c = $hash->{CODE}{$c};
  300. # As after a rename the $name my be different from the $defptr{$c}{$n}
  301. # we look for the hash.
  302. foreach my $dname ( keys %{ $modules{SOMFY}{defptr}{$c} } ) {
  303. if ( $modules{SOMFY}{defptr}{$c}{$dname} == $hash ) {
  304. delete( $modules{SOMFY}{defptr}{$c}{$dname} );
  305. }
  306. }
  307. }
  308. return undef;
  309. }
  310. #####################################
  311. sub SOMFY_SendCommand($@)
  312. {
  313. my ($hash, @args) = @_;
  314. my $ret = undef;
  315. my $cmd = $args[0];
  316. my $message;
  317. my $name = $hash->{NAME};
  318. my $numberOfArgs = int(@args);
  319. my $io = $hash->{IODev};
  320. my $ioType = $io->{TYPE};
  321. Log3($name,4,"SOMFY_sendCommand: $name -> cmd :$cmd: ");
  322. # custom control needs 2 digit hex code
  323. return "Bad custom control code, use 2 digit hex codes only" if($args[0] eq "z_custom"
  324. && ($numberOfArgs == 1
  325. || ($numberOfArgs == 2 && $args[1] !~ m/^[a-fA-F0-9]{2}$/)));
  326. my $command = $somfy_c2b{ $cmd };
  327. # eigentlich überflüssig, da oben schon auf Existenz geprüft wird -> %sets
  328. if ( !defined($command) ) {
  329. return "Unknown argument $cmd, choose one of "
  330. . join( " ", sort keys %somfy_c2b );
  331. }
  332. # CUL specifics
  333. if ($ioType ne "SIGNALduino") {
  334. ## Do we need to change RFMode to SlowRF?
  335. if ( defined( $attr{ $name } )
  336. && defined( $attr{ $name }{"switch_rfmode"} ) )
  337. {
  338. if ( $attr{ $name }{"switch_rfmode"} eq "1" )
  339. { # do we need to change RFMode of IODev
  340. my $ret =
  341. CallFn( $io->{NAME}, "AttrFn", "set",
  342. ( $io->{NAME}, "rfmode", "SlowRF" ) );
  343. }
  344. }
  345. ## Do we need to change symbol length?
  346. if ( defined( $attr{ $name } )
  347. && defined( $attr{ $name }{"symbol-length"} ) )
  348. {
  349. $message = "t" . $attr{ $name }{"symbol-length"};
  350. IOWrite( $hash, "Y", $message );
  351. Log GetLogLevel( $name, 4 ),
  352. "SOMFY set symbol-length: $message for $io->{NAME}";
  353. }
  354. ## Do we need to change frame repetition?
  355. if ( defined( $attr{ $name } )
  356. && defined( $attr{ $name }{"repetition"} ) )
  357. {
  358. $message = "r" . $attr{ $name }{"repetition"};
  359. IOWrite( $hash, "Y", $message );
  360. Log GetLogLevel( $name, 4 ),
  361. "SOMFY set repetition: $message for $io->{NAME}";
  362. }
  363. }
  364. # convert old attribute values to READINGs
  365. my $timestamp = TimeNow();
  366. if(defined($attr{$name}{"enc-key"} && defined($attr{$name}{"rolling-code"}))) {
  367. setReadingsVal($hash, "enc_key", $attr{$name}{"enc-key"}, $timestamp);
  368. setReadingsVal($hash, "rolling_code", $attr{$name}{"rolling-code"}, $timestamp);
  369. # delete old attribute
  370. delete($attr{$name}{"enc-key"});
  371. delete($attr{$name}{"rolling-code"});
  372. }
  373. # message looks like this
  374. # Ys_key_ctrl_cks_rollcode_a0_a1_a2
  375. # Ys ad 20 0ae3 a2 98 42
  376. my $enckey = uc(ReadingsVal($name, "enc_key", "A0"));
  377. my $rollingcode = uc(ReadingsVal($name, "rolling_code", "0000"));
  378. if($command eq "XX") {
  379. # use user-supplied custom command
  380. $command = $args[1];
  381. }
  382. $message = "s"
  383. . $enckey
  384. . $command
  385. . $rollingcode
  386. . uc( $hash->{ADDRESS} );
  387. ## Log that we are going to switch Somfy
  388. Log GetLogLevel( $name, 4 ), "SOMFY set $name " . join(" ", @args) . ": $message";
  389. ## Send Message to IODev using IOWrite
  390. if ($ioType eq "SIGNALduino") {
  391. my $SignalRepeats = AttrVal($name,'repetition', '6');
  392. # swap address, remove leading s
  393. my $decData = substr($message, 1, 8) . substr($message, 13, 2) . substr($message, 11, 2) . substr($message, 9, 2);
  394. my $check = SOMFY_RTS_Check($name, $decData);
  395. my $encData = SOMFY_RTS_Crypt("e", $name, substr($decData, 0, 3) . $check . substr($decData, 4));
  396. $message = 'P43#' . $encData . '#R' . $SignalRepeats;
  397. #Log3 $hash, 4, "$hash->{IODev}->{NAME} SOMFY_sendCommand: $name -> message :$message: ";
  398. IOWrite($hash, 'sendMsg', $message);
  399. } else {
  400. Log3($name,5,"SOMFY_sendCommand: $name -> message :$message: ");
  401. IOWrite( $hash, "Y", $message );
  402. }
  403. # increment encryption key and rolling code
  404. my $enc_key_increment = hex( $enckey );
  405. my $rolling_code_increment = hex( $rollingcode );
  406. my $new_enc_key = sprintf( "%02X", ( ++$enc_key_increment & hex("0xAF") ) );
  407. my $new_rolling_code = sprintf( "%04X", ( ++$rolling_code_increment ) );
  408. # update the readings, but do not generate an event
  409. setReadingsVal($hash, "enc_key", $new_enc_key, $timestamp);
  410. setReadingsVal($hash, "rolling_code", $new_rolling_code, $timestamp);
  411. # CUL specifics
  412. if ($ioType ne "SIGNALduino") {
  413. ## Do we need to change symbol length back?
  414. if ( defined( $attr{ $name } )
  415. && defined( $attr{ $name }{"symbol-length"} ) )
  416. {
  417. $message = "t" . $somfy_defsymbolwidth;
  418. IOWrite( $hash, "Y", $message );
  419. Log GetLogLevel( $name, 4 ),
  420. "SOMFY set symbol-length back: $message for $io->{NAME}";
  421. }
  422. ## Do we need to change repetition back?
  423. if ( defined( $attr{ $name } )
  424. && defined( $attr{ $name }{"repetition"} ) )
  425. {
  426. $message = "r" . $somfy_defrepetition;
  427. IOWrite( $hash, "Y", $message );
  428. Log GetLogLevel( $name, 4 ),
  429. "SOMFY set repetition back: $message for $io->{NAME}";
  430. }
  431. ## Do we need to change RFMode back to HomeMatic??
  432. if ( defined( $attr{ $name } )
  433. && defined( $attr{ $name }{"switch_rfmode"} ) )
  434. {
  435. if ( $attr{ $name }{"switch_rfmode"} eq "1" )
  436. { # do we need to change RFMode of IODev?
  437. my $ret =
  438. CallFn( $io->{NAME}, "AttrFn", "set",
  439. ( $io->{NAME}, "rfmode", "HomeMatic" ) );
  440. }
  441. }
  442. }
  443. ##########################
  444. # Look for all devices with the same address, and set state, enc-key, rolling-code and timestamp
  445. my $code = "$hash->{ADDRESS}";
  446. my $tn = TimeNow();
  447. foreach my $n ( keys %{ $modules{SOMFY}{defptr}{$code} } ) {
  448. my $lh = $modules{SOMFY}{defptr}{$code}{$n};
  449. $lh->{READINGS}{enc_key}{TIME} = $tn;
  450. $lh->{READINGS}{enc_key}{VAL} = $new_enc_key;
  451. $lh->{READINGS}{rolling_code}{TIME} = $tn;
  452. $lh->{READINGS}{rolling_code}{VAL} = $new_rolling_code;
  453. }
  454. return $ret;
  455. } # end sub SOMFY_SendCommand
  456. ###################################
  457. sub SOMFY_Runden($) {
  458. my ($v) = @_;
  459. if ( ( $v > 105 ) && ( $v < 195 ) ) {
  460. $v = 150;
  461. } else {
  462. $v = int(($v + 5) /10) * 10;
  463. }
  464. return sprintf("%d", $v );
  465. } # end sub SOMFY_Runden
  466. ###################################
  467. sub SOMFY_Translate($) {
  468. my ($v) = @_;
  469. if(exists($translations{$v})) {
  470. $v = $translations{$v}
  471. }
  472. return $v
  473. }
  474. ###################################
  475. sub SOMFY_Translate100To0($) {
  476. my ($v) = @_;
  477. if(exists($translations100To0{$v})) {
  478. $v = $translations100To0{$v}
  479. }
  480. return $v
  481. }
  482. #############################
  483. sub SOMFY_Parse($$) {
  484. my ($hash, $msg) = @_;
  485. my $name = $hash->{NAME};
  486. my $ioType = $hash->{TYPE};
  487. # return "IODev unsupported" if ((my $ioType = $hash->{TYPE}) !~ m/^(CUL|SIGNALduino)$/);
  488. # preprocessing if IODev is SIGNALduino
  489. if ($ioType eq "SIGNALduino") {
  490. my $encData = substr($msg, 2);
  491. return "Somfy RTS message format error!" if ($encData !~ m/A[0-9A-F]{13}/);
  492. my $decData = SOMFY_RTS_Crypt("d", $name, $encData);
  493. my $check = SOMFY_RTS_Check($name, $decData);
  494. return "Somfy RTS checksum error!" if ($check ne substr($decData, 3, 1));
  495. Log3 $name, 4, "$name: Somfy RTS preprocessing check: $check enc: $encData dec: $decData";
  496. $msg = substr($msg, 0, 2) . $decData;
  497. }
  498. # Msg format:
  499. # Ys AB 2C 004B 010010
  500. # address needs bytes 1 and 3 swapped
  501. if (substr($msg, 0, 2) eq "Yr" || substr($msg, 0, 2) eq "Yt") {
  502. # changed time or repetition, just return the name
  503. return $hash->{NAME};
  504. }
  505. # Check for correct length
  506. return "SOMFY incorrect length for command (".$msg.") / length should be 16" if ( length($msg) != 16 );
  507. # get address
  508. my $address = uc(substr($msg, 14, 2).substr($msg, 12, 2).substr($msg, 10, 2));
  509. # get command and set new state
  510. my $cmd = sprintf("%X", hex(substr($msg, 4, 2)) & 0xF0);
  511. if ($cmd eq "10") {
  512. $cmd = "11"; # use "stop" instead of "go-my"
  513. }
  514. my $newstate = $codes{ $cmd };
  515. my $def = $modules{SOMFY}{defptr}{$address};
  516. if ( ($def) && (keys %{ $def }) ) { # Check also for empty hash --> issue #5
  517. my @list;
  518. foreach my $name (keys %{ $def }) {
  519. my $lh = $def->{$name};
  520. $name = $lh->{NAME}; # It may be renamed
  521. return "" if(IsIgnored($name)); # Little strange.
  522. # update the state and log it
  523. ### NEEDS to be deactivated due to the state being maintained by the timer
  524. # readingsSingleUpdate($lh, "state", $newstate, 1);
  525. readingsSingleUpdate($lh, "parsestate", $newstate, 1);
  526. Log3 $name, 4, "SOMFY $name $newstate";
  527. push(@list, $name);
  528. }
  529. # return list of affected devices
  530. return @list;
  531. } else {
  532. # rolling code and enckey
  533. my $rolling = substr($msg, 6, 4);
  534. my $encKey = substr($msg, 2, 2);
  535. Log3 $hash, 1, "SOMFY Unknown device $address ($encKey $rolling), please define it";
  536. return "UNDEFINED SOMFY_$address SOMFY $address $encKey $rolling";
  537. }
  538. }
  539. ##############################
  540. sub SOMFY_Attr(@) {
  541. my ($cmd,$name,$aName,$aVal) = @_;
  542. my $hash = $defs{$name};
  543. return "\"SOMFY Attr: \" $name does not exist" if (!defined($hash));
  544. # $cmd can be "del" or "set"
  545. # $name is device name
  546. # aName and aVal are Attribute name and value
  547. # Convert in case of change to positionINverse --> but only after init is done on restart this should not be recalculated
  548. if ( ($aName eq 'positionInverse') && ( $init_done ) ) {
  549. my $rounded;
  550. my $stateTrans;
  551. my $pos = ReadingsVal($name,'exact',undef);
  552. if ( !defined($pos) ) {
  553. $pos = ReadingsVal($name,'position',undef);
  554. }
  555. if ($cmd eq "set") {
  556. if ( ( $aVal ) && ( ! AttrVal( $name, "positionInverse", 0 ) ) ) {
  557. # set to 1 and was 0 before - convert To100To10
  558. # first exact then round to pos
  559. $pos = SOMFY_ConvertTo100To0( $pos );
  560. $rounded = SOMFY_Runden( $pos );
  561. $stateTrans = SOMFY_Translate100To0( $rounded );
  562. } elsif ( ( ! $aVal ) && ( AttrVal( $name, "positionInverse", 0 ) ) ) {
  563. # set to 0 and was 1 before - convert From100To10
  564. # first exact then round to pos
  565. $pos = SOMFY_ConvertFrom100To0( $pos );
  566. $rounded = SOMFY_Runden( $pos );
  567. $stateTrans = SOMFY_Translate( $rounded );
  568. }
  569. } elsif ($cmd eq "del") {
  570. if ( AttrVal( $name, "positionInverse", 0 ) ) {
  571. # delete and was 1 before - convert From100To10
  572. # first exact then round to pos
  573. $pos = SOMFY_ConvertFrom100To0( $pos );
  574. $rounded = SOMFY_Runden( $pos );
  575. $stateTrans = SOMFY_Translate( $rounded );
  576. }
  577. }
  578. if ( defined( $rounded ) ) {
  579. readingsBeginUpdate($hash);
  580. readingsBulkUpdate($hash,"position",$rounded);
  581. readingsBulkUpdate($hash,"exact",$pos);
  582. readingsBulkUpdate($hash,"state",$stateTrans);
  583. $hash->{STATE} = $stateTrans;
  584. readingsEndUpdate($hash,1);
  585. }
  586. } elsif ($cmd eq "set") {
  587. if($aName eq 'drive-up-time-to-100') {
  588. return "SOMFY_attr: value must be >=0 and <= 100" if($aVal < 0 || $aVal > 100);
  589. } elsif ($aName =~/drive-(down|up)-time-to.*/) {
  590. # check name and value
  591. return "SOMFY_attr: value must be >0 and <= 100" if($aVal <= 0 || $aVal > 100);
  592. }
  593. if ($aName eq 'drive-down-time-to-100') {
  594. $attr{$name}{'drive-down-time-to-100'} = $aVal;
  595. $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));
  596. } elsif($aName eq 'drive-down-time-to-close') {
  597. $attr{$name}{'drive-down-time-to-close'} = $aVal;
  598. $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));
  599. } elsif($aName eq 'drive-up-time-to-100') {
  600. $attr{$name}{'drive-up-time-to-100'} = $aVal;
  601. $attr{$name}{'drive-up-time-to-open'} = $aVal if(!defined($attr{$name}{'drive-up-time-to-open'}) || ($attr{$name}{'drive-up-time-to-open'} < $aVal));
  602. } elsif($aName eq 'drive-up-time-to-open') {
  603. $attr{$name}{'drive-up-time-to-open'} = $aVal;
  604. $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));
  605. }
  606. }
  607. return undef;
  608. }
  609. #############################
  610. ######################################################
  611. ######################################################
  612. ######################################################
  613. ##################################################
  614. ### New set (state) method (using internalset)
  615. ###
  616. ### Reimplemented calculations for position readings and state
  617. ### Allowed sets to be done without sending actually commands to the blinds
  618. ### syntax set <name> [ <virtual|send> ] <normal set parameter>
  619. ### position and state are also updated on stop or other commands based on remaining time
  620. ### position is handled between 0 and 100 blinds down but not completely closed and 200 completely closed
  621. ### if timings for 100 and close are equal no position above 100 is used (then 100 == closed)
  622. ### position is rounded to a value of 5 and state is rounded to a value of 10
  623. #
  624. ### General assumption times are rather on the upper limit to reach desired state
  625. # Readings
  626. ## state contains rounded (to 10) position and/or textField
  627. ## position contains rounded position (limited detail)
  628. # STATE
  629. ## might contain position or textual form of the state (same as STATE reading)
  630. ###################################
  631. # call with hash, name, [virtual/send], set-args (send is default if ommitted)
  632. sub SOMFY_Set($@) {
  633. my ( $hash, $name, @args ) = @_;
  634. if ( lc($args[0]) =~m/(virtual|send)/ ) {
  635. SOMFY_InternalSet( $hash, $name, @args );
  636. } else {
  637. SOMFY_InternalSet( $hash, $name, 'send', @args );
  638. }
  639. }
  640. ###################################
  641. # call with hash, name, virtual/send, set-args
  642. sub SOMFY_InternalSet($@) {
  643. my ( $hash, $name, $mode, @args ) = @_;
  644. ### Check Args
  645. return "SOMFY_InternalSet: mode must be virtual or send: $mode " if ( $mode !~m/(virtual|send)/ );
  646. my $numberOfArgs = int(@args);
  647. return "SOMFY_set: No set value specified" if ( $numberOfArgs < 1 );
  648. my $cmd = lc($args[0]);
  649. # just a number provided, assume "pos" command
  650. if ($cmd =~ m/^\d{1,3}$/) {
  651. pop @args;
  652. push @args, "pos";
  653. push @args, $cmd;
  654. $cmd = "pos";
  655. $numberOfArgs = int(@args);
  656. }
  657. if(!exists($sets{$cmd})) {
  658. my @cList;
  659. foreach my $k (sort keys %sets) {
  660. my $opts = undef;
  661. $opts = $sets{$k};
  662. if (defined($opts)) {
  663. $opts = "100,90,80,70,60,50,40,30,20,10,0" if ( $k eq "pos" );
  664. push(@cList,$k . ':' . $opts);
  665. } else {
  666. push (@cList,$k);
  667. }
  668. } # end foreach
  669. return "SOMFY_set: Unknown argument $cmd, choose one of " . join(" ", @cList);
  670. } # error unknown cmd handling
  671. my $arg1 = "";
  672. if ( $numberOfArgs >= 2 ) {
  673. $arg1 = $args[1];
  674. }
  675. return "SOMFY_set: Bad time spec" if($cmd =~m/(on|off)-for-timer/ && $numberOfArgs == 2 && $arg1 !~ m/^\d*\.?\d+$/);
  676. # read timing variables
  677. my ($t1down100, $t1downclose, $t1upopen, $t1up100) = SOMFY_getTimingValues($hash);
  678. #Log3($name,5,"SOMFY_set: $name -> timings -> td1:$t1down100: tdc :$t1downclose: tuo :$t1upopen: tu1 :$t1up100: ");
  679. my $model = AttrVal($name,'model',$models{somfyblinds});
  680. if($cmd eq 'pos') {
  681. return "SOMFY_set: No pos specification" if(!defined($arg1));
  682. return "SOMFY_set: $arg1 must be between 0 and 100 for pos" if($arg1 < 0 || $arg1 > 100);
  683. return "SOMFY_set: Please set attr drive-down-time-to-100, drive-down-time-to-close, etc"
  684. if(!defined($t1downclose) || !defined($t1down100) || !defined($t1upopen) || !defined($t1up100));
  685. }
  686. # get current infos
  687. my $state = $hash->{STATE};
  688. my $pos = ReadingsVal($name,'exact',undef);
  689. if ( !defined($pos) ) {
  690. $pos = ReadingsVal($name,'position',undef);
  691. }
  692. # do conversions
  693. if ( AttrVal( $name, "positionInverse", 0 ) ) {
  694. Log3($name,4,"SOMFY_set: $name Inverse before cmd:$cmd: arg1:$arg1: pos:$pos:");
  695. $arg1 = SOMFY_ConvertFrom100To0( $arg1 ) if($cmd eq 'pos');
  696. $pos = SOMFY_ConvertFrom100To0( $pos );
  697. # $cmd = $inverseCommands{$cmd} if(exists($inverseCommands{$cmd}));
  698. Log3($name,4,"SOMFY_set: $name Inverse after cmd:$cmd: arg1:$arg1: pos:$pos:");
  699. }
  700. ### initialize locals
  701. my $drivetime = 0; # timings until halt command to be sent for on/off-for-timer and pos <value> -> move by time
  702. my $updatetime = 0; # timing until update of pos to be done for any unlimited move move to endpos or go-my / stop
  703. my $move = $cmd;
  704. my $newState;
  705. my $updateState;
  706. # translate state info to numbers - closed = 200 , open = 0 (correct missing values)
  707. if ( !defined($pos) ) {
  708. if(exists($positions{$state})) {
  709. $pos = $positions{$state};
  710. } else {
  711. $pos = ($state ne "???" ? $state : 0); # fix runtime error
  712. }
  713. $pos = sprintf( "%d", $pos );
  714. }
  715. Log3($name,4,"SOMFY_set: $name -> entering with mode :$mode: cmd :$cmd: arg1 :$arg1: pos :$pos: ");
  716. # check timer running - stop timer if running and update detail pos
  717. # recognize timer running if internal updateState is still set
  718. if ( defined( $hash->{updateState} )) {
  719. # timer is running so timer needs to be stopped and pos needs update
  720. RemoveInternalTimer($hash);
  721. $pos = SOMFY_CalcCurrentPos( $hash, $hash->{move}, $pos, SOMFY_UpdateStartTime($hash) );
  722. delete $hash->{starttime};
  723. delete $hash->{updateState};
  724. delete $hash->{runningtime};
  725. delete $hash->{runningcmd};
  726. }
  727. ################ No error returns after this point to avoid stopped timer causing confusion...
  728. # calc posRounded
  729. my $posRounded = SOMFY_RoundInternal( $pos );
  730. ### handle commands
  731. if(!defined($t1downclose) || !defined($t1down100) || !defined($t1upopen) || !defined($t1up100)) {
  732. #if timings not set
  733. if($cmd eq 'on') {
  734. $newState = 'closed';
  735. # $newState = 'moving';
  736. # $updatetime = $somfy_maxRuntime;
  737. # $updateState = 'closed';
  738. } elsif($cmd eq 'off') {
  739. $newState = 'open';
  740. # $newState = 'moving';
  741. # $updatetime = $somfy_maxRuntime;
  742. # $updateState = 'open';
  743. } elsif($cmd eq 'on-for-timer') {
  744. # elsif cmd == on-for-timer - time x
  745. $move = 'on';
  746. $newState = 'moving';
  747. $drivetime = $arg1;
  748. if ( $drivetime == 0 ) {
  749. $move = 'stop';
  750. } else {
  751. $updateState = 'moving';
  752. }
  753. } elsif($cmd eq 'off-for-timer') {
  754. # elsif cmd == off-for-timer - time x
  755. $move = 'off';
  756. $newState = 'moving';
  757. $drivetime = $arg1;
  758. if ( $drivetime == 0 ) {
  759. $move = 'stop';
  760. } else {
  761. $updateState = 'moving';
  762. }
  763. } elsif($cmd =~m/go-my/) {
  764. $move = 'stop';
  765. $newState = 'go-my';
  766. } elsif($cmd =~m/stop/) {
  767. $move = 'stop';
  768. $newState = $state;
  769. } else {
  770. $newState = $state;
  771. }
  772. ###else (here timing is set)
  773. } else {
  774. # default is roundedPos as new StatePos
  775. $newState = $posRounded;
  776. if($cmd eq 'on') {
  777. if ( $posRounded == 200 ) {
  778. # if pos == 200 - no state pos change / no timer
  779. } elsif ( $posRounded >= 100 ) {
  780. # elsif pos >= 100 - set timer for 100-to-closed --> update timer(newState 200)
  781. my $remTime = ( $t1downclose - $t1down100 ) * ( (200-$pos) / 100 );
  782. $updatetime = $remTime;
  783. $updateState = 200;
  784. } elsif ( $posRounded < 100 ) {
  785. # elseif pos < 100 - set timer for remaining time to 100+time-to-close --> update timer( newState 200)
  786. my $remTime = $t1down100 * ( (100 - $pos) / 100 );
  787. $updatetime = ( $t1downclose - $t1down100 ) + $remTime;
  788. $updateState = 200;
  789. } else {
  790. # else - unknown pos - assume pos 0 set timer for full time --> update timer( newState 200)
  791. $newState = 0;
  792. $updatetime = $t1downclose;
  793. $updateState = 200;
  794. }
  795. } elsif($cmd eq 'off') {
  796. if ( $posRounded == 0 ) {
  797. # if pos == 0 - no state pos change / no timer
  798. } elsif ( $posRounded <= 100 ) {
  799. # elsif pos <= 100 - set timer for remaining time to 0 --> update timer( newState 0 )
  800. my $remTime = ( $t1upopen - $t1up100 ) * ( $pos / 100 );
  801. $updatetime = $remTime;
  802. $updateState = 0;
  803. } elsif ( $posRounded > 100 ) {
  804. # elseif ( pos > 100 ) - set timer for remaining time to 100+time-to-open --> update timer( newState 0 )
  805. my $remTime = $t1up100 * ( ($pos - 100 ) / 100 );
  806. $updatetime = ( $t1upopen - $t1up100 ) + $remTime;
  807. $updateState = 0;
  808. } else {
  809. # else - unknown pos assume pos 200 set time for full time --> update timer( newState 0 )
  810. $newState = 200;
  811. $updatetime = $t1upopen;
  812. $updateState = 0;
  813. }
  814. } elsif($cmd eq 'pos') {
  815. if ( $pos < $arg1 ) {
  816. # if pos < x - set halt timer for remaining time to x / cmd close --> halt timer`( newState x )
  817. $move = 'on';
  818. my $remTime = $t1down100 * ( ( $arg1 - $pos ) / 100 );
  819. $drivetime = $remTime;
  820. $updateState = $arg1;
  821. } elsif ( ( $pos >= $arg1 ) && ( $posRounded <= 100 ) ) {
  822. # elsif pos <= 100 & pos > x - set halt timer for remaining time to x / cmd open --> halt timer ( newState x )
  823. $move = 'off';
  824. my $remTime = ( $t1upopen - $t1up100 ) * ( ( $pos - $arg1) / 100 );
  825. $drivetime = $remTime;
  826. if ( $drivetime == 0 ) {
  827. # $move = 'stop'; # avoid sending stop to move to my-pos
  828. $move = 'none';
  829. } else {
  830. $updateState = $arg1;
  831. }
  832. } elsif ( $pos > 100 ) {
  833. # else if pos > 100 - set timer for remaining time to 100+time for 100-x / cmd open --> halt timer ( newState x )
  834. $move = 'off';
  835. my $remTime = ( $t1upopen - $t1up100 ) * ( ( 100 - $arg1) / 100 );
  836. my $posTime = $t1up100 * ( ( $pos - 100) / 100 );
  837. $drivetime = $remTime + $posTime;
  838. $updateState = $arg1;
  839. } else {
  840. # else - send error (might be changed to first open completely then drive to pos x) / assume open
  841. $newState = 0;
  842. $move = 'on';
  843. my $remTime = $t1down100 * ( ( $arg1 - 0 ) / 100 );
  844. $drivetime = $remTime;
  845. $updateState = $arg1;
  846. ### return "SOMFY_set: Pos not currently known please open or close first";
  847. }
  848. } elsif($cmd =~m/stop|go-my/) {
  849. # update pos according to current detail pos
  850. $move = 'stop';
  851. } elsif($cmd eq 'off-for-timer') {
  852. # calcPos at new time y / cmd close --> halt timer ( newState y )
  853. $move = 'off';
  854. $drivetime = $arg1;
  855. if ( $drivetime == 0 ) {
  856. $move = 'stop';
  857. } else {
  858. $updateState = SOMFY_CalcCurrentPos( $hash, $move, $pos, $arg1 );
  859. }
  860. } elsif($cmd eq 'on-for-timer') {
  861. # calcPos at new time y / cmd open --> halt timer ( newState y )
  862. $move = 'on';
  863. $drivetime = $arg1;
  864. if ( $drivetime == 0 ) {
  865. $move = 'stop';
  866. } else {
  867. $updateState = SOMFY_CalcCurrentPos( $hash, $move, $pos, $arg1 );
  868. }
  869. }
  870. ## special case close is at 100 ("markisen")
  871. if( ( $t1downclose == $t1down100) && ( $t1up100 == 0 ) ) {
  872. if ( defined( $updateState )) {
  873. $updateState = minNum( 100, $updateState );
  874. }
  875. $newState = minNum( 100, $posRounded );
  876. }
  877. }
  878. ### update hash / readings
  879. Log3($name,4,"SOMFY_set: handled command $cmd --> move :$move: newState :$newState: ");
  880. if ( defined($updateState)) {
  881. Log3($name,5,"SOMFY_set: handled for drive/udpate: updateState :$updateState: drivet :$drivetime: updatet :$updatetime: ");
  882. } else {
  883. Log3($name,5,"SOMFY_set: handled for drive/udpate: updateState :: drivet :$drivetime: updatet :$updatetime: ");
  884. }
  885. # bulk update should do trigger if virtual mode
  886. SOMFY_UpdateState( $hash, $newState, $move, $updateState, ( $mode eq 'virtual' ) );
  887. ### send command
  888. if ( $mode ne 'virtual' ) {
  889. if(exists($sendCommands{$move})) {
  890. $args[0] = $sendCommands{$move};
  891. SOMFY_SendCommand($hash,@args);
  892. } elsif ( $move eq 'none' ) {
  893. # do nothing if commmand / move is set to none
  894. } else {
  895. Log3($name,1,"SOMFY_set: Error - unknown move for sendCommands: $move");
  896. }
  897. }
  898. ### start timer
  899. if ( $mode eq 'virtual' ) {
  900. # in virtual mode define drivetime as updatetime only, so no commands will be send
  901. if ( $updatetime == 0 ) {
  902. $updatetime = $drivetime;
  903. }
  904. $drivetime = 0;
  905. }
  906. ### update time stamp
  907. SOMFY_UpdateStartTime($hash);
  908. $hash->{runningtime} = 0;
  909. if($drivetime > 0) {
  910. $hash->{runningcmd} = 'stop';
  911. $hash->{runningtime} = $drivetime;
  912. } elsif($updatetime > 0) {
  913. $hash->{runningtime} = $updatetime;
  914. }
  915. if($hash->{runningtime} > 0) {
  916. # timer fuer stop starten
  917. if ( defined( $hash->{runningcmd} )) {
  918. Log3($name,4,"SOMFY_set: $name -> stopping in $hash->{runningtime} sec");
  919. } else {
  920. Log3($name,4,"SOMFY_set: $name -> update state in $hash->{runningtime} sec");
  921. }
  922. my $utime = $hash->{runningtime} ;
  923. if($utime > $somfy_updateFreq) {
  924. $utime = $somfy_updateFreq;
  925. }
  926. InternalTimer(gettimeofday()+$utime,"SOMFY_TimedUpdate",$hash,0);
  927. } else {
  928. delete $hash->{runningtime};
  929. delete $hash->{starttime};
  930. }
  931. return undef;
  932. } # end sub SOMFY_setFN
  933. ###############################
  934. ######################################################
  935. ######################################################
  936. ###
  937. ### Helper for set routine
  938. ###
  939. ######################################################
  940. #############################
  941. sub SOMFY_ConvertFrom100To0($) {
  942. my ($v) = @_;
  943. return $v if ( ! defined($v) );
  944. return $v if ( length($v) == 0 );
  945. $v = minNum( 100, maxNum( 0, $v ) );
  946. return (( $v < 10 ) ? ( 200-($v*10.0) ) : ( (100-$v)*10.0/9 ));
  947. }
  948. #############################
  949. sub SOMFY_ConvertTo100To0($) {
  950. my ($v) = @_;
  951. return $v if ( ! defined($v) );
  952. return $v if ( length($v) == 0 );
  953. $v = minNum( 200, maxNum( 0, $v ) );
  954. return ( $v > 100 ) ? ( (200-$v)/10.0 ) : ( 100-(9*$v/10.0) );
  955. }
  956. #############################
  957. sub SOMFY_RTS_Crypt($$$)
  958. {
  959. my ($operation, $name, $data) = @_;
  960. my $res = substr($data, 0, 2);
  961. my $ref = ($operation eq "e" ? \$res : \$data);
  962. for (my $idx=1; $idx < 7; $idx++)
  963. {
  964. my $high = hex(substr($data, $idx * 2, 2));
  965. my $low = hex(substr(${$ref}, ($idx - 1) * 2, 2));
  966. my $val = $high ^ $low;
  967. $res .= sprintf("%02X", $val);
  968. }
  969. return $res;
  970. }
  971. #############################
  972. sub SOMFY_RTS_Check($$)
  973. {
  974. my ($name, $data) = @_;
  975. my $checkSum = 0;
  976. for (my $idx=0; $idx < 7; $idx++)
  977. {
  978. my $val = hex(substr($data, $idx * 2, 2));
  979. $val &= 0xF0 if ($idx == 1);
  980. $checkSum = $checkSum ^ $val ^ ($val >> 4);
  981. ##Log3 $name, 4, "$name: Somfy RTS check: " . sprintf("%02X, %02X", $val, $checkSum);
  982. }
  983. $checkSum &= hex("0x0F");
  984. return sprintf("%X", $checkSum);
  985. }
  986. #############################
  987. sub SOMFY_RoundInternal($) {
  988. my ($v) = @_;
  989. return sprintf("%d", ($v + ($somfy_posAccuracy/2)) / $somfy_posAccuracy) * $somfy_posAccuracy;
  990. } # end sub SOMFY_RoundInternal
  991. #############################
  992. sub SOMFY_UpdateStartTime($) {
  993. my ($d) = @_;
  994. my ($s, $ms) = gettimeofday();
  995. my $t = $s + ($ms / 1000000); # 10 msec
  996. my $t1 = 0;
  997. $t1 = $d->{starttime} if(exists($d->{starttime} ));
  998. $d->{starttime} = $t;
  999. my $dt = sprintf("%.2f", $t - $t1);
  1000. return $dt;
  1001. } # end sub SOMFY_UpdateStartTime
  1002. ###################################
  1003. sub SOMFY_TimedUpdate($) {
  1004. my ($hash) = @_;
  1005. Log3($hash->{NAME},4,"SOMFY_TimedUpdate");
  1006. # get current infos
  1007. my $pos = ReadingsVal($hash->{NAME},'exact',undef);
  1008. if ( AttrVal( $hash->{NAME}, "positionInverse", 0 ) ) {
  1009. Log3($hash->{NAME},5,"SOMFY_TimedUpdate : pos before convert so far : $pos");
  1010. $pos = SOMFY_ConvertFrom100To0( $pos );
  1011. }
  1012. Log3($hash->{NAME},5,"SOMFY_TimedUpdate : pos so far : $pos");
  1013. my $dt = SOMFY_UpdateStartTime($hash);
  1014. my $nowt = gettimeofday();
  1015. $pos = SOMFY_CalcCurrentPos( $hash, $hash->{move}, $pos, $dt );
  1016. # my $posRounded = SOMFY_RoundInternal( $pos );
  1017. Log3($hash->{NAME},5,"SOMFY_TimedUpdate : delta time : $dt new rounde pos (rounded): $pos ");
  1018. $hash->{runningtime} = $hash->{runningtime} - $dt;
  1019. if ( $hash->{runningtime} <= 0.1) {
  1020. if ( defined( $hash->{runningcmd} ) ) {
  1021. SOMFY_SendCommand($hash, $hash->{runningcmd});
  1022. }
  1023. # trigger update from timer
  1024. SOMFY_UpdateState( $hash, $hash->{updateState}, 'stop', undef, 1 );
  1025. delete $hash->{starttime};
  1026. delete $hash->{runningtime};
  1027. delete $hash->{runningcmd};
  1028. } else {
  1029. my $utime = $hash->{runningtime} ;
  1030. if($utime > $somfy_updateFreq) {
  1031. $utime = $somfy_updateFreq;
  1032. }
  1033. SOMFY_UpdateState( $hash, $pos, $hash->{move}, $hash->{updateState}, 1 );
  1034. if ( defined( $hash->{runningcmd} )) {
  1035. Log3($hash->{NAME},4,"SOMFY_TimedUpdate: $hash->{NAME} -> stopping in $hash->{runningtime} sec");
  1036. } else {
  1037. Log3($hash->{NAME},4,"SOMFY_TimedUpdate: $hash->{NAME} -> update state in $hash->{runningtime} sec");
  1038. }
  1039. my $nstt = maxNum($nowt+$utime-0.01, gettimeofday()+.1 );
  1040. Log3($hash->{NAME},5,"SOMFY_TimedUpdate: $hash->{NAME} -> next time to stop: $nstt");
  1041. InternalTimer($nstt,"SOMFY_TimedUpdate",$hash,0);
  1042. }
  1043. Log3($hash->{NAME},5,"SOMFY_TimedUpdate DONE");
  1044. } # end sub SOMFY_TimedUpdate
  1045. ###################################
  1046. # SOMFY_UpdateState( $hash, $newState, $move, $updateState );
  1047. sub SOMFY_UpdateState($$$$$) {
  1048. my ($hash, $newState, $move, $updateState, $doTrigger) = @_;
  1049. my $name = $hash->{NAME};
  1050. my $addtlPosReading = AttrVal($hash->{NAME},'additionalPosReading',undef);
  1051. if ( defined($addtlPosReading )) {
  1052. $addtlPosReading = undef if ( ( $addtlPosReading eq "" ) or ( $addtlPosReading eq "state" ) or ( $addtlPosReading eq "position" ) or ( $addtlPosReading eq "exact" ) );
  1053. }
  1054. my $newExact = $newState;
  1055. readingsBeginUpdate($hash);
  1056. if(exists($positions{$newState})) {
  1057. readingsBulkUpdate($hash,"state",$newState);
  1058. $hash->{STATE} = $newState;
  1059. $newExact = $positions{$newState};
  1060. readingsBulkUpdate($hash,"position",$newExact);
  1061. readingsBulkUpdate($hash,$addtlPosReading,$newExact) if ( defined($addtlPosReading) );
  1062. } else {
  1063. my $rounded;
  1064. my $stateTrans;
  1065. Log3($name,4,"SOMFY_UpdateState: $name enter with newState:$newState: updatestate:".(defined( $updateState )?$updateState:"<undef>").
  1066. ": move:$move:");
  1067. # do conversions
  1068. if ( AttrVal( $name, "positionInverse", 0 ) ) {
  1069. $newState = SOMFY_ConvertTo100To0( $newState );
  1070. $newExact = $newState;
  1071. $rounded = SOMFY_Runden( $newState );
  1072. $stateTrans = SOMFY_Translate100To0( $rounded );
  1073. } else {
  1074. $rounded = SOMFY_Runden( $newState );
  1075. $stateTrans = SOMFY_Translate( $rounded );
  1076. }
  1077. Log3($name,4,"SOMFY_UpdateState: $name after conversions newState:$newState: rounded:$rounded: stateTrans:$stateTrans:");
  1078. readingsBulkUpdate($hash,"state",$stateTrans);
  1079. $hash->{STATE} = $stateTrans;
  1080. readingsBulkUpdate($hash,"position",$rounded);
  1081. readingsBulkUpdate($hash,$addtlPosReading,$rounded) if ( defined($addtlPosReading) );
  1082. }
  1083. readingsBulkUpdate($hash,"exact",$newExact);
  1084. if ( defined( $updateState ) ) {
  1085. $hash->{updateState} = $updateState;
  1086. } else {
  1087. delete $hash->{updateState};
  1088. }
  1089. $hash->{move} = $move;
  1090. readingsEndUpdate($hash,$doTrigger);
  1091. } # end sub SOMFY_UpdateState
  1092. ###################################
  1093. # Return timingvalues from attr and after correction
  1094. sub SOMFY_getTimingValues($) {
  1095. my ($hash) = @_;
  1096. my $name = $hash->{NAME};
  1097. my $t1down100 = AttrVal($name,'drive-down-time-to-100',undef);
  1098. my $t1downclose = AttrVal($name,'drive-down-time-to-close',undef);
  1099. my $t1upopen = AttrVal($name,'drive-up-time-to-open',undef);
  1100. my $t1up100 = AttrVal($name,'drive-up-time-to-100',undef);
  1101. return (undef, undef, undef, undef) if(!defined($t1downclose) || !defined($t1down100) || !defined($t1upopen) || !defined($t1up100));
  1102. if ( ( $t1downclose < 0 ) || ( $t1down100 < 0 ) || ( $t1upopen < 0 ) || ( $t1up100 < 0 ) ) {
  1103. Log3($name,1,"SOMFY_getTimingValues: $name time values need to be positive values");
  1104. return (undef, undef, undef, undef);
  1105. }
  1106. if ( $t1downclose < $t1down100 ) {
  1107. Log3($name,1,"SOMFY_getTimingValues: $name close time needs to be higher or equal than time to pos100");
  1108. return (undef, undef, undef, undef);
  1109. } elsif ( $t1downclose == $t1down100 ) {
  1110. $t1up100 = 0;
  1111. }
  1112. if ( $t1upopen <= $t1up100 ) {
  1113. Log3($name,1,"SOMFY_getTimingValues: $name open time needs to be higher or equal than time to pos100");
  1114. return (undef, undef, undef, undef);
  1115. }
  1116. if ( $t1upopen < 1 ) {
  1117. Log3($name,1,"SOMFY_getTimingValues: $name time to open needs to be at least 1 second");
  1118. return (undef, undef, undef, undef);
  1119. }
  1120. if ( $t1downclose < 1 ) {
  1121. Log3($name,1,"SOMFY_getTimingValues: $name time to close needs to be at least 1 second");
  1122. return (undef, undef, undef, undef);
  1123. }
  1124. return ($t1down100, $t1downclose, $t1upopen, $t1up100);
  1125. }
  1126. ###################################
  1127. # call with hash, translated state
  1128. sub SOMFY_CalcCurrentPos($$$$) {
  1129. my ($hash, $move, $pos, $dt) = @_;
  1130. my $name = $hash->{NAME};
  1131. my $newPos = $pos;
  1132. # Attributes for calculation
  1133. my ($t1down100, $t1downclose, $t1upopen, $t1up100) = SOMFY_getTimingValues($hash);
  1134. if(defined($t1down100) && defined($t1downclose) && defined($t1up100) && defined($t1upopen)) {
  1135. if( ( $t1downclose == $t1down100) && ( $t1up100 == 0 ) ) {
  1136. $pos = minNum( 100, $pos );
  1137. if($move eq 'on') {
  1138. $newPos = minNum( 100, $pos );
  1139. if ( $pos < 100 ) {
  1140. # calc remaining time to 100%
  1141. my $remTime = ( 100 - $pos ) * $t1down100 / 100;
  1142. if ( $remTime > $dt ) {
  1143. $newPos = $pos + ( $dt * 100 / $t1down100 );
  1144. }
  1145. }
  1146. } elsif($move eq 'off') {
  1147. $newPos = maxNum( 0, $pos );
  1148. if ( $pos > 0 ) {
  1149. $newPos = $dt * 100 / ( $t1upopen );
  1150. $newPos = maxNum( 0, ($pos - $newPos) );
  1151. }
  1152. } else {
  1153. Log3($name,1,"SOMFY_CalcCurrentPos: $name move wrong $move");
  1154. }
  1155. } else {
  1156. if($move eq 'on') {
  1157. if ( $pos >= 100 ) {
  1158. $newPos = $dt * 100 / ( $t1downclose - $t1down100 );
  1159. $newPos = minNum( 200, $pos + $newPos );
  1160. } else {
  1161. # calc remaining time to 100%
  1162. my $remTime = ( 100 - $pos ) * $t1down100 / 100;
  1163. if ( $remTime > $dt ) {
  1164. $newPos = $pos + ( $dt * 100 / $t1down100 );
  1165. } else {
  1166. $dt = $dt - $remTime;
  1167. $newPos = 100 + ( $dt * 100 / ( $t1downclose - $t1down100 ) );
  1168. }
  1169. }
  1170. } elsif($move eq 'off') {
  1171. if ( $pos <= 100 ) {
  1172. $newPos = $dt * 100 / ( $t1upopen - $t1up100 );
  1173. $newPos = maxNum( 0, $pos - $newPos );
  1174. } else {
  1175. # calc remaining time to 100%
  1176. my $remTime = ( $pos - 100 ) * $t1up100 / 100;
  1177. if ( $remTime > $dt ) {
  1178. $newPos = $pos - ( $dt * 100 / $t1up100 );
  1179. } else {
  1180. $dt = $dt - $remTime;
  1181. $newPos = 100 - ( $dt * 100 / ( $t1upopen - $t1up100 ) );
  1182. }
  1183. }
  1184. } else {
  1185. Log3($name,1,"SOMFY_CalcCurrentPos: $name move wrong $move");
  1186. }
  1187. }
  1188. } else {
  1189. ### no timings set so just assume it is always moving
  1190. $newPos = $positions{'moving'};
  1191. }
  1192. return $newPos;
  1193. }
  1194. ######################################################
  1195. ######################################################
  1196. ######################################################
  1197. 1;
  1198. =pod
  1199. =item summary supporting devices using the SOMFY RTS protocol - window shades
  1200. =item summary_DE für Geräte, die das SOMFY RTS protocol unterstützen - Rolläden
  1201. =begin html
  1202. <a name="SOMFY"></a>
  1203. <h3>SOMFY - Somfy RTS / Simu Hz protocol</h3>
  1204. <ul>
  1205. The Somfy RTS (identical to Simu Hz) protocol is used by a wide range of devices,
  1206. which are either senders or receivers/actuators.
  1207. Right now only SENDING of Somfy commands is implemented in the CULFW, so this module currently only
  1208. supports devices like blinds, dimmers, etc. through a <a href="#CUL">CUL</a> device (which must be defined first).
  1209. Reception of Somfy remotes is only supported indirectly through the usage of an FHEMduino
  1210. <a href="http://www.fhemwiki.de/wiki/FHEMduino">http://www.fhemwiki.de/wiki/FHEMduino</a>
  1211. which can then be used to connect to the SOMFY device.
  1212. <br><br>
  1213. <a name="SOMFYdefine"></a>
  1214. <b>Define</b>
  1215. <ul>
  1216. <code>define &lt;name&gt; SOMFY &lt;address&gt; [&lt;encryption-key&gt;] [&lt;rolling-code&gt;] </code>
  1217. <br><br>
  1218. The address is a 6-digit hex code, that uniquely identifies a single remote control channel.
  1219. It is used to pair the remote to the blind or dimmer it should control.
  1220. <br>
  1221. Pairing is done by setting the blind in programming mode, either by disconnecting/reconnecting the power,
  1222. or by pressing the program button on an already associated remote.
  1223. <br>
  1224. Once the blind is in programming mode, send the "prog" command from within FHEM to complete the pairing.
  1225. The blind will move up and down shortly to indicate completion.
  1226. <br>
  1227. You are now able to control this blind from FHEM, the receiver thinks it is just another remote control.
  1228. <ul>
  1229. <li><code>&lt;address&gt;</code> is a 6 digit hex number that uniquely identifies FHEM as a new remote control channel.
  1230. <br>You should use a different one for each device definition, and group them using a structure.
  1231. </li>
  1232. <li>The optional <code>&lt;encryption-key&gt;</code> is a 2 digit hex number (first letter should always be A)
  1233. that can be set to clone an existing remote control channel.</li>
  1234. <li>The optional <code>&lt;rolling-code&gt;</code> is a 4 digit hex number that can be set
  1235. to clone an existing remote control channel.<br>
  1236. If you set one of them, you need to pick the same address as an existing remote.
  1237. Be aware that the receiver might not accept commands from the remote any longer,<br>
  1238. if you used FHEM to clone an existing remote.
  1239. <br>
  1240. This is because the code is original remote's codes are out of sync.</li>
  1241. </ul>
  1242. <br>
  1243. Examples:
  1244. <ul>
  1245. <code>define rollo_1 SOMFY 000001</code><br>
  1246. <code>define rollo_2 SOMFY 000002</code><br>
  1247. <code>define rollo_3_original SOMFY 42ABCD A5 0A1C</code><br>
  1248. </ul>
  1249. </ul>
  1250. <br>
  1251. <a name="SOMFYset"></a>
  1252. <b>Set </b>
  1253. <ul>
  1254. <code>set &lt;name&gt; &lt;value&gt; [&lt;time&gt]</code>
  1255. <br><br>
  1256. where <code>value</code> is one of:<br>
  1257. <pre>
  1258. on
  1259. off
  1260. go-my
  1261. stop
  1262. pos value (0..100) # see note
  1263. prog # Special, see note
  1264. on-for-timer
  1265. off-for-timer
  1266. </pre>
  1267. Examples:
  1268. <ul>
  1269. <code>set rollo_1 on</code><br>
  1270. <code>set rollo_1,rollo_2,rollo_3 on</code><br>
  1271. <code>set rollo_1-rollo_3 on</code><br>
  1272. <code>set rollo_1 off</code><br>
  1273. <code>set rollo_1 pos 50</code><br>
  1274. </ul>
  1275. <br>
  1276. Notes:
  1277. <ul>
  1278. <li>prog is a special command used to pair the receiver to FHEM:
  1279. Set the receiver in programming mode (eg. by pressing the program-button on the original remote)
  1280. and send the "prog" command from FHEM to finish pairing.<br>
  1281. The blind will move up and down shortly to indicate success.
  1282. </li>
  1283. <li>on-for-timer and off-for-timer send a stop command after the specified time,
  1284. instead of reversing the blind.<br>
  1285. This can be used to go to a specific position by measuring the time it takes to close the blind completely.
  1286. </li>
  1287. <li>pos value<br>
  1288. The position is variying between 0 completely open and 100 for covering the full window.
  1289. The position must be between 0 and 100 and the appropriate
  1290. attributes drive-down-time-to-100, drive-down-time-to-close,
  1291. drive-up-time-to-100 and drive-up-time-to-open must be set. See also positionInverse attribute.<br>
  1292. </li>
  1293. </ul>
  1294. The position reading distinuishes between multiple cases
  1295. <ul>
  1296. <li>Without timing values (see attributes) set only generic values are used for status and position: <pre>open, closed, moving</pre> are used
  1297. </li>
  1298. <li>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
  1299. the device is considered to only vary between 0 and 100 (100 being completely closed)
  1300. </li>
  1301. <li>With full timing values set the device is considerd a window shutter (Rolladen) with a difference between
  1302. covering the full window (position 100) and being completely closed (position 200)
  1303. </li>
  1304. </ul>
  1305. </ul>
  1306. <br>
  1307. <b>Get</b> <ul>N/A</ul><br>
  1308. <a name="SOMFYattr"></a>
  1309. <b>Attributes</b>
  1310. <ul>
  1311. <a name="IODev"></a>
  1312. <li>IODev<br>
  1313. Set the IO or physical device which should be used for sending signals
  1314. for this "logical" device. An example for the physical device is a CUL.<br>
  1315. Note: The IODev has to be set, otherwise no commands will be sent!<br>
  1316. If you have both a CUL868 and CUL433, use the CUL433 as IODev for increased range.
  1317. </li><br>
  1318. <a name="positionInverse"></a>
  1319. <li>positionInverse<br>
  1320. 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.
  1321. </li><br>
  1322. <a name="additionalPosReading"></a>
  1323. <li>additionalPosReading<br>
  1324. Position of the shutter will be stored in the reading <code>pos</code> as numeric value.
  1325. Additionally this attribute might specify a name for an additional reading to be updated with the same value than the pos.
  1326. </li><br>
  1327. <a name="rolling-code"></a>
  1328. <li>rolling-code &lt; 4 digit hex &gt; <br>
  1329. Can be used to overwrite the rolling-code manually with a new value (rolling-code will be automatically increased with every command sent)
  1330. This requires also setting enc-key: only with bot attributes set the value will be accepted for the internal reading
  1331. </li><br>
  1332. <a name="enc-key"></a>
  1333. <li>enc-key &lt; 2 digit hex &gt; <br>
  1334. Can be used to overwrite the enc-key manually with a new value
  1335. This requires also setting rolling-code: only with bot attributes set the value will be accepted for the internal reading
  1336. </li><br>
  1337. <a name="eventMap"></a>
  1338. <li>eventMap<br>
  1339. Replace event names and set arguments. The value of this attribute
  1340. consists of a list of space separated values, each value is a colon
  1341. separated pair. The first part specifies the "old" value, the second
  1342. the new/desired value. If the first character is slash(/) or comma(,)
  1343. then split not by space but by this character, enabling to embed spaces.
  1344. Examples:<ul><code>
  1345. attr store eventMap on:open off:closed<br>
  1346. attr store eventMap /on-for-timer 10:open/off:closed/<br>
  1347. set store open
  1348. </code></ul>
  1349. </li><br>
  1350. <li><a href="#do_not_notify">do_not_notify</a></li><br>
  1351. <a name="attrdummy"></a>
  1352. <li>dummy<br>
  1353. Set the device attribute dummy to define devices which should not
  1354. output any radio signals. Associated notifys will be executed if
  1355. the signal is received. Used e.g. to react to a code from a sender, but
  1356. it will not emit radio signal if triggered in the web frontend.
  1357. </li><br>
  1358. <li><a href="#loglevel">loglevel</a></li><br>
  1359. <li><a href="#showtime">showtime</a></li><br>
  1360. <a name="model"></a>
  1361. <li>model<br>
  1362. The model attribute denotes the model type of the device.
  1363. The attributes will (currently) not be used by the fhem.pl directly.
  1364. It can be used by e.g. external programs or web interfaces to
  1365. distinguish classes of devices and send the appropriate commands
  1366. (e.g. "on" or "off" to a switch, "dim..%" to dimmers etc.).<br>
  1367. The spelling of the model names are as quoted on the printed
  1368. documentation which comes which each device. This name is used
  1369. without blanks in all lower-case letters. Valid characters should be
  1370. <code>a-z 0-9</code> and <code>-</code> (dash),
  1371. other characters should be ommited.<br>
  1372. Here is a list of "official" devices:<br>
  1373. <b>Receiver/Actor</b>: somfyblinds<br>
  1374. </li><br>
  1375. <a name="ignore"></a>
  1376. <li>ignore<br>
  1377. Ignore this device, e.g. if it belongs to your neighbour. The device
  1378. won't trigger any FileLogs/notifys, issued commands will silently
  1379. ignored (no RF signal will be sent out, just like for the <a
  1380. href="#attrdummy">dummy</a> attribute). The device won't appear in the
  1381. list command (only if it is explicitely asked for it), nor will it
  1382. appear in commands which use some wildcard/attribute as name specifiers
  1383. (see <a href="#devspec">devspec</a>). You still get them with the
  1384. "ignored=1" special devspec.
  1385. </li><br>
  1386. <a name="drive-down-time-to-100"></a>
  1387. <li>drive-down-time-to-100<br>
  1388. The time the blind needs to drive down from "open" (pos 0) to pos 100.<br>
  1389. In this position, the lower edge touches the window frame, but it is not completely shut.<br>
  1390. For a mid-size window this time is about 12 to 15 seconds.
  1391. </li><br>
  1392. <a name="drive-down-time-to-close"></a>
  1393. <li>drive-down-time-to-close<br>
  1394. The time the blind needs to drive down from "open" (pos 0) to "close", the end position of the blind.<br>
  1395. Note: If set, this value always needs to be higher than drive-down-time-to-100
  1396. This is about 3 to 5 seonds more than the "drive-down-time-to-100" value.
  1397. </li><br>
  1398. <a name="drive-up-time-to-100"></a>
  1399. <li>drive-up-time-to-100<br>
  1400. The time the blind needs to drive up from "close" (endposition) to "pos 100".<br>
  1401. This usually takes about 3 to 5 seconds.
  1402. </li><br>
  1403. <a name="drive-up-time-to-open"></a>
  1404. <li>drive-up-time-to-open<br>
  1405. The time the blind needs drive up from "close" (endposition) to "open" (upper endposition).<br>
  1406. Note: If set, this value always needs to be higher than drive-down-time-to-100
  1407. This value is usually a bit higher than "drive-down-time-to-close", due to the blind's weight.
  1408. </li><br>
  1409. </ul>
  1410. </ul>
  1411. =end html
  1412. =cut