98_WKRCD4.pm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. #########################################################################
  2. # $Id: 98_WKRCD4.pm 10632 2016-01-25 18:55:16Z ststrobel $
  3. # fhem Modul für Waterkotte Wärmepumpe mit Resümat CD4 Steuerung
  4. # Vorlage: Modul WHR962, diverse Foreneinträge sowie Artikel über Auswertung der
  5. # Wärmepumpe mit Linux / Perl im Linux Magazin aus 2010
  6. # insbesondere:
  7. # http://www.haustechnikdialog.de/Forum/t/6144/Waterkotte-5017-3-an-den-Computer-anschliessen?page=2 (Speicheradressen-Liste)
  8. # http://www.ip-symcon.de/forum/threads/2092-ComPort-und-Waterkotte-abfragen (Protokollbeschreibung)
  9. # http://www.haustechnikdialog.de/Forum/t/6144/Waterkotte-5017-3-an-den-Computer-anschliessen?page=4 (Beispiel Befehls-Strings)
  10. #
  11. # This file is part of fhem.
  12. #
  13. # Fhem is free software: you can redistribute it and/or modify
  14. # it under the terms of the GNU General Public License as published by
  15. # the Free Software Foundation, either version 2 of the License, or
  16. # (at your option) any later version.
  17. #
  18. # Fhem is distributed in the hope that it will be useful,
  19. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. # GNU General Public License for more details.
  22. #
  23. # You should have received a copy of the GNU General Public License
  24. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  25. #
  26. ##############################################################################
  27. # Changelog:
  28. #
  29. # 2013-11-1 initial version
  30. # 2013-12-25 bug fixes, GPL comment added, loglevel reading removed
  31. # 2014-4-7 performance tuning, read handling with binary string instead of hex string
  32. # 2014-5-22 decode mode - heating, cooling, warm water on / off
  33. # 2015-7-23 added utf8 encoding of state string, Added name to Log3 calls
  34. #
  35. package main;
  36. use strict;
  37. use warnings;
  38. use Time::HiRes qw(gettimeofday);
  39. use Encode qw(decode encode);
  40. #
  41. # list of Readings / values that can explicitely be requested
  42. # from the WP with the GET command
  43. my %WKRCD4_gets = (
  44. "Hzg-TempBasisSoll" => "Hzg-TempBasisSoll",
  45. "WW-Temp-Soll" => "Temp-WW-Soll"
  46. );
  47. # list of Readings / values that can be written to the WP
  48. my %WKRCD4_sets = (
  49. "Hzg-TempBasisSoll" => "Hzg-TempBasisSoll"
  50. );
  51. # Definition of the values that can be read / written
  52. # with the relative address, number of bytes and
  53. # fmat to be used in sprintf when formatting the value
  54. # unp to be used in pack / unpack commands
  55. # min / max for setting values
  56. #
  57. my %frameReadings = (
  58. 'Versions-Nummer' => { addr => 0x0000, bytes => 0x0002, unp => 'n' },
  59. 'Temp-Aussen' => { addr => 0x0008, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  60. 'Temp-Ruecklauf-Soll' => { addr => 0x0014, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  61. 'Temp-Ruecklauf' => { addr => 0x0018, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  62. 'Temp-Vorlauf' => { addr => 0x001C, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  63. 'Temp-WW-Soll' => { addr => 0x0020, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  64. 'Temp-WW' => { addr => 0x0024, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  65. 'Temp-Raum' => { addr => 0x0028, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  66. 'Temp-WQuelle-Ein' => { addr => 0x0030, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  67. 'Temp-WQuelle-Aus' => { addr => 0x0034, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  68. 'Temp-Verdampfer' => { addr => 0x0038, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  69. 'Temp-Kondensator' => { addr => 0x003C, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  70. 'Temp-Saugleitung' => { addr => 0x0040, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  71. 'Druck-Verdampfer' => { addr => 0x0048, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  72. 'Druck-Kondensator' => { addr => 0x004C, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  73. 'Hzg-TempEinsatz' => { addr => 0x00F4, bytes => 0x0004, fmat => '%0.1f', unp => 'f<', min => 15.0, max => 20.0 },
  74. 'Hzg-TempBasisSoll' => { addr => 0x00F8, bytes => 0x0004, fmat => '%0.1f', unp => 'f<', min => 20.0, max => 24.0 },
  75. 'Hzg-KlSteilheit' => { addr => 0x00FC, bytes => 0x0004, fmat => '%0.1f', unp => 'f<', min => 15.0, max => 30.0 },
  76. 'Hzg-KlBegrenz' => { addr => 0x0100, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  77. 'Hzg-TempRlSoll' => { addr => 0x0050, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  78. 'Hzg-TempRlIst' => { addr => 0x0054, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  79. 'Hzg-TmpRaumSoll' => { addr => 0x0105, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  80. 'Hzg-RaumEinfluss' => { addr => 0x0109, bytes => 0x0001, unp => 'C' },
  81. 'Hzg-ExtAnhebung' => { addr => 0x010A, bytes => 0x0004, fmat => '%0.1f', unp => 'f<', min => -5.0, max => 5.0 },
  82. 'Hzg-Zeit-Ein' => { addr => 0x010E, bytes => 0x0003, fmat => '%3$02d:%2$02d:%1$02d', unp => 'CCC'},
  83. 'Hzg-Zeit-Aus' => { addr => 0x0111, bytes => 0x0003, fmat => '%3$02d:%2$02d:%1$02d', unp => 'CCC' },
  84. 'Hzg-AnhebungEin' => { addr => 0x0114, bytes => 0x0003, fmat => '%3$02d:%2$02d:%1$02d', unp => 'CCC' },
  85. 'Hzg-AnhebungAus' => { addr => 0x0117, bytes => 0x0003, fmat => '%3$02d:%2$02d:%1$02d', unp => 'CCC' },
  86. 'Hzg-St2Begrenz' => { addr => 0x011A, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  87. 'Hzg-Hysterese' => { addr => 0x011E, bytes => 0x0004, fmat => '%0.1f', unp => 'f<', min => 1.0, max => 3.0 },
  88. 'Hzg-PumpenNachl' => { addr => 0x0122, bytes => 0x0001, unp => 'C', min => 0, max => 120 },
  89. 'Klg-Abschaltung' => { addr => 0x0123, bytes => 0x0001, unp => 'C' },
  90. 'Klg-Temp-Einsatz' => { addr => 0x0124, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  91. 'Klg-TeBasisSoll' => { addr => 0x0128, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  92. 'Klg-KlSteilheit' => { addr => 0x012C, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  93. 'Klg-KlBegrenz' => { addr => 0x0130, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  94. 'Klg-KlSollwert' => { addr => 0x0058, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  95. 'Klg-Temp-Rl' => { addr => 0x005C, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  96. 'Ww-Abschaltung' => { addr => 0x0134, bytes => 0x0001 },
  97. 'Ww-Zeit-Ein' => { addr => 0x0135, bytes => 0x0003, fmat => '%3$02d:%2$02d:%1$02d', unp => 'CCC' },
  98. 'Ww-Zeit-Aus' => { addr => 0x0138, bytes => 0x0003, fmat => '%3$02d:%2$02d:%1$02d', unp => 'CCC' },
  99. 'Ww-Temp-Ist' => { addr => 0x0060, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  100. 'Ww-Temp-Soll' => { addr => 0x013b, bytes => 0x0004, fmat => '%0.1f', unp => 'f<', min => 35, max => 55 },
  101. 'Ww-Hysterese' => { addr => 0x0143, bytes => 0x0004, fmat => '%0.1f', unp => 'f<', min => 5, max => 10},
  102. 'Uhrzeit' => { addr => 0x0064, bytes => 0x0003, fmat => '%3$02d:%2$02d:%1$02d', unp => 'CCC' },
  103. 'Datum' => { addr => 0x0067, bytes => 0x0003, fmat => '%02d.%02d.%02d', unp => 'CCC'},
  104. 'BetrStundenKompressor' => { addr => 0x006A, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  105. 'BetrStundenHzgPu' => { addr => 0x006E, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  106. 'BetrStundenWwPu' => { addr => 0x0072, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  107. 'BetrStundenSt2' => { addr => 0x0076, bytes => 0x0004, fmat => '%0.1f', unp => 'f<' },
  108. 'Zeit' => { addr => 0x0064, bytes => 0x0006, fmat=> '%4$02d.%5$02d.%6$02d %3$02d:%2$02d:%1$02d', unp => 'CCCCCC'},
  109. 'SetBetriebsMode' => { addr => 0x014E, bytes => 0x0003, unp => 'N', pack => 'xC*'},
  110. 'Display-Zeile-1' => { addr => 0x008E, bytes => 0x0002, unp => 'n' },
  111. 'Display-Zeile-2' => { addr => 0x0090, bytes => 0x0001, unp => 'C' },
  112. 'Status-Gesamt' => { addr => 0x00D2, bytes => 0x0001, unp => 'C' },
  113. 'Status-Heizung' => { addr => 0x00D4, bytes => 0x0003, unp => 'B24' },
  114. 'Status-Kuehlung' => { addr => 0x00DA, bytes => 0x0003, unp => 'B24' },
  115. 'Mode-Heizung' => { addr => 0x00DF, bytes => 0x0001, unp => 'B8' },
  116. 'Mode-Kuehlung' => { addr => 0x00E0, bytes => 0x0001, unp => 'B8' },
  117. 'Mode-Warmwasser' => { addr => 0x00E1, bytes => 0x0001, unp => 'B8' },
  118. 'Heizung' => { addr => 0x00DF, bytes => 0x0001, unp => 'b' },
  119. 'Kuehlung' => { addr => 0x00E0, bytes => 0x0001, unp => 'b' },
  120. 'Warmwasser' => { addr => 0x00E1, bytes => 0x0001, unp => 'b' }
  121. );
  122. #
  123. # FHEM module intitialisation
  124. # defines the functions to be called from FHEM
  125. #########################################################################
  126. sub WKRCD4_Initialize($)
  127. {
  128. my ($hash) = @_;
  129. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  130. $hash->{ReadFn} = "WKRCD4_Read";
  131. $hash->{ReadyFn} = "WKRCD4_Ready";
  132. $hash->{DefFn} = "WKRCD4_Define";
  133. $hash->{UndefFn} = "WKRCD4_Undef";
  134. $hash->{SetFn} = "WKRCD4_Set";
  135. $hash->{GetFn} = "WKRCD4_Get";
  136. $hash->{AttrList} =
  137. "do_not_notify:1,0 " . $readingFnAttributes;
  138. }
  139. #
  140. # Define command
  141. # init internal values, open device,
  142. # set internal timer to send read command / wakeup
  143. ######################################################################### #
  144. sub WKRCD4_Define($$)
  145. {
  146. my ( $hash, $def ) = @_;
  147. my @a = split( "[ \t][ \t]*", $def );
  148. return "wrong syntax: define <name> WKRCD4 [devicename\@speed|none] [interval]"
  149. if ( @a < 3 );
  150. DevIo_CloseDev($hash);
  151. my $name = $a[0];
  152. my $dev = $a[2];
  153. my $interval = 60;
  154. if ( $dev eq "none" ) {
  155. Log3 undef, 1, "$name: device is none, commands will be echoed only";
  156. return undef;
  157. }
  158. if(int(@a) == 4) {
  159. $interval= $a[3];
  160. if ($interval < 20) {
  161. return "interval too small, please use something > 20, default is 60";
  162. }
  163. }
  164. $hash->{buffer} = "";
  165. $hash->{DeviceName} = $dev;
  166. $hash->{INTERVAL} = $interval;
  167. $hash->{SerialRequests} = 0;
  168. $hash->{SerialGoodReads} = 0;
  169. $hash->{SerialBadReads} = 0;
  170. # send wakeup string (read 2 values preceeded with AT)
  171. $hash->{LastRequestAdr} = 8;
  172. $hash->{LastRequestLen} = 4;
  173. $hash->{LastRequest} = gettimeofday();
  174. my $ret = DevIo_OpenDev( $hash, 0, "WKRCD4_Wakeup" );
  175. # initial read after 3 secs, there timer is set to interval for update and wakeup
  176. InternalTimer(gettimeofday()+3, "WKRCD4_GetUpdate", $hash, 0);
  177. return $ret;
  178. }
  179. #
  180. # undefine command when device is deleted
  181. #########################################################################
  182. sub WKRCD4_Undef($$)
  183. {
  184. my ( $hash, $arg ) = @_;
  185. DevIo_CloseDev($hash);
  186. RemoveInternalTimer($hash);
  187. return undef;
  188. }
  189. #
  190. # Encode the data to be sent to the device (0x10 gets doubled)
  191. #########################################################################
  192. sub Encode10 (@) {
  193. my @a = ();
  194. for my $byte (@_) {
  195. push @a, $byte;
  196. push @a, $byte if $byte == 0x10;
  197. }
  198. return @a;
  199. }
  200. #
  201. # create a command for the WP as byte array
  202. #########################################################################
  203. sub WPCMD($$$$;@)
  204. {
  205. my ($hash, $cmd, $addr, $len, @value ) = @_;
  206. my $name = $hash->{NAME};
  207. my @frame = ();
  208. if ($cmd eq "read") {
  209. @frame = (0x01, 0x15, Encode10($addr>>8, $addr%256), Encode10($len>>8, $len%256));
  210. } elsif ($cmd eq "write") {
  211. @frame = (0x01, 0x13, Encode10($addr>>8, $addr%256), Encode10(@value));
  212. } else {
  213. Log3 $name, 3, "$name: undefined cmd ($cmd) in WPCMD";
  214. return 0;
  215. }
  216. my $crc = CRC16(@frame);
  217. return (0xff, 0x10, 0x02, @frame, 0x10, 0x03, $crc >> 8, $crc % 256, 0xff);
  218. }
  219. #
  220. # GET command
  221. #########################################################################
  222. sub WKRCD4_Get($@)
  223. {
  224. my ( $hash, @a ) = @_;
  225. return "\"get WKRCD4\" needs at least an argument" if ( @a < 2 );
  226. my $name = shift @a;
  227. my $attr = shift @a;
  228. my $arg = join("", @a);
  229. if(!$WKRCD4_gets{$attr}) {
  230. my @cList = keys %WKRCD4_gets;
  231. return "Unknown argument $attr, choose one of " . join(" ", @cList);
  232. }
  233. # get Hash pointer for the attribute requested from the global hash
  234. my $properties = $frameReadings{$WKRCD4_gets{$attr}};
  235. if(!$properties) {
  236. return "No Entry in frameReadings found for $attr";
  237. }
  238. # get details about the attribute requested from its hash
  239. my $addr = $properties->{addr};
  240. my $bytes = $properties->{bytes};
  241. Log3 $name, 4, sprintf ("$name: Get will read %02x bytes starting from %02x for $attr", $bytes, $addr);
  242. # create command for WP
  243. my $cmd = pack('C*', WPCMD($hash, 'read', $addr, $bytes));
  244. # set internal variables to track what is happending
  245. $hash->{LastRequestAdr} = $addr;
  246. $hash->{LastRequestLen} = $bytes;
  247. $hash->{LastRequest} = gettimeofday();
  248. $hash->{SerialRequests}++;
  249. Log3 $name, 4, "$name: Get -> Call DevIo_SimpleWrite: " . unpack ('H*', $cmd);
  250. DevIo_SimpleWrite( $hash, $cmd , 0 );
  251. return sprintf ("Read %02x bytes starting from %02x", $bytes, $addr);
  252. }
  253. #
  254. # SET command
  255. #########################################################################
  256. sub WKRCD4_Set($@)
  257. {
  258. my ( $hash, @a ) = @_;
  259. return "\"set WKRCD4\" needs at least an argument" if ( @a < 2 );
  260. my $name = shift @a;
  261. my $attr = shift @a;
  262. my $arg = join("", @a);
  263. if(!defined($WKRCD4_sets{$attr})) {
  264. my @cList = keys %WKRCD4_sets;
  265. return "Unknown argument $attr, choose one of " . join(" ", @cList);
  266. }
  267. # get Hash pointer for the attribute requested from the global hash
  268. my $properties = $frameReadings{$WKRCD4_sets{$attr}};
  269. if(!$properties) {
  270. return "No Entry in frameReadings found for $attr";
  271. }
  272. # get details about the attribute requested from its hash
  273. my $addr = $properties->{addr};
  274. my $bytes = $properties->{bytes};
  275. my $min = $properties->{min};
  276. my $max = $properties->{max};
  277. my $unp = $properties->{unp};
  278. return "a numerical value between $min and $max is expected, got $arg instead"
  279. if($arg !~ m/^[\d.]+$/ || $arg < $min || $arg > $max);
  280. # convert string to value needed for command
  281. my $vp = pack($unp, $arg);
  282. my @value = unpack ('C*', $vp);
  283. Log3 $name, 4, sprintf ("$name: Set will write $attr: %02x bytes starting from %02x with %s (%s) packed with $unp", $bytes, $addr, unpack ('H*', $vp), unpack ($unp, $vp));
  284. my $cmd = pack('C*', WPCMD($hash, 'write', $addr, $bytes, @value));
  285. # set internal variables to track what is happending
  286. $hash->{LastRequestAdr} = $addr;
  287. $hash->{LastRequestLen} = $bytes;
  288. $hash->{LastRequest} = gettimeofday();
  289. $hash->{SerialRequests}++;
  290. Log3 $name, 4, "Set -> Call DevIo_SimpleWrite: " . unpack ('H*', $cmd);
  291. DevIo_SimpleWrite( $hash, $cmd , 0 );
  292. return sprintf ("Wrote %02x bytes starting from %02x with %s (%s)", $bytes, $addr, unpack ('H*', $vp), unpack ($unp, $vp));
  293. }
  294. #########################################################################
  295. # called from the global loop, when the select for hash->{FD} reports data
  296. sub WKRCD4_Read($)
  297. {
  298. my ($hash) = @_;
  299. my $name = $hash->{NAME};
  300. # read from serial device
  301. my $buf = DevIo_SimpleRead($hash);
  302. return "" if ( !defined($buf) );
  303. $hash->{buffer} .= $buf;
  304. Log3 $name, 5, "$name: read buffer content: " . unpack ('H*', $hash->{buffer});
  305. # did we already get a full frame?
  306. if ( $hash->{buffer} !~ /\xff\x10\x02(.{2})(.*)\x10\x03(.{2})\xff(.*)/s )
  307. {
  308. Log3 $name, 5, "$name: read NoMatch: " . unpack ('H*', $hash->{buffer});
  309. return "";
  310. }
  311. my $msg = unpack ('H*', $1);
  312. my @aframe = unpack ('C*', $1 . $2);
  313. my $crc = unpack ('S>', $3);
  314. my $rest = $4;
  315. $hash->{buffer} = $rest;
  316. Log3 $name, 4, "$name: read match msg: $msg CRC $crc";
  317. Log3 $name, 5, "$name: read frame is " . unpack ('H*', pack ('C*', @aframe)) . ", Rest " . unpack ('H*', $rest);
  318. # calculate CRC and compare with CRC from read
  319. my $crc2 = CRC16(@aframe);
  320. if ($crc != $crc2) {
  321. Log3 $name, 3, "$name: read Bad CRC from WP: $crc, berechnet: $crc2";
  322. Log3 $name, 4, "$name: read Frame was " . unpack ('H*', pack ('C*', @aframe));
  323. $hash->{SerialBadReads} ++;
  324. @aframe = ();
  325. return "";
  326. };
  327. Log3 $name, 4, "$name: read CRC Ok.";
  328. $hash->{SerialGoodReads}++;
  329. # reply to read request ?
  330. if ($msg eq "0017") {
  331. my @data;
  332. for(my $i=0,my $offset=2;$offset<=$#aframe;$offset++,$i++)
  333. {
  334. # remove duplicate 0x10 (frames are encoded like this)
  335. if (($aframe[$offset]==16)&&($aframe[$offset+1]==16)) { $offset++; }
  336. $data[$i] = $aframe[$offset];
  337. }
  338. Log3 $name, 4, "$name: read -> Parse with relative request start " . $hash->{LastRequestAdr} . " Len " . $hash->{LastRequestLen};
  339. # extract values from data
  340. parseReadings($hash, @data);
  341. } elsif ($msg eq "0011") {
  342. # reply to write
  343. } else {
  344. Log3 $name, 3, "$name: read got unknown Msg type " . $msg . " in " . $hash->{buffer};
  345. }
  346. @aframe = ();
  347. return "";
  348. }
  349. #
  350. # copied from other FHEM modules
  351. #########################################################################
  352. sub WKRCD4_Ready($)
  353. {
  354. my ($hash) = @_;
  355. return DevIo_OpenDev( $hash, 1, undef )
  356. if ( $hash->{STATE} eq "disconnected" );
  357. # This is relevant for windows/USB only
  358. my $po = $hash->{USBDev};
  359. my ( $BlockingFlags, $InBytes, $OutBytes, $ErrorFlags ) = $po->status;
  360. return ( $InBytes > 0 );
  361. }
  362. #
  363. # send wakeup /at least my waterkotte WP doesn't respond otherwise
  364. #########################################################################
  365. sub WKRCD4_Wakeup($)
  366. {
  367. my ($hash) = @_;
  368. my $name = $hash->{NAME};
  369. $hash->{SerialRequests}++;
  370. $hash->{LastRequestAdr} = 8;
  371. $hash->{LastRequestLen} = 4;
  372. $hash->{LastRequest} = gettimeofday();
  373. my $cmd = "41540D100201150008000410037EA010020115003000041003FDC3100201150034000410037D90";
  374. DevIo_SimpleWrite( $hash, $cmd , 1 );
  375. Log3 $name, 5, "$name: sent wakeup string: " . $cmd . " done.";
  376. return undef;
  377. }
  378. #
  379. # request new data from WP
  380. ###################################
  381. sub WKRCD4_GetUpdate($)
  382. {
  383. my ($hash) = @_;
  384. my $name = $hash->{NAME};
  385. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "WKRCD4_GetUpdate", $hash, 1);
  386. InternalTimer(gettimeofday()+$hash->{INTERVAL}/2, "WKRCD4_Wakeup", $hash, 1);
  387. $hash->{SerialRequests}++;
  388. my $cmd = pack('C*', WPCMD($hash, 'read', 0, 0x170));
  389. $hash->{LastRequestAdr} = 0;
  390. $hash->{LastRequestLen} = 0x170;
  391. $hash->{LastRequest} = gettimeofday();
  392. DevIo_SimpleWrite( $hash, $cmd , 0 );
  393. Log3 $name, 5, "$name: GetUpdate -> Call DevIo_SimpleWrite: " . unpack ('H*', $cmd);
  394. return 1;
  395. }
  396. #
  397. # calculate CRC16 for communication with the WP
  398. #####################################################################################################
  399. sub CRC16
  400. {
  401. my $CRC = 0;
  402. my $POLY = 0x800500;
  403. for my $byte (@_, 0, 0) {
  404. $CRC |= $byte;
  405. for (0 .. 7) {
  406. $CRC <<= 1;
  407. if ($CRC & 0x1000000) { $CRC ^= $POLY; }
  408. $CRC &= 0xffffff;
  409. }
  410. }
  411. return $CRC >> 8;
  412. }
  413. #
  414. # get Values out of data read
  415. #####################################################################################################
  416. sub parseReadings
  417. {
  418. my ($hash, @data) = @_;
  419. my $name = $hash->{NAME};
  420. my $reqStart = $hash->{LastRequestAdr};
  421. my $reqLen = $hash->{LastRequestLen};
  422. # get enough bytes?
  423. if (@data >= $reqLen)
  424. {
  425. readingsBeginUpdate($hash);
  426. # go through all possible readings from global hash
  427. while (my ($reading, $property) = each(%frameReadings))
  428. {
  429. my $addr = $property->{addr};
  430. my $bytes = $property->{bytes};
  431. # is reading inside data we got?
  432. if (($addr >= $reqStart) &&
  433. ($addr + $bytes <= $reqStart + $reqLen))
  434. {
  435. my $Idx = $addr - $reqStart;
  436. # get relevant slice from data array
  437. my @slice = @data[$Idx .. $Idx + $bytes - 1];
  438. # convert according to rules in global hash or defaults
  439. my $pack = ($property->{pack}) ? $property->{pack} : 'C*';
  440. my $unpack = ($property->{unp}) ? $property->{unp} : 'H*';
  441. my $fmat = ($property->{fmat}) ? $property->{fmat} : '%s';
  442. #my $value = sprintf ($fmat, unpack ($unpack, pack ($pack, @slice))) . " packed with $pack, unpacked with $unpack, (hex " . unpack ('H*', pack ('C*', @slice)) . ") format $fmat";
  443. my $value = sprintf ($fmat, unpack ($unpack, pack ($pack, @slice)));
  444. readingsBulkUpdate( $hash, $reading, $value );
  445. Log3 $name, 4, "$name: parse set reading $reading to $value" if (@data <= 20);
  446. }
  447. }
  448. my $Status = "WP idle";
  449. if (ReadingsVal($name, "Heizung", 0)) {
  450. $Status = sprintf ("Heizung %s", ReadingsVal ($name, "Temp-Vorlauf", 0));
  451. } elsif (ReadingsVal($name, "Kuehlung", 0)) {
  452. $Status = sprintf ("Kühlung %s", ReadingsVal ($name, "Temp-Vorlauf", 0));
  453. } elsif (ReadingsVal($name, "Kuehlung", 0)) {
  454. $Status = sprintf ("Warmwasser %s", ReadingsVal ($name, "Temp-WW", 0));
  455. }
  456. $Status = encode ("utf8", $Status);
  457. readingsBulkUpdate( $hash, "Status", $Status);
  458. readingsEndUpdate( $hash, 1 );
  459. }
  460. else
  461. {
  462. Log3 $name, 3, "$name: parse - data len smaller than requested ($reqLen) : " . unpack ('H*', pack ('C*', @data));
  463. return 0;
  464. }
  465. }
  466. 1;