10_UNIRoll.pm 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. ###################LoTT Uniroll###################
  2. # First release by D. Fuchs and rudolfkoenig
  3. # improved by C_Herrmann
  4. # $Id: 10_UNIRoll.pm 12819 2016-12-18 14:27:48Z C_Herrmann $
  5. #
  6. # UNIRoll:no synchronisation, the message protocoll begins directly with datas
  7. # group address 16 Bit like an housecode
  8. # channel address 4 Bit up to 16 devices
  9. # command 4 Bit up: E(1110), stop: D(1101), down: B(1011)
  10. # end off 1 Bit, zero or one it doesnot matter
  11. # whole length 25 Bit
  12. #time intervall:
  13. #Bit-digit 0 high ca. 1,6 ms equal 100(h64) 16us steps
  14. # low ca. 0,576 ms 36(h24)
  15. #Bit-digit 1 high ca. 0,576 ms 36(h24)
  16. # low ca. 1,6 ms 100(h64)
  17. #timespace ca. 100 - 170 ms
  18. #binary : 1010 1011 1100 1101 1110 1
  19. #hexa: a b c d e (an additional one bit)
  20. #the message is sent with the general cul-command: G
  21. #G0031A364242464abcd6e8 : 00 synchbits 3 databytes, 1 databit, HHLLLLHH, data
  22. package main;
  23. use strict;
  24. use warnings;
  25. # Stings für Anfang und Ende des Raw-Kommandos
  26. # Die Zeiten für die Impulslänge wurden anhand meiner 1-Kanal-FB etwas angepasst.
  27. # Sie können durch Veränderung der letzten 4 Hex-Bytes in $rawpre geändert werden.
  28. # siehe auch: http://culfw.de/commandref.html#cmd_G
  29. # Seit der Entwickler-Version 1.58 vom 29.03.2014 gibt es einen UNIRoll-Send-Befehl "U".
  30. my $rawpre_old = "G0036E368232368"; # geänderte Timings und 1 Bit am Ende
  31. # culfw bis einschl. 1.58
  32. my $rawpre = "U"; # Nutzt UNIRoll-Send ab FW 1.58
  33. my $rawpost_old = "80"; # ein 1-Bit senden, culfw bis einschl. 1.58
  34. my $rawpost = ""; # ein 1-Bit wird mit aktueller culfw automatisch gesendt
  35. my $rPos;
  36. my $tm;
  37. my %codes = (
  38. "e" => "up", #1110 e
  39. "d" => "stop", #1101 d
  40. "b" => "down", #1011 b
  41. "a" => "pos", # Pseudobefehl: gezielt eine Position anfahren
  42. );
  43. use vars qw(%UNIRoll_c2b); # Peter would like to access it from outside
  44. my $UNIRoll_simple = "up stop down pos";
  45. my %models = (
  46. R_23700 => 'simple',
  47. dummySimple => 'simple',
  48. );
  49. #############################
  50. sub
  51. UNIRoll_Initialize($)
  52. {
  53. my ($hash) = @_;
  54. foreach my $k (keys %codes) {
  55. $UNIRoll_c2b{$codes{$k}} = $k; # c2b liest das allgmeine Array der Gerätegruppe
  56. }
  57. # print "UNIRoll_Initialize \n";
  58. $hash->{Match} = "^(G|U).*";
  59. $hash->{SetFn} = "UNIRoll_Set";
  60. # $hash->{StateFn} = "UNIRoll_SetState";
  61. $hash->{DefFn} = "UNIRoll_Define";
  62. $hash->{UndefFn} = "UNIRoll_Undef";
  63. $hash->{ParseFn} = "UNIRoll_Parse";
  64. $hash->{AttrFn} = "UNIRoll_Attr";
  65. $hash->{AttrList} = "IODev do_not_notify:1,0 ".
  66. "ignore:1,0 showtime:1,0 ".
  67. "rMin:slider,0,1,120 rMax:slider,0,1,120 ".
  68. "rPos:slider,0,1,120 useRolloPos:1,0 " .
  69. "sendStopBeforeCmd:1,0,2,3 " .
  70. "model:".join(",", sort keys %models);
  71. }
  72. ## Neues Attribut sendStopBeforeCmd hinzugefügt. Default ist 1 - Stop wird gesendet.
  73. ## Bei 0 wird kein Stop-Befehl vor dem auf/ab-Befehl gesendet.
  74. ## Bei 2 wird Stop nur vor "auf" und bei 3 nur vor "ab" gesendet.
  75. ## Hier ging der auf-Befehl immer zuverlässig. Ab funktionierte
  76. ## nur sporadisch, insbesondere wenn es von einem "at" gesendet wurde.
  77. #####################################
  78. # sub
  79. # UNIRoll_SetState($$$$) # 4 Skalare Parameter
  80. # {
  81. # return undef;
  82. # }
  83. ###################################
  84. sub
  85. UNIRoll_Set($@)
  86. {
  87. my ($hash, @a) = @_; # Eingabewerte nach define name typ devicecode channelcode
  88. my $ret = undef;
  89. my $na = int(@a); #na Anzahl Felder in a
  90. # print "UNIRoll_Set \n";
  91. return "no set value specified" if($na < 2 || $na > 3);
  92. my $c = $UNIRoll_c2b{$a[1]}; # Wert des Kommandos: up stop down pos
  93. my $name = $a[0]; # Gerätename
  94. $tm = 0 if(defined($c)); # optionaler Zeitwert
  95. if($na == 3) {
  96. $tm = $a[2];
  97. return "Argument for <time> must be a number" if($tm !~ m/^\d*\.?\d*$/);
  98. }
  99. my $tPos = $tm;
  100. if(!defined($c)) {
  101. # Model specific set arguments
  102. my $mt = AttrVal($name, "model", undef);
  103. return "Unknown argument $a[1], choose one of $UNIRoll_simple"
  104. if($mt && $mt eq "simple");
  105. return "Unknown argument $a[1], choose one of " .
  106. join(" ", sort keys %UNIRoll_c2b);
  107. }
  108. # RolloPos ausführen, wenn aktiviert
  109. if(AttrVal($name, "useRolloPos", "0") eq "1") {
  110. ($ret, $c, $tPos) = UNIRoll_RolloPos($hash, $name, $c, $tPos, $a[1]);
  111. return $ret if(defined($ret) || !defined($c));
  112. } else {
  113. return "Please set useRolloPos to 1 to use pos commands with $name." if($c eq "a");
  114. }
  115. my $v = join(" ", @a);
  116. Log3 $name, 3, "UNIRoll set $v";
  117. (undef, $v) = split(" ", $v, 2); # Not interested in the name...
  118. # CUL-Kommandos ermitteln und Sendestrings anpassen
  119. my $culcmds = $hash->{IODev}->{CMDS}; # BCFiAZEGMKURTVWXefmltux
  120. if(!defined($culcmds) || $culcmds !~ m/U/) {
  121. $rawpre = $rawpre_old;
  122. $rawpost = $rawpost_old;
  123. }
  124. # G0030A364242464abcd6e8 : 00 Synchbits 3 Datenbytes, 1 Datenbit, HHLLLLHH, Daten
  125. # Damit kein Befehl einen zufälligen Betrieb stoppt
  126. # vorher ein gezielter Stopp Befehl
  127. # Abschaltbar mit sendStopBeforeCmd 0, 2, 3
  128. my $stop = "d";
  129. my $sendstop = AttrVal($name, "sendStopBeforeCmd", "1");
  130. if($sendstop eq "1" || ($sendstop eq "2" && $c eq "e") || ($sendstop eq "3" && $c eq "b") || $c eq $stop) {
  131. IOWrite($hash, "",$rawpre.$hash->{XMIT}.$hash->{BTN}.$stop.$rawpost);
  132. sleep(0.1);
  133. }
  134. IOWrite($hash, "",$rawpre.$hash->{XMIT}.$hash->{BTN}.$c.$rawpost) if($c ne $stop); # Auf-/Ab-Befehl ausführen
  135. # XMIT: Gerätegruppe, BTN: Kanalnummer, c: Commando
  136. # Zeit für up/down pausieren, dann Stop-Befehl ausführen und Reading aktualisieren
  137. InternalTimer(gettimeofday()+$tPos,"UNIRoll_Timer",$hash,0) if($c ne $stop);
  138. sleep(0.3);
  139. ##########################
  140. # Look for all devices with the same code, and set state, timestamp
  141. my $code = "$hash->{XMIT} $hash->{BTN}";
  142. my $tn = TimeNow();
  143. my $defptr = $modules{UNIRoll}{defptr};
  144. foreach my $n (keys %{ $defptr->{$code} }) {
  145. my $lh = $defptr->{$code}{$n};
  146. $lh->{CHANGED}[0] = $v;
  147. $lh->{STATE} = $v;
  148. $lh->{READINGS}{state}{TIME} = $tn;
  149. $lh->{READINGS}{state}{VAL} = $v;
  150. my $lhname = $lh->{NAME};
  151. if($name ne $lhname) {
  152. DoTrigger($lhname, undef);
  153. }
  154. }
  155. return $ret;
  156. }
  157. #############################
  158. sub
  159. UNIRoll_Define($$)
  160. # Gerät anmelden hash: Hash-adresse, def: Eingabe bei define .....
  161. # Hauscode, Kanalnummer aufbereiten prüfen
  162. {
  163. my ($hash, $def) = @_;
  164. # my $name = $a[0];
  165. my @a = split("[ \t][ \t]*", $def);
  166. my $u = "wrong syntax: define <name> UNIRoll device adress " .
  167. "addr [fg addr] [lm addr] [gm FF]";
  168. # print "UNIRoll_Define \n";
  169. return $u if(int(@a) < 4);
  170. return "Define $a[0]: wrong device address format: specify a 4 digit hex value "
  171. if( ($a[2] !~ m/^[a-f0-9]{4}$/i) );
  172. return "Define $a[0]: wrong chanal format: specify a 1 digit hex value "
  173. if( ($a[3] !~ m/^[a-f0-9]{1}$/i) );
  174. my $devcode = $a[2];
  175. my $chacode = $a[3];
  176. $hash->{XMIT} = lc($devcode); # hex Kleinschreibung ?
  177. $hash->{BTN} = lc($chacode);
  178. # Gerätedaten aufbauen,
  179. # defptr: device pointer global
  180. my $code = lc("$devcode $chacode"); #lc lowercase Kleinschreibung
  181. my $ncode = 1; #?
  182. my $name = $a[0]; #Gerätename
  183. $hash->{CODE}{$ncode++} = $code;
  184. $modules{UNIRoll}{defptr}{$code}{$name} = $hash;
  185. # print "Test IoPort $hash def $def code $code.\n";
  186. $attr{$name}{"webCmd"} = "up:stop:down";
  187. AssignIoPort($hash); # Gerät anmelden
  188. }
  189. #############################
  190. sub
  191. UNIRoll_Undef($$)
  192. {
  193. my ($hash, $name) = @_;
  194. foreach my $c (keys %{ $hash->{CODE} } ) {
  195. $c = $hash->{CODE}{$c};
  196. # print "UNIRoll_Undef \n";
  197. # As after a rename the $name my be different from the $defptr{$c}{$n}
  198. # we look for the hash.
  199. foreach my $dname (keys %{ $modules{UNIRoll}{defptr}{$c} }) {
  200. delete($modules{UNIRoll}{defptr}{$c}{$dname})
  201. if($modules{UNIRoll}{defptr}{$c}{$dname} == $hash);
  202. }
  203. }
  204. return undef;
  205. }
  206. #############################
  207. sub
  208. UNIRoll_Parse($$)
  209. {
  210. # print "UNIRoll_Parse \n";
  211. }
  212. #############################
  213. sub
  214. UNIRoll_Attr(@)
  215. {
  216. return if(!$init_done); # AttrFn erst nach Initialisierung ausführen
  217. my ($cmd,$name,$aName,$aVal) = @_;
  218. if($aName eq "useRolloPos") {
  219. if(defined($aVal) && $aVal == 1) {
  220. my $st = ReadingsVal($name, "state", "");
  221. return "Please set $name to the topmost position before activating RolloPos!"
  222. if($st ne "up");
  223. CommandSetReading(undef, "$name oldstate $st 0");
  224. CommandSetReading(undef, "$name oldPos 0");
  225. $attr{$name}{"useRolloPos"} = "1";
  226. $attr{$name}{"rPos"} = 0;
  227. $attr{$name}{"rMin"} = 0 if(!defined(AttrVal($name, "rMin", undef)));
  228. $attr{$name}{"rMax"} = 0 if(!defined(AttrVal($name, "rMax", undef)));
  229. return "Please set time for min and max position for $name!" if($attr{$name}{"rMax"} eq "0");
  230. } else {
  231. $attr{$name}{"useRolloPos"} = "0";
  232. CommandDeleteReading(undef, "$name old.*");
  233. }
  234. }
  235. return "This attribute cannot be deletet if useRolloPos is activated"
  236. if(($aName eq "rMax" || $aName eq "rMin") && $cmd eq "del" && AttrVal($name, "useRolloPos", undef) == 1);
  237. return "This attribute is read-only and must not be changed!"
  238. if($aName eq "rPos" && AttrVal($name,"useRolloPos","") eq "1");
  239. }
  240. #############################
  241. sub
  242. UNIRoll_Timer($)
  243. {
  244. my $hash = shift;
  245. my $stop = "d";
  246. IOWrite($hash, "",$rawpre.$hash->{XMIT}.$hash->{BTN}.$stop.$rawpost) if($tm ne "0");
  247. readingsSingleUpdate($hash, "oldPos", $rPos, 1);
  248. }
  249. #############################
  250. sub
  251. UNIRoll_RolloPos($$$$$)
  252. {
  253. # RolloPos - Position Speichern und Positionsbefehle in up/down umwandeln
  254. # Variablen einlesen
  255. my($hash, $name, $c, $tPos, $nstate) = @_;
  256. my $ret;
  257. my $rMax = AttrVal($name, "rMax", "0");
  258. my $rMin = AttrVal($name, "rMin", "0");
  259. return ("Please check rMin and rMax values in attributes", undef, undef) if ($rMax eq "0" || $rMax <= $rMin);
  260. $rPos = AttrVal($name, "rPos", "0");
  261. my $oldPos = ReadingsVal($name, "oldPos", "0");
  262. # Zeit und Fahrtrichtung des letzten Befehls ermitteln, falls neuer Befehl vor
  263. # Beendigung der letzten Fahrt abgesetzt wurde, nur Stop zulassen.
  264. # rPos entsprechend anpassen!
  265. my $tdiff = int(gettimeofday()) - time_str2num(ReadingsTimestamp($name,"state","")); # Zeit seit letztem Befehl in Sekunden
  266. my ($lastcmd, $lasttime) = split(" ", ReadingsVal($name,"oldstate","")); # letzter Befehl z.B. down 9
  267. if(!defined($lastcmd)) {
  268. my $nst = ReadingsVal($name, "state", "0");
  269. $lasttime = 0;
  270. readingsSingleUpdate($hash, "oldstate", "$nst 0", 1 );
  271. }
  272. if($lasttime > $tdiff) { # wenn letzter Befehl noch nicht abgeschlossen
  273. return (undef, undef, undef) if($c ne "d"); # wenn kein Stop -> return
  274. RemoveInternalTimer($hash);
  275. $rPos = $oldPos + $tdiff if($lastcmd eq "down");
  276. $rPos = $oldPos - $tdiff if($lastcmd eq "up");
  277. $oldPos = $rPos;
  278. goto DOCMD;
  279. }
  280. # Befehl ohne Zeitangabe
  281. if($tm == "0") {
  282. if($c eq "b") { # ab
  283. $tPos = $rMax - $rPos;
  284. $rPos = $rMax;
  285. } elsif($c eq "e") { # auf
  286. $tPos = $rPos - $rMin;
  287. $rPos = $rMin;
  288. }
  289. goto DOCMD;
  290. }
  291. # Befehl mit Zeitangabe
  292. if($c eq "b") { # ab
  293. return (undef, undef, undef) if($rPos >= $rMax);
  294. if($tm >= $rMax - $rPos) {
  295. $tPos = $rMax - $rPos;
  296. $rPos = $rMax;
  297. $tm = "0";
  298. } else {
  299. $rPos = $rPos + $tm;
  300. }
  301. } elsif($c eq "e") { # auf
  302. return (undef, undef, undef) if($rPos <= $rMin);
  303. if($tm > $rPos) {
  304. $tPos = $rPos - $rMin;
  305. $rPos = $rMin;
  306. $tm = "0";
  307. } else {
  308. $rPos = $rPos - $tm;
  309. }
  310. } elsif($c eq "a") { # pos
  311. return (undef, undef, undef) if($rPos eq $tm);
  312. return ("Invalid position $tm for $name. Maximum value is $rMax.", undef, undef) if($tm > $rMax);
  313. if($rPos > $tm) { # neue Position kleiner
  314. $c = "e";
  315. $tPos = $rPos - $tm;
  316. } elsif($rPos < $tm) { # neue Position größer
  317. $c = "b";
  318. $tPos = $tm - $rPos;
  319. }
  320. $rPos = $tm;
  321. $tm = $tPos;
  322. }
  323. DOCMD:
  324. $attr{$name}{"rPos"} = $rPos;
  325. # my $nstate = $a[1];
  326. $nstate = "down" if($c eq "b");
  327. $nstate = "up" if($c eq "e");
  328. $nstate = "$nstate $tPos";
  329. ### state ändern!
  330. readingsBeginUpdate($hash);
  331. readingsBulkUpdate($hash, "state", $nstate, 1 );
  332. readingsBulkUpdate($hash, "oldPos", $oldPos, 1 );
  333. readingsBulkUpdate($hash, "oldstate", $nstate, 1 );
  334. readingsEndUpdate($hash, 1);
  335. return (undef, $c, $tPos);
  336. }
  337. # Ende RolloPos
  338. 1;
  339. =pod
  340. =item device
  341. =item summary controls UNIRoll devices with wireless extension
  342. =item summary_DE Steuert UNIRoll-Gurtwickler mit Fernbedienungs-Modul
  343. =begin html_DE
  344. <a name="UNIRoll"></a>
  345. <h3>UNIRoll</h3>
  346. Deutsche Version der Doku nicht vorhanden. Englische Version unter
  347. <a href='http://fhem.de/commandref.html#<UNIRoll>'>UNIRoll</a> &nbsp;
  348. =end html_DE
  349. =begin html
  350. <a name="UNIRoll"></a>
  351. <h3>UNIRoll</h3>
  352. <ul>
  353. The protocol is used by the Lott UNIROLL R-23700 reciever. The radio
  354. (868.35 MHz) messages are either received through an <a href="#FHZ">FHZ</a>
  355. or an <a href="#CUL">CUL</a> device, so this must be defined first.
  356. Recieving sender messages is not integrated jet.
  357. The CUL has to allow working with zero synchbits at the beginning of a raw-message.
  358. This is possible with culfw 1.49 or higher.
  359. <br><br>
  360. <a name="UNIRolldefine"></a>
  361. <b>Define</b>
  362. <ul>
  363. <code>define &lt;name&gt; UNIRoll &lt;devicegroup&gt; &lt;deviceaddress&gt; </code>
  364. <br><br>
  365. The values of devicegroup address (similar to the housecode) and device address (button)
  366. has to be defined as hexadecimal value.
  367. There is no master or group code integrated.
  368. <br>
  369. <ul>
  370. <li><code>&lt;devicecode&gt;</code> is a 4 digit hex number,
  371. corresponding to the housecode address.</li>
  372. <li><code>&lt;channel&gt;</code> is a 1 digit hex number,
  373. corresponding to a button of the transmitter.</li>
  374. </ul>
  375. <br>
  376. Example:
  377. <ul>
  378. <code>define roll UNIRoll 7777 0</code><br>
  379. </ul>
  380. </ul>
  381. <br>
  382. <a name="UNIRollset"></a>
  383. <b>Set </b>
  384. <ul>
  385. <code>set &lt;name&gt; &lt;value&gt; [&lt;time&gt]</code>
  386. <br><br>
  387. where <code>value</code> is one of:<br>
  388. <pre>
  389. up
  390. stop
  391. down
  392. pos (The attribute useRolloPos has to be set to 1 to use this.)
  393. [&lt;time&gt] in seconds for up, down or pos
  394. </pre>
  395. Examples:
  396. <ul>
  397. <code>set roll up</code><br>
  398. <code>set roll up 10</code><br>
  399. <code>set roll1,roll2,roll3 up</code><br>
  400. <code>set roll1-roll3 up</code><br>
  401. </ul>
  402. <br></ul>
  403. <b>Get</b> <ul>N/A</ul><br>
  404. <a name="UNIRollattr"></a>
  405. <b>Attributes</b>
  406. <ul>
  407. <a name="IODev"></a>
  408. <li>IODev<br>
  409. Set the IO or physical device which should be used for sending signals
  410. for this "logical" device. An example for the physical device is an FHZ
  411. or a CUL. The device will not work without this entry.</li><br>
  412. <a name="eventMap"></a>
  413. <li>eventMap<br>
  414. Replace event names and set arguments. The value of this attribute
  415. consists of a list of space separated values, each value is a colon
  416. separated pair. The first part specifies the "old" value, the second
  417. the new/desired value. If the first character is slash(/) or komma(,)
  418. then split not by space but by this character, enabling to embed spaces.<br><br>
  419. Examples:<ul><code>
  420. attr device eventMap up:open down:closed<br>
  421. set device open
  422. </code></ul>
  423. </li><br>
  424. <li><a href="#showtime">showtime</a></li><br>
  425. <a name="sendStopBeforeCmd"></a>
  426. <li>sendStopBeforeCmd &lt;value&gt;<br>
  427. Before any up/down-command a stop-command will be sent to stop a random
  428. operation. This might cause failure in some situations. This attribute
  429. can be used to switch off the stop-command by setting it to these values.<br><br>
  430. where <code>value</code> is one of:<br>
  431. <pre>
  432. 1 - send always stop (default)
  433. 0 - send no stop
  434. 2 - send stop only before up
  435. 3 - send stop only before down
  436. </pre></li>
  437. <a name="useRolloPos"></a>
  438. <li>useRolloPos &lt;value&gt;<br>
  439. The position of each device can be stored. By this it is possible to move from
  440. any position to any other position. As this feature is software-based, a
  441. manual operation will not be recognized. To set the device into a definite
  442. state, a up or down command will reset the counter for the position.<br><br>
  443. where <code>value</code> is one of:<br>
  444. <pre>
  445. 1 - RolloPos will be used
  446. 0 - RolloPos is not used (default)
  447. </pre><br>
  448. These attributes will be created automatical if useRolloPos is set to 1.
  449. They will not be deleted, if the value is set to 0 or the attribut is deleted.
  450. <pre>
  451. rMin - Time in seconds for the topmost position
  452. rMax - Time in seconds until the device is fully closed
  453. rPos - This is an internal value and must not be changed!
  454. </pre></li>
  455. <a name="model"></a>
  456. <li>model<br>
  457. The model attribute denotes the model type of the device.
  458. The attributes will (currently) not be used by the fhem.pl directly.
  459. It can be used by e.g. external programs or web interfaces to
  460. distinguish classes of devices and send the appropriate commands.
  461. The spelling of the model names are as quoted on the printed
  462. documentation which comes which each device. This name is used
  463. without blanks in all lower-case letters. Valid characters should be
  464. <code>a-z 0-9</code> and <code>-</code> (dash),
  465. other characters should be ommited. Here is a list of "official"
  466. devices:<br><br>
  467. <b>Receiver/Actor</b>: there is only one reciever: R_23700
  468. </li><br>
  469. </ul>
  470. <br>
  471. </ul>