em1010.pl 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. use Device::SerialPort;
  5. sub b($$);
  6. sub w($$);
  7. sub docrc($$);
  8. sub checkcrc($$);
  9. sub getData($);
  10. sub makemsg($);
  11. sub maketime($);
  12. my %cmd = (
  13. "getVersion" => 1,
  14. "getTime" => 1,
  15. "getDevStatus" => 1,
  16. "getDevPage" => 1,
  17. "getDevData" => 1,
  18. "setPrice" => 1,
  19. "setAlarm" => 1,
  20. "setRperKW" => 1,
  21. "get62" => 1,
  22. "setTime" => 1,
  23. "reset" => 1,
  24. );
  25. if(@ARGV < 2) {
  26. printf("Usage: perl em1010.pl serial-device command args\n");
  27. exit(1);
  28. }
  29. my $ser = $ARGV[0];
  30. my $fd;
  31. #####################
  32. # Open serial port
  33. my $serport = new Device::SerialPort ($ser);
  34. die "Can't open $ser: $!\n" if(!$serport);
  35. $serport->reset_error();
  36. $serport->baudrate(38400);
  37. $serport->databits(8);
  38. $serport->parity('none');
  39. $serport->stopbits(1);
  40. $serport->handshake('none');
  41. my $cmd = $ARGV[1];
  42. if(!defined($cmd{$cmd})) {
  43. printf("Unknown command $cmd, use one of " . join(" ",sort keys %cmd) . "\n");
  44. exit(0);
  45. }
  46. ###########################
  47. no strict "refs";
  48. &{$cmd }();
  49. use strict "refs";
  50. exit(0);
  51. #########################
  52. sub
  53. maketime($)
  54. {
  55. my @l = localtime(shift);
  56. return sprintf("%04d-%02d-%02d_%02d:%02d:00",
  57. 1900+$l[5],$l[4]+1,$l[3],$l[2],$l[1]-$l[1]%5);
  58. }
  59. #########################
  60. sub
  61. b($$)
  62. {
  63. my ($t,$p) = @_;
  64. return ord(substr($t,$p,1));
  65. }
  66. #########################
  67. sub
  68. w($$)
  69. {
  70. my ($t,$p) = @_;
  71. return b($t,$p+1)*256 + b($t,$p);
  72. }
  73. #########################
  74. sub
  75. dw($$)
  76. {
  77. my ($t,$p) = @_;
  78. return w($t,$p+2)*65536 + w($t,$p);
  79. }
  80. #########################
  81. sub
  82. docrc($$)
  83. {
  84. my ($in, $val) = @_;
  85. my ($crc, $bits) = (0, 8);
  86. my $k = (($in >> 8) ^ $val) << 8;
  87. while($bits--) {
  88. if(($crc ^ $k) & 0x8000) {
  89. $crc = ($crc << 1) ^ 0x8005;
  90. } else {
  91. $crc <<= 1;
  92. }
  93. $k <<= 1;
  94. }
  95. return (($in << 8) ^ $crc) & 0xffff;
  96. }
  97. #########################
  98. sub
  99. checkcrc($$)
  100. {
  101. my ($otxt, $len) = @_;
  102. my $crc = 0x8c27;
  103. for(my $l = 2; $l < $len+4; $l++) {
  104. my $b = ord(substr($otxt,$l,1));
  105. $crc = docrc($crc, 0x10) if($b==0x02 || $b==0x03 || $b==0x10);
  106. $crc = docrc($crc, $b);
  107. }
  108. return ($crc == w($otxt, $len+4));
  109. }
  110. #########################
  111. sub
  112. esc($)
  113. {
  114. my ($b) = @_;
  115. my $out = "";
  116. $out .= chr(0x10) if($b==0x02 || $b==0x03 || $b==0x10);
  117. $out .= chr($b);
  118. }
  119. #########################
  120. sub
  121. makemsg($)
  122. {
  123. my ($data) = @_;
  124. my $len = length($data);
  125. $data = chr($len&0xff) . chr(int($len/256)) . $data;
  126. my $out = pack('H*', "0200");
  127. my $crc = 0x8c27;
  128. for(my $l = 0; $l < $len+2; $l++) {
  129. my $b = ord(substr($data,$l,1));
  130. $crc = docrc($crc, 0x10) if($b==0x02 || $b==0x03 || $b==0x10);
  131. $crc = docrc($crc, $b);
  132. $out .= esc($b);
  133. }
  134. $out .= esc($crc&0xff);
  135. $out .= esc($crc/256);
  136. $out .= chr(0x03);
  137. return $out;
  138. }
  139. #########################
  140. sub
  141. getData($)
  142. {
  143. my ($d) = @_;
  144. $d = makemsg(pack('H*', $d));
  145. #print "Sending: " . unpack('H*', $d) . "\n";
  146. for(my $rep = 0; $rep < 3; $rep++) {
  147. #printf "write (try nr $rep)\n";
  148. $serport->write($d);
  149. my $retval = "";
  150. my $esc = 0;
  151. my $started = 0;
  152. my $complete = 0;
  153. for(;;) {
  154. my ($rout, $rin) = ('', '');
  155. vec($rin, $serport->FILENO, 1) = 1;
  156. my $nfound = select($rout=$rin, undef, undef, 1.0);
  157. die("Select error $nfound / $!\n") if($nfound < 0);
  158. last if($nfound == 0);
  159. my $buf = $serport->input();
  160. die "EOF on $ser\n" if(!defined($buf) || length($buf) == 0);
  161. for(my $i = 0; $i < length($buf); $i++) {
  162. my $b = ord(substr($buf,$i,1));
  163. if(!$started && $b != 0x02) { next; }
  164. $started = 1;
  165. if($esc) { $retval .= chr($b); $esc = 0; next; }
  166. if($b == 0x10) { $esc = 1; next; }
  167. $retval .= chr($b);
  168. if($b == 0x03) { $complete = 1; last; }
  169. }
  170. if($complete) {
  171. my $l = length($retval);
  172. if($l < 8) { printf("Msg too short\n"); last; }
  173. if(b($retval,1) != 0) { printf("Bad second byte\n"); last; }
  174. if(w($retval,2) != $l-7) { printf("Length mismatch\n"); last; }
  175. if(!checkcrc($retval,$l-7)) { printf("Bad CRC\n"); last; }
  176. return substr($retval, 4, $l-7);
  177. }
  178. }
  179. }
  180. printf "Timeout reading the answer\n";
  181. exit(1);
  182. }
  183. #########################
  184. sub
  185. hexdump($)
  186. {
  187. my ($d) = @_;
  188. for(my $i = 0; $i < length($d); $i += 16) {
  189. my $h = unpack("H*", substr($d, $i, 16));
  190. $h =~ s/(....)/$1 /g;
  191. printf "RAW %-40s\n", $h;
  192. }
  193. }
  194. #########################
  195. sub
  196. getVersion()
  197. {
  198. my $d = getData("76");
  199. printf "%d.%d\n", b($d,0), b($d,1);
  200. }
  201. #########################
  202. sub
  203. getTime()
  204. {
  205. my $d = getData("74");
  206. printf("%4d-%02d-%02d %02d:%02d:%02d\n",
  207. b($d,5)+2006, b($d,4), b($d,3),
  208. b($d,0), b($d,1), b($d,2));
  209. }
  210. #########################
  211. sub
  212. getDevStatus()
  213. {
  214. die "Usage: getDevStatus devicenumber (1-12)\n" if(@ARGV != 3);
  215. my $d = getData(sprintf("7a%02x",$ARGV[2]-1));
  216. if($d eq ((pack('H*',"00") x 45) . pack('H*',"FF") x 6)) {
  217. printf(" No device no. $ARGV[2] present\n");
  218. return;
  219. }
  220. my $pulses=w($d,13);
  221. my $pulses_max=w($d,15);
  222. my $ec=w($d,49) / 10;
  223. my $cur_energy=0;
  224. my $cur_power=0;
  225. my $cur_power_max=0;
  226. my $sum_h_energy=0;
  227. my $sum_d_energy=0;
  228. my $sum_w_energy=0;
  229. my $total_energy=0;
  230. my $iec=0;
  231. printf(" Readings (off 2): %d\n", w($d,2));
  232. printf(" Nr devs (off 6): %d\n", b($d,6));
  233. printf(" puls/5min (off 13): %d\n", $pulses);
  234. printf(" puls.max/5min (off 15): %d\n", $pulses_max);
  235. #printf(" Startblk (off 18): %d\n", b($d,18)+13);
  236. #for (my $lauf = 19; $lauf < 45; $lauf += 2) {
  237. # printf(" t wert (off $lauf): %d\n", w($d,$lauf));
  238. #}
  239. # The data must interpreted depending on the sensor type.
  240. # Currently we use the EC value to quess the sensor type.
  241. if ($ec eq 0) {
  242. # Sensor 5..
  243. $iec = 1000;
  244. $cur_power = $pulses / 100;
  245. $cur_power_max = $pulses_max / 100;
  246. } else {
  247. # Sensor 1..4
  248. $iec = $ec;
  249. $cur_energy = $pulses / $ec; # ec = U/kWh
  250. $cur_power = $cur_energy / 5 * 60; # 5minute interval scaled to 1h
  251. printf(" cur.energy(off ): %.3f kWh\n", $cur_energy);
  252. }
  253. $sum_h_energy= dw($d,33) / $iec; # 33= pulses this hour
  254. $sum_d_energy= dw($d,37) / $iec; # 37= pulses today
  255. $sum_w_energy= dw($d,41) / $iec; # 41= pulses this week
  256. $total_energy= dw($d, 7) / $iec; # 7= pulses total
  257. printf(" cur.power ( ): %.3f kW\n", $cur_power);
  258. printf(" cur.power max ( ): %.3f kW\n", $cur_power_max);
  259. printf(" energy h (off 33): %.3f kWh (h)\n", $sum_h_energy);
  260. printf(" energy d (off 37): %.3f kWh (d)\n", $sum_d_energy);
  261. printf(" energy w (off 41): %.3f kWh (w)\n", $sum_w_energy);
  262. printf(" total energy (off 7): %.3f kWh (total)\n", $total_energy);
  263. printf(" Alarm PA (off 45): %d W\n", w($d,45));
  264. printf(" Price CF (off 47): %0.2f EUR/kWh\n", w($d,47)/10000);
  265. printf(" R/kW EC (off 49): %d\n", $ec);
  266. hexdump($d);
  267. }
  268. #########################
  269. sub
  270. getDevPage()
  271. {
  272. die "Usage: getDevPage pagenumber [length] (default length is 264)\n"
  273. if(@ARGV < 3);
  274. my $l = (@ARGV > 3 ? $ARGV[3] : 264);
  275. my $d = getData(sprintf("52%02x%02x0000%02x%02x",
  276. $ARGV[2]%256, int($ARGV[2]/256), $l%256, int($l/256)));
  277. hexdump($d);
  278. }
  279. #########################
  280. sub
  281. getDevData()
  282. {
  283. my $smooth = 1; # Set this to 0 to get the "real" values
  284. die "Usage: getDevData devicenumber (1-12)\n" if(@ARGV != 3);
  285. my $d = getData(sprintf("7a%02x",$ARGV[2]-1));
  286. if($d eq ((pack('H*',"00") x 45) . pack('H*',"FF") x 6)) {
  287. printf(" No device no. $ARGV[2] present\n");
  288. return;
  289. }
  290. my $nrreadings = w($d,2);
  291. if($nrreadings == 0) {
  292. printf("No data to read (yet?)\n");
  293. exit(0);
  294. }
  295. my $step = b($d,6);
  296. my $start = b($d,18)+13;
  297. my $end = $start + int(($nrreadings-1)/64)*$step;
  298. my $div = w($d,49)/10;
  299. if ($div eq 0) {
  300. $div = 1;
  301. }
  302. #printf("Total $nrreadings, $start - $end, Nr $step\n");
  303. my $tm = time()-(($nrreadings-1)*300);
  304. my $backlog = 0;
  305. for(my $p = $start; $p <= $end; $p += $step) {
  306. #printf("Get page $p\n");
  307. $d = getData(sprintf("52%02x%02x00000801", $p%256, int($p/256)));
  308. #hexdump($d);
  309. my $max = (($p == $end) ? ($nrreadings%64)*4+4 : 260);
  310. my $step = b($d, 7); # Switched from 6 to 7 (Thomas, 2009-12-31)
  311. for(my $off = 8; $off <= $max; $off += 4) {
  312. $backlog++;
  313. if($smooth && (w($d,$off+2) == 0xffff)) { # "smoothing"
  314. next;
  315. } else {
  316. my $v = w($d,$off)*12/$div/$backlog;
  317. my $f1 = b($d,$off+2);
  318. my $f2 = b($d,$off+3);
  319. my $f3 = w($d,$off+2);
  320. while($backlog--) {
  321. printf("%s %0.3f kWh (%d %d %d)\n", maketime($tm), $v,
  322. ($backlog?-1:$f1), ($backlog?-1:$f2), ($backlog?-1:$f3));
  323. $tm += 300;
  324. }
  325. $backlog = 0;
  326. }
  327. }
  328. }
  329. }
  330. sub
  331. setPrice()
  332. {
  333. die "Usage: setPrice device value_in_cent\n"
  334. if(@ARGV != 4);
  335. my $d = $ARGV[2];
  336. my $v = $ARGV[3];
  337. $d = getData(sprintf("79%02x2f02%02x%02x", $d-1, $v%256, int($v/256)));
  338. if(b($d,0) == 6) {
  339. print("OK");
  340. } else {
  341. print("Error occured");
  342. hexdump($d);
  343. }
  344. }
  345. sub
  346. setAlarm()
  347. {
  348. die "Usage: setAlarm device value_in_kWh\n"
  349. if(@ARGV != 4);
  350. my $d = $ARGV[2];
  351. my $v = $ARGV[3];
  352. $d = getData(sprintf("79%02x2d02%02x%02x", $d-1, $v%256, int($v/256)));
  353. if(b($d,0) == 6) {
  354. print("OK");
  355. } else {
  356. print("Error occured");
  357. hexdump($d);
  358. }
  359. }
  360. sub
  361. setRperKW()
  362. {
  363. die "Usage: setRperKW device rotations_per_KW\n"
  364. if(@ARGV != 4);
  365. my $d = $ARGV[2];
  366. my $v = $ARGV[3];
  367. $v = $v * 10;
  368. $d = getData(sprintf("79%02x3102%02x%02x", $d-1, $v%256, int($v/256)));
  369. if(b($d,0) == 6) {
  370. print("OK");
  371. } else {
  372. print("Error occured");
  373. hexdump($d);
  374. }
  375. }
  376. sub
  377. reset()
  378. {
  379. my $d = getData("4545");
  380. hexdump($d);
  381. }
  382. sub
  383. get62()
  384. {
  385. my $d = getData("62");
  386. hexdump($d);
  387. }
  388. sub
  389. setTime()
  390. {
  391. my $a2 = '';
  392. my $a3 = '';
  393. if (@ARGV == 2) {
  394. my @lt = localtime;
  395. $a2 = sprintf ("%04d-%02d-%02d", $lt[5]+1900, $lt[4]+1, $lt[3]);
  396. $a3 = sprintf ("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
  397. } else {
  398. die "Usage: setTime [time] (as YYYY-MM-DD HH:MM:SS, localtime if empty)\n"
  399. if(@ARGV != 4);
  400. $a2 = $ARGV[2];
  401. $a3 = $ARGV[3];
  402. }
  403. my @d = split("-", $a2);
  404. my @t = split(":", $a3);
  405. my $s = sprintf("73%02x%02x%02x00%02x%02x%02x",
  406. $d[2],$d[1],$d[0]-2000+0xd0,
  407. $t[0],$t[1],$t[2]);
  408. print("-> $s\n");
  409. my $d = getData($s);
  410. if(b($d,0) == 6) {
  411. print("OK");
  412. } else {
  413. print("Error occured");
  414. hexdump($d);
  415. }
  416. }