14_SD_WS.pm 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. ##############################################
  2. # $Id: 14_SD_WS.pm 16935 2018-07-02 20:22:34Z Sidey $
  3. #
  4. # The purpose of this module is to support serval
  5. # weather sensors which use various protocol
  6. # Sidey79 & Ralf9 2016 - 2017
  7. # Joerg 2017
  8. # 17.04.2017 WH2 (TFA 30.3157 nur Temp, Hum = 255),es wird das Perlmodul Digest:CRC benoetigt fuer CRC-Pruefung benoetigt
  9. # 29.05.2017 Test ob Digest::CRC installiert
  10. # 22.07.2017 WH2 angepasst
  11. # 21.08.2017 WH2 Abbruch wenn kein "FF" am Anfang
  12. package main;
  13. use strict;
  14. use warnings;
  15. # use Digest::CRC qw(crc);
  16. # use Data::Dumper;
  17. sub SD_WS_Initialize($)
  18. {
  19. my ($hash) = @_;
  20. $hash->{Match} = '^W\d+x{0,1}#.*';
  21. $hash->{DefFn} = "SD_WS_Define";
  22. $hash->{UndefFn} = "SD_WS_Undef";
  23. $hash->{ParseFn} = "SD_WS_Parse";
  24. $hash->{AttrFn} = "SD_WS_Attr";
  25. $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 showtime:1,0 " .
  26. "$readingFnAttributes ";
  27. $hash->{AutoCreate} =
  28. {
  29. "SD_WS37_TH.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "2:180"},
  30. "SD_WS50_SM.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "2:180"},
  31. "BresserTemeo.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "2:180"},
  32. "SD_WS51_TH.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "2:180"},
  33. "SD_WS58_TH.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "2:90"},
  34. "SD_WH2.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "2:90"},
  35. "SD_WS71_T.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "2:180"},
  36. };
  37. }
  38. #############################
  39. sub SD_WS_Define($$)
  40. {
  41. my ($hash, $def) = @_;
  42. my @a = split("[ \t][ \t]*", $def);
  43. return "wrong syntax: define <name> SD_WS <code> ".int(@a) if(int(@a) < 3 );
  44. $hash->{CODE} = $a[2];
  45. $hash->{lastMSG} = "";
  46. $hash->{bitMSG} = "";
  47. $modules{SD_WS}{defptr}{$a[2]} = $hash;
  48. $hash->{STATE} = "Defined";
  49. my $name= $hash->{NAME};
  50. return undef;
  51. }
  52. #####################################
  53. sub SD_WS_Undef($$)
  54. {
  55. my ($hash, $name) = @_;
  56. delete($modules{SD_WS}{defptr}{$hash->{CODE}})
  57. if(defined($hash->{CODE}) && defined($modules{SD_WS}{defptr}{$hash->{CODE}}));
  58. return undef;
  59. }
  60. ###################################
  61. sub SD_WS_Parse($$)
  62. {
  63. my ($iohash, $msg) = @_;
  64. #my $rawData = substr($msg, 2);
  65. my $name = $iohash->{NAME};
  66. my ($protocol,$rawData) = split("#",$msg);
  67. $protocol=~ s/^[WP](\d+)/$1/; # extract protocol
  68. my $dummyreturnvalue= "Unknown, please report";
  69. my $hlen = length($rawData);
  70. my $blen = $hlen * 4;
  71. my $bitData = unpack("B$blen", pack("H$hlen", $rawData));
  72. my $bitData2;
  73. my $model; # wenn im elsif Abschnitt definiert, dann wird der Sensor per AutoCreate angelegt
  74. my $SensorTyp;
  75. my $id;
  76. my $bat;
  77. my $channel;
  78. my $rawTemp;
  79. my $temp;
  80. my $hum;
  81. my $trend;
  82. my %decodingSubs = (
  83. 50 => # Protocol 50
  84. # FF550545FF9E
  85. # FF550541FF9A
  86. # AABCDDEEFFGG
  87. # A = Preamble, always FF
  88. # B = TX type, always 5
  89. # C = Address (5/6/7) > low 2 bits = 1/2/3
  90. # D = Soil moisture 05%
  91. # E = temperature
  92. # F = security code, always F
  93. # G = Checksum 55+05+45+FF=19E CRC value = 9E
  94. { # subs to decode this
  95. sensortype => 'XT300',
  96. model => 'SD_WS_50_SM',
  97. prematch => sub {my $msg = shift; return 1 if ($msg =~ /^FF5[0-9A-F]{5}FF[0-9A-F]{2}/); }, # prematch
  98. crcok => sub {my $msg = shift; return 1 if ((hex(substr($msg,2,2))+hex(substr($msg,4,2))+hex(substr($msg,6,2))+hex(substr($msg,8,2))&0xFF) == (hex(substr($msg,10,2))) ); }, # crc
  99. id => sub {my $msg = shift; return (hex(substr($msg,2,2)) &0x03 ); }, #id
  100. temp => sub {my $msg = shift; return ((hex(substr($msg,6,2)))-40) }, #temp
  101. hum => sub {my $msg = shift; return hex(substr($msg,4,2)); }, #hum
  102. channel => sub {my (undef,$bitData) = @_; return ( SD_WS_binaryToNumber($bitData,12,15)&0x03 ); }, #channel
  103. bat => sub { return "";},
  104. },
  105. 71 =>
  106. # 5C2A909F792F
  107. # 589A829FDFF4
  108. # PiiTTTK?CCCC
  109. # P = Preamble (immer 5 ?)
  110. # i = ID
  111. # T = Temperatur
  112. # K = Kanal (B/A/9)
  113. # ? = immer F ?
  114. # C = Checksum ?
  115. {
  116. sensortype => 'PV-8644',
  117. model => 'SD_WS71_T',
  118. prematch => sub {my $msg = shift; return 1 if ($msg =~ /^5[A-F0-9]{6}F[A-F0-9]{2}/); }, # prematch
  119. crcok => sub {return 1; }, # crc is unknown
  120. id => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,4,11); }, # id
  121. temp => sub {my (undef,$bitData) = @_; return ((SD_WS_binaryToNumber($bitData,12,23) - 2448) / 10); }, #temp
  122. channel => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,26,27); }, #channel
  123. hum => sub {return undef;},
  124. bat => sub {return undef;},
  125. },
  126. 33 =>
  127. {
  128. sensortype => 's014/TFA 30.3200/TCM/Conrad',
  129. model => 'SD_WS_33_TH',
  130. prematch => sub {my $msg = shift; return 1 if ($msg =~ /^[0-9A-F]{10,11}/); }, # prematch
  131. crcok => sub {return SD_WS_binaryToNumber($bitData,36,39)+1; }, # crc currently not calculated
  132. id => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,0,9); }, # id
  133. # sendmode => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,10,11) eq "1" ? "manual" : "auto"; }
  134. temp => sub {my (undef,$bitData) = @_; return (((SD_WS_binaryToNumber($bitData,22,25)*256 + SD_WS_binaryToNumber($bitData,18,21)*16 + SD_WS_binaryToNumber($bitData,14,17)) *10 -12200) /18)/10; }, #temp
  135. hum => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,30,33)*16 + SD_WS_binaryToNumber($bitData,26,29)); }, #hum
  136. channel => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,12,13)+1 ); }, #channel
  137. bat => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,34) eq "0" ? "ok" : "low";},
  138. # sync => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,35,35) eq "1" ? "true" : "false");},
  139. } ,
  140. 51 =>
  141. {
  142. sensortype => 'Lidl Wetterstation 2759001/IAN114324',
  143. model => 'SD_WS_51_TH',
  144. prematch => sub {my $msg = shift; return 1 if ($msg =~ /^[0-9A-F]{10}/); }, # prematch
  145. crcok => sub {return 1; }, # crc is unknown
  146. id => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,0,12); }, # random id?
  147. # sendmode => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,10,11) eq "1" ? "manual" : "auto"; }
  148. temp => sub {my (undef,$bitData) = @_; return round(((SD_WS_binaryToNumber($bitData,16,27)) -1220) *5 /90.0,1); }, #temp
  149. hum => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,28,31)*10) + (SD_WS_binaryToNumber($bitData,32,35)); }, #hum
  150. channel => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,36,39) ); }, #channel
  151. bat => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,13) eq "1" ? "low" : "ok";},
  152. trend => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,15,16) eq "01" ? "rising" : SD_WS_binaryToNumber($bitData,14,15) eq "00" ? "neutral" : "rising";},
  153. # sync => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,35,35) eq "1" ? "true" : "false");},
  154. } ,
  155. 58 =>
  156. {
  157. sensortype => 'TFA 3032080',
  158. model => 'SD_WS_58_TH',
  159. prematch => sub {my $msg = shift; return 1 if ($msg =~ /^45[0-9A-F]{11}/); }, # prematch
  160. crcok => sub { my $msg = shift;
  161. my @buff = split(//,substr($msg,index($msg,"45"),10));
  162. my $crc_check = substr($msg,index($msg,"45")+10,2);
  163. my $mask = 0x7C;
  164. my $checksum = 0x64;
  165. my $data;
  166. my $nibbleCount;
  167. for ( $nibbleCount=0; $nibbleCount < scalar @buff; $nibbleCount+=2)
  168. {
  169. my $bitCnt;
  170. if ($nibbleCount+1 <scalar @buff)
  171. {
  172. $data = hex($buff[$nibbleCount].$buff[$nibbleCount+1]);
  173. } else {
  174. $data = hex($buff[$nibbleCount]);
  175. }
  176. for ( my $bitCnt= 7; $bitCnt >= 0 ; $bitCnt-- )
  177. {
  178. my $bit;
  179. # Rotate mask right
  180. $bit = $mask & 1;
  181. $mask = ($mask >> 1 ) | ($mask << 7) & 0xFF;
  182. if ( $bit )
  183. {
  184. $mask ^= 0x18 & 0xFF;
  185. }
  186. # XOR mask into checksum if data bit is 1
  187. if ( $data & 0x80 )
  188. {
  189. $checksum ^= $mask & 0xFF;
  190. }
  191. $data <<= 1 & 0xFF;
  192. }
  193. }
  194. if ($checksum == hex($crc_check)) {
  195. return 1;
  196. } else {
  197. return 0;
  198. }
  199. },
  200. id => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,8,15); }, # random id
  201. bat => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,16) eq "1" ? "low" : "ok";}, # bat?
  202. channel => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,17,19)+1 ); }, # channel
  203. temp => sub {my (undef,$bitData) = @_; return round((SD_WS_binaryToNumber($bitData,20,31)-720)*0.0556,1); }, # temp
  204. hum => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,32,39)); }, # hum
  205. } ,
  206. );
  207. Log3 $name, 4, "SD_WS_Parse: Protocol: $protocol, rawData: $rawData";
  208. if ($protocol eq "37") { # Bresser 7009994
  209. # Protokollbeschreibung:
  210. # https://github.com/merbanan/rtl_433_tests/tree/master/tests/bresser_3ch
  211. # The data is grouped in 5 bytes / 10 nibbles
  212. # ------------------------------------------------------------------------
  213. # 0 | 8 12 | 16 | 24 | 32
  214. # 1111 1100 | 0001 0110 | 0001 0000 | 0011 0111 | 0101 1001 0 65.1 F 55 %
  215. # iiii iiii | bscc tttt | tttt tttt | hhhh hhhh | xxxx xxxx
  216. # i: 8 bit random id (changes on power-loss)
  217. # b: battery indicator (0=>OK, 1=>LOW)
  218. # s: Test/Sync (0=>Normal, 1=>Test-Button pressed / Sync)
  219. # c: Channel (MSB-first, valid channels are 1-3)
  220. # t: Temperature (MSB-first, Big-endian)
  221. # 12 bit unsigned fahrenheit offset by 90 and scaled by 10
  222. # h: Humidity (MSB-first) 8 bit relative humidity percentage
  223. # x: checksum (byte1 + byte2 + byte3 + byte4) % 256
  224. # Check with e.g. (byte1 + byte2 + byte3 + byte4 - byte5) % 256) = 0
  225. $model = "SD_WS37_TH";
  226. $SensorTyp = "Bresser 7009994";
  227. my $checksum = (SD_WS_binaryToNumber($bitData,0,7) + SD_WS_binaryToNumber($bitData,8,15) + SD_WS_binaryToNumber($bitData,16,23) + SD_WS_binaryToNumber($bitData,24,31)) & 0xFF;
  228. if ($checksum != SD_WS_binaryToNumber($bitData,32,39)) {
  229. Log3 $name, 3, "$name: SD_WS37 ERROR - checksum $checksum != ".SD_WS_binaryToNumber($bitData,32,39);
  230. return "";
  231. } else {
  232. Log3 $name, 4, "$name: SD_WS37 checksum ok $checksum = ".SD_WS_binaryToNumber($bitData,32,39);
  233. $id = SD_WS_binaryToNumber($bitData,0,7);
  234. $id = sprintf('%02X', $id); # wandeln nach hex
  235. $bat = int(substr($bitData,8,1)) eq "0" ? "ok" : "low"; # Batterie-Bit konnte nicht geprueft werden
  236. $channel = SD_WS_binaryToNumber($bitData,10,11);
  237. $rawTemp = SD_WS_binaryToNumber($bitData,12,23);
  238. $hum = SD_WS_binaryToNumber($bitData,24,31);
  239. my $tempFh = $rawTemp / 10 - 90; # Grad Fahrenheit
  240. $temp = (($tempFh - 32) * 5 / 9); # Grad Celsius
  241. $temp = sprintf("%.1f", $temp + 0.05); # round
  242. Log3 $name, 4, "$name: SD_WS37 tempraw = $rawTemp, temp = $tempFh F, temp = $temp C, Hum = $hum";
  243. Log3 $name, 4, "$name: SD_WS37 decoded protocol = $protocol ($SensorTyp), sensor id = $id, channel = $channel";
  244. }
  245. }
  246. elsif ($protocol eq "44" || $protocol eq "44x") # BresserTemeo
  247. {
  248. # 0 4 8 12 20 24 28 32 36 40 44 52 56 60
  249. # 0101 0111 1001 00010101 0010 0100 0001 1010 1000 0110 11101010 1101 1011 1110 110110010
  250. # hhhh hhhh ?bcc viiiiiii sttt tttt tttt xxxx xxxx ?BCC VIIIIIII Syyy yyyy yyyy
  251. # - h humidity / -x checksum
  252. # - t temp / -y checksum
  253. # - c Channel / -C checksum
  254. # - V sign / -V checksum
  255. # - i 7 bit random id (aendert sich beim Batterie- und Kanalwechsel) / - I checksum
  256. # - b battery indicator (0=>OK, 1=>LOW) / - B checksum
  257. # - s Test/Sync (0=>Normal, 1=>Test-Button pressed) / - S checksum
  258. $model= "BresserTemeo";
  259. $SensorTyp = "BresserTemeo";
  260. #my $binvalue = unpack("B*" ,pack("H*", $rawData));
  261. my $binvalue = $bitData;
  262. if (length($binvalue) != 72) {
  263. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: length error (72 bits expected)!!!";
  264. return "";
  265. }
  266. # Check what Humidity Prefix (*sigh* Bresser!!!)
  267. if ($protocol eq "44")
  268. {
  269. $binvalue = "0".$binvalue;
  270. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: Humidity <= 79 Flag";
  271. }
  272. else
  273. {
  274. $binvalue = "1".$binvalue;
  275. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: Humidity > 79 Flag";
  276. }
  277. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: new bin $binvalue";
  278. my $checksumOkay = 1;
  279. my $hum1Dec = SD_WS_binaryToNumber($binvalue, 0, 3);
  280. my $hum2Dec = SD_WS_binaryToNumber($binvalue, 4, 7);
  281. my $checkHum1 = SD_WS_binaryToNumber($binvalue, 32, 35) ^ 0b1111;
  282. my $checkHum2 = SD_WS_binaryToNumber($binvalue, 36, 39) ^ 0b1111;
  283. if ($checkHum1 != $hum1Dec || $checkHum2 != $hum2Dec)
  284. {
  285. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error in Humidity";
  286. }
  287. else
  288. {
  289. $hum = $hum1Dec.$hum2Dec;
  290. if ($hum < 1 || $hum > 100)
  291. {
  292. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: Humidity Error. Humidity=$hum";
  293. return "";
  294. }
  295. }
  296. my $temp1Dec = SD_WS_binaryToNumber($binvalue, 21, 23);
  297. my $temp2Dec = SD_WS_binaryToNumber($binvalue, 24, 27);
  298. my $temp3Dec = SD_WS_binaryToNumber($binvalue, 28, 31);
  299. my $checkTemp1 = SD_WS_binaryToNumber($binvalue, 53, 55) ^ 0b111;
  300. my $checkTemp2 = SD_WS_binaryToNumber($binvalue, 56, 59) ^ 0b1111;
  301. my $checkTemp3 = SD_WS_binaryToNumber($binvalue, 60, 63) ^ 0b1111;
  302. $temp = $temp1Dec.$temp2Dec.".".$temp3Dec;
  303. if ($checkTemp1 != $temp1Dec || $checkTemp2 != $temp2Dec || $checkTemp3 != $temp3Dec)
  304. {
  305. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error in Temperature";
  306. $checksumOkay = 0;
  307. }
  308. if ($temp > 60)
  309. {
  310. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: Temperature Error. temp=$temp";
  311. return "";
  312. }
  313. my $sign = substr($binvalue,12,1);
  314. my $checkSign = substr($binvalue,44,1) ^ 0b1;
  315. if ($sign != $checkSign)
  316. {
  317. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error in Sign";
  318. $checksumOkay = 0;
  319. }
  320. else
  321. {
  322. if ($sign)
  323. {
  324. $temp = 0 - $temp
  325. }
  326. }
  327. $bat = substr($binvalue,9,1);
  328. my $checkBat = substr($binvalue,41,1) ^ 0b1;
  329. if ($bat != $checkBat)
  330. {
  331. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error in Bat";
  332. $bat = undef;
  333. }
  334. else
  335. {
  336. $bat = ($bat == 0) ? "ok" : "low";
  337. }
  338. $channel = SD_WS_binaryToNumber($binvalue, 10, 11);
  339. my $checkChannel = SD_WS_binaryToNumber($binvalue, 42, 43) ^ 0b11;
  340. $id = SD_WS_binaryToNumber($binvalue, 13, 19);
  341. my $checkId = SD_WS_binaryToNumber($binvalue, 45, 51) ^ 0b1111111;
  342. if ($channel != $checkChannel || $id != $checkId)
  343. {
  344. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error in Channel or Id";
  345. $checksumOkay = 0;
  346. }
  347. if ($checksumOkay == 0)
  348. {
  349. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error!!! These Values seem incorrect: temp=$temp, channel=$channel, id=$id";
  350. return "";
  351. }
  352. $id = sprintf('%02X', $id); # wandeln nach hex
  353. Log3 $iohash, 4, "$name SD_WS_Parse: model=$model, temp=$temp, hum=$hum, channel=$channel, id=$id, bat=$bat";
  354. } elsif ($protocol eq "64") # WH2
  355. {
  356. #* Fine Offset Electronics WH2 Temperature/Humidity sensor protocol
  357. #* aka Agimex Rosenborg 66796 (sold in Denmark)
  358. #* aka ClimeMET CM9088 (Sold in UK)
  359. #* aka TFA Dostmann/Wertheim 30.3157 (Temperature only!) (sold in Germany)
  360. #* aka ...
  361. #*
  362. #* The sensor sends two identical packages of 48 bits each ~48s. The bits are PWM modulated with On Off Keying
  363. # * The data is grouped in 6 bytes / 12 nibbles
  364. #* [pre] [pre] [type] [id] [id] [temp] [temp] [temp] [humi] [humi] [crc] [crc]
  365. #*
  366. #* pre is always 0xFF
  367. #* type is always 0x4 (may be different for different sensor type?)
  368. #* id is a random id that is generated when the sensor starts
  369. #* temp is 12 bit signed magnitude scaled by 10 celcius
  370. #* humi is 8 bit relative humidity percentage
  371. #* Based on reverse engineering with gnu-radio and the nice article here:
  372. #* http://lucsmall.com/2012/04/29/weather-station-hacking-part-2/
  373. # 0x4A/74 0x70/112 0xEF/239 0xFF/255 0x97/151 | Sensor ID: 0x4A7 | 255% | 239 | OK
  374. #{ Dispatch($defs{sduino}, "W64#FF48D0C9FFBA", undef) }
  375. #* Message Format:
  376. #* .- [0] -. .- [1] -. .- [2] -. .- [3] -. .- [4] -.
  377. #* | | | | | | | | | |
  378. #* SSSS.DDDD DDN_.TTTT TTTT.TTTT WHHH.HHHH CCCC.CCCC
  379. #* | | | || | | | | | | || | | |
  380. #* | | | || | | | | | | || | `--------- CRC
  381. #* | | | || | | | | | | |`-------- Humidity
  382. #* | | | || | | | | | | |
  383. #* | | | || | | | | | | `---- weak battery
  384. #* | | | || | | | | | |
  385. #* | | | || | | | | `----- Temperature T * 0.1
  386. #* | | | || | | | |
  387. #* | | | || | | `---------- Temperature T * 1
  388. #* | | | || | |
  389. #* | | | || `--------------- Temperature T * 10
  390. #* | | | | `--- new battery
  391. #* | | `---------- ID
  392. #* `---- START = 9
  393. #*
  394. #*/
  395. $msg = substr($msg,0,16);
  396. my (undef ,$rawData) = split("#",$msg);
  397. my $hlen = length($rawData);
  398. my $blen = $hlen * 4;
  399. my $msg_vor ="W64#";
  400. my $bitData20;
  401. my $sign = 0;
  402. my $rr2;
  403. my $vorpre = -1;
  404. my $bitData = unpack("B$blen", pack("H$hlen", $rawData));
  405. my $temptyp = substr($bitData,0,8);
  406. if( $temptyp == "11111110" ) {
  407. $rawData = SD_WS_WH2SHIFT($rawData);
  408. $msg = $msg_vor.$rawData;
  409. $bitData = unpack("B$blen", pack("H$hlen", $rawData));
  410. Log3 $iohash, 4, "$name: SD_WS_WH2_1 msg=$msg length:".length($bitData) ;
  411. Log3 $iohash, 4, "$name: SD_WS_WH2_1 bitdata: $bitData" ;
  412. } else{
  413. if ( $temptyp == "11111101" ) {
  414. $rawData = SD_WS_WH2SHIFT($rawData);
  415. $rawData = SD_WS_WH2SHIFT($rawData);
  416. $msg = $msg_vor.$rawData;
  417. $bitData = unpack("B$blen", pack("H$hlen", $rawData));
  418. Log3 $iohash, 4, "$name: SD_WS_WH2_2 msg=$msg length:".length($bitData) ;
  419. Log3 $iohash, 4, "$name: SD_WS_WH2_2 bitdata: $bitData" ;
  420. }
  421. }
  422. if( $temptyp == "11111111" ) {
  423. $vorpre = 8;
  424. }else{
  425. Log3 $iohash, 4, "$name: SD_WS_WH2_4 Error kein WH2: Typ: $temptyp" ;
  426. return "";
  427. }
  428. my $rc = eval
  429. {
  430. require Digest::CRC;
  431. Digest::CRC->import();
  432. 1;
  433. };
  434. if($rc)
  435. {
  436. # Digest::CRC loaded and imported successfully
  437. Log3 $iohash, 4, "$name: SD_WS_WH2_1 msg: $msg raw: $rawData " ;
  438. $rr2 = SD_WS_WH2CRCCHECK($rawData);
  439. if ($rr2 == 0 ){
  440. # 1.CRC OK
  441. Log3 $iohash, 4, "$name: SD_WS_WH2_1 CRC_OK : CRC=$rr2 msg: $msg check:".$rawData ;
  442. }else{
  443. Log3 $iohash, 4, "$name: SD_WS_WH2_4 CRC_Error: CRC=$rr2 msg: $msg check:".$rawData ;
  444. return "";
  445. }
  446. }else {
  447. Log3 $iohash, 1, "$name: SD_WS_WH2_3 CRC_not_load: Modul Digest::CRC fehlt" ;
  448. return "";
  449. }
  450. $bitData = unpack("B$blen", pack("H$hlen", $rawData));
  451. Log3 $iohash, 4, "$name converted to bits: WH2 " . $bitData;
  452. $model = "SD_WS_WH2";
  453. $SensorTyp = "WH2";
  454. $id = SD_WS_bin2dec(substr($bitData,$vorpre + 4,6));
  455. $id = sprintf('%03X', $id);
  456. $channel = 0;
  457. $bat = SD_WS_binaryToNumber($bitData,$vorpre + 20) eq "1" ? "low" : "ok";
  458. $sign = SD_WS_bin2dec(substr($bitData,$vorpre + 12,1));
  459. if ($sign == 0) {
  460. # Temp positiv
  461. $temp = (SD_WS_bin2dec(substr($bitData,$vorpre + 13,11))) / 10;
  462. }else{
  463. # Temp negativ
  464. $temp = -(SD_WS_bin2dec(substr($bitData,$vorpre + 13,11))) / 10;
  465. }
  466. Log3 $iohash, 4, "$name decoded protocolid: $protocol ($SensorTyp) sensor id=$id, Data:".substr($bitData,$vorpre + 12,12)." temp=$temp";
  467. $hum = SD_WS_bin2dec(substr($bitData,$vorpre + 24,8)); # TFA 30.3157 nur Temp, Hum = 255
  468. Log3 $iohash, 4, "$name SD_WS_WH2_8: $protocol ($SensorTyp) sensor id=$id, Data:".substr($bitData,$vorpre + 24,8)." hum=$hum";
  469. Log3 $iohash, 4, "$name SD_WS_WH2_9: $protocol ($SensorTyp) sensor id=$id, channel=$channel, temp=$temp, hum=$hum";
  470. }
  471. elsif (defined($decodingSubs{$protocol})) # durch den hash decodieren
  472. {
  473. $SensorTyp=$decodingSubs{$protocol}{sensortype};
  474. if (!$decodingSubs{$protocol}{prematch}->( $rawData ))
  475. {
  476. Log3 $iohash, 4, "$name decoded protocolid: $protocol ($SensorTyp) prematch error" ;
  477. return "";
  478. }
  479. my $retcrc=$decodingSubs{$protocol}{crcok}->( $rawData );
  480. if (!$retcrc) {
  481. Log3 $iohash, 4, "$name decoded protocolid: $protocol ($SensorTyp) crc error: $retcrc";
  482. return "";
  483. }
  484. $id=$decodingSubs{$protocol}{id}->( $rawData,$bitData );
  485. #my $temphex=$decodingSubs{$protocol}{temphex}->( $rawData,$bitData );
  486. $temp=$decodingSubs{$protocol}{temp}->( $rawData,$bitData );
  487. $hum=$decodingSubs{$protocol}{hum}->( $rawData,$bitData );
  488. $channel=$decodingSubs{$protocol}{channel}->( $rawData,$bitData );
  489. $model = $decodingSubs{$protocol}{model};
  490. $bat = $decodingSubs{$protocol}{bat}->( $rawData,$bitData );
  491. $trend = $decodingSubs{$protocol}{trend}->( $rawData,$bitData ) if (defined($decodingSubs{$protocol}{trend}));
  492. Log3 $iohash, 4, "$name decoded protocolid: $protocol ($SensorTyp) sensor id=$id, channel=$channel, temp=$temp, hum=$hum, bat=$bat";
  493. }
  494. else {
  495. Log3 $iohash, 2, "SD_WS_WH2: unknown message, please report. converted to bits: $bitData";
  496. return undef;
  497. }
  498. if (!defined($model)) {
  499. return undef;
  500. }
  501. my $deviceCode;
  502. my $longids = AttrVal($iohash->{NAME},'longids',0);
  503. if (($longids ne "0") && ($longids eq "1" || $longids eq "ALL" || (",$longids," =~ m/,$model,/)))
  504. {
  505. $deviceCode = $model . '_' . $id . $channel;
  506. Log3 $iohash,4, "$name using longid: $longids model: $model";
  507. } else {
  508. $deviceCode = $model . "_" . $channel;
  509. }
  510. #print Dumper($modules{SD_WS}{defptr});
  511. my $def = $modules{SD_WS}{defptr}{$iohash->{NAME} . "." . $deviceCode};
  512. $def = $modules{SD_WS}{defptr}{$deviceCode} if(!$def);
  513. if(!$def) {
  514. Log3 $iohash, 1, 'SD_WS: UNDEFINED sensor ' . $model . ' detected, code ' . $deviceCode;
  515. return "UNDEFINED $deviceCode SD_WS $deviceCode";
  516. }
  517. my $hash = $def;
  518. $name = $hash->{NAME};
  519. return "" if(IsIgnored($name));
  520. Log3 $name, 4, "SD_WS: $name ($rawData)";
  521. if (!defined(AttrVal($hash->{NAME},"event-min-interval",undef)))
  522. {
  523. my $minsecs = AttrVal($iohash->{NAME},'minsecs',0);
  524. if($hash->{lastReceive} && (time() - $hash->{lastReceive} < $minsecs)) {
  525. Log3 $hash, 4, "$deviceCode Dropped due to short time. minsecs=$minsecs";
  526. return "";
  527. }
  528. }
  529. $hash->{lastReceive} = time();
  530. $hash->{lastMSG} = $rawData;
  531. if (defined($bitData2)) {
  532. $hash->{bitMSG} = $bitData2;
  533. } else {
  534. $hash->{bitMSG} = $bitData;
  535. }
  536. my $state = (($temp > -60 && $temp < 70) ? "T: $temp":"T: xx") . (($hum > 0 && $hum < 100) ? " H: $hum":"");
  537. readingsBeginUpdate($hash);
  538. readingsBulkUpdate($hash, "state", $state);
  539. readingsBulkUpdate($hash, "temperature", $temp) if (defined($temp)&& ($temp > -60 && $temp < 70 ));
  540. readingsBulkUpdate($hash, "humidity", $hum) if (defined($hum) && ($hum > 0 && $hum < 100 )) ;
  541. readingsBulkUpdate($hash, "battery", $bat) if (defined($bat) && length($bat) > 0) ;
  542. readingsBulkUpdate($hash, "batteryState", $bat) if (defined($bat) && length($bat) > 0) ;
  543. readingsBulkUpdate($hash, "channel", $channel) if (defined($channel)&& length($channel) > 0);
  544. readingsBulkUpdate($hash, "trend", $trend) if (defined($trend) && length($trend) > 0);
  545. readingsEndUpdate($hash, 1); # Notify is done by Dispatch
  546. return $name;
  547. }
  548. sub SD_WS_Attr(@)
  549. {
  550. my @a = @_;
  551. # Make possible to use the same code for different logical devices when they
  552. # are received through different physical devices.
  553. return if($a[0] ne "set" || $a[2] ne "IODev");
  554. my $hash = $defs{$a[1]};
  555. my $iohash = $defs{$a[3]};
  556. my $cde = $hash->{CODE};
  557. delete($modules{SD_WS}{defptr}{$cde});
  558. $modules{SD_WS}{defptr}{$iohash->{NAME} . "." . $cde} = $hash;
  559. return undef;
  560. }
  561. sub SD_WS_bin2dec($)
  562. {
  563. my $h = shift;
  564. my $int = unpack("N", pack("B32",substr("0" x 32 . $h, -32)));
  565. return sprintf("%d", $int);
  566. }
  567. sub SD_WS_binaryToNumber
  568. {
  569. my $binstr=shift;
  570. my $fbit=shift;
  571. my $lbit=$fbit;
  572. $lbit=shift if @_;
  573. return oct("0b".substr($binstr,$fbit,($lbit-$fbit)+1));
  574. }
  575. sub SD_WS_WH2CRCCHECK($) {
  576. my $rawData = shift;
  577. my $datacheck1 = pack( 'H*', substr($rawData,2,length($rawData)-2) );
  578. my $crcmein1 = Digest::CRC->new(width => 8, poly => 0x31);
  579. my $rr3 = $crcmein1->add($datacheck1)->hexdigest;
  580. $rr3 = sprintf("%d", hex($rr3));
  581. Log3 "SD_WS_CRCCHECK", 4, "SD_WS_WH2CRCCHECK : raw:$rawData CRC=$rr3 " ;
  582. return $rr3 ;
  583. }
  584. sub SD_WS_WH2SHIFT($){
  585. my $rawData = shift;
  586. my $hlen = length($rawData);
  587. my $blen = $hlen * 4;
  588. my $bitData = unpack("B$blen", pack("H$hlen", $rawData));
  589. my $bitData2 = '1'.unpack("B$blen", pack("H$hlen", $rawData));
  590. my $bitData20 = substr($bitData2,0,length($bitData2)-1);
  591. $blen = length($bitData20);
  592. $hlen = $blen / 4;
  593. $rawData = uc(unpack("H$hlen", pack("B$blen", $bitData20)));
  594. $bitData = $bitData20;
  595. Log3 "SD_WS_WH2SHIFT", 4, "SD_WS_WH2SHIFT_0 raw: $rawData length:".length($bitData) ;
  596. Log3 "SD_WS_WH2SHIFT", 4, "SD_WS_WH2SHIFT_1 bitdata: $bitData" ;
  597. return $rawData;
  598. }
  599. 1;
  600. =pod
  601. =item summary Supports various weather stations
  602. =item summary_DE Unterst&uumltzt verschiedene Funk Wetterstationen
  603. =begin html
  604. <a name="SD_WS"></a>
  605. <h3>Weather Sensors various protocols</h3>
  606. <ul>
  607. The SD_WS module interprets temperature sensor messages received by a Device like CUL, CUN, SIGNALduino etc.<br>
  608. <br>
  609. <b>Known models:</b>
  610. <ul>
  611. <li>Bresser 7009994</li>
  612. <li>Opus XT300</li>
  613. <li>BresserTemeo</li>
  614. <li>WH2 (TFA Dostmann/Wertheim 30.3157(Temperature only!) (sold in Germany), Agimex Rosenborg 66796 (sold in Denmark),ClimeMET CM9088 (Sold in UK)</li>
  615. <li>PV-8644 infactory Poolthermometer</li>
  616. </ul>
  617. <br>
  618. New received device are add in fhem with autocreate.
  619. <br><br>
  620. <a name="SD_WS_Define"></a>
  621. <b>Define</b>
  622. <ul>The received devices created automatically.<br>
  623. The ID of the defice is the cannel or, if the longid attribute is specified, it is a combination of channel and some random generated bits at powering the sensor and the channel.<br>
  624. If you want to use more sensors, than channels available, you can use the longid option to differentiate them.
  625. </ul>
  626. <br>
  627. <a name="SD_WS Events"></a>
  628. <b>Generated readings:</b>
  629. <br>Some devices may not support all readings, so they will not be presented<br>
  630. <ul>
  631. <li>State (T: H:)</li>
  632. <li>temperature (&deg;C)</li>
  633. <li>humidity: (The humidity (1-100 if available)</li>
  634. <li>battery: (low or ok)</li>
  635. <li>channel: (The Channelnumber (number if)</li>
  636. </ul>
  637. <br>
  638. <b>Attributes</b>
  639. <ul>
  640. <li><a href="#do_not_notify">do_not_notify</a></li>
  641. <li><a href="#ignore">ignore</a></li>
  642. <li><a href="#showtime">showtime</a></li>
  643. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  644. </ul>
  645. <a name="SD_WS_Set"></a>
  646. <b>Set</b> <ul>N/A</ul><br>
  647. <a name="SD_WS_Parse"></a>
  648. <b>Set</b> <ul>N/A</ul><br>
  649. </ul>
  650. =end html
  651. =begin html_DE
  652. <a name="SD_WS"></a>
  653. <h3>SD_WS</h3>
  654. <ul>
  655. Das SD_WS Modul verarbeitet von einem IO Ger&aumlt (CUL, CUN, SIGNALDuino, etc.) empfangene Nachrichten von Temperatur-Sensoren.<br>
  656. <br>
  657. <b>Unterst&uumltzte Modelle:</b>
  658. <ul>
  659. <li>Bresser 7009994</li>
  660. <li>Opus XT300</li>
  661. <li>BresserTemeo</li>
  662. <li>WH2 (TFA Dostmann/Wertheim 30.3157(Temperatur!) (Deutschland), Agimex Rosenborg 66796 (Denmark),ClimeMET CM9088 (UK)</li>
  663. <li>PV-8644 infactory Poolthermometer</li>
  664. </ul>
  665. <br>
  666. Neu empfangene Sensoren werden in FHEM per autocreate angelegt.
  667. <br><br>
  668. <a name="SD_WS_Define"></a>
  669. <b>Define</b>
  670. <ul>Die empfangenen Sensoren werden automatisch angelegt.<br>
  671. Die ID der angelgten Sensoren ist entweder der Kanal des Sensors, oder wenn das Attribut longid gesetzt ist, dann wird die ID aus dem Kanal und einer Reihe von Bits erzeugt, welche der Sensor beim Einschalten zuf&aumlllig vergibt.<br>
  672. </ul>
  673. <br>
  674. <a name="SD_WS Events"></a>
  675. <b>Generierte Readings:</b>
  676. <ul>
  677. <li>State (T: H:)</li>
  678. <li>temperature (&deg;C)</li>
  679. <li>humidity: (Luftfeuchte (1-100)</li>
  680. <li>battery: (low oder ok)</li>
  681. <li>channel: (Der Sensor Kanal)</li>
  682. </ul>
  683. <br>
  684. <b>Attribute</b>
  685. <ul>
  686. <li><a href="#do_not_notify">do_not_notify</a></li>
  687. <li><a href="#ignore">ignore</a></li>
  688. <li><a href="#showtime">showtime</a></li>
  689. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  690. </ul>
  691. <a name="SD_WS_Set"></a>
  692. <b>Set</b> <ul>N/A</ul><br>
  693. <a name="SD_WS_Parse"></a>
  694. <b>Set</b> <ul>N/A</ul><br>
  695. </ul>
  696. =end html_DE
  697. =cut