60_EM.pm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. ##############################################
  2. # $Id: 60_EM.pm 11984 2016-08-19 12:47:50Z rudolfkoenig $
  3. package main;
  4. use strict;
  5. use warnings;
  6. sub EM_Write($$);
  7. sub EmCrc($$);
  8. sub EmCrcCheck($$);
  9. sub EmEsc($);
  10. sub EmGetData($$);
  11. sub EmMakeMsg($);
  12. sub EM_Set($@);
  13. # Following one-byte commands are trange, as they cause a timeout:
  14. # 124 127 150 153 155 156
  15. #####################################
  16. sub
  17. EM_Initialize($)
  18. {
  19. my ($hash) = @_;
  20. # Provider
  21. $hash->{WriteFn} = "EM_Write";
  22. $hash->{Clients} = ":EMWZ:EMEM:EMGZ:";
  23. # Consumer
  24. $hash->{DefFn} = "EM_Define";
  25. $hash->{UndefFn} = "EM_Undef";
  26. $hash->{GetFn} = "EM_Get";
  27. $hash->{SetFn} = "EM_Set";
  28. $hash->{AttrList}= "model:em1010pc dummy:1,0 ";
  29. }
  30. #####################################
  31. sub
  32. EM_Define($$)
  33. {
  34. my ($hash, $def) = @_;
  35. my @a = split("[ \t][ \t]*", $def);
  36. my $po;
  37. $hash->{STATE} = "Initialized";
  38. my $name = $a[0];
  39. my $dev = $a[2];
  40. if($dev eq "none") {
  41. Log3 $name, 1, "EM device is none, commands will be echoed only";
  42. $attr{$name}{dummy} = 1;
  43. return undef;
  44. }
  45. Log3 $name, 3, "EM opening device $dev";
  46. if ( $^O =~ /Win/) {
  47. eval ("use Win32::SerialPort;");
  48. $po = new Win32::SerialPort ($dev);
  49. }else{
  50. eval ("use Device::SerialPort;");
  51. $po = new Device::SerialPort ($dev);
  52. }
  53. return "Can't open $dev: $!" if(!$po);
  54. Log3 $name, 3, "EM opened device $dev";
  55. $po->close();
  56. $hash->{DeviceName} = $dev;
  57. return undef;
  58. }
  59. #####################################
  60. sub
  61. EM_Undef($$)
  62. {
  63. my ($hash, $arg) = @_;
  64. my $name = $hash->{NAME};
  65. foreach my $d (sort keys %defs) {
  66. if(defined($defs{$d}) &&
  67. defined($defs{$d}{IODev}) &&
  68. $defs{$d}{IODev} == $hash)
  69. {
  70. my $lev = ($reread_active ? 4 : 2);
  71. Log3 $name, $lev, "deleting port for $d";
  72. delete $defs{$d}{IODev};
  73. }
  74. }
  75. return undef;
  76. }
  77. #####################################
  78. sub
  79. EM_Set($@)
  80. {
  81. my ($hash, @a) = @_;
  82. my $u1 = "Usage: set <name> reset\n" .
  83. " set <name> time [YYYY-MM-DD HH:MM:SS]";
  84. return $u1 if(int(@a) < 2);
  85. my $name = $hash->{DeviceName};
  86. if($a[1] eq "time") {
  87. if (int(@a) == 2) {
  88. my @lt = localtime;
  89. $a[2] = sprintf ("%04d-%02d-%02d", $lt[5]+1900, $lt[4]+1, $lt[3]);
  90. $a[3] = sprintf ("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
  91. } elsif (int(@a) != 4) {
  92. return $u1;
  93. }
  94. my @d = split("-", $a[2]);
  95. my @t = split(":", $a[3]);
  96. my $msg = sprintf("73%02x%02x%02x00%02x%02x%02x",
  97. $d[2],$d[1],$d[0]-2000+0xd0, $t[0],$t[1],$t[2]);
  98. my $d = EmGetData($name, $msg);
  99. return "Read error" if(!defined($d));
  100. return b($d,0);
  101. } elsif($a[1] eq "reset") {
  102. my $d = EmGetData($name, "4545"); # Reset
  103. return "Read error" if(!defined($d));
  104. sleep(10);
  105. EM_Set($hash, ($a[0], "time")); # Set the time
  106. sleep(1);
  107. $d = EmGetData($name, "67"); # "Push the button", we don't want usesr interaction
  108. return "Read error" if(!defined($d));
  109. } else {
  110. return "Unknown argument $a[1], choose one of reset time"
  111. }
  112. return undef;
  113. }
  114. #########################
  115. sub
  116. b($$)
  117. {
  118. my ($t,$p) = @_;
  119. return -1 if(!defined($t) || length($t) < $p);
  120. return ord(substr($t,$p,1));
  121. }
  122. sub
  123. w($$)
  124. {
  125. my ($t,$p) = @_;
  126. return b($t,$p+1)*256 + b($t,$p);
  127. }
  128. sub
  129. dw($$)
  130. {
  131. my ($t,$p) = @_;
  132. return w($t,$p+2)*65536 + w($t,$p);
  133. }
  134. #####################################
  135. sub
  136. EM_Get($@)
  137. {
  138. my ($hash, @a) = @_;
  139. return "\"get EM\" needs only one parameter" if(@a != 2);
  140. my $v;
  141. if($a[1] eq "time") {
  142. my $d = EmGetData($hash->{DeviceName}, "74");
  143. return "Read error" if(!defined($d));
  144. $v = sprintf "%4d-%02d-%02d %02d:%02d:%02d",
  145. b($d,5)+2006, b($d,4), b($d,3),
  146. b($d,0), b($d,1), b($d,2);
  147. } elsif($a[1] eq "version") {
  148. my $d = EmGetData($hash->{DeviceName},"76");
  149. return "Read error" if(!defined($d));
  150. $v = sprintf "%d.%d", b($d,0), b($d,1);
  151. } else {
  152. return "Unknown argument $a[1], choose one of time,version";
  153. }
  154. $hash->{READINGS}{$a[1]}{VAL} = $v;
  155. $hash->{READINGS}{$a[1]}{TIME} = TimeNow();
  156. return "$a[0] $a[1] => $v";
  157. }
  158. #####################################
  159. sub
  160. EM_Write($$)
  161. {
  162. my ($hash,$msg) = @_;
  163. return EmGetData($hash->{DeviceName}, $msg);
  164. }
  165. #####################################
  166. sub
  167. EmCrc($$)
  168. {
  169. my ($in, $val) = @_;
  170. my ($crc, $bits) = (0, 8);
  171. my $k = (($in >> 8) ^ $val) << 8;
  172. while($bits--) {
  173. if(($crc ^ $k) & 0x8000) {
  174. $crc = ($crc << 1) ^ 0x8005;
  175. } else {
  176. $crc <<= 1;
  177. }
  178. $k <<= 1;
  179. }
  180. return (($in << 8) ^ $crc) & 0xffff;
  181. }
  182. #########################
  183. sub
  184. EmEsc($)
  185. {
  186. my ($b) = @_;
  187. my $out = "";
  188. $out .= chr(0x10) if($b==0x02 || $b==0x03 || $b==0x10);
  189. $out .= chr($b);
  190. }
  191. #####################################
  192. sub
  193. EmCrcCheck($$)
  194. {
  195. my ($otxt, $len) = @_;
  196. my $crc = 0x8c27;
  197. for(my $l = 2; $l < $len+4; $l++) {
  198. my $b = ord(substr($otxt,$l,1));
  199. $crc = EmCrc($crc, 0x10) if($b==0x02 || $b==0x03 || $b==0x10);
  200. $crc = EmCrc($crc, $b);
  201. }
  202. return ($crc == w($otxt, $len+4));
  203. }
  204. #########################
  205. sub
  206. EmMakeMsg($)
  207. {
  208. my ($data) = @_;
  209. my $len = length($data);
  210. $data = chr($len&0xff) . chr(int($len/256)) . $data;
  211. my $out = pack('H*', "0200");
  212. my $crc = 0x8c27;
  213. for(my $l = 0; $l < $len+2; $l++) {
  214. my $b = ord(substr($data,$l,1));
  215. $crc = EmCrc($crc, 0x10) if($b==0x02 || $b==0x03 || $b==0x10);
  216. $crc = EmCrc($crc, $b);
  217. $out .= EmEsc($b);
  218. }
  219. $out .= EmEsc($crc&0xff);
  220. $out .= EmEsc($crc/256);
  221. $out .= chr(0x03);
  222. return $out;
  223. }
  224. #####################################
  225. # This is the only
  226. sub
  227. EmGetData($$)
  228. {
  229. my ($dev, $d) = @_;
  230. $d = EmMakeMsg(pack('H*', $d));
  231. my $serport;
  232. my $rm;
  233. return undef if(!$dev);
  234. #OS depends
  235. if ($^O=~/Win/) {
  236. $serport = new Win32::SerialPort ($dev);
  237. }else{
  238. $serport = new Device::SerialPort ($dev);
  239. }
  240. if(!$serport) {
  241. Log3 undef, 1, "EM: Can't open $dev: $!";
  242. return undef;
  243. }
  244. $serport->reset_error();
  245. $serport->baudrate(38400);
  246. $serport->databits(8);
  247. $serport->parity('none');
  248. $serport->stopbits(1);
  249. $serport->handshake('none');
  250. if ( $^O =~ /Win/ ) {
  251. unless ($serport->write_settings) {
  252. $rm= "EM:Can't change Device_Control_Block: $^E\n";
  253. goto DONE;
  254. }
  255. }
  256. Log3 undef, 4, "EM: Sending " . unpack('H*', $d);
  257. $rm = "EM: timeout reading the answer";
  258. for(my $rep = 0; $rep < 3; $rep++) {
  259. $serport->write($d);
  260. my $retval = "";
  261. my $esc = 0;
  262. my $started = 0;
  263. my $complete = 0;
  264. my $buf;
  265. my $i;
  266. my $b;
  267. for(;;) {
  268. if($^O =~ /Win/) {
  269. #select will not work on windows, replaced with status
  270. my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags)=(0,0,0,0);
  271. for ($i=0;$i<9; $i++) {
  272. sleep(1); #waiiiit
  273. ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags)=$serport->status;
  274. last if $InBytes>0;
  275. }
  276. Log3 undef, 5,"EM: read returned $InBytes Bytes($i trys)";
  277. last if ($InBytes<1);
  278. $buf = $serport->input();
  279. } else {
  280. my ($rout, $rin) = ('', '');
  281. vec($rin, $serport->FILENO, 1) = 1;
  282. my $nfound = select($rout=$rin, undef, undef, 1.0);
  283. if($nfound < 0) {
  284. $rm = "EM Select error $nfound / $!";
  285. goto DONE;
  286. }
  287. last if($nfound == 0);
  288. $buf = $serport->input();
  289. }
  290. if(!defined($buf) || length($buf) == 0) {
  291. $rm = "EM EOF on $dev";
  292. goto DONE;
  293. }
  294. for($i = 0; $i < length($buf); $i++) {
  295. $b = ord(substr($buf,$i,1));
  296. if(!$started && $b != 0x02) { next; }
  297. $started = 1;
  298. if($esc) { $retval .= chr($b); $esc = 0; next; }
  299. if($b == 0x10) { $esc = 1; next; }
  300. $retval .= chr($b);
  301. if($b == 0x03) { $complete = 1; last; }
  302. }
  303. if($complete) {
  304. my $l = length($retval);
  305. if($l < 8) { $rm = "EM Msg too short"; goto DONE; }
  306. if(b($retval,1) != 0) { $rm = "EM Bad second byte"; goto DONE; }
  307. if(w($retval,2) != $l-7) { $rm = "EM Length mismatch"; goto DONE; }
  308. if(!EmCrcCheck($retval,$l-7)) { $rm = "EM Bad CRC"; goto DONE; }
  309. $serport->close();
  310. my $data=substr($retval, 4, $l-7);
  311. Log3 undef, 5,"EM: returned ".unpack("H*",$data);
  312. return $data;
  313. }
  314. }
  315. }
  316. DONE:
  317. Log3 undef, 5,$rm;
  318. $serport->close();
  319. return undef;
  320. }
  321. #########################
  322. # Interpretation is left for the "user";
  323. sub
  324. EmGetDevData($)
  325. {
  326. my ($hash) = @_;
  327. my $dnr = $hash->{DEVNR};
  328. my $d = IOWrite($hash, sprintf("7a%02x", $dnr-1));
  329. return("ERROR: No device no. $dnr present")
  330. if($d eq ((pack('H*',"00") x 45) . pack('H*',"FF") x 6));
  331. my $nrreadings = w($d,2);
  332. return("ERROR: No data to read (yet?)")
  333. if($nrreadings == 0);
  334. my $step = b($d,6);
  335. my $start = b($d,18)+13;
  336. my $end = $start + int(($nrreadings-1)/64)*$step;
  337. my @ret;
  338. my $max;
  339. my $off;
  340. for(my $p = $start; $p <= $end; $p += $step) { # blockwise
  341. $d = IOWrite($hash, sprintf("52%02x%02x00000801", $p%256, int($p/256)));
  342. $max = (($p == $end) ? ($nrreadings%64)*4+4 : 260);
  343. $step = b($d, 6);
  344. for($off = 8; $off <= $max; $off += 4) { # Samples in each block
  345. push(@ret, sprintf("%04x%04x\n", w($d,$off), w($d,$off+2)));
  346. }
  347. }
  348. return @ret;
  349. }
  350. 1;
  351. =pod
  352. =item summary basis module for the ELV EM1010PC
  353. =item summary_DE Anbindung der ELV EM1010PC
  354. =begin html
  355. <a name="EM"></a>
  356. <h3>EM</h3>
  357. <ul>
  358. <a name="EMdefine"></a>
  359. <b>Define</b>
  360. <ul>
  361. <code>define &lt;name&gt; EM &lt;em1010pc-device&gt;</code>
  362. <br><br>
  363. Define a EM1010PC USB device. As the EM1010PC was not designed to be used
  364. with a PC attached to it all the time, it won't transmit received signals
  365. automatically, fhem has to poll it every 5 minutes.<br>
  366. Currently there is no way to read the internal log of the EM1010PC with
  367. fhem, use the program em1010.pl in the contrib directory for this
  368. purpose.<br><br>
  369. Examples:
  370. <ul>
  371. <code>define em EM /dev/elv_em1010pc</code><br>
  372. </ul>
  373. </ul>
  374. <br>
  375. <a name="EMset"></a>
  376. <b>Set</b>
  377. <ul>
  378. <code>set EM &lt;value&gt;</code>
  379. <br><br>
  380. where <code>value</code> is either time or reset.<br>
  381. If time has arguments of the form YYYY-MM-DD HH:MM:SS, then the specified
  382. time will be set, else the time from the host.<br>
  383. Note: after reset you should set the time.
  384. </ul>
  385. <br>
  386. <a name="EMget"></a>
  387. <b>Get</b>
  388. <ul>
  389. <code>get EM &lt;value&gt;</code>
  390. <br><br>
  391. where <code>value</code> is either version or time.
  392. </ul>
  393. <a name="EMattr"></a>
  394. <b>Attributes</b>
  395. <ul>
  396. <li><a href="#model">model</a> (em1010pc)</li>
  397. <li><a href="#attrdummy">dummy</a></li>
  398. </ul>
  399. <br>
  400. </ul>
  401. =end html
  402. =cut