98_Dooya.pm 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252
  1. ######################################################
  2. # $Id: 98_Dooya.pm 12233 2016-10-01 22:22:51Z mrsidey $
  3. #
  4. # Dooya module for FHEM
  5. # Thanks for templates/coding from Somfy and SIGNALduino team
  6. #
  7. # Needs SIGNALduino.
  8. # Published under GNU GPL License, v2
  9. # History:
  10. # 0.10 2016-02-16 Jarnsen initial template
  11. # 0.20 2016-03-06 darkmission first functions, renamed from 10_ to 99_
  12. # 0.21 2016-03-06 darkmission Bug default channel corrected, changed attribute repetition to SignalRepeats
  13. # 0.22 2016-03-10 darkmission code cleaned, renamed from 99_ to 98_
  14. # 0.23 2016-03-11 Jarnsen AttrList cleaned and change priority
  15. # 1.00 2016-03-12 darkmission autocreate, parse communication from SIGNALduino for correct position when using remote from doooya
  16. # 1.10 2016-03-13 Ralf9 changed SendCommand with sendMsg
  17. # 1.11 2016-03-17 Ralf9 ID + Channel = DeviceCode
  18. # 1.12 2016-04-26 Jarnsen im Dooya parse cmd geändert
  19. #
  20. #TODOS:
  21. # - Groups, diff by channels
  22. #
  23. #
  24. ######################################################
  25. package main;
  26. use strict;
  27. use warnings;
  28. #use List::Util qw(first max maxstr min minstr reduce shuffle sum);
  29. my %codes = (
  30. "01010101" => "stop", # stop the current movement
  31. "00010001" => "off", # go "up"
  32. "00110011" => "on", # go "down"
  33. "11001100" => "prog", # finish pairing
  34. );
  35. my %sets = (
  36. "off" => "noArg",
  37. "on" => "noArg",
  38. "down"=> "noArg",
  39. "stop" => "noArg",
  40. "prog" => "noArg",
  41. # "on-for-timer" => "textField",
  42. # "off-for-timer" => "textField",
  43. "pos" => "0,10,20,30,40,50,60,70,80,90,100" # Todo: Warum nicht als Slider?
  44. # "pos" => "slider,0,10,100"
  45. );
  46. my %sendCommands = (
  47. "off" => "off",
  48. "open" => "off",
  49. "on" => "on",
  50. "close" => "on",
  51. "prog" => "prog",
  52. "stop" => "stop"
  53. );
  54. my %dooya_c2b; # Todo: Als internal speichern
  55. my $dooya_updateFreq = 3; # Interval for State update # Todo: Als internal speichern
  56. # supported models (blinds and shutters)
  57. my %models = (
  58. dooyablinds => 'blinds',
  59. dooyashutter => 'shutter'
  60. );
  61. ##################################################
  62. # new globals for new set
  63. #
  64. my $dooya_posAccuracy = 2; # Todo: Als internal speichern
  65. my $dooya_maxRuntime = 50; # Todo: Als internal speichern
  66. my %positions = (
  67. "moving" => "50",
  68. "open" => "0",
  69. "off" => "0",
  70. "down" => "150",
  71. "closed" => "200",
  72. "on" => "200"
  73. );
  74. my %translations = (
  75. "0" => "open",
  76. "10" => "10",
  77. "20" => "20",
  78. "30" => "30",
  79. "40" => "40",
  80. "50" => "50",
  81. "60" => "60",
  82. "70" => "70",
  83. "80" => "80",
  84. "90" => "90",
  85. "100" => "100",
  86. "150" => "down",
  87. "200" => "closed"
  88. );
  89. ######################################################
  90. # Forward declarations
  91. #
  92. sub Dooya_CalcCurrentPos($$$$);
  93. ######################################################
  94. sub myUtilsDooya_Initialize($) {
  95. $modules{Dooya}{LOADED} = 1;
  96. my $hash = $modules{Dooya};
  97. Dooya_Initialize($hash);
  98. } # end sub myUtilsDooya_initialize
  99. ######################################################
  100. sub Dooya_Initialize($) {
  101. my ($hash) = @_;
  102. # map commands from web interface to codes used in Dooya
  103. foreach my $k ( keys %codes ) {
  104. $dooya_c2b{ $codes{$k} } = $k;
  105. }
  106. $hash->{SetFn} = "Dooya_Set";
  107. #$hash->{StateFn} = "Dooya_SetState";
  108. $hash->{DefFn} = "Dooya_Define";
  109. $hash->{UndefFn} = "Dooya_Undef";
  110. $hash->{ParseFn} = "Dooya_Parse";
  111. $hash->{AttrFn} = "Dooya_Attr";
  112. $hash->{Match} = "^P16#[A-Fa-f0-9]+";
  113. $hash->{AttrList} = " IODev"
  114. . " SignalRepeats:5,10,15,20"
  115. . " channel:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15"
  116. . " drive-down-time-to-100"
  117. . " drive-down-time-to-close"
  118. . " drive-up-time-to-100"
  119. . " drive-up-time-to-open"
  120. . " additionalPosReading"
  121. . " $readingFnAttributes"
  122. . " setList"
  123. . " ignore:0,1"
  124. . " dummy:1,0"
  125. # . " model:dooyablinds,dooyashutter"
  126. . " loglevel:0,1,2,3,4,5,6";
  127. $hash->{AutoCreate} =
  128. { "Dooya.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*",
  129. FILTER => "%NAME",
  130. autocreateThreshold => "2:10" } };
  131. }
  132. ######################################################
  133. sub Dooya_StartTime($) {
  134. my ($d) = @_;
  135. my ($s, $ms) = gettimeofday();
  136. my $t = $s + ($ms / 1000000); # 10 msec
  137. my $t1 = 0;
  138. $t1 = $d->{'starttime'} if(exists($d->{'starttime'} ));
  139. $d->{'starttime'} = $t;
  140. my $dt = sprintf("%.2f", $t - $t1);
  141. return $dt;
  142. } # end sub Dooya_StartTime
  143. ######################################################
  144. sub Dooya_Define($$) {
  145. my ( $hash, $def ) = @_;
  146. my @a = split( "[ \t][ \t]*", $def );
  147. my $u = "wrong syntax: define <name> Dooya id ";
  148. # fail early and display syntax help
  149. if ( int(@a) < 3 ) {
  150. return $u;
  151. }
  152. my ($id, $channel) = split('_', $a[2]);
  153. Log3 $hash,4 ,"Dooya_Define: id = $id channel = $channel";
  154. # check id format (28 binaer digits)
  155. if ( ( $id !~ m/^[0-1]{28}$/i ) ) {
  156. return "Define $a[0]: wrong address format: specify a 28 binaer id value "
  157. }
  158. # group devices by their id
  159. my $name = $a[0];
  160. $hash->{ID} = uc($id);
  161. $hash->{CHANNEL} = $channel;
  162. my $tn = TimeNow();
  163. my $code = uc($a[2]);
  164. my $ncode = 1;
  165. $hash->{CODE}{ $ncode++ } = $code;
  166. $modules{Dooya}{defptr}{$code}{$name} = $hash;
  167. $hash->{move} = 'stop';
  168. AssignIoPort($hash);
  169. }
  170. ######################################################
  171. sub Dooya_Undef($$) {
  172. my ( $hash, $name ) = @_;
  173. foreach my $c ( keys %{ $hash->{CODE} } ) {
  174. $c = $hash->{CODE}{$c};
  175. # As after a rename the $name my be different from the $defptr{$c}{$n}
  176. # we look for the hash.
  177. foreach my $dname ( keys %{ $modules{Dooya}{defptr}{$c} } ) {
  178. if ( $modules{Dooya}{defptr}{$c}{$dname} == $hash ) {
  179. delete( $modules{Dooya}{defptr}{$c}{$dname} );
  180. }
  181. }
  182. }
  183. return undef;
  184. }
  185. ######################################################
  186. sub Dooya_SendCommand($@){
  187. my ($hash, @args) = @_;
  188. my $ret = undef;
  189. my $cmd = $args[0];
  190. my $message;
  191. my $chan;
  192. my $channel;
  193. my $SignalRepeats;
  194. my $name = $hash->{NAME};
  195. my $bin;
  196. my $numberOfArgs = int(@args);
  197. Log3($name,4,"Dooya_sendCommand: $name -> cmd :$cmd: ");
  198. my $command = $dooya_c2b{ $cmd };
  199. # eigentlich ueberfluessig, da oben schon auf Existenz geprueft wird -> %sets
  200. if ( !defined($command) ) {
  201. return "Unknown argument $cmd, choose one of "
  202. . join( " ", sort keys %dooya_c2b );
  203. }
  204. my $io = $hash->{IODev};
  205. $SignalRepeats = AttrVal($name,'SignalRepeats', '10');
  206. Log3 $io,4, "Dooya set SignalRepeats: $SignalRepeats for $io->{NAME}";
  207. $chan = AttrVal($name,'channel', undef);
  208. if (!defined($chan)) {
  209. $chan = $hash->{CHANNEL};
  210. }
  211. $channel = sprintf("%04b",$chan);
  212. Log3 $io,4, "Dooya set channel: $chan ($channel) for $io->{NAME}";
  213. my $value = $name ." ". join(" ", @args);
  214. $bin = uc($hash->{ID}) . $channel . $command;
  215. #print ("data = $bin \n");
  216. Log3 $io, 4, "Dooya set value = $value";
  217. ## Send Message to IODev using IOWrite
  218. $message = 'P16#' . $bin . '#R' . $SignalRepeats;
  219. Log3 $io, 4, "Dooya_sendCommand: $name -> message :$message: ";
  220. IOWrite($hash, 'sendMsg', $message);
  221. return $ret;
  222. } # end sub Dooya_SendCommand
  223. ######################################################
  224. sub Dooya_Runden($) {
  225. my ($v) = @_;
  226. if ( ( $v > 105 ) && ( $v < 195 ) ) {
  227. $v = 150;
  228. } else {
  229. $v = int(($v + 5) /10) * 10;
  230. }
  231. return sprintf("%d", $v );
  232. } # end sub Dooya_Runden
  233. ######################################################
  234. sub Dooya_Translate($) {
  235. my ($v) = @_;
  236. if(exists($translations{$v})) {
  237. $v = $translations{$v}
  238. }
  239. return $v
  240. } # end sub Dooya_Runden
  241. ######################################################
  242. sub Dooya_Parse($$) {
  243. my ($hash, $msg) = @_;
  244. my (undef ,$rawData) = split("#",$msg);
  245. my $hlen = length($rawData);
  246. my $blen = $hlen * 4;
  247. my $bitData = unpack("B$blen", pack("H$hlen", $rawData));
  248. Log3 $hash, 4, "Dooya_Parse: rawData = $rawData length: $hlen";
  249. Log3 $hash, 4, "Dooya_Parse: converted to bits: $bitData";
  250. # get id, channel, cmd
  251. my $id = substr($bitData, 0, 28);
  252. my $BitChannel = substr($bitData, 28, 4); #noch nicht benoetigt
  253. my $channel = oct("0b" . $BitChannel);
  254. my $cmd = substr($bitData, 32, 4);
  255. my $newstate = $codes{ $cmd . $cmd}; # set new state
  256. my $deviceCode = $id . '_' . $channel;
  257. Log3 $hash, 4, "Dooya_Parse: device ID: $id";
  258. Log3 $hash, 4, "Dooya_Parse: Channel: $channel";
  259. Log3 $hash, 4, "Dooya_Parse: Cmd: $cmd Newstate: $newstate";
  260. Log3 $hash, 4, "Dooya_Parse: deviceCode: $deviceCode";
  261. my $def = $modules{Dooya}{defptr}{$deviceCode};
  262. if($def) {
  263. my @list;
  264. foreach my $name (keys %{ $def }) {
  265. my $lh = $def->{$name};
  266. $name = $lh->{NAME}; # It may be renamed
  267. return "" if(IsIgnored($name)); # Little strange.
  268. # update the state and log it
  269. ### NEEDS to be deactivated due to the state being maintained by the timer
  270. # readingsSingleUpdate($lh, "state", $newstate, 1);
  271. readingsSingleUpdate($lh, "parsestate", $newstate, 1);
  272. Log3 $name, 4, "Dooya_Parse $name $newstate";
  273. push(@list, $name);
  274. }
  275. # return list of affected devices
  276. return @list;
  277. } else {
  278. Log3 $hash, 3, "Dooya Unknown device $deviceCode, please define it";
  279. return "UNDEFINED Dooya_$deviceCode Dooya $deviceCode";
  280. }
  281. }
  282. ######################################################
  283. sub Dooya_Attr(@) {
  284. my ($cmd,$name,$aName,$aVal) = @_;
  285. my $hash = $defs{$name};
  286. return "\"Dooya Attr: \" $name does not exist" if (!defined($hash));
  287. # $cmd can be "del" or "set"
  288. # $name is device name
  289. # aName and aVal are Attribute name and value
  290. if ($cmd eq "set") {
  291. if($aName eq 'drive-up-time-to-100') {
  292. return "Dooya_attr: value must be >=0 and <= 100" if($aVal < 0 || $aVal > 100);
  293. } elsif ($aName =~/drive-(down|up)-time-to.*/) {
  294. # check name and value
  295. return "Dooya_attr: value must be >0 and <= 100" if($aVal <= 0 || $aVal > 100);
  296. }
  297. if ($aName eq 'drive-down-time-to-100') {
  298. $attr{$name}{'drive-down-time-to-100'} = $aVal;
  299. $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));
  300. } elsif($aName eq 'drive-down-time-to-close') {
  301. $attr{$name}{'drive-down-time-to-close'} = $aVal;
  302. $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));
  303. } elsif($aName eq 'drive-up-time-to-100') {
  304. $attr{$name}{'drive-up-time-to-100'} = $aVal;
  305. } elsif($aName eq 'drive-up-time-to-open') {
  306. $attr{$name}{'drive-up-time-to-open'} = $aVal;
  307. $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));
  308. }
  309. }
  310. return undef;
  311. }
  312. ##################################################
  313. ### New set (state) method (using internalset)
  314. ###
  315. ### Reimplemented calculations for position readings and state
  316. ### Allowed sets to be done without sending actually commands to the blinds
  317. ### syntax set <name> [ <virtual|send> ] <normal set parameter>
  318. ### position and state are also updated on stop or other commands based on remaining time
  319. ### position is handled between 0 and 100 blinds down but not completely closed and 200 completely closed
  320. ### if timings for 100 and close are equal no position above 100 is used (then 100 == closed)
  321. ### position is rounded to a value of 5 and state is rounded to a value of 10
  322. #
  323. ### General assumption times are rather on the upper limit to reach desired state
  324. # Readings
  325. ## state contains rounded (to 10) position and/or textField
  326. ## position contains rounded position (limited detail)
  327. # STATE
  328. ## might contain position or textual form of the state (same as STATE reading)
  329. ######################################################
  330. # call with hash, name, [virtual/send], set-args (send is default if ommitted)
  331. sub Dooya_Set($@) {
  332. my ( $hash, $name, @args ) = @_;
  333. if ( lc($args[0]) =~m/(virtual|send)/ ) {
  334. Dooya_InternalSet( $hash, $name, @args );
  335. } else {
  336. Dooya_InternalSet( $hash, $name, 'send', @args );
  337. }
  338. }
  339. ######################################################
  340. # call with hash, name, virtual/send, set-args
  341. sub Dooya_InternalSet($@) {
  342. my ( $hash, $name, $mode, @args ) = @_;
  343. ### Check Args
  344. return "Dooya_InternalSet: mode must be virtual or send: $mode " if ( $mode !~m/(virtual|send)/ );
  345. my $numberOfArgs = int(@args);
  346. return "Dooya_set: No set value specified" if ( $numberOfArgs < 1 );
  347. my $cmd = lc($args[0]);
  348. # just a number provided, assume "pos" command
  349. if ($cmd =~ m/\d{1,3}/) {
  350. pop @args;
  351. push @args, "pos";
  352. push @args, $cmd;
  353. $cmd = "pos";
  354. $numberOfArgs = int(@args);
  355. }
  356. if(!exists($sets{$cmd})) {
  357. my @cList;
  358. # overwrite %sets with setList
  359. my $atts = AttrVal($name,'setList',"");
  360. my %setlist = split("[: ][ ]*", $atts);
  361. foreach my $k (sort keys %sets) {
  362. my $opts = undef;
  363. $opts = $sets{$k};
  364. $opts = $setlist{$k} if(exists($setlist{$k}));
  365. if (defined($opts)) {
  366. push(@cList,$k . ':' . $opts);
  367. } else {
  368. push (@cList,$k);
  369. }
  370. } # end foreach
  371. return "Dooya_set: Unknown argument $cmd, choose one of " . join(" ", @cList);
  372. } # error unknown cmd handling
  373. my $arg1 = "";
  374. if ( $numberOfArgs >= 2 ) {
  375. $arg1 = $args[1];
  376. }
  377. return "Dooya_set: Bad time spec" if($cmd =~m/(on|off)-for-timer/ && $numberOfArgs == 2 && $arg1 !~ m/^\d*\.?\d+$/);
  378. # read timing variables
  379. my ($t1down100, $t1downclose, $t1upopen, $t1up100) = Dooya_getTimingValues($hash);
  380. Log3($name,5,"Dooya_set: $name -> timings -> td1:$t1down100: tdc :$t1downclose: tuo :$t1upopen: tu1 :$t1up100: ");
  381. my $model = AttrVal($name,'model',$models{dooyablinds});
  382. if($cmd eq 'pos') {
  383. return "Dooya_set: No pos specification" if(!defined($arg1));
  384. return "Dooya_set: $arg1 must be > 0 and < 100 for pos" if($arg1 < 0 || $arg1 > 100);
  385. return "Dooya_set: Please set attr drive-down-time-to-100, drive-down-time-to-close, etc"
  386. if(!defined($t1downclose) || !defined($t1down100) || !defined($t1upopen) || !defined($t1up100));
  387. }
  388. ### initialize locals
  389. my $drivetime = 0; # timings until halt command to be sent for on/off-for-timer and pos <value> -> move by time
  390. my $updatetime = 0; # timing until update of pos to be done for any unlimited move move to endpos or go-my / stop
  391. my $move = $cmd;
  392. my $newState;
  393. my $updateState;
  394. # get current infos
  395. my $state = $hash->{STATE};
  396. my $pos = ReadingsVal($name,'exact',undef);
  397. if ( !defined($pos) ) {
  398. $pos = ReadingsVal($name,'position',undef);
  399. }
  400. # translate state info to numbers - closed = 200 , open = 0 (correct missing values)
  401. if ( !defined($pos) ) {
  402. if(exists($positions{$state})) {
  403. $pos = $positions{$state};
  404. } else {
  405. $pos = $state;
  406. }
  407. $pos = sprintf( "%d", $pos );
  408. }
  409. Log3($name,4,"Dooya_set: $name -> entering with mode :$mode: cmd :$cmd: arg1 :$arg1: pos :$pos: ");
  410. # check timer running - stop timer if running and update detail pos
  411. # recognize timer running if internal updateState is still set
  412. if ( defined( $hash->{updateState} )) {
  413. # timer is running so timer needs to be stopped and pos needs update
  414. RemoveInternalTimer($hash);
  415. $pos = Dooya_CalcCurrentPos( $hash, $hash->{move}, $pos, Dooya_UpdateStartTime($hash) );
  416. delete $hash->{starttime};
  417. delete $hash->{updateState};
  418. delete $hash->{runningtime};
  419. delete $hash->{runningcmd};
  420. }
  421. ################ No error returns after this point to avoid stopped timer causing confusion...
  422. # calc posRounded
  423. my $posRounded = Dooya_RoundInternal( $pos );
  424. ### handle commands
  425. if(!defined($t1downclose) || !defined($t1down100) || !defined($t1upopen) || !defined($t1up100)) {
  426. #if timings not set
  427. if($cmd eq 'on') {
  428. $newState = 'closed';
  429. # $newState = 'moving';
  430. # $updatetime = $dooya_maxRuntime;
  431. # $updateState = 'closed';
  432. } elsif($cmd eq 'off') {
  433. $newState = 'open';
  434. # $newState = 'moving';
  435. # $updatetime = $dooya_maxRuntime;
  436. # $updateState = 'open';
  437. } elsif($cmd eq 'on-for-timer') {
  438. # elsif cmd == on-for-timer - time x
  439. $move = 'on';
  440. $newState = 'moving';
  441. $drivetime = $arg1;
  442. if ( $drivetime == 0 ) {
  443. $move = 'stop';
  444. } else {
  445. $updateState = 'moving';
  446. }
  447. } elsif($cmd eq 'off-for-timer') {
  448. # elsif cmd == off-for-timer - time x
  449. $move = 'off';
  450. $newState = 'moving';
  451. $drivetime = $arg1;
  452. if ( $drivetime == 0 ) {
  453. $move = 'stop';
  454. } else {
  455. $updateState = 'moving';
  456. }
  457. } elsif($cmd =~m/stop|go-my/) {
  458. $move = 'stop';
  459. $newState = $state
  460. } else {
  461. $newState = $state;
  462. }
  463. ###else (here timing is set)
  464. } else {
  465. # default is roundedPos as new StatePos
  466. $newState = $posRounded;
  467. if($cmd eq 'on') {
  468. if ( $posRounded == 200 ) {
  469. # if pos == 200 - no state pos change / no timer
  470. } elsif ( $posRounded >= 100 ) {
  471. # elsif pos >= 100 - set timer for 100-to-closed --> update timer(newState 200)
  472. my $remTime = ( $t1downclose - $t1down100 ) * ( (200-$pos) / 100 );
  473. $updatetime = $remTime;
  474. $updateState = 200;
  475. } elsif ( $posRounded < 100 ) {
  476. # elseif pos < 100 - set timer for remaining time to 100+time-to-close --> update timer( newState 200)
  477. my $remTime = $t1down100 * ( (100 - $pos) / 100 );
  478. $updatetime = ( $t1downclose - $t1down100 ) + $remTime;
  479. $updateState = 200;
  480. } else {
  481. # else - unknown pos - assume pos 0 set timer for full time --> update timer( newState 200)
  482. $newState = 0;
  483. $updatetime = $t1downclose;
  484. $updateState = 200;
  485. }
  486. } elsif($cmd eq 'off') {
  487. if ( $posRounded == 0 ) {
  488. # if pos == 0 - no state pos change / no timer
  489. } elsif ( $posRounded <= 100 ) {
  490. # elsif pos <= 100 - set timer for remaining time to 0 --> update timer( newState 0 )
  491. my $remTime = ( $t1upopen - $t1up100 ) * ( $pos / 100 );
  492. $updatetime = $remTime;
  493. $updateState = 0;
  494. } elsif ( $posRounded > 100 ) {
  495. # elseif ( pos > 100 ) - set timer for remaining time to 100+time-to-open --> update timer( newState 0 )
  496. my $remTime = $t1up100 * ( ($pos - 100 ) / 100 );
  497. $updatetime = ( $t1upopen - $t1up100 ) + $remTime;
  498. $updateState = 0;
  499. } else {
  500. # else - unknown pos assume pos 200 set time for full time --> update timer( newState 0 )
  501. $newState = 200;
  502. $updatetime = $t1upopen;
  503. $updateState = 0;
  504. }
  505. } elsif($cmd eq 'pos') {
  506. if ( $pos < $arg1 ) {
  507. # if pos < x - set halt timer for remaining time to x / cmd close --> halt timer`( newState x )
  508. $move = 'on';
  509. my $remTime = $t1down100 * ( ( $arg1 - $pos ) / 100 );
  510. $drivetime = $remTime;
  511. $updateState = $arg1;
  512. } elsif ( ( $pos >= $arg1 ) && ( $posRounded <= 100 ) ) {
  513. # elsif pos <= 100 & pos > x - set halt timer for remaining time to x / cmd open --> halt timer ( newState x )
  514. $move = 'off';
  515. my $remTime = ( $t1upopen - $t1up100 ) * ( ( $pos - $arg1) / 100 );
  516. $drivetime = $remTime;
  517. if ( $drivetime == 0 ) {
  518. # $move = 'stop'; # avoid sending stop to move to my-pos
  519. $move = 'none';
  520. } else {
  521. $updateState = $arg1;
  522. }
  523. } elsif ( $pos > 100 ) {
  524. # else if pos > 100 - set timer for remaining time to 100+time for 100-x / cmd open --> halt timer ( newState x )
  525. $move = 'off';
  526. my $remTime = ( $t1upopen - $t1up100 ) * ( ( 100 - $arg1) / 100 );
  527. my $posTime = $t1up100 * ( ( $pos - 100) / 100 );
  528. $drivetime = $remTime + $posTime;
  529. $updateState = $arg1;
  530. } else {
  531. # else - send error (might be changed to first open completely then drive to pos x) / assume open
  532. $newState = 0;
  533. $move = 'on';
  534. my $remTime = $t1down100 * ( ( $arg1 - 0 ) / 100 );
  535. $drivetime = $remTime;
  536. $updateState = $arg1;
  537. ### return "Dooya_set: Pos not currently known please open or close first";
  538. }
  539. } elsif($cmd =~m/stop|go-my/) {
  540. # update pos according to current detail pos
  541. $move = 'stop';
  542. } elsif($cmd eq 'off-for-timer') {
  543. # calcPos at new time y / cmd close --> halt timer ( newState y )
  544. $move = 'off';
  545. $drivetime = $arg1;
  546. if ( $drivetime == 0 ) {
  547. $move = 'stop';
  548. } else {
  549. $updateState = Dooya_CalcCurrentPos( $hash, $move, $pos, $arg1 );
  550. }
  551. } elsif($cmd eq 'on-for-timer') {
  552. # calcPos at new time y / cmd open --> halt timer ( newState y )
  553. $move = 'on';
  554. $drivetime = $arg1;
  555. if ( $drivetime == 0 ) {
  556. $move = 'stop';
  557. } else {
  558. $updateState = Dooya_CalcCurrentPos( $hash, $move, $pos, $arg1 );
  559. }
  560. }
  561. ## special case close is at 100 ("markisen")
  562. if( ( $t1downclose == $t1down100) && ( $t1up100 == 0 ) ) {
  563. if ( defined( $updateState )) {
  564. $updateState = min( 100, $updateState );
  565. }
  566. $newState = min( 100, $posRounded );
  567. }
  568. }
  569. ### update hash / readings
  570. Log3($name,3,"Dooya_set: handled command $cmd --> move :$move: newState :$newState: ");
  571. if ( defined($updateState)) {
  572. Log3($name,5,"Dooya_set: handled for drive/udpate: updateState :$updateState: drivet :$drivetime: updatet :$updatetime: ");
  573. } else {
  574. Log3($name,5,"Dooya_set: handled for drive/udpate: updateState :: drivet :$drivetime: updatet :$updatetime: ");
  575. }
  576. # bulk update should do trigger if virtual mode
  577. Dooya_UpdateState( $hash, $newState, $move, $updateState, ( $mode eq 'virtual' ) );
  578. ### send command
  579. if ( $mode ne 'virtual' ) {
  580. if(exists($sendCommands{$move})) {
  581. $args[0] = $sendCommands{$move};
  582. Dooya_SendCommand($hash,@args);
  583. } elsif ( $move eq 'none' ) {
  584. # do nothing if commmand / move is set to none
  585. } else {
  586. Log3($name,1,"Dooya_set: Error - unknown move for sendCommands: $move");
  587. }
  588. }
  589. ### start timer
  590. if ( $mode eq 'virtual' ) {
  591. # in virtual mode define drivetime as updatetime only, so no commands will be send
  592. if ( $updatetime == 0 ) {
  593. $updatetime = $drivetime;
  594. }
  595. $drivetime = 0;
  596. }
  597. ### update time stamp
  598. Dooya_UpdateStartTime($hash);
  599. $hash->{runningtime} = 0;
  600. if($drivetime > 0) {
  601. $hash->{runningcmd} = 'stop';
  602. $hash->{runningtime} = $drivetime;
  603. } elsif($updatetime > 0) {
  604. $hash->{runningtime} = $updatetime;
  605. }
  606. if($hash->{runningtime} > 0) {
  607. # timer fuer stop starten
  608. if ( defined( $hash->{runningcmd} )) {
  609. Log3($name,4,"Dooya_set: $name -> stopping in $hash->{runningtime} sec");
  610. } else {
  611. Log3($name,4,"Dooya_set: $name -> update state in $hash->{runningtime} sec");
  612. }
  613. my $utime = $hash->{runningtime} ;
  614. if($utime > $dooya_updateFreq) {
  615. $utime = $dooya_updateFreq;
  616. }
  617. InternalTimer(gettimeofday()+$utime,"Dooya_TimedUpdate",$hash,0);
  618. } else {
  619. delete $hash->{runningtime};
  620. delete $hash->{starttime};
  621. }
  622. return undef;
  623. } # end sub Dooya_setFN
  624. ######################################################
  625. ######################################################
  626. ###
  627. ### Helper for set routine
  628. ###
  629. ######################################################
  630. ######################################################
  631. sub Dooya_RoundInternal($) { # Todo: kann das nicht die Round Funktion von FHEM?
  632. my ($v) = @_;
  633. return sprintf("%d", ($v + ($dooya_posAccuracy/2)) / $dooya_posAccuracy) * $dooya_posAccuracy;
  634. } # end sub Dooya_RoundInternal
  635. ######################################################
  636. sub Dooya_UpdateStartTime($) {
  637. my ($d) = @_;
  638. my ($s, $ms) = gettimeofday();
  639. my $t = $s + ($ms / 1000000); # 10 msec
  640. my $t1 = 0;
  641. $t1 = $d->{starttime} if(exists($d->{starttime} ));
  642. $d->{starttime} = $t;
  643. my $dt = sprintf("%.2f", $t - $t1);
  644. return $dt;
  645. } # end sub Dooya_UpdateStartTime
  646. ######################################################
  647. sub Dooya_TimedUpdate($) {
  648. my ($hash) = @_;
  649. Log3($hash->{NAME},4,"Dooya_TimedUpdate");
  650. # get current infos
  651. my $pos = ReadingsVal($hash->{NAME},'exact',undef);
  652. Log3($hash->{NAME},5,"Dooya_TimedUpdate : pos so far : $pos");
  653. my $dt = Dooya_UpdateStartTime($hash);
  654. my $nowt = gettimeofday();
  655. $pos = Dooya_CalcCurrentPos( $hash, $hash->{move}, $pos, $dt );
  656. # my $posRounded = Dooya_RoundInternal( $pos );
  657. Log3($hash->{NAME},5,"Dooya_TimedUpdate : delta time : $dt new rounde pos (rounded): $pos ");
  658. $hash->{runningtime} = $hash->{runningtime} - $dt;
  659. if ( $hash->{runningtime} <= 0.1) {
  660. if ( defined( $hash->{runningcmd} ) ) {
  661. Dooya_SendCommand($hash, $hash->{runningcmd});
  662. }
  663. # trigger update from timer
  664. Dooya_UpdateState( $hash, $hash->{updateState}, 'stop', undef, 1 );
  665. delete $hash->{starttime};
  666. delete $hash->{runningtime};
  667. delete $hash->{runningcmd};
  668. } else {
  669. my $utime = $hash->{runningtime} ;
  670. if($utime > $dooya_updateFreq) {
  671. $utime = $dooya_updateFreq;
  672. }
  673. Dooya_UpdateState( $hash, $pos, $hash->{move}, $hash->{updateState}, 1 );
  674. if ( defined( $hash->{runningcmd} )) {
  675. Log3($hash->{NAME},4,"Dooya_TimedUpdate: $hash->{NAME} -> stopping in $hash->{runningtime} sec");
  676. } else {
  677. Log3($hash->{NAME},4,"Dooya_TimedUpdate: $hash->{NAME} -> update state in $hash->{runningtime} sec");
  678. }
  679. my $nstt = max($nowt+$utime-0.01, gettimeofday()+.1 );
  680. Log3($hash->{NAME},5,"Dooya_TimedUpdate: $hash->{NAME} -> next time to stop: $nstt");
  681. InternalTimer($nstt,"Dooya_TimedUpdate",$hash,0);
  682. }
  683. Log3($hash->{NAME},5,"Dooya_TimedUpdate DONE");
  684. } # end sub Dooya_TimedUpdate
  685. ######################################################
  686. # Dooya_UpdateState( $hash, $newState, $move, $updateState );
  687. sub Dooya_UpdateState($$$$$) {
  688. my ($hash, $newState, $move, $updateState, $doTrigger) = @_;
  689. my $addtlPosReading = AttrVal($hash->{NAME},'additionalPosReading',undef);
  690. if ( defined($addtlPosReading )) {
  691. $addtlPosReading = undef if ( ( $addtlPosReading eq "" ) or ( $addtlPosReading eq "state" ) or ( $addtlPosReading eq "position" ) or ( $addtlPosReading eq "exact" ) );
  692. }
  693. readingsBeginUpdate($hash);
  694. if(exists($positions{$newState})) {
  695. readingsBulkUpdate($hash,"state",$newState);
  696. $hash->{STATE} = $newState;
  697. readingsBulkUpdate($hash,"position",$positions{$newState});
  698. $hash->{position} = $positions{$newState};
  699. readingsBulkUpdate($hash,$addtlPosReading,$positions{$newState}) if ( defined($addtlPosReading) );
  700. } else {
  701. my $rounded = Dooya_Runden( $newState );
  702. my $stateTrans = Dooya_Translate( $rounded );
  703. readingsBulkUpdate($hash,"state",$stateTrans);
  704. $hash->{STATE} = $stateTrans;
  705. readingsBulkUpdate($hash,"position",$rounded);
  706. $hash->{position} = $rounded;
  707. readingsBulkUpdate($hash,$addtlPosReading,$rounded) if ( defined($addtlPosReading) );
  708. }
  709. readingsBulkUpdate($hash,"exact",$newState);
  710. $hash->{exact} = $newState;
  711. if ( defined( $updateState ) ) {
  712. $hash->{updateState} = $updateState;
  713. } else {
  714. delete $hash->{updateState};
  715. }
  716. $hash->{move} = $move;
  717. readingsEndUpdate($hash,$doTrigger);
  718. } # end sub Dooya_UpdateState
  719. ######################################################
  720. # Return timingvalues from attr and after correction
  721. sub Dooya_getTimingValues($) {
  722. my ($hash) = @_;
  723. my $name = $hash->{NAME};
  724. my $t1down100 = AttrVal($name,'drive-down-time-to-100',undef);
  725. my $t1downclose = AttrVal($name,'drive-down-time-to-close',undef);
  726. my $t1upopen = AttrVal($name,'drive-up-time-to-open',undef);
  727. my $t1up100 = AttrVal($name,'drive-up-time-to-100',undef);
  728. return (undef, undef, undef, undef) if(!defined($t1downclose) || !defined($t1down100) || !defined($t1upopen) || !defined($t1up100));
  729. if ( ( $t1downclose < 0 ) || ( $t1down100 < 0 ) || ( $t1upopen < 0 ) || ( $t1up100 < 0 ) ) {
  730. Log3($name,1,"Dooya_getTimingValues: $name time values need to be positive values");
  731. return (undef, undef, undef, undef);
  732. }
  733. if ( $t1downclose < $t1down100 ) {
  734. Log3($name,1,"Dooya_getTimingValues: $name close time needs to be higher or equal than time to pos100");
  735. return (undef, undef, undef, undef);
  736. } elsif ( $t1downclose == $t1down100 ) {
  737. $t1up100 = 0;
  738. }
  739. if ( $t1upopen <= $t1up100 ) {
  740. Log3($name,1,"Dooya_getTimingValues: $name open time needs to be higher or equal than time to pos100");
  741. return (undef, undef, undef, undef);
  742. }
  743. if ( $t1upopen < 1 ) {
  744. Log3($name,1,"Dooya_getTimingValues: $name time to open needs to be at least 1 second");
  745. return (undef, undef, undef, undef);
  746. }
  747. if ( $t1downclose < 1 ) {
  748. Log3($name,1,"Dooya_getTimingValues: $name time to close needs to be at least 1 second");
  749. return (undef, undef, undef, undef);
  750. }
  751. return ($t1down100, $t1downclose, $t1upopen, $t1up100);
  752. }
  753. ######################################################
  754. # call with hash, translated state
  755. sub Dooya_CalcCurrentPos($$$$) {
  756. my ($hash, $move, $pos, $dt) = @_;
  757. my $name = $hash->{NAME};
  758. my $newPos = $pos;
  759. # Attributes for calculation
  760. my ($t1down100, $t1downclose, $t1upopen, $t1up100) = Dooya_getTimingValues($hash);
  761. if(defined($t1down100) && defined($t1downclose) && defined($t1up100) && defined($t1upopen)) {
  762. if( ( $t1downclose == $t1down100) && ( $t1up100 == 0 ) ) {
  763. $pos = min( 100, $pos );
  764. if($move eq 'on') {
  765. $newPos = min( 100, $pos );
  766. if ( $pos < 100 ) {
  767. # calc remaining time to 100%
  768. my $remTime = ( 100 - $pos ) * $t1down100 / 100;
  769. if ( $remTime > $dt ) {
  770. $newPos = $pos + ( $dt * 100 / $t1down100 );
  771. }
  772. }
  773. } elsif($move eq 'off') {
  774. $newPos = max( 0, $pos );
  775. if ( $pos > 0 ) {
  776. $newPos = $dt * 100 / ( $t1upopen );
  777. $newPos = max( 0, ($pos - $newPos) );
  778. }
  779. } else {
  780. Log3($name,1,"Dooya_CalcCurrentPos: $name move wrong $move");
  781. }
  782. } else {
  783. if($move eq 'on') {
  784. if ( $pos >= 100 ) {
  785. $newPos = $dt * 100 / ( $t1downclose - $t1down100 );
  786. $newPos = min( 200, $pos + $newPos );
  787. } else {
  788. # calc remaining time to 100%
  789. my $remTime = ( 100 - $pos ) * $t1down100 / 100;
  790. if ( $remTime > $dt ) {
  791. $newPos = $pos + ( $dt * 100 / $t1down100 );
  792. } else {
  793. $dt = $dt - $remTime;
  794. $newPos = 100 + ( $dt * 100 / ( $t1downclose - $t1down100 ) );
  795. }
  796. }
  797. } elsif($move eq 'off') {
  798. if ( $pos <= 100 ) {
  799. $newPos = $dt * 100 / ( $t1upopen - $t1up100 );
  800. $newPos = max( 0, $pos - $newPos );
  801. } else {
  802. # calc remaining time to 100%
  803. my $remTime = ( $pos - 100 ) * $t1up100 / 100;
  804. if ( $remTime > $dt ) {
  805. $newPos = $pos - ( $dt * 100 / $t1up100 );
  806. } else {
  807. $dt = $dt - $remTime;
  808. $newPos = 100 - ( $dt * 100 / ( $t1upopen - $t1up100 ) );
  809. }
  810. }
  811. } else {
  812. Log3($name,1,"Dooya_CalcCurrentPos: $name move wrong $move");
  813. }
  814. }
  815. } else {
  816. ### no timings set so just assume it is always moving
  817. $newPos = $positions{'moving'};
  818. }
  819. return $newPos;
  820. }
  821. ######################################################
  822. ######################################################
  823. ######################################################
  824. 1;
  825. =pod
  826. =item summary Supports rf shutters from dooya
  827. =item summary_DE Unterst&uumltzt dooya Funkrolladen
  828. =begin html
  829. <a name="Dooya"></a>
  830. <h3>Dooya protocol</h3>
  831. <ul>
  832. The Dooya protocol is used by a wide range of devices,
  833. which are either senders or receivers/actuators.
  834. The RECIVING and SENDING of Dooya commands is implemented in the SIGNALduino, so this module currently supports
  835. devices like blinds and shutters. The Dooya protocol is used from a lot of different shutter companies in Germanyr. Examples are Rohrmotor24 or Nobily.
  836. <br><br>
  837. <pre>
  838. <code>4: sduino/msg READ: MU;P0=4717;P1=-1577;P2=284;P3=-786;P4=649;P5=-423;D=01232345[......]445232;CP=2;</code>
  839. <code>4: sduino: Fingerprint for MU Protocol id 16 -> Dooya shutter matches, trying to demodulate</code>
  840. <code>4: sduino: decoded matched MU Protocol id 16 dmsg u16#370658E133 length 40</code>
  841. <code>4: SIGNALduino_unknown Protocol: 16</code>
  842. <code>4: SIGNALduino_unknown converted to bits: 0011011100000110010110001110000100110011</code>
  843. <code>4: SIGNALduino_unknown / shutter Dooya 0011011100000110010110001110000100110011 received</code>
  844. <code>4: 00110111000001100101100 1110 0001 0011 0011</code>
  845. <code>4: SIGNALduino_unknown found shutter from Dooya. id=3606104, remotetype=14, channel=1, direction=down, all_shutters=false</code>
  846. </pre>
  847. <br> a <a href="#SIGNALduino">SIGNALduino</a> device (must be defined first) <br>
  848. <br>
  849. <br>
  850. <a name="Dooyadefine"></a>
  851. <br>
  852. <b>Define</b>
  853. <br>
  854. <ul>
  855. <code>define &lt;name&gt; Dooya &lt;id&gt;_&lt;channel&gt; </code>
  856. <br>
  857. <br>
  858. The id is a 28-digit binar code, that uniquely identifies a single remote control.
  859. <br>
  860. Pairing is done by setting the shutter in programming mode, either by disconnecting/reconnecting the power,
  861. and by pressing the program button on an already associated remote.
  862. <br>
  863. Once the shutter is in programming mode, send the "prog" command from within FHEM to complete the pairing.
  864. The shutter will peep shortly to indicate completion.
  865. <br>
  866. You are now able to control this blind from FHEM, the receiver thinks it is just another remote control or the real exist remote.
  867. For the shutter it´s the same.
  868. <ul>
  869. <li><code>&lt;id&gt;</code> is a 28 digit binar number that uniquely identifies FHEM as a new remote control.
  870. <br>You can use a different one for each device definition, and group them using a structure. You can use the same ID for a couple of shutters
  871. and you can give every one an other channel. (0 to 15, 0 ist the MASTER and conrols all other channels.)
  872. If you set one of them, you need to pick the same address as an existing remote. You can create the Device with autocreate with a real remote or manuel without remote control.</li>
  873. </ul>
  874. <br>
  875. Examples:
  876. <ul>
  877. <code>define Rollo_Master Dooya 0011011100000110010110001110_0</code><br> Rollo_Master channel 0 controls all shutters (channel 1 - 15) with the same ID, in this case Rollo_1 and Rollo_2 <br>
  878. <br>
  879. <code>define Rollo_1 Dooya 0011011100000110010110001110_1</code><br> Rollo_1 channel 1<br>
  880. <code>define Rollo_2 Dooya 0011011100000110010110101110_2</code><br> Rollo_2 channel 2<br>
  881. </ul>
  882. </ul>
  883. <br>
  884. <a name="Dooyaset"></a>
  885. <b>Set </b>
  886. <ul>
  887. <code>set &lt;name&gt; &lt;value&gt; [&lt;time&gt]</code>
  888. <br><br>
  889. where <code>value</code> is one of:<br>
  890. <pre>
  891. on
  892. off
  893. stop
  894. pos value (0..100) # see note
  895. prog # Special, see note
  896. </pre>
  897. Examples:
  898. <ul>
  899. <code>set rollo_1 on</code><br>
  900. <code>set rollo_1 on,sleep 1,rollo_2 on,sleep 1,rollo_3 on</code><br>
  901. <code>set rollo_1 off</code><br>
  902. <code>set rollo_1 pos 50</code><br>
  903. </ul>
  904. <br>
  905. Notes:
  906. <ul>
  907. <li>prog is a special command used to pair the receiver to FHEM:
  908. Set the receiver in programming mode and send the "prog" command from FHEM to finish pairing.<br>
  909. The shutter will peep shortly to indicate success.
  910. </li>
  911. <li>pos value<br>
  912. The position is variying between 0 completely open and 100 for covering the full window.
  913. The position must be between 0 and 100 and the appropriate
  914. attributes drive-down-time-to-100, drive-down-time-to-close,
  915. drive-up-time-to-100 and drive-up-time-to-open must be set.<br>
  916. </li>
  917. </ul>
  918. The position reading distinuishes between multiple cases
  919. <ul>
  920. <li>Without timing values set only generic values are used for status and position: <pre>open, closed, moving</pre> are used
  921. </li>
  922. <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
  923. the device is considered to only vary between 0 and 100 (100 being completely closed)
  924. </li>
  925. <li>With full timing values set the device is considerd a window shutter (Rolladen) with a difference between
  926. covering the full window (position 100) and being completely closed (position 200)
  927. </li>
  928. </ul>
  929. </ul>
  930. <br>
  931. <b>Get</b>
  932. <ul>N/A</ul><br>
  933. <a name="Dooyaattr"></a>
  934. <b>Attributes</b>
  935. <ul>
  936. <a name="IODev"></a>
  937. <li>IODev<br>
  938. Set the IO or physical device which should be used for sending signals
  939. for this "logical" device. It must be the SIGNALduino.<br>
  940. Note: The IODev has to be set, otherwise no commands will be sent!<br>
  941. </li><br>
  942. <a name="channel"></a>
  943. <li>channel<br>
  944. Set the channel of the remote. You can use 0 (MASTER) to 15.<br>
  945. Note: The MASTER conrols all remotes with the same ID!!!<br>
  946. </li><br>
  947. <a name="SignalRepeats"></a>
  948. <li>SignalRepeats<br>
  949. Set the repeats for sending signal. You can use 5, 10, 15 and 20.
  950. </li><br>
  951. <a name="setList"></a>
  952. <li>setList<br>
  953. Space separated list of commands, which will be returned upon "set name ?",
  954. so the FHEMWEB frontend can construct the correct control and command dropdown. Specific controls can be added after a colon for each command
  955. <br>
  956. </li><br>
  957. <a name="additionalPosReading"></a>
  958. <li>additionalPosReading<br>
  959. Position of the shutter will be stored in the reading <code>pos</code> as numeric value.
  960. Additionally this attribute might specify a name for an additional reading to be updated with the same value than the pos.
  961. </li><br>
  962. <a name="eventMap"></a>
  963. <li>eventMap<br>
  964. Replace event names and set arguments. The value of this attribute
  965. consists of a list of space separated values, each value is a colon
  966. separated pair. The first part specifies the "old" value, the second
  967. the new/desired value. If the first character is slash(/) or comma(,)
  968. then split not by space but by this character, enabling to embed spaces.
  969. Examples:
  970. <ul><code>
  971. attr store eventMap on:open off:closed<br>
  972. attr store eventMap /on-for-timer 10:open/off:closed/<br>
  973. set store open
  974. </code>
  975. </ul>
  976. </li>
  977. <br>
  978. <li><a href="#do_not_notify">do_not_notify</a></li><br>
  979. <a name="attrdummy"></a>
  980. <li>dummy<br>
  981. Set the device attribute dummy to define devices which should not
  982. output any radio signals. Associated notifys will be executed if
  983. the signal is received. Used e.g. to react to a code from a sender, but
  984. it will not emit radio signal if triggered in the web frontend.
  985. </li><br>
  986. <li><a href="#loglevel">loglevel</a></li><br>
  987. <li><a href="#showtime">showtime</a></li><br>
  988. <a name="model"></a>
  989. <a name="ignore"></a>
  990. <li>ignore<br>
  991. Ignore this device, e.g. if it belongs to your neighbour. The device
  992. won't trigger any FileLogs/notifys, issued commands will silently
  993. ignored (no RF signal will be sent out, just like for the <a
  994. href="#attrdummy">dummy</a> attribute). The device won't appear in the
  995. list command (only if it is explicitely asked for it), nor will it
  996. appear in commands which use some wildcard/attribute as name specifiers
  997. (see <a href="#devspec">devspec</a>). You still get them with the
  998. "ignored=1" special devspec.
  999. </li><br>
  1000. <a name="drive-down-time-to-100"></a>
  1001. <li>drive-down-time-to-100<br>
  1002. The time the blind needs to drive down from "open" (pos 0) to pos 100.<br>
  1003. In this position, the lower edge touches the window frame, but it is not completely shut.<br>
  1004. For a mid-size window this time is about 12 to 15 seconds.
  1005. </li><br>
  1006. <a name="drive-down-time-to-close"></a>
  1007. <li>drive-down-time-to-close<br>
  1008. The time the blind needs to drive down from "open" (pos 0) to "close", the end position of the blind.<br>
  1009. This is about 3 to 5 seonds more than the "drive-down-time-to-100" value.
  1010. </li><br>
  1011. <a name="drive-up-time-to-100"></a>
  1012. <li>drive-up-time-to-100<br>
  1013. The time the blind needs to drive up from "close" (endposition) to "pos 100".<br>
  1014. This usually takes about 3 to 5 seconds.
  1015. </li><br>
  1016. <a name="drive-up-time-to-open"></a>
  1017. <li>drive-up-time-to-open<br>
  1018. The time the blind needs drive up from "close" (endposition) to "open" (upper endposition).<br>
  1019. This value is usually a bit higher than "drive-down-time-to-close", due to the blind's weight.
  1020. </li><br>
  1021. </ul>
  1022. <br>
  1023. <a name="Dooyaevents"></a>
  1024. <b>Generated events:</b>
  1025. <ul>
  1026. From a Dooya device you can receive one of the following events.
  1027. <li>on</li>
  1028. <li>off</li>
  1029. <li>stop</li>
  1030. Which event is sent is device dependent and can sometimes be configured on
  1031. the device.
  1032. </ul>
  1033. </ul>
  1034. =end html
  1035. =cut