36_EleroStick.pm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. # $Id: 36_EleroStick.pm 14777 2017-07-24 10:48:30Z HCS $
  2. package main;
  3. use strict;
  4. use warnings;
  5. use Time::HiRes qw(gettimeofday);
  6. my $clients = ":EleroDrive:EleroSwitch";
  7. my %matchList = ("1:EleroDrive" => ".*",
  8. "2:EleroSwitch" => ".*");
  9. # Answer Types
  10. my $easy_confirm = "aa044b";
  11. my $easy_ack = "aa054d";
  12. #=======================================================================================
  13. sub EleroStick_Initialize($) {
  14. my ($hash) = @_;
  15. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  16. $hash->{ReadFn} = "EleroStick_Read";
  17. $hash->{WriteFn} = "EleroStick_Write";
  18. $hash->{ReadyFn} = "EleroStick_Ready";
  19. $hash->{DefFn} = "EleroStick_Define";
  20. $hash->{UndefFn} = "EleroStick_Undef";
  21. $hash->{GetFn} = "EleroStick_Get";
  22. $hash->{SetFn} = "EleroStick_Set";
  23. $hash->{AttrFn} = "EleroStick_Attr";
  24. $hash->{FingerprintFn} = "EleroStick_Fingerprint";
  25. $hash->{ShutdownFn} = "EleroStick_Shutdown";
  26. $hash->{AttrList} = "Clients " .
  27. "MatchList " .
  28. "ChannelTimeout " .
  29. "Interval " .
  30. "Delay " .
  31. "DisableTimer:1,0 " .
  32. "SwitchChannels " .
  33. "$readingFnAttributes ";
  34. }
  35. #=======================================================================================
  36. sub EleroStick_Fingerprint($$) {
  37. }
  38. #=======================================================================================
  39. sub EleroStick_Enqueue($$) {
  40. my ($hash, $data) = @_;
  41. my $name = $hash->{NAME};
  42. if(!$hash->{QUEUE}) {
  43. $hash->{QUEUE} = [""];
  44. EleroStick_SimpleWrite($hash, $data);
  45. my $timerName = $name . "#QueueTimer";
  46. my $interval = 0.1;
  47. InternalTimer(gettimeofday() + $interval, "EleroStick_OnQueueTimer", $timerName, 0);
  48. }
  49. else {
  50. push(@{$hash->{QUEUE}}, $data);
  51. }
  52. }
  53. #=======================================================================================
  54. sub EleroStick_StartQueueTimer($) {
  55. my $hash = shift;
  56. my $name = $hash->{NAME};
  57. my $timerName = $name . "#QueueTimer";
  58. my $interval = AttrVal($name, "Delay", 0.5);
  59. InternalTimer(gettimeofday() + $interval, "EleroStick_OnQueueTimer", $timerName, 0);
  60. }
  61. #=======================================================================================
  62. sub EleroStick_OnQueueTimer($) {
  63. my ($timerName) = @_;
  64. my ($name, $suffix) = split("#", $timerName);
  65. my $hash = $defs{$name};
  66. my $queue = $hash->{QUEUE};
  67. if (defined($queue) && @{$queue} > 0) {
  68. my $data = $queue->[0];
  69. if ($data ne "") {
  70. EleroStick_SimpleWrite($hash, $data);
  71. }
  72. shift(@{$queue});
  73. if (@{$queue} == 0) {
  74. delete($hash->{QUEUE});
  75. }
  76. else {
  77. EleroStick_StartQueueTimer($hash);
  78. }
  79. }
  80. }
  81. #=======================================================================================
  82. sub EleroStick_SimpleWrite($$) {
  83. my ($hash, $data) = @_;
  84. DevIo_SimpleWrite($hash, $data, 1);
  85. if(index($data, "aa054c", 0) == 0) {
  86. readingsSingleUpdate($hash, 'SendType', "easy_send", 1);
  87. }
  88. elsif(index($data, "aa044e", 0) == 0) {
  89. readingsSingleUpdate($hash, 'SendType', "easy_info", 1);
  90. }
  91. elsif(index($data, "aa024a", 0) == 0) {
  92. readingsSingleUpdate($hash, 'SendType', "easy_check", 1);
  93. }
  94. readingsSingleUpdate($hash, 'SendMsg', $data, 1);
  95. }
  96. #=======================================================================================
  97. sub EleroStick_Define($$) {
  98. my ( $hash, $def ) = @_;
  99. my @a = split( "[ \t][ \t]*", $def );
  100. my $name = $a[0];
  101. my $type = $a[1];
  102. my $dev = $a[2];
  103. $hash->{USBDev} = $dev;
  104. $hash->{DeviceName} = $dev;
  105. $hash->{NAME} = $name;
  106. $hash->{TYPE} = $type;
  107. $hash->{Clients} = $clients;
  108. $hash->{MatchList} = \%matchList;
  109. DevIo_OpenDev($hash, 0, undef);
  110. EleroStick_SendEasyCheck($hash);
  111. InternalTimer(gettimeofday()+2, "EleroStick_OnTimer", $hash, 0);
  112. return undef;
  113. }
  114. #=======================================================================================
  115. sub EleroStick_Undef($$) {
  116. my ( $hash, $arg ) = @_;
  117. if($hash->{STATE} ne "disconnected") {
  118. DevIo_CloseDev($hash);
  119. }
  120. RemoveInternalTimer($hash);
  121. return undef;
  122. }
  123. #=======================================================================================
  124. sub EleroStick_Shutdown($) {
  125. my ($hash) = @_;
  126. $hash->{channels} = "";
  127. return undef;
  128. }
  129. #=======================================================================================
  130. sub EleroStick_SendEasyCheck($) {
  131. my ($hash) = @_;
  132. my $name = $hash->{NAME};
  133. if($hash->{STATE} ne "disconnected") {
  134. my $head = 'aa';
  135. my $msgLength = '02';
  136. my $msgCmd = '4a';
  137. my $checksumNumber = hex($head) + hex($msgLength) + hex($msgCmd);
  138. my $byteUpperBound = 256;
  139. my $upperBound = $byteUpperBound;
  140. while($checksumNumber > $upperBound){
  141. $upperBound = $upperBound + $byteUpperBound;
  142. }
  143. $checksumNumber = $upperBound - $checksumNumber;
  144. my $checksum = sprintf('%02x', $checksumNumber);
  145. my $byteMsg = $head.$msgLength.$msgCmd.$checksum;
  146. EleroStick_Enqueue($hash, $byteMsg);
  147. }
  148. }
  149. #=======================================================================================
  150. sub EleroStick_SendEasyInfo($$) {
  151. my ($hash, $channel) = @_;
  152. my $name = $hash->{NAME};
  153. if($hash->{STATE} ne "disconnected") {
  154. my $head = 'aa';
  155. my $msgLength = '04';
  156. my $msgCmd = '4e';
  157. my $firstBits = '';
  158. my $secondBits = '';
  159. my $firstChannels = '';
  160. my $secondChannels = '';
  161. if($channel <= 8){
  162. $firstChannels = '00';
  163. $secondChannels = 2**($channel-1);
  164. $secondChannels = sprintf('%02x', $secondChannels);
  165. }
  166. else {
  167. $secondChannels = '00';
  168. $firstChannels = 2**($channel-1-8);
  169. $firstChannels = sprintf('%02x', $firstChannels);
  170. }
  171. my $checksumNumber = hex($head) + hex($msgLength) + hex($msgCmd) + hex($firstChannels) + hex($secondChannels);
  172. my $byteUpperBound = 256;
  173. my $upperBound = $byteUpperBound;
  174. while($checksumNumber > $upperBound){
  175. $upperBound = $upperBound + $byteUpperBound;
  176. }
  177. $checksumNumber = $upperBound - $checksumNumber;
  178. my $checksum = sprintf('%02x', $checksumNumber);
  179. my $byteMsg = $head.$msgLength.$msgCmd.$firstChannels.$secondChannels.$checksum;
  180. EleroStick_Enqueue($hash, $byteMsg);
  181. }
  182. }
  183. #=======================================================================================
  184. sub EleroStick_OnTimer($$) {
  185. my ($hash, @params) = @_;
  186. my $name = $hash->{NAME};
  187. my $timerInterval = AttrVal($name, "ChannelTimeout", 5);
  188. if($hash->{STATE} ne "disconnected" && AttrVal($name, "DisableTimer", 0) ne 1) {
  189. if($hash->{channels}) {
  190. my $channels = $hash->{channels};
  191. if(index($channels, "x") eq -1) {
  192. # We were at the end of the learned channels or lost our position
  193. my $flc = index($channels, "1");
  194. if($flc ne -1) {
  195. substr($channels, $flc, 1, "x");
  196. }
  197. }
  198. my $now = index($channels, "x");
  199. if($now ne -1) {
  200. substr($channels, $now, 1, "1");
  201. EleroStick_SendEasyInfo($hash, $now +1);
  202. for(my $i = $now +1; $i<15; $i++) {
  203. if(substr($channels, $i, 1) eq "1") {
  204. substr($channels,$i,1,"x");
  205. last;
  206. }
  207. }
  208. $hash->{channels} = $channels;
  209. }
  210. # All Channels completed, wait interval seconds
  211. if (index($channels, "x") eq -1) {
  212. $timerInterval = AttrVal($name, "Interval", 60);
  213. }
  214. }
  215. }
  216. InternalTimer(gettimeofday()+$timerInterval, "EleroStick_OnTimer", $hash, 0);
  217. return undef;
  218. }
  219. #=======================================================================================
  220. sub EleroStick_Set($@) {
  221. my ($hash, @a) = @_;
  222. my $name = shift @a;
  223. my $cmd = shift @a;
  224. my $arg = join(" ", @a);
  225. my $list = "parse";
  226. return $list if( $cmd eq '?' || $cmd eq '');
  227. if ($cmd eq "parse") {
  228. $hash->{buffer} = $arg;
  229. EleroStick_Parse($hash);
  230. }
  231. else {
  232. return "Unknown argument $cmd, choose one of ".$list;
  233. }
  234. return undef;
  235. }
  236. #=======================================================================================
  237. sub EleroStick_Get($@) {
  238. return undef;
  239. }
  240. #=======================================================================================
  241. sub EleroStick_Write($$) {
  242. my ($hash, $cmd, $msg) = @_;
  243. my $name = $hash->{NAME};
  244. # Send to the transmitter stick
  245. if($cmd eq 'send'){
  246. EleroStick_Enqueue($hash, $msg);
  247. }
  248. # Request status for a channel
  249. elsif ($cmd eq 'refresh') {
  250. EleroStick_SendEasyInfo($hash, $msg);
  251. }
  252. }
  253. #=======================================================================================
  254. sub EleroStick_Parse($) {
  255. my ($hash) = @_;
  256. my $name = $hash->{NAME};
  257. readingsSingleUpdate($hash,'AnswerMsg', $hash->{buffer},1);
  258. if(index($hash->{buffer}, $easy_confirm, 0) == 0) {
  259. $hash->{lastAnswerType} = "easy_confirm";
  260. my $cc = substr($hash->{buffer},6,4);
  261. my $firstChannels = substr($cc,0,2);
  262. my $secondChannels = substr($cc,2,2);
  263. my $bytes = $firstChannels.$secondChannels ;
  264. $bytes = hex ($bytes);
  265. my $dummy="";
  266. my $learndChannelFound = 0;
  267. for (my $i=0; $i < 15; $i++) {
  268. if($bytes & 1 << $i) {
  269. if(!$learndChannelFound) {
  270. $dummy = $dummy . "x";
  271. $learndChannelFound = 1;
  272. }
  273. else {
  274. $dummy = $dummy . "1";
  275. }
  276. }
  277. else {
  278. $dummy = $dummy . "0";
  279. }
  280. }
  281. $hash->{channels} = $dummy;
  282. }
  283. elsif(index($hash->{buffer}, $easy_ack, 0) == 0) {
  284. $hash->{lastAnswerType} = "easy_ack";
  285. my $buffer = $hash->{buffer};
  286. Dispatch($hash, $buffer, "");
  287. }
  288. readingsSingleUpdate($hash, 'AnswerType', $hash->{lastAnswerType}, 1);
  289. Log3 $name, 4, "Current buffer content: " . $hash->{buffer}." Name ". $hash->{NAME};
  290. }
  291. #=======================================================================================
  292. sub EleroStick_Read($) {
  293. my ($hash) = @_;
  294. my $name = $hash->{NAME};
  295. # read from serial device
  296. my $buf = DevIo_SimpleRead($hash);
  297. return "" if ( !defined($buf) );
  298. # convert to hex string to make parsing with regex easier
  299. my $answer = unpack ('H*', $buf);
  300. if(index($answer, 'aa', 0) == 0){
  301. # New Byte String
  302. $hash->{buffer} = $answer;
  303. }
  304. else{
  305. # Append to Byte String
  306. $hash->{buffer} .= $answer;
  307. }
  308. my $strLen = substr($hash->{buffer},3-1,2);
  309. $strLen = hex($strLen);
  310. my $calLen = ($strLen * 2) + 4;
  311. if($calLen == length($hash->{buffer})){
  312. EleroStick_Parse($hash);
  313. }
  314. else {
  315. # Wait for the rest of the data
  316. Log3 $name, 5, "Current buffer is not long enough ";
  317. }
  318. }
  319. #=======================================================================================
  320. sub EleroStick_Ready($) {
  321. my ($hash) = @_;
  322. my $name = $hash->{NAME};
  323. my $openResult = DevIo_OpenDev($hash, 1, undef);
  324. if($hash->{STATE} eq "disconnected") {
  325. $hash->{channels} = "";
  326. }
  327. else {
  328. EleroStick_SendEasyCheck($hash);
  329. }
  330. return $openResult if($hash->{STATE} eq "disconnected");
  331. # This is relevant for windows/USB only
  332. my $po = $hash->{USBDev};
  333. my ( $BlockingFlags, $InBytes, $OutBytes, $ErrorFlags ) = $po->status;
  334. return ( $InBytes > 0 );
  335. }
  336. #=======================================================================================
  337. sub EleroStick_Attr(@) {
  338. my ($cmd, $name, $aName, $aVal) = @_;
  339. my $hash = $defs{$name};
  340. if($aName eq "Clients") {
  341. $hash->{Clients} = $aVal;
  342. $hash->{Clients} = $clients if( !$hash->{Clients});
  343. }
  344. elsif($aName eq "MatchList") {
  345. my $match_list;
  346. if($cmd eq "set") {
  347. $match_list = eval $aVal;
  348. if( $@ ) {
  349. Log3 $name, 2, $name .": $aVal: ". $@;
  350. }
  351. }
  352. if(ref($match_list) eq 'HASH') {
  353. $hash->{MatchList} = $match_list;
  354. }
  355. else {
  356. $hash->{MatchList} = \%matchList;
  357. }
  358. }
  359. return undef;
  360. }
  361. #=======================================================================================
  362. 1;
  363. =pod
  364. =item summary IO-Device for 36_EleroDrive. Communicates with the Elero-Stick.
  365. =item summary_DE IO-Device für 36_EleroDrive. Kommuniziert mit dem Elero-Stick.
  366. =begin html
  367. <a name="EleroStick"></a>
  368. <h3>EleroStick</h3>
  369. <ul>
  370. This module provides the IODevice for EleroDrive and other future modules that implement Elero components<br>
  371. It handles the communication with an "Elero Transmitter Stick"
  372. <br><br>
  373. <a name="EleroStick_Define"></a>
  374. <b>Define</b>
  375. <ul>
  376. <li>
  377. <code>define &lt;name&gt; EleroStick &lt;port&gt;</code> <br>
  378. &lt;port&gt; specifies the serial port where the transmitter stick is attached.<br>
  379. The name of the serial-device depends on your OS. Example: /dev/ttyUSB1@38400<br>
  380. The baud rate must be 38400 baud.<br><br>
  381. </li>
  382. </ul>
  383. <a name="EleroStick_Set"></a>
  384. <b>Set</b>
  385. <ul>
  386. <li>no sets<br>
  387. </li><br>
  388. </ul>
  389. <a name="EleroStick_Get"></a>
  390. <b>Get</b>
  391. <ul>
  392. <li>no gets<br>
  393. </li><br>
  394. </ul>
  395. <a name="EleroStick_Attr"></a>
  396. <b>Attributes</b>
  397. <ul>
  398. <li>Clients<br>
  399. The received data gets distributed to a client (e.g. EleroDrive, ...) that handles the data.
  400. This attribute tells, which are the clients, that handle the data. If you add a new module to FHEM, that shall handle
  401. data distributed by the EleroStick module, you must add it to the Clients attribute.
  402. </li>
  403. <br>
  404. <li>MatchList<br>
  405. The MatchList defines, which data shall be distributed to which device.<br>
  406. It can be set to a perl expression that returns a hash that is used as the MatchList<br>
  407. Example: <code>attr myElero MatchList {'1:EleroDrive' => '.*'}</code>
  408. </li>
  409. <br>
  410. <li>ChannelTimeout<br>
  411. The delay, how long the modul waits for an answer after sending a command to a drive.<br>
  412. Default is 5 seconds.
  413. </li>
  414. <br>
  415. <li>Delay<br>
  416. If something like structure send commands very fast, Delay (seconds) throttles the transmission down that the Elero-system gets time to handle each command.
  417. </li>
  418. <br>
  419. <li>DisableTimer<br>
  420. Disables the periodically request of the status. Should normally not be set to 1.
  421. </li>
  422. <br>
  423. <li>SwitchChannels<br>
  424. Comma separated list of channels that are a switch device.
  425. </li>
  426. <br>
  427. <li>Interval<br>
  428. When all channels are checkt, this number of seconds will be waited, until the channels will be checked again.<br>
  429. Default is 60 seconds.
  430. </li><br>
  431. </ul>
  432. <a name="EleroStick_Readings"></a>
  433. <b>Readings</b>
  434. <ul>
  435. <li>state<br>
  436. disconnected or opened if a transmitter stick is connected</li>
  437. <li>SendType<br>
  438. Type of the last command sent to the stick</li>
  439. <li>SendMsg<br>
  440. Last command sent to the stick</li>
  441. <li>AnswerType<br>
  442. Type of the last Answer received from the stick</li>
  443. <li>AnswerMsg<br>
  444. Last Answer received from the stick</li>
  445. </ul><br>
  446. </ul>
  447. =end html
  448. =cut