36_EleroStick.pm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. # $Id: 36_EleroStick.pm 13403 2017-02-12 17:32:09Z HCS $
  2. # ToDo-List
  3. # ---------
  4. # [ ] Disable the timer when a Drive asks for information to avoid conflicts
  5. # After getting a message in EleroStick_Write we must interupt the timer
  6. # for "ChannelTimeout" seconds
  7. #
  8. # [ ] Perhaps we need a cache for incoming commands that delays the incoming commands
  9. # for "ChannelTimeout" seconds to give the previous command a chance to hear its acknowledge
  10. #
  11. #
  12. package main;
  13. use strict;
  14. use warnings;
  15. use Time::HiRes qw(gettimeofday);
  16. my $clients = ":EleroDrive";
  17. my %matchList = ("1:EleroDrive" => ".*",);
  18. # Answer Types
  19. my $easy_confirm = "aa044b";
  20. my $easy_ack = "aa054d";
  21. #=======================================================================================
  22. sub EleroStick_Initialize($) {
  23. my ($hash) = @_;
  24. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  25. $hash->{ReadFn} = "EleroStick_Read";
  26. $hash->{WriteFn} = "EleroStick_Write";
  27. $hash->{ReadyFn} = "EleroStick_Ready";
  28. $hash->{DefFn} = "EleroStick_Define";
  29. $hash->{UndefFn} = "EleroStick_Undef";
  30. $hash->{GetFn} = "EleroStick_Get";
  31. $hash->{SetFn} = "EleroStick_Set";
  32. $hash->{AttrFn} = "EleroStick_Attr";
  33. $hash->{FingerprintFn} = "EleroStick_Fingerprint";
  34. $hash->{ShutdownFn} = "EleroStick_Shutdown";
  35. $hash->{AttrList} = "Clients " .
  36. "MatchList " .
  37. "ChannelTimeout " .
  38. "Interval " .
  39. "Delay " .
  40. "DisableTimer:1,0 " .
  41. "$readingFnAttributes ";
  42. }
  43. #=======================================================================================
  44. sub EleroStick_Fingerprint($$) {
  45. }
  46. #=======================================================================================
  47. sub EleroStick_Enqueue($$) {
  48. my ($hash, $data) = @_;
  49. my $name = $hash->{NAME};
  50. if(!$hash->{QUEUE}) {
  51. $hash->{QUEUE} = [""];
  52. ###debugLog($name, "QUEUE created with: $data");
  53. EleroStick_SimpleWrite($hash, $data);
  54. my $timerName = $name . "#QueueTimer";
  55. my $interval = 0.1;
  56. InternalTimer(gettimeofday() + $interval, "EleroStick_OnQueueTimer", $timerName, 0);
  57. }
  58. else {
  59. push(@{$hash->{QUEUE}}, $data);
  60. ###debugLog($name, "Pushed to QUEUE: $data");
  61. }
  62. }
  63. #=======================================================================================
  64. sub EleroStick_StartQueueTimer($) {
  65. my $hash = shift;
  66. my $name = $hash->{NAME};
  67. my $timerName = $name . "#QueueTimer";
  68. my $interval = AttrVal($name, "Delay", 0.5);
  69. InternalTimer(gettimeofday() + $interval, "EleroStick_OnQueueTimer", $timerName, 0);
  70. ####debugLog($name, "Timer started: $timerName");
  71. }
  72. #=======================================================================================
  73. sub EleroStick_OnQueueTimer($) {
  74. my ($timerName) = @_;
  75. my ($name, $suffix) = split("#", $timerName);
  76. my $hash = $defs{$name};
  77. my $queue = $hash->{QUEUE};
  78. ###debugLog($name, "OnQueueTimer");
  79. if (defined($queue) && @{$queue} > 0) {
  80. my $data = $queue->[0];
  81. if ($data ne "") {
  82. EleroStick_SimpleWrite($hash, $data);
  83. ###debugLog($name, "Timer msg=$data");
  84. }
  85. shift(@{$queue});
  86. if (@{$queue} == 0) {
  87. delete($hash->{QUEUE});
  88. }
  89. else {
  90. EleroStick_StartQueueTimer($hash);
  91. }
  92. }
  93. }
  94. #=======================================================================================
  95. sub EleroStick_SimpleWrite($$) {
  96. my ($hash, $data) = @_;
  97. DevIo_SimpleWrite($hash, $data, 1);
  98. if(index($data, "aa054c", 0) == 0) {
  99. readingsSingleUpdate($hash, 'SendType', "easy_send", 1);
  100. }
  101. elsif(index($data, "aa044e", 0) == 0) {
  102. readingsSingleUpdate($hash, 'SendType', "easy_info", 1);
  103. }
  104. elsif(index($data, "aa024a", 0) == 0) {
  105. readingsSingleUpdate($hash, 'SendType', "easy_check", 1);
  106. }
  107. readingsSingleUpdate($hash, 'SendMsg', $data, 1);
  108. }
  109. #=======================================================================================
  110. sub EleroStick_Define($$) {
  111. my ( $hash, $def ) = @_;
  112. my @a = split( "[ \t][ \t]*", $def );
  113. my $name = $a[0];
  114. my $type = $a[1];
  115. my $dev = $a[2];
  116. $hash->{USBDev} = $dev;
  117. $hash->{DeviceName} = $dev;
  118. $hash->{NAME} = $name;
  119. $hash->{TYPE} = $type;
  120. $hash->{Clients} = $clients;
  121. $hash->{MatchList} = \%matchList;
  122. DevIo_OpenDev($hash, 0, undef);
  123. EleroStick_SendEasyCheck($hash);
  124. InternalTimer(gettimeofday()+2, "EleroStick_OnTimer", $hash, 0);
  125. return undef;
  126. }
  127. #=======================================================================================
  128. sub EleroStick_Undef($$) {
  129. my ( $hash, $arg ) = @_;
  130. if($hash->{STATE} ne "disconnected") {
  131. DevIo_CloseDev($hash);
  132. }
  133. RemoveInternalTimer($hash);
  134. return undef;
  135. }
  136. #=======================================================================================
  137. sub EleroStick_Shutdown($) {
  138. my ($hash) = @_;
  139. $hash->{channels} = "";
  140. return undef;
  141. }
  142. #=======================================================================================
  143. sub EleroStick_SendEasyCheck($) {
  144. my ($hash) = @_;
  145. my $name = $hash->{NAME};
  146. if($hash->{STATE} ne "disconnected") {
  147. my $head = 'aa';
  148. my $msgLength = '02';
  149. my $msgCmd = '4a';
  150. my $checksumNumber = hex($head) + hex($msgLength) + hex($msgCmd);
  151. my $byteUpperBound = 256;
  152. my $upperBound = $byteUpperBound;
  153. while($checksumNumber > $upperBound){
  154. $upperBound = $upperBound + $byteUpperBound;
  155. }
  156. $checksumNumber = $upperBound - $checksumNumber;
  157. my $checksum = sprintf('%02x', $checksumNumber);
  158. my $byteMsg = $head.$msgLength.$msgCmd.$checksum;
  159. EleroStick_Enqueue($hash, $byteMsg);
  160. ###EleroStick_SimpleWrite($hash, $byteMsg)
  161. }
  162. }
  163. #=======================================================================================
  164. sub EleroStick_SendEasyInfo($$) {
  165. my ($hash, $channel) = @_;
  166. my $name = $hash->{NAME};
  167. if($hash->{STATE} ne "disconnected") {
  168. my $head = 'aa';
  169. my $msgLength = '04';
  170. my $msgCmd = '4e';
  171. my $firstBits = '';
  172. my $secondBits = '';
  173. my $firstChannels = '';
  174. my $secondChannels = '';
  175. if($channel <= 8){
  176. $firstChannels = '00';
  177. $secondChannels = 2**($channel-1);
  178. $secondChannels = sprintf('%02x', $secondChannels);
  179. }
  180. else {
  181. $secondChannels = '00';
  182. $firstChannels = 2**($channel-1-8);
  183. $firstChannels = sprintf('%02x', $firstChannels);
  184. }
  185. my $checksumNumber = hex($head) + hex($msgLength) + hex($msgCmd) + hex($firstChannels) + hex($secondChannels);
  186. my $byteUpperBound = 256;
  187. my $upperBound = $byteUpperBound;
  188. while($checksumNumber > $upperBound){
  189. $upperBound = $upperBound + $byteUpperBound;
  190. }
  191. $checksumNumber = $upperBound - $checksumNumber;
  192. my $checksum = sprintf('%02x', $checksumNumber);
  193. my $byteMsg = $head.$msgLength.$msgCmd.$firstChannels.$secondChannels.$checksum;
  194. EleroStick_Enqueue($hash, $byteMsg);
  195. }
  196. }
  197. #=======================================================================================
  198. sub EleroStick_OnTimer($$) {
  199. my ($hash, @params) = @_;
  200. my $name = $hash->{NAME};
  201. my $timerInterval = AttrVal($name, "ChannelTimeout", 5);
  202. if($hash->{STATE} ne "disconnected" && AttrVal($name, "DisableTimer", 0) ne 1) {
  203. if($hash->{channels}) {
  204. my $channels = $hash->{channels};
  205. if(index($channels, "x") eq -1) {
  206. # We were at the end of the learned channels or lost our position
  207. my $flc = index($channels, "1");
  208. if($flc ne -1) {
  209. substr($channels, $flc, 1, "x");
  210. }
  211. }
  212. my $now = index($channels, "x");
  213. if($now ne -1) {
  214. substr($channels, $now, 1, "1");
  215. ###debugLog($name, "now " . ($now +1));
  216. EleroStick_SendEasyInfo($hash, $now +1);
  217. for(my $i = $now +1; $i<15; $i++) {
  218. if(substr($channels, $i, 1) eq "1") {
  219. substr($channels,$i,1,"x");
  220. last;
  221. }
  222. }
  223. $hash->{channels} = $channels;
  224. }
  225. # All Channels completed, wait interval seconds
  226. if (index($channels, "x") eq -1) {
  227. $timerInterval = AttrVal($name, "Interval", 60);
  228. }
  229. }
  230. }
  231. InternalTimer(gettimeofday()+$timerInterval, "EleroStick_OnTimer", $hash, 0);
  232. return undef;
  233. }
  234. #=======================================================================================
  235. sub EleroStick_Set($@) {
  236. return undef;
  237. }
  238. #=======================================================================================
  239. sub EleroStick_Get($@) {
  240. return undef;
  241. }
  242. #=======================================================================================
  243. sub EleroStick_Write($$) {
  244. my ($hash, $cmd, $msg) = @_;
  245. my $name = $hash->{NAME};
  246. # Send to the transmitter stick
  247. if($cmd eq 'send'){
  248. ###debugLog($name, "EleroStick send cmd=send msg=$msg");
  249. EleroStick_Enqueue($hash, $msg);
  250. }
  251. # Request status for a channel
  252. elsif ($cmd eq 'refresh') {
  253. ###debugLog($name, "EleroStick cmd=refresh msg=$msg");
  254. EleroStick_SendEasyInfo($hash, $msg);
  255. }
  256. }
  257. #=======================================================================================
  258. sub EleroStick_Read($) {
  259. my ($hash) = @_;
  260. my $name = $hash->{NAME};
  261. # read from serial device
  262. my $buf = DevIo_SimpleRead($hash);
  263. return "" if ( !defined($buf) );
  264. # convert to hex string to make parsing with regex easier
  265. my $answer = unpack ('H*', $buf);
  266. if(index($answer, 'aa', 0) == 0){
  267. # New Byte String
  268. $hash->{buffer} = $answer;
  269. }
  270. else{
  271. # Append to Byte String
  272. $hash->{buffer} .= $answer;
  273. }
  274. my $strLen = substr($hash->{buffer},3-1,2);
  275. $strLen = hex($strLen);
  276. my $calLen = ($strLen * 2) + 4;
  277. if($calLen == length($hash->{buffer})){
  278. # Die Länge der Nachricht entspricht der Vorgabe im Header
  279. readingsSingleUpdate($hash,'AnswerMsg', $hash->{buffer},1);
  280. if(index($hash->{buffer}, $easy_confirm, 0) == 0) {
  281. $hash->{lastAnswerType} = "easy_confirm";
  282. my $cc = substr($hash->{buffer},6,4);
  283. my $firstChannels = substr($cc,0,2);
  284. my $secondChannels = substr($cc,2,2);
  285. my $bytes = $firstChannels.$secondChannels ;
  286. $bytes = hex ($bytes);
  287. my $dummy="";
  288. my $learndChannelFound = 0;
  289. for (my $i=0; $i < 15; $i++) {
  290. if($bytes & 1 << $i) {
  291. if(!$learndChannelFound) {
  292. $dummy = $dummy . "x";
  293. $learndChannelFound = 1;
  294. }
  295. else {
  296. $dummy = $dummy . "1";
  297. }
  298. }
  299. else {
  300. $dummy = $dummy . "0";
  301. }
  302. }
  303. $hash->{channels} = $dummy;
  304. }
  305. elsif(index($hash->{buffer}, $easy_ack, 0) == 0) {
  306. $hash->{lastAnswerType} = "easy_ack";
  307. my $buffer = $hash->{buffer};
  308. Dispatch($hash, $buffer, "");
  309. }
  310. readingsSingleUpdate($hash, 'AnswerType', $hash->{lastAnswerType}, 1);
  311. Log3 $name, 4, "Current buffer content: " . $hash->{buffer}." Name ". $hash->{NAME};
  312. }
  313. else {
  314. # Wait for the rest of the data
  315. Log3 $name, 5, "Current buffer is not long enough ";
  316. }
  317. }
  318. #=======================================================================================
  319. sub EleroStick_Ready($) {
  320. my ($hash) = @_;
  321. my $name = $hash->{NAME};
  322. my $openResult = DevIo_OpenDev($hash, 1, undef);
  323. if($hash->{STATE} eq "disconnected") {
  324. $hash->{channels} = "";
  325. }
  326. else {
  327. EleroStick_SendEasyCheck($hash);
  328. ###debugLog($name, "EleroStick_Ready -> SendEasyInfo");
  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>Interval<br>
  424. When all channels are checkt, this number of seconds will be waited, until the channels will be checked again.<br>
  425. Default is 60 seconds.
  426. </li><br>
  427. </ul>
  428. <a name="EleroStick_Readings"></a>
  429. <b>Readings</b>
  430. <ul>
  431. <li>state<br>
  432. disconnected or opened if a transmitter stick is connected</li>
  433. <li>SendType<br>
  434. Type of the last command sent to the stick</li>
  435. <li>SendMsg<br>
  436. Last command sent to the stick</li>
  437. <li>AnswerType<br>
  438. Type of the last Answer received from the stick</li>
  439. <li>AnswerMsg<br>
  440. Last Answer received from the stick</li>
  441. </ul><br>
  442. </ul>
  443. =end html
  444. =cut