36_LaCrosseGateway.pm 32 KB

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