36_LaCrosseGateway.pm 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025
  1. # $Id: 36_LaCrosseGateway.pm 17131 2018-08-12 19:01:28Z HCS $
  2. package main;
  3. use strict;
  4. use warnings;
  5. use Time::HiRes qw(gettimeofday);
  6. use Time::Local;
  7. my $clients = ":PCA301:EC3000:LaCrosse:Level:EMT7110:KeyValueProtocol:CapacitiveLevel";
  8. my %matchList = (
  9. "1:PCA301" => "^\\S+\\s+24",
  10. "2:EC3000" => "^\\S+\\s+22",
  11. "3:LaCrosse" => "^(\\S+\\s+9 |OK\\sWS\\s)",
  12. "4:EMT7110" => "^OK\\sEMT7110\\s",
  13. "5:Level" => "^OK\\sLS\\s",
  14. "6:KeyValueProtocol" => "^OK\\sVALUES\\s",
  15. "7:CapacitiveLevel" => "^OK\\sCL\\s",
  16. );
  17. sub LaCrosseGateway_Initialize($) {
  18. my ($hash) = @_;
  19. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  20. $hash->{ReadFn} = "LaCrosseGateway_Read";
  21. $hash->{WriteFn} = "LaCrosseGateway_Write";
  22. $hash->{ReadyFn} = "LaCrosseGateway_Ready";
  23. $hash->{NotifyFn} = "LaCrosseGateway_Notify";
  24. $hash->{DefFn} = "LaCrosseGateway_Define";
  25. $hash->{FingerprintFn} = "LaCrosseGateway_Fingerprint";
  26. $hash->{UndefFn} = "LaCrosseGateway_Undef";
  27. $hash->{SetFn} = "LaCrosseGateway_Set";
  28. $hash->{AttrFn} = "LaCrosseGateway_Attr";
  29. $hash->{AttrList} = " Clients"
  30. ." MatchList"
  31. ." dummy"
  32. ." initCommands"
  33. ." timeout"
  34. ." watchdog"
  35. ." disable:0,1"
  36. ." tftFile"
  37. ." kvp:dispatch,readings,both"
  38. ." loopTimeReadings:1,0"
  39. ." ownSensors:dispatch,readings,both"
  40. ." mode:USB,WiFi,Cable"
  41. ." usbFlashCommand"
  42. ." filter"
  43. ." $readingFnAttributes";
  44. }
  45. #=======================================================================================
  46. sub LaCrosseGateway_Fingerprint($$) {
  47. }
  48. #=======================================================================================
  49. sub LaCrosseGateway_Notify($$) {
  50. my ($hash, $source_hash) = @_;
  51. my $name = $hash->{NAME};
  52. my $sourceName = $source_hash->{NAME};
  53. my $events = deviceEvents($source_hash, 1);
  54. if($sourceName eq "global" && grep(m/^INITIALIZED|REREADCFG$/, @{$events})) {
  55. LaCrosseGateway_Connect($hash)
  56. }
  57. }
  58. #=======================================================================================
  59. sub LaCrosseGateway_Define($$) {my ($hash, $def) = @_;
  60. my @a = split("[ \t][ \t]*", $def);
  61. if(@a != 3) {
  62. my $msg = "wrong syntax: define <name> LaCrosseGateway {none | devicename[\@baudrate] | devicename\@directio | hostname:port}";
  63. Log3 undef, 2, $msg;
  64. return $msg;
  65. }
  66. DevIo_CloseDev($hash);
  67. my $name = $a[0];
  68. my $dev = $a[2];
  69. $hash->{Clients} = $clients;
  70. $hash->{MatchList} = \%matchList;
  71. $hash->{TIMEOUT} = 0.5;
  72. if( !defined( $attr{$name}{usbFlashCommand} ) ) {
  73. $attr{$name}{usbFlashCommand} = "./FHEM/firmware/esptool.py -b 921600 -p [PORT] write_flash -ff 80m -fm dio -fs 4MB-c1 0x00000 [BINFILE] > [LOGFILE]"
  74. }
  75. if($dev eq "none") {
  76. Log3 $name, 1, "$name device is none, commands will be echoed only";
  77. $attr{$name}{dummy} = 1;
  78. return undef;
  79. }
  80. $dev .= "\@57600" if( $dev !~ m/\@/ && $def !~ m/:/ );
  81. $hash->{DeviceName} = $dev;
  82. LaCrosseGateway_Connect($hash) if($init_done);
  83. return undef;
  84. }
  85. #=======================================================================================
  86. sub LaCrosseGateway_Undef($$) {
  87. my ($hash, $arg) = @_;
  88. my $name = $hash->{NAME};
  89. BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID}));
  90. if($hash->{STATE} ne "disconnected") {
  91. DevIo_CloseDev($hash);
  92. }
  93. return undef;
  94. }
  95. #=======================================================================================
  96. sub LaCrosseGateway_RemoveLaCrossePair($) {
  97. my $hash = shift;
  98. delete($hash->{LaCrossePair});
  99. }
  100. #=======================================================================================
  101. sub LaCrosseGateway_LogOTA($) {
  102. my($text) = @_;
  103. BlockingInformParent("DoTrigger", ["global", " $text"], 0);
  104. }
  105. #=======================================================================================
  106. sub LaCrosseGateway_StartUpload($) {
  107. my ($string) = @_;
  108. my ($name, $argument) = split("\\|", $string);
  109. my $hash = $defs{$name};
  110. sleep(2);
  111. LaCrosseGateway_LogOTA("Started not blocking");
  112. my @deviceName = split('@', $hash->{DeviceName});
  113. my $port = $deviceName[0];
  114. my $logFile = AttrVal("global", "logdir", "./log") . "/LaCrosseGatewayFlash.log";
  115. my $hexFile;
  116. if($hash->{model} =~ m/^\[LaCrosseGateway32 V/) {
  117. $hexFile = "./FHEM/firmware/LaCrosseGateway32.bin";
  118. }
  119. else {
  120. $hexFile = "./FHEM/firmware/JeeLink_LaCrosseGateway.bin";
  121. }
  122. if(!-e $hexFile) {
  123. LaCrosseGateway_LogOTA("The file '$hexFile' does not exist");
  124. return $name;
  125. }
  126. LaCrosseGateway_LogOTA("flashing LaCrosseGateway $name");
  127. LaCrosseGateway_LogOTA("hex file: $hexFile");
  128. if(AttrVal($name, "mode", "WiFi") eq "WiFi") {
  129. eval "use LWP::UserAgent";
  130. if($@) {
  131. LaCrosseGateway_LogOTA("ERROR: Please install LWP::UserAgent");
  132. return $name;
  133. }
  134. eval "use HTTP::Request::Common";
  135. if($@) {
  136. LaCrosseGateway_LogOTA("ERROR: Please install HTTP::Request::Common");
  137. return $name;
  138. }
  139. LaCrosseGateway_LogOTA("Mode is LaCrosseGateway OTA-update");
  140. DevIo_CloseDev($hash);
  141. readingsSingleUpdate($hash, "state", "disconnected", 1);
  142. LaCrosseGateway_LogOTA("$name closed");
  143. my @spl = split(':', $hash->{DeviceName});
  144. my $targetIP = $spl[0];
  145. my $targetURL = "http://" . $targetIP . "/ota/firmware.bin";
  146. LaCrosseGateway_LogOTA("target: $targetURL");
  147. my $request = POST($targetURL, Content_Type => 'multipart/form-data', Content => [ file => [$hexFile, "firmware.bin"] ]);
  148. my $userAgent = LWP::UserAgent->new;
  149. $userAgent->timeout(120);
  150. LaCrosseGateway_LogOTA("Upload started, please wait a minute or two ...");
  151. my $response = $userAgent->request($request);
  152. if ($response->is_success) {
  153. LaCrosseGateway_LogOTA("");
  154. LaCrosseGateway_LogOTA("--- LGW reports ---------------------------------------------------------------------------");
  155. my @lines = split /\r\n|\n|\r/, $response->decoded_content;
  156. foreach (@lines) {
  157. LaCrosseGateway_LogOTA($_);
  158. }
  159. LaCrosseGateway_LogOTA("----------------------------------------------------------------------------------------------------");
  160. }
  161. else {
  162. LaCrosseGateway_LogOTA("");
  163. LaCrosseGateway_LogOTA("ERROR: " . $response->code);
  164. my @lines = split /\r\n|\n|\r/, $response->decoded_content;
  165. foreach (@lines) {
  166. LaCrosseGateway_LogOTA($_);
  167. }
  168. }
  169. }
  170. else {
  171. LaCrosseGateway_LogOTA("Mode is LaCrosseGateway USB-update");
  172. my $usbFlashCommand = AttrVal($name, "usbFlashCommand", "");
  173. if ($usbFlashCommand ne "") {
  174. my $command = $usbFlashCommand;
  175. $command =~ s/\Q[PORT]\E/$port/g;
  176. $command =~ s/\Q[BINFILE]\E/$hexFile/g;
  177. $command =~ s/\Q[LOGFILE]\E/$logFile/g;
  178. LaCrosseGateway_LogOTA("");
  179. LaCrosseGateway_LogOTA("Upload started, please wait a minute or two ...");
  180. `$command`;
  181. if (-e $logFile) {
  182. LaCrosseGateway_LogOTA("");
  183. LaCrosseGateway_LogOTA("--- esptool ---------------------------------------------------------------------------------");
  184. local $/ = undef;
  185. open FILE, $logFile;
  186. my $content = <FILE>;
  187. my @lines = split /\r\n|\n|\r/, $content;
  188. foreach (@lines) {
  189. LaCrosseGateway_LogOTA($_);
  190. }
  191. LaCrosseGateway_LogOTA("--- esptool ---------------------------------------------------------------------------------");
  192. LaCrosseGateway_LogOTA("");
  193. }
  194. else {
  195. LaCrosseGateway_LogOTA("WARNING: esptool created no log file");
  196. }
  197. }
  198. else {
  199. LaCrosseGateway_LogOTA("No usbFlashCommand found. Please define this attribute.");
  200. }
  201. }
  202. return $name;
  203. }
  204. #=======================================================================================
  205. sub LaCrosseGateway_UploadDone($) {
  206. my ($name) = @_;
  207. return unless(defined($name));
  208. my $hash = $defs{$name};
  209. delete($hash->{helper}{RUNNING_PID});
  210. delete($hash->{helper}{FLASHING});
  211. LaCrosseGateway_Connect($hash);
  212. LaCrosseGateway_LogOTA("$name opened");
  213. LaCrosseGateway_LogOTA("Finshed");
  214. }
  215. #=======================================================================================
  216. sub LaCrosseGateway_UploadError($) {
  217. my ($hash) = @_;
  218. my $name = $hash->{NAME};
  219. delete($hash->{helper}{RUNNING_PID});
  220. delete($hash->{helper}{FLASHING});
  221. LaCrosseGateway_LogOTA("Upload failed");
  222. LaCrosseGateway_Connect($hash);
  223. LaCrosseGateway_LogOTA("$name opened");
  224. }
  225. #=======================================================================================
  226. sub LaCrosseGateway_StartNextion($) {
  227. my ($string) = @_;
  228. my ($name, $argument) = split("\\|", $string);
  229. my $hash = $defs{$name};
  230. sleep(2);
  231. LaCrosseGateway_LogOTA("Started not blocking");
  232. my @deviceName = split('@', $hash->{DeviceName});
  233. my $port = $deviceName[0];
  234. my $logFile = AttrVal("global", "logdir", "./log") . "/NextionUpload.log";
  235. my $tftFile = AttrVal($name, "tftFile", "./FHEM/firmware/nextion.tft");
  236. if(!-e $tftFile) {
  237. LaCrosseGateway_LogOTA("The file '$tftFile' does not exist");
  238. return $name;
  239. }
  240. LaCrosseGateway_LogOTA("upload Nextion firmware to $name");
  241. LaCrosseGateway_LogOTA("tft file: $tftFile");
  242. eval "use LWP::UserAgent";
  243. if($@) {
  244. LaCrosseGateway_LogOTA("ERROR: Please install LWP::UserAgent");
  245. return $name;
  246. }
  247. eval "use HTTP::Request::Common";
  248. if($@) {
  249. LaCrosseGateway_LogOTA("ERROR: Please install HTTP::Request::Common");
  250. return $name;
  251. }
  252. my @spl = split(':', $hash->{DeviceName});
  253. my $targetIP = $spl[0];
  254. my $targetURL = "http://" . $targetIP . "/ota/nextion";
  255. LaCrosseGateway_LogOTA("target: $targetURL");
  256. my $request = POST($targetURL, Content_Type => 'multipart/form-data', Content => [ file => [$tftFile, "nextion.tft"] ]);
  257. my $userAgent = LWP::UserAgent->new;
  258. $userAgent->timeout(900);
  259. LaCrosseGateway_LogOTA("");
  260. LaCrosseGateway_LogOTA("Upload started, this can take 10 minutes or more ...");
  261. my $response = $userAgent->request($request);
  262. if ($response->is_success) {
  263. LaCrosseGateway_LogOTA("");
  264. LaCrosseGateway_LogOTA("--- LGW reports ---------------------------------------------------------------------------");
  265. my @lines = split /\r\n|\n|\r/, $response->decoded_content;
  266. foreach (@lines) {
  267. LaCrosseGateway_LogOTA($_);
  268. }
  269. LaCrosseGateway_LogOTA("----------------------------------------------------------------------------------------------------");
  270. }
  271. else {
  272. LaCrosseGateway_LogOTA("");
  273. LaCrosseGateway_LogOTA("ERROR: " . $response->code);
  274. my @lines = split /\r\n|\n|\r/, $response->decoded_content;
  275. foreach (@lines) {
  276. LaCrosseGateway_LogOTA($_);
  277. }
  278. }
  279. return $name;
  280. }
  281. #=======================================================================================
  282. sub LaCrosseGateway_Set($@) {
  283. my ($hash, @a) = @_;
  284. my $name = shift @a;
  285. my $cmd = shift @a;
  286. my $arg = join(" ", @a);
  287. my $list = "raw connect LaCrossePairForSec flash nextionUpload parse reboot";
  288. return $list if( $cmd eq '?' || $cmd eq '');
  289. if ($cmd eq "raw") {
  290. Log3 $name, 4, "set $name $cmd $arg";
  291. LaCrosseGateway_SimpleWrite($hash, $arg);
  292. }
  293. elsif ($cmd eq "flash") {
  294. CallFn("WEB", "ActivateInformFn", $hash, "global");
  295. DevIo_CloseDev($hash);
  296. readingsSingleUpdate($hash, "state", "disconnected", 1);
  297. $hash->{helper}{FLASHING} = 1;
  298. $hash->{helper}{RUNNING_PID} = BlockingCall("LaCrosseGateway_StartUpload", $name . "|" . $arg, "LaCrosseGateway_UploadDone", 300, "LaCrosseGateway_UploadError", $hash);
  299. return undef;
  300. }
  301. elsif ($cmd eq "nextionUpload") {
  302. CallFn("WEB", "ActivateInformFn", $hash, "global");
  303. DevIo_CloseDev($hash);
  304. readingsSingleUpdate($hash, "state", "disconnected", 1);
  305. $hash->{helper}{FLASHING} = 1;
  306. $hash->{helper}{RUNNING_PID} = BlockingCall("LaCrosseGateway_StartNextion", $name . "|" . $arg, "LaCrosseGateway_UploadDone", 1200, "LaCrosseGateway_UploadError", $hash);
  307. return undef;
  308. }
  309. elsif ($cmd eq "LaCrossePairForSec") {
  310. my @args = split(' ', $arg);
  311. return "Usage: set $name LaCrossePairForSec <seconds_active> [ignore_battery]" if(!$arg || $args[0] !~ m/^\d+$/ || ($args[1] && $args[1] ne "ignore_battery") );
  312. $hash->{LaCrossePair} = $args[1]?2:1;
  313. InternalTimer(gettimeofday()+$args[0], "LaCrosseGateway_RemoveLaCrossePair", $hash, 0);
  314. }
  315. elsif ($cmd eq "connect") {
  316. DevIo_CloseDev($hash);
  317. return LaCrosseGateway_Connect($hash);
  318. }
  319. elsif ($cmd eq "reboot") {
  320. if(AttrVal($name, "mode", "WiFi") eq "WiFi") {
  321. LaCrosseGateway_SimpleWrite($hash, "8377e\n");
  322. }
  323. else {
  324. my $po = $hash->{USBDev};
  325. $po->rts_active(0);
  326. $po->dtr_active(0);
  327. select undef, undef, undef, 0.01;
  328. $po->rts_active(1);
  329. $po->dtr_active(1);
  330. }
  331. LaCrosseGateway_Connect($hash);
  332. }
  333. elsif ($cmd eq "parse") {
  334. LaCrosseGateway_Parse($hash, $hash, $name, $arg);
  335. }
  336. else {
  337. return "Unknown argument $cmd, choose one of ".$list;
  338. }
  339. return undef;
  340. }
  341. #=======================================================================================
  342. sub LaCrosseGateway_OnInitTimer($) {
  343. my ($hash) = @_;
  344. my $name = $hash->{NAME};
  345. LaCrosseGateway_SimpleWrite($hash, "v\n");
  346. }
  347. #=======================================================================================
  348. sub LaCrosseGateway_DoInit($) {
  349. my $hash = shift;
  350. my $name = $hash->{NAME};
  351. my $enabled = AttrVal($name, "disable", "0") != "1" && !defined($hash->{helper}{FLASHING});
  352. if($enabled) {
  353. readingsSingleUpdate($hash, "state", "opened", 1);
  354. if(AttrVal($name, "mode", "") ne "USB") {
  355. InternalTimer(gettimeofday() +3, "LaCrosseGateway_OnInitTimer", $hash, 1);
  356. }
  357. }
  358. else {
  359. readingsSingleUpdate($hash, "state", "disabled", 1);
  360. }
  361. return undef;
  362. }
  363. #=======================================================================================
  364. sub LaCrosseGateway_Ready($) {
  365. my ($hash) = @_;
  366. my $name = $hash->{NAME};
  367. LaCrosseGateway_Connect($hash, 1);
  368. # This is relevant for windows/USB only
  369. my $po = $hash->{USBDev};
  370. my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags);
  371. if($po) {
  372. ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
  373. }
  374. return ($InBytes && $InBytes>0);
  375. }
  376. #=======================================================================================
  377. sub LaCrosseGateway_Write($$) {
  378. my ($hash, $cmd, $msg) = @_;
  379. my $name = $hash->{NAME};
  380. my $arg = $cmd;
  381. $arg .= " " . $msg if(defined($msg));
  382. LaCrosseGateway_SimpleWrite($hash, $arg);
  383. }
  384. #=======================================================================================
  385. sub LaCrosseGateway_Read($) {
  386. my ($hash) = @_;
  387. my $name = $hash->{NAME};
  388. my $buf = DevIo_SimpleRead($hash);
  389. return "" if(!defined($buf));
  390. my $data = $hash->{PARTIAL};
  391. $data .= $buf;
  392. while($data =~ m/\n/) {
  393. my $rmsg;
  394. ($rmsg,$data) = split("\n", $data, 2);
  395. $rmsg =~ s/\r//;
  396. LaCrosseGateway_Parse($hash, $hash, $name, $rmsg) if($rmsg);
  397. }
  398. $hash->{PARTIAL} = $data;
  399. }
  400. #=======================================================================================
  401. sub LaCrosseGateway_DeleteKVPReadings($) {
  402. my ($hash) = @_;
  403. delete $hash->{READINGS}{"FramesPerMinute"};
  404. delete $hash->{READINGS}{"RSSI"};
  405. delete $hash->{READINGS}{"UpTime"};
  406. }
  407. #=======================================================================================
  408. sub LaCrosseGateway_HandleKVP($$) {
  409. my ($hash, $kvp) = @_;
  410. my $name = $hash->{NAME};
  411. $kvp .= ",";
  412. readingsBeginUpdate($hash);
  413. if($kvp =~ m/UpTimeText=(.*?)(\,|\ ,)/) {
  414. readingsBulkUpdate($hash, "UpTime", $1);
  415. }
  416. if($kvp =~ m/UpTimeSeconds=(.*?)(\,|\ ,)/) {
  417. readingsBulkUpdate($hash, "UpTimeSeconds", $1);
  418. }
  419. if($kvp =~ m/RSSI=(.*?)(\,|\ ,)/) {
  420. readingsBulkUpdate($hash, "RSSI", $1);
  421. }
  422. if($kvp =~ m/FramesPerMinute=(.*?)(\,|\ ,)/) {
  423. readingsBulkUpdate($hash, "FramesPerMinute", $1);
  424. }
  425. if($kvp =~ m/ReceivedFrames=(.*?)(\,|\ ,)/) {
  426. readingsBulkUpdate($hash, "ReceivedFrames", $1);
  427. }
  428. if($kvp =~ m/FreeHeap=(.*?)(\,|\ ,)/) {
  429. readingsBulkUpdate($hash, "FreeHeap", $1);
  430. }
  431. if($kvp =~ m/OLED=(.*?)(\,|\ ,)/) {
  432. readingsBulkUpdate($hash, "OLED", $1);
  433. }
  434. if($kvp =~ m/CPU-Temperature=(.*?)(\,|\ ,)/) {
  435. readingsBulkUpdate($hash, "CPU-Temperature", $1);
  436. }
  437. if(AttrVal($name, "loopTimeReadings", "0") == "1") {
  438. if($kvp =~ m/LD\.Min=(.*?)(\,|\ ,)/) {
  439. readingsBulkUpdate($hash, "LD.Min", $1);
  440. }
  441. if($kvp =~ m/LD\.Avg=(.*?)(\,|\ ,)/) {
  442. readingsBulkUpdate($hash, "LD.Avg", $1);
  443. }
  444. if($kvp =~ m/LD\.Max=(.*?)(\,|\ ,)/) {
  445. readingsBulkUpdate($hash, "LD.Max", $1);
  446. }
  447. }
  448. readingsEndUpdate($hash, 1);
  449. }
  450. #=======================================================================================
  451. sub LaCrosseGateway_DeleteOwnSensorsReadings($) {
  452. my ($hash) = @_;
  453. delete $hash->{READINGS}{"temperature"};
  454. delete $hash->{READINGS}{"humidity"};
  455. delete $hash->{READINGS}{"pressure"};
  456. delete $hash->{READINGS}{"gas"};
  457. delete $hash->{READINGS}{"debug"};
  458. }
  459. #=======================================================================================
  460. sub LaCrosseGateway_HandleOwnSensors($$) {
  461. my ($hash, $data) = @_;
  462. readingsBeginUpdate($hash);
  463. my @bytes = split( ' ', substr($data, 5) );
  464. return "" if(@bytes < 14);
  465. my $temperature = undef;
  466. my $humidity = undef;
  467. my $pressure = undef;
  468. my $gas = undef;
  469. my $debug = undef;
  470. if($bytes[2] != 0xFF) {
  471. $temperature = ($bytes[2]*256 + $bytes[3] - 1000)/10;
  472. readingsBulkUpdate($hash, "temperature", $temperature);
  473. }
  474. if($bytes[4] != 0xFF) {
  475. $humidity = $bytes[4];
  476. readingsBulkUpdate($hash, "humidity", $humidity);
  477. }
  478. if(@bytes >= 16 && $bytes[14] != 0xFF) {
  479. $pressure = $bytes[14] * 256 + $bytes[15];
  480. $pressure /= 10.0 if $pressure > 5000;
  481. readingsBulkUpdate($hash, "pressure", $pressure);
  482. }
  483. if(@bytes >= 19 && $bytes[16] != 0xFF) {
  484. $gas = $bytes[16] * 65536 + $bytes[17] * 256 + $bytes[18];
  485. readingsBulkUpdate($hash, "gas", $gas);
  486. }
  487. if(@bytes >= 22 && $bytes[19] != 0xFF) {
  488. $debug = $bytes[19] * 65536 + $bytes[20] * 256 + $bytes[21];
  489. readingsBulkUpdate($hash, "debug", $debug);
  490. }
  491. readingsEndUpdate($hash, 1);
  492. delete $hash->{READINGS}{"temperature"} if(!defined($temperature));
  493. delete $hash->{READINGS}{"humidity"} if(!defined($humidity));
  494. delete $hash->{READINGS}{"pressure"} if(!defined($pressure));
  495. delete $hash->{READINGS}{"gas"} if(!defined($gas));
  496. delete $hash->{READINGS}{"debug"} if(!defined($debug));
  497. }
  498. #=======================================================================================
  499. sub LaCrosseGateway_HandleAnalogData($$) {
  500. my ($hash, $data) = @_;
  501. if ($data =~ m/^LGW ANALOG /) {
  502. readingsBeginUpdate($hash);
  503. my @bytes = split( ' ', substr($data, 10) );
  504. return "" if(@bytes < 2);
  505. my $value = $bytes[0]*256 + $bytes[1];
  506. readingsBulkUpdate($hash, "analog", $value);
  507. readingsEndUpdate($hash, 1);
  508. }}
  509. #=======================================================================================
  510. sub LaCrosseGateway_Parse($$$$) {
  511. my ($hash, $iohash, $name, $msg) = @_;
  512. next if (!$msg || length($msg) < 1);
  513. return if ($msg =~ m/^\*\*\*CLEARLOG/);
  514. return if ($msg =~ m/[^\x20-\x7E]/);
  515. my $filter = AttrVal($name, "filter", undef);
  516. if(defined($filter)) {
  517. return if ($msg =~ m/$filter/);
  518. }
  519. if ($msg =~ m/^LGW/) {
  520. if ($msg =~ /ALIVE/) {
  521. $hash->{Alive} = TimeNow();
  522. }
  523. LaCrosseGateway_HandleAnalogData($hash, $msg);
  524. return;
  525. }
  526. if($msg =~ m/^\[LaCrosseITPlusReader.Gateway|\[LaCrosseGateway32 V/) {
  527. my $model = "";
  528. my $version = "";
  529. my $settings = "";
  530. if($msg =~ m/^\[LaCrosseGateway32 V/) {
  531. ($model, $version, $settings) = split(/ /, $msg, 3);
  532. $model .= " $version";
  533. }
  534. else {
  535. ($model, $settings) = split(/ /, $msg, 2);
  536. }
  537. $model = substr($model, 1);
  538. $hash->{model} = $model;
  539. $hash->{settings} = $settings;
  540. my $attrVal = AttrVal($name, "timeout", undef);
  541. if(defined($attrVal)) {
  542. my ($timeout, $interval) = split(',', $attrVal);
  543. if (!$interval) {
  544. $hash->{Alive} = TimeNow();
  545. }
  546. }
  547. if (ReadingsVal($name, "state", "") eq "opened") {
  548. if (my $initCommandsString = AttrVal($name, "initCommands", undef)) {
  549. my @initCommands = split(' ', $initCommandsString);
  550. foreach my $command (@initCommands) {
  551. LaCrosseGateway_SimpleWrite($hash, $command);
  552. }
  553. }
  554. readingsSingleUpdate($hash, "state", "initialized", 1);
  555. }
  556. return;
  557. }
  558. $hash->{"${name}_MSGCNT"}++;
  559. $hash->{"${name}_TIME"} = TimeNow();
  560. readingsSingleUpdate($hash, "state", $hash->{READINGS}{state}{VAL}, 0);
  561. $hash->{RAWMSG} = $msg;
  562. if($msg =~ m/^OK WS \d{1,3} 4 /) {
  563. my $osa = AttrVal($name, "ownSensors", "dispatch");
  564. if($osa eq "readings") {
  565. LaCrosseGateway_HandleOwnSensors($hash, $msg);
  566. return;
  567. }
  568. elsif ($osa eq "both") {
  569. LaCrosseGateway_HandleOwnSensors($hash, $msg);
  570. }
  571. else {
  572. LaCrosseGateway_DeleteOwnSensorsReadings($hash);
  573. }
  574. }
  575. if($msg =~ m/^OK VALUES LGW/) {
  576. my $osa = AttrVal($name, "kvp", "dispatch");
  577. if($osa eq "readings") {
  578. LaCrosseGateway_HandleKVP($hash, $msg);
  579. return;
  580. }
  581. elsif ($osa eq "both") {
  582. LaCrosseGateway_HandleKVP($hash, $msg);
  583. }
  584. else {
  585. LaCrosseGateway_DeleteKVPReadings($hash);
  586. }
  587. return if AttrVal($name, "dispatchKVP", "1") ne "1";
  588. }
  589. Dispatch($hash, $msg, "");
  590. }
  591. #=======================================================================================
  592. sub LaCrosseGateway_SimpleWrite(@) {
  593. my ($hash, $msg, $nocr) = @_;
  594. return if(!$hash);
  595. my $name = $hash->{NAME};
  596. Log3 $name, 5, "SW: $msg";
  597. $msg .= "\n" unless($nocr);
  598. $hash->{USBDev}->write($msg) if($hash->{USBDev});
  599. syswrite($hash->{TCPDev}, $msg) if($hash->{TCPDev});
  600. syswrite($hash->{DIODev}, $msg) if($hash->{DIODev});
  601. # Some linux installations are broken with 0.001, T01 returns no answer
  602. select(undef, undef, undef, 0.01);
  603. }
  604. #=======================================================================================
  605. sub LaCrosseGateway_Connect($;$) {
  606. my ($hash, $mode) = @_;
  607. my $name = $hash->{NAME};
  608. DevIo_CloseDev($hash);
  609. $mode = 0 if!($mode);
  610. my $enabled = AttrVal($name, "disable", "0") != "1" && !defined($hash->{helper}{FLASHING});
  611. if($enabled) {
  612. $hash->{nextOpenDelay} = 2;
  613. my $ret = DevIo_OpenDev($hash, $mode, "LaCrosseGateway_DoInit");
  614. return $ret;
  615. }
  616. return undef;
  617. }
  618. #=======================================================================================
  619. sub LaCrosseGateway_TriggerWatchdog($) {
  620. my ($hash) = @_;
  621. my $name = $hash->{NAME};
  622. my $watchDog = "";
  623. my $watchDogAttribute = AttrVal($name, "watchdog", undef);
  624. if($watchDogAttribute) {
  625. $watchDog = "=$watchDogAttribute"
  626. }
  627. my $command = "\"WATCHDOG Ping$watchDog\"";
  628. LaCrosseGateway_SimpleWrite($hash, $command);
  629. }
  630. #=======================================================================================
  631. sub LaCrosseGateway_OnConnectTimer($) {
  632. my ($hash) = @_;
  633. my $name = $hash->{NAME};
  634. RemoveInternalTimer($hash, "LaCrosseGateway_OnConnectTimer");
  635. my $attrVal = AttrVal($name, "timeout", undef);
  636. if(defined($attrVal)) {
  637. my ($timeout, $interval) = split(',', $attrVal);
  638. my $useOldMethod = $interval;
  639. $interval = $timeout if !$interval;
  640. InternalTimer(gettimeofday() + $interval, "LaCrosseGateway_OnConnectTimer", $hash, 0);
  641. if(AttrVal($name, "disable", "0") != "1" && !defined($hash->{helper}{FLASHING})) {
  642. my ($date, $time, $year, $month, $day, $hour, $min, $sec, $timestamp, $alive);
  643. if($useOldMethod) {
  644. $alive = InternalVal($name, "${name}_TIME", "2000-01-01 00:00:00");
  645. }
  646. else {
  647. LaCrosseGateway_TriggerWatchdog($hash);
  648. $timeout += 5;
  649. $alive = $hash->{Alive};
  650. $alive = "2000-01-01 00:00:00" if !$alive;
  651. }
  652. ($date, $time) = split( ' ', $alive);
  653. ($year, $month, $day) = split( '-', $date);
  654. ($hour, $min, $sec) = split( ':', $time);
  655. $month -= 01;
  656. $timestamp = timelocal($sec, $min, $hour, $day, $month, $year);
  657. if (gettimeofday() - $timestamp > $timeout) {
  658. return LaCrosseGateway_Connect($hash, 1);
  659. }
  660. }
  661. }
  662. }
  663. #=======================================================================================
  664. sub LaCrosseGateway_Attr(@) {
  665. my ($cmd,$name,$aName,$aVal) = @_;
  666. my $hash = $defs{$name};
  667. if( $aName eq "Clients" ) {
  668. $hash->{Clients} = $aVal;
  669. $hash->{Clients} = $clients if( !$hash->{Clients});
  670. }
  671. elsif ($aName eq "timeout") {
  672. return "Usage: attr $name $aName <checkInterval>" if($aVal && $aVal !~ m/^[0-9]{1,6}(,[0-9]{1,6})*/);
  673. RemoveInternalTimer($hash, "LaCrosseGateway_OnConnectTimer");
  674. if($aVal) {
  675. LaCrosseGateway_TriggerWatchdog($hash);
  676. my ($timeout, $interval) = split(',', $aVal);
  677. $interval = $timeout if !$interval;
  678. InternalTimer(gettimeofday()+$interval, "LaCrosseGateway_OnConnectTimer", $hash, 0);
  679. }
  680. }
  681. elsif ($aName eq "disable") {
  682. if($aVal eq "1") {
  683. DevIo_CloseDev($hash);
  684. readingsSingleUpdate($hash, "state", "disabled", 1);
  685. $hash->{"RAWMSG"} = "";
  686. $hash->{"model"} = "";
  687. }
  688. else {
  689. if($hash->{READINGS}{state}{VAL} eq "disabled") {
  690. readingsSingleUpdate($hash, "state", "disconnected", 1);
  691. InternalTimer(gettimeofday()+1, "LaCrosseGateway_Connect", $hash, 0);
  692. }
  693. }
  694. }
  695. elsif ($aName eq "MatchList") {
  696. my $match_list;
  697. if( $cmd eq "set" ) {
  698. $match_list = eval $aVal;
  699. if( $@ ) {
  700. Log3 $name, 2, $name .": $aVal: ". $@;
  701. }
  702. }
  703. if (ref($match_list) eq 'HASH') {
  704. $hash->{MatchList} = $match_list;
  705. }
  706. else {
  707. $hash->{MatchList} = \%matchList;
  708. Log3 $name, 2, $name .": $aVal: not a HASH" if( $aVal );
  709. }
  710. }
  711. elsif ($aName eq "ownSensors" && $aVal eq "dispatch") {
  712. LaCrosseGateway_DeleteOwnSensorsReadings($hash);
  713. }
  714. elsif ($aName eq "kvp" && $aVal eq "dispatch") {
  715. LaCrosseGateway_DeleteKVPReadings($hash);
  716. }
  717. return undef;
  718. }
  719. #=======================================================================================
  720. 1;
  721. =pod
  722. =item summary The IODevice for the LaCrosseGateway
  723. =item summary_DE Das IODevice für das LaCrosseGateway
  724. =begin html
  725. <a name="LaCrosseGateway"></a>
  726. <h3>LaCrosseGateway</h3>
  727. <ul>
  728. For more information about the LaCrosseGateway see here: <a href="http://www.fhemwiki.de/wiki/LaCrosseGateway">FHEM wiki</a>
  729. <br><br>
  730. <a name="LaCrosseGateway_Define"></a>
  731. <b>Define</b>
  732. <ul>
  733. <code>define &lt;name&gt; LaCrosseGateway &lt;device&gt;</code> <br>
  734. <br>
  735. USB-connected devices:<br><ul>
  736. &lt;device&gt; specifies the serial port to communicate with the LaCrosseGateway.
  737. The name of the serial-device depends on your distribution, under
  738. linux it is something like /dev/ttyACM0 or /dev/ttyUSB0.<br><br>
  739. </ul>
  740. Network-connected devices:<br><ul>
  741. &lt;device&gt; specifies the network device<br>
  742. Normally this is the IP-address and the port in the form ip:port<br>
  743. Example: 192.168.1.100:81<br>
  744. You must define the port number on the setup page of the LaCrosseGateway and use the same number here.<br>
  745. The default is 81
  746. <br><br>
  747. </ul>
  748. <br>
  749. </ul>
  750. <a name="LaCrosseGateway_Set"></a>
  751. <b>Set</b>
  752. <ul>
  753. <li>raw &lt;data&gt;<br>
  754. send &lt;data&gt; to the LaCrosseGateway. The possible command can be found in the wiki.
  755. </li><br>
  756. <li>connect<br>
  757. tries to (re-)connect to the LaCrosseGateway. It does not reset the LaCrosseGateway but only try to get a connection to it.
  758. </li><br>
  759. <li>reboot<br>
  760. Reboots the ESP8266. Works only if we are connected (state is opened or initialized)
  761. </li><br>
  762. <li>LaCrossePairForSec &lt;sec&gt; [ignore_battery]<br>
  763. enable autocreate of new LaCrosse sensors for &lt;sec&gt; seconds. If ignore_battery is not given only sensors
  764. sending the 'new battery' flag will be created.
  765. </li><br>
  766. <li>flash<br>
  767. The LaCrosseGateway needs the right firmware to be able to receive and deliver the sensor data to fhem.<br>
  768. This provides a way to flash it directly from FHEM.
  769. </li><br>
  770. <li>nextionUpload<br>
  771. Requires LaCrosseGateway V1.24 or newer.<br>
  772. Sends a Nextion firmware file (.tft) to the LaCrosseGateway. The LaCrosseGateway then distributes it to a connected Nextion display.<br>
  773. You can define the .tft file that shall be uploaded in the tftFile attribute. If this attribute does not exists, it will try to use FHEM/firmware/nextion.tft
  774. </li><br>
  775. </ul>
  776. <a name="LaCrosseGateway_Get"></a>
  777. <b>Get</b>
  778. <ul>
  779. ---
  780. </ul>
  781. <br>
  782. <a name="LaCrosseGateway_Attr"></a>
  783. <b>Attributes</b>
  784. <ul>
  785. <li>Clients<br>
  786. The received data gets distributed to a client (e.g. LaCrosse, EMT7110, ...) that handles the data.
  787. This attribute tells, which are the clients, that handle the data. If you add a new module to FHEM, that shall handle
  788. data distributed by the LaCrosseGateway module, you must add it to the Clients attribute.
  789. </li><br>
  790. <li>MatchList<br>
  791. Can be set to a perl expression that returns a hash that is used as the MatchList
  792. </li><br>
  793. <li>initCommands<br>
  794. Space separated list of commands to send for device initialization.
  795. </li><br>
  796. <li>timeout<br>
  797. format: &lt;timeout&gt<br>
  798. Asks the LaCrosseGateway every timeout seconds if it is still alive. If there is no response it reconnects to the LaCrosseGateway.<br>
  799. Can be combined with the watchdog attribute. If the watchdog attribute is set, the LaCrosseGateway also will check if it gets
  800. a request within watchdog seconds and if not, it will reboot.
  801. watchdog must be longer than timeout and does only work in combination with timeout.<br>
  802. Both should not be too short because the LaCrosseGateway needs enough time to boot before the next check.<br>
  803. Good values are: timeout 60 and watchdog 300<br>
  804. This mode needs LaCrosseGateway V1.24 or newer.
  805. <br><br><u>Old version (still working):</u><br>
  806. format: &lt;timeout, checkInterval&gt;<br>
  807. Checks every 'checkInterval' seconds if the last data reception is longer than 'timeout' seconds ago.<br>
  808. If this is the case, a new connect will be tried.
  809. </li><br>
  810. <li>watchdog<br>
  811. see timeout attribute.
  812. </li><br>
  813. <li>disable<br>
  814. if disabled, it does not try to connect and does not dispatch data
  815. </li><br>
  816. <li>kvp<br>
  817. defines how the incoming KVP-data of the LaCrosseGateway is handled<br>
  818. dispatch: (default) dispatch it to a KVP device<br>
  819. readings: create readings (e.g. RSSI, ...) in this device<br>
  820. both: dispatch and create readings
  821. </li><br>
  822. <li>ownSensors<br>
  823. defines how the incoming data of the internal LaCrosseGateway sensors is handled<br>
  824. dispatch: (default) dispatch it to a LaCrosse device<br>
  825. readings: create readings (e.g. temperature, humidity, ...) in this device<br>
  826. both: dispatch and create readings
  827. </li><br>
  828. <li>mode<br>
  829. USB, WiFi or Cable<br>
  830. Depending on how the LaCrosseGateway is connected, it must be handled differently (init, ...)
  831. </li><br>
  832. <li>tftFile<br>
  833. defines the .tft file that shall be used by the Nextion firmware upload (set nextionUpload)
  834. </li><br>
  835. <li>filter<br>
  836. defines a filter (regular expression) that is applied to the incoming data. If the regex matches, the data will be discarded.<br>
  837. This allows to suppress sensors, for example those of the neighbour.<br>
  838. The data of different kinds of sensors starts with (where xx is the ID):<br>
  839. LaCrosse sensor: OK 9 xx<br>
  840. EnergyCount 3000: OK 22 xx xx<br>
  841. EMT7110: OK EMT7110 xx xx<br>
  842. LevelSender: OK LS xx<br>
  843. Example: set lgw filter ^OK 22 117 196|^OK 9 49<br>
  844. will filter the LaCrosse sensor with ID "49" and the EC3000 with ID "117 196"
  845. </li><br>
  846. </ul>
  847. <br>
  848. </ul>
  849. =end html
  850. =cut