10_pilight_ctrl.pm 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054
  1. ##############################################
  2. # $Id: 10_pilight_ctrl.pm 16028 2018-01-28 18:34:25Z Risiko $
  3. #
  4. # Usage
  5. #
  6. # define <name> pilight_ctrl <host:port> [5.0]
  7. #
  8. # Changelog
  9. #
  10. # V 0.10 2015-02-22 - initial beta version
  11. # V 0.20 2015-02-25 - new: dimmer
  12. # V 0.21 2015-03-01 - API 6.0 as default
  13. # V 0.22 2015-03-03 - support more switch protocols
  14. # V 0.23 2015-03-14 - fix: id isn't numeric
  15. # V 0.24 2015-03-20 - new: add cleverwatts protocol
  16. # V 0.25 2015-03-26 - new: cleverwatts unit all
  17. # - fix: unit isn't numeric
  18. # V 0.26 2015-03-29 - new: temperature and humidity sensor support (pilight_temp)
  19. # V 0.27 2015-03-30 - new: ignore complete protocols with <protocol>:* in attr ignore
  20. # 2015-03-30 - new: GPIO temperature and humidity sensors
  21. # V 0.28 2015-04-09 - fix: if not connected to pilight-daemon, do not try to send messages
  22. # V 0.29 2015-04-12 - fix: identify intertechno_old as switch
  23. # V 0.50 2015-04-17 - fix: queue of sending messages
  24. # - fix: same spelling errors - thanks to pattex
  25. # V 0.51 2015-04-29 - CHG: rename attribute ignore to ignoreProtocol because with ignore the whole device is ignored in FHEMWEB
  26. # V 1.00 2015-05-09 - NEW: white list for defined submodules activating by ignoreProtocol *
  27. # V 1.01 2015-05-09 - NEW: add quigg_gt* protocol (e.q quigg_gt7000)
  28. # V 1.02 2015-05-16 - NEW: battery state for temperature sensors
  29. # V 1.03 2015-05-20 - NEW: handle screen messages (up,down)
  30. # V 1.04 2015-05-30 - FIX: StateFn
  31. # V 1.05 2015-06-07 - FIX: Reset
  32. # V 1.06 2015-06-20 - NEW: set <ctrl> disconnect, checking reading state
  33. # V 1.07 2015-06-23 - FIX: reading state always contains a valid value, checking reading state removed
  34. # V 1.08 2015-06-23 - FIX: clear send queue by reset
  35. # V 1.08 2015-06-23 - NEW: attribute SendTimeout for abort sending command non blocking
  36. # V 1.09 2015-07-21 - NEW: support submodule pilight_raw to send raw codes
  37. # V 1.10 2015-08-30 - NEW: support pressure, windavg, winddir, windgust from weather stations and GPIO sensors
  38. # V 1.11 2015-09-06 - FIX: pressure, windavg, winddir, windgust from weather stations without temperature
  39. # V 1.12 2015-09-11 - FIX: handling ContactAsSwitch befor white list check
  40. # V 1.13 2015-11-10 - FIX: POSIX isdigit is deprecated replaced by own isDigit
  41. # V 1.14 2016-03-20 - FIX: send delimiter to signal end of stream if length of data > 1024
  42. # V 1.15 2016-03-28 - NEW: protocol daycom (switch)
  43. # V 1.16 2016-06-02 - NEW: protocol oregon_21 (temp)
  44. # V 1.17 2016-06-28 - FIX: Experimental splice on scalar is now forbidden - use explizit array notation
  45. # V 1.18 2016-06-28 - NEW: support smoke sensors (protocol: secudo_smoke_sensor)
  46. # V 1.19 2016-09-20 - FIX: PERL WARNING: Subroutine from Blocking.pm redefined
  47. # V 1.20 2016-10-27 - FIX: ContactAsSwitch protocol independend
  48. # V 1.21 2016-11-13 - NEW: support contact sensors
  49. # V 1.22 2017-04-08 - NEW: support contact sensor GW-iwds07
  50. # V 1.23 2017-04-08 - NEW: support new temperature protocols bmp085 and bmp180
  51. # V 1.24 2017-04-22 - FIX: GS-iwds07 support
  52. # V 1.25 2017-04-23 - FIX: react only of global::INITIALIZED m/^INITIALIZED$/
  53. # V 1.26 2017-09-03 - FIX: heitech support
  54. # V 1.27 2018-01-28 - NEW: handle bh1750 illuminance sensor as weather station
  55. ##############################################
  56. package main;
  57. use strict;
  58. use warnings;
  59. use Time::HiRes qw(gettimeofday);
  60. use JSON; #libjson-perl
  61. use Switch; #libswitch-perl
  62. require 'DevIo.pm';
  63. require 'Blocking.pm';
  64. sub pilight_ctrl_Parse($$);
  65. sub pilight_ctrl_Read($);
  66. sub pilight_ctrl_Ready($);
  67. sub pilight_ctrl_Write($@);
  68. sub pilight_ctrl_SimpleWrite(@);
  69. sub pilight_ctrl_ClientAccepted(@);
  70. sub pilight_ctrl_Send($);
  71. sub pilight_ctrl_Reset($);
  72. my %sets = ( "reset:noArg" => "", "disconnect:noArg" => "");
  73. my %matchList = ( "1:pilight_switch" => "^PISWITCH",
  74. "2:pilight_dimmer" => "^PISWITCH|^PIDIMMER|^PISCREEN",
  75. "3:pilight_temp" => "^PITEMP",
  76. "4:pilight_raw" => "^PIRAW",
  77. "5:pilight_smoke" => "^PISMOKE",
  78. "6:pilight_contact"=> "^PICONTACT");
  79. my @idList = ("id","systemcode","gpio");
  80. my @unitList = ("unit","unitcode","programcode");
  81. #ignore tfa:0,... list of <protocol>:<id> to ignore
  82. #brands arctech:kaku,... list of <search>:<replace> protocol names
  83. #ContactAsSwitch 1234,... list of ids where contact is transformed to switch
  84. sub isDigit($)
  85. {
  86. my ($d) = @_;
  87. return $d =~ /^\d+?$/ ? 1 : 0;
  88. }
  89. sub pilight_ctrl_Initialize($)
  90. {
  91. my ($hash) = @_;
  92. $hash->{ReadFn} = "pilight_ctrl_Read";
  93. $hash->{WriteFn} = "pilight_ctrl_Write";
  94. $hash->{ReadyFn} = "pilight_ctrl_Ready";
  95. $hash->{DefFn} = "pilight_ctrl_Define";
  96. $hash->{UndefFn} = "pilight_ctrl_Undef";
  97. $hash->{SetFn} = "pilight_ctrl_Set";
  98. $hash->{NotifyFn}= "pilight_ctrl_Notify";
  99. $hash->{StateFn} = "pilight_ctrl_State";
  100. $hash->{AttrList}= "ignoreProtocol brands ContactAsSwitch SendTimeout ".$readingFnAttributes;
  101. $hash->{Clients} = ":pilight_switch:pilight_dimmer:pilight_temp:pilight_raw:pilight_smoke:pilight_contact:";
  102. #$hash->{MatchList} = \%matchList; #only for autocreate
  103. }
  104. #####################################
  105. sub pilight_ctrl_Define($$)
  106. {
  107. my ($hash, $def) = @_;
  108. my @a = split("[ \t][ \t]*", $def);
  109. if(@a < 3) {
  110. my $msg = "wrong syntax: define <name> pilight_ctrl hostname:port [5.0]";
  111. Log3 undef, 2, $msg;
  112. return $msg;
  113. }
  114. DevIo_CloseDev($hash);
  115. RemoveInternalTimer($hash);
  116. my $me = $a[0];
  117. my $dev = $a[2];
  118. $hash->{DeviceName} = $dev;
  119. $hash->{STATE} = "defined";
  120. $hash->{API} = "6.0";
  121. $hash->{API} = "5.0" if (defined($a[3]) && $a[3] =~/5/);
  122. $hash->{RETRY_INTERVAL} = 60;
  123. $hash->{helper}{CON} = "define";
  124. $hash->{helper}{CHECK} = 0;
  125. my @sendQueue = ();
  126. $hash->{helper}->{sendQueue} = \@sendQueue;
  127. my @whiteList = ();
  128. $hash->{helper}->{whiteList} = \@whiteList;
  129. #$attr{$me}{verbose} = 5;
  130. return pilight_ctrl_TryConnect($hash);
  131. }
  132. sub pilight_ctrl_setStates($$)
  133. {
  134. my ($hash, $val) = @_;
  135. $hash->{STATE} = $val;
  136. $val = "disconnected" if ($val eq "closed");
  137. setReadingsVal($hash, "state", $val, TimeNow());
  138. }
  139. #####################################
  140. sub pilight_ctrl_State($$$$)
  141. {
  142. my ($hash, $time, $name, $val) = @_;
  143. my $me = $hash->{NAME};
  144. if ($name eq "STATE" && $val eq "closed") {
  145. Log3 $me, 3, "$me(State): abort connecting because of saved STATE";
  146. pilight_ctrl_Close($hash);
  147. return undef;
  148. }
  149. # gespeicherten Readings nicht wieder herstellen
  150. if ($name eq "state" && $hash->{STATE}) {
  151. setReadingsVal($hash, $name, "disconnected", TimeNow());
  152. }
  153. if ($name eq "rcv_raw") {
  154. setReadingsVal($hash, $name, "empty", TimeNow());
  155. }
  156. return undef;
  157. }
  158. sub pilight_ctrl_CheckReadingState($)
  159. {
  160. my ($hash) = @_;
  161. my $me = $hash->{NAME};
  162. my $state = ReadingsVal($me,"state",undef);
  163. if (defined($state) && $state ne "opened" && $state ne "disconnected") {
  164. Log3 $me, 3, "$me(CheckReadingState): Unknown error: unnormal value for reading state";
  165. $hash->{STATE} = $hash->{helper}{CON};
  166. $hash->{STATE} = "opened" if ($hash->{helper}{CON} eq "connected");
  167. }
  168. return undef;
  169. }
  170. sub pilight_ctrl_Close($)
  171. {
  172. my $hash = shift;
  173. my $me = $hash->{NAME};
  174. if (exists($hash->{helper}{RUNNING_PID})) {
  175. Log3 $me, 5, "$me(Close): call BlockingKill";
  176. BlockingKill($hash->{helper}{RUNNING_PID});
  177. delete($hash->{helper}{RUNNING_PID});
  178. }
  179. splice(@{$hash->{helper}->{sendQueue}});
  180. RemoveInternalTimer($hash);
  181. Log3 $me, 5, "$me(Close): close DevIo";
  182. DevIo_CloseDev($hash);
  183. pilight_ctrl_setStates($hash,"closed");
  184. $hash->{helper}{CON} = "closed";
  185. delete $hash->{DevIoJustClosed};
  186. }
  187. #####################################
  188. sub pilight_ctrl_Undef($$)
  189. {
  190. my ($hash, $arg) = @_;
  191. my $me = $hash->{NAME};
  192. pilight_ctrl_Close($hash);
  193. foreach my $d (sort keys %defs) {
  194. if(defined($defs{$d}) &&
  195. defined($defs{$d}{IODev}) &&
  196. $defs{$d}{IODev} == $hash)
  197. {
  198. delete $defs{$d}{IODev};
  199. }
  200. }
  201. return undef;
  202. }
  203. #####################################
  204. sub pilight_ctrl_TryConnect($)
  205. {
  206. my $hash = shift;
  207. my $me = $hash->{NAME};
  208. Log3 $me, 5, "$me(TryConnect): $hash->{STATE}";
  209. $hash->{helper}{CHECK} = 0;
  210. RemoveInternalTimer($hash);
  211. delete $hash->{NEXT_OPEN};
  212. delete $hash->{DevIoJustClosed};
  213. my $ret = DevIo_OpenDev($hash, 0, "pilight_ctrl_DoInit");
  214. #DevIO set state to opened
  215. setReadingsVal($hash, "state", "disconnected", TimeNow());
  216. delete $hash->{NEXT_OPEN};
  217. $hash->{helper}{NEXT_TRY} = time()+$hash->{RETRY_INTERVAL};
  218. InternalTimer(gettimeofday()+1,"pilight_ctrl_Check", $hash, 0);
  219. return $ret;
  220. }
  221. #####################################
  222. sub pilight_ctrl_Reset($)
  223. {
  224. my ($hash) = @_;
  225. pilight_ctrl_Close($hash);
  226. return pilight_ctrl_TryConnect($hash);
  227. }
  228. #####################################
  229. sub pilight_ctrl_Set($@)
  230. {
  231. my ($hash, @a) = @_;
  232. return "set $hash->{NAME} needs at least one parameter" if(@a < 2);
  233. my $me = shift @a;
  234. my $cmd = shift @a;
  235. return join(" ", sort keys %sets) if ($cmd eq "?");
  236. if ($cmd eq "reset")
  237. {
  238. return pilight_ctrl_Reset($hash);
  239. }
  240. if ($cmd eq "disconnect") {
  241. pilight_ctrl_Close($hash);
  242. return undef;
  243. }
  244. return "Unknown argument $cmd, choose one of ". join(" ", sort keys %sets);
  245. }
  246. #####################################
  247. sub pilight_ctrl_Check($)
  248. {
  249. my $hash = shift;
  250. my $me = $hash->{NAME};
  251. RemoveInternalTimer($hash);
  252. $hash->{helper}{CHECK} = 0 if (!isDigit($hash->{helper}{CHECK}));
  253. $hash->{helper}{CHECK} +=1;
  254. Log3 $me, 5, "$me(Check): $hash->{STATE}";
  255. if($hash->{STATE} eq "disconnected") {
  256. Log3 $me, 2, "$me(Check): Could not connect to pilight-daemon $hash->{DeviceName}";
  257. $hash->{helper}{CON} = "disconnected";
  258. pilight_ctrl_setStates($hash,"disconnected");
  259. }
  260. return if ($hash->{helper}{CON} eq "disconnected" || $hash->{helper}{CON} eq "closed");
  261. if ($hash->{helper}{CON} eq "define") {
  262. Log3 $me, 2, "$me(Check): connection to $hash->{DeviceName} failed";
  263. $hash->{helper}{CHECK} = 0;
  264. $hash->{helper}{NEXT_TRY} = time()+$hash->{RETRY_INTERVAL};
  265. return;
  266. }
  267. if ($hash->{helper}{CON} eq "identify") {
  268. if ($hash->{helper}{CHECK} % 3 == 0 && $hash->{helper}{CHECK} < 12) { #retry
  269. pilight_ctrl_DoInit($hash);
  270. } elsif ($hash->{helper}{CHECK} >= 12) {
  271. Log3 $me, 4, "$me(Check): Could not connect to pilight-daemon $hash->{DeviceName} - maybe wrong api version or port";
  272. DevIo_Disconnected($hash);
  273. $hash->{helper}{CHECK} = 0;
  274. $hash->{helper}{CON} = "disconnected";
  275. pilight_ctrl_setStates($hash,"disconnected");
  276. $hash->{helper}{NEXT_TRY} = time()+$hash->{RETRY_INTERVAL};
  277. return;
  278. }
  279. }
  280. if ($hash->{helper}{CON} eq "identify-failed" || $hash->{helper}{CHECK} > 20) {
  281. delete $hash->{helper}{CHECK};
  282. $hash->{helper}{CON} = "disconnected";
  283. pilight_ctrl_setStates($hash,"disconnected");
  284. Log3 $me, 2, "$me(Check): identification to pilight-daemon $hash->{DeviceName} failed";
  285. $hash->{helper}{NEXT_TRY} = time()+$hash->{RETRY_INTERVAL};
  286. return;
  287. }
  288. if ($hash->{helper}{CON} eq "identify-rejected" || $hash->{helper}{CHECK} > 20) {
  289. Log3 $me, 2, "$me(Parse): connection to pilight-daemon $hash->{DeviceName} rejected";
  290. delete $hash->{helper}{CHECK};
  291. $hash->{helper}{CON} = "disconnected";
  292. pilight_ctrl_setStates($hash,"disconnected");
  293. $hash->{helper}{NEXT_TRY} = time()+$hash->{RETRY_INTERVAL};
  294. return;
  295. }
  296. if ($hash->{helper}{CON} eq "connected") {
  297. delete $hash->{helper}{CHECK};
  298. delete $hash->{helper}{NEXT_TRY};
  299. pilight_ctrl_setStates($hash,"connected");
  300. return;
  301. }
  302. InternalTimer(gettimeofday()+1,"pilight_ctrl_Check", $hash, 0);
  303. return 1;
  304. }
  305. #####################################
  306. sub pilight_ctrl_DoInit($)
  307. {
  308. my $hash = shift;
  309. return "No FD" if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD})));
  310. my $me = $hash->{NAME};
  311. my $msg;
  312. my $api;
  313. Log3 $me, 5, "$me(DoInit): $hash->{STATE}";
  314. $hash->{helper}{CON} = "identify";
  315. if ($hash->{API} eq "6.0") {
  316. $msg = '{"action":"identify","options":{"receiver":1},"media":"all"}';
  317. } else {
  318. $msg = "{ \"message\": \"client receiver\" }";
  319. }
  320. Log3 $me, 5, "$me(DoInit): send $msg";
  321. pilight_ctrl_SimpleWrite($hash,$msg);
  322. return;
  323. }
  324. #####################################
  325. sub pilight_ctrl_Write($@)
  326. {
  327. my ($hash,$rmsg) = @_;
  328. my $me = $hash->{NAME};
  329. if ($hash->{helper}{CON} eq "closed") {
  330. return;
  331. }
  332. if ($hash->{helper}{CON} ne "connected") {
  333. Log3 $me, 2, "$me(Write): ERROR: no connection to pilight-daemon $hash->{DeviceName}";
  334. return;
  335. }
  336. my ($cName,$state,@args) = split(",",$rmsg);
  337. my $cType = lc($defs{$cName}->{TYPE});
  338. Log3 $me, 4, "$me(Write): RCV ($cType) -> $rmsg";
  339. my $proto = $defs{$cName}->{PROTOCOL};
  340. my $id = $defs{$cName}->{ID};
  341. my $unit = $defs{$cName}->{UNIT};
  342. my $syscode = undef;
  343. $syscode = $defs{$cName}->{SYSCODE} if defined($defs{$cName}->{SYSCODE});
  344. $id = "\"".$id."\"" if (defined($id) && !isDigit($id));
  345. $unit = "\"".$unit."\"" if (defined($unit) && !isDigit($unit));
  346. $syscode = "\"".$syscode."\"" if (defined($syscode) && !isDigit($syscode));
  347. my $code;
  348. switch($cType){
  349. case m/switch/ {
  350. $code = "{\"protocol\":[\"$proto\"],";
  351. switch ($proto) {
  352. case m/elro/ {$code .= "\"systemcode\":$id,\"unitcode\":$unit,";}
  353. case m/silvercrest/ {$code .= "\"systemcode\":$id,\"unitcode\":$unit,";}
  354. case m/mumbi/ {$code .= "\"systemcode\":$id,\"unitcode\":$unit,";}
  355. case m/brennenstuhl/ {$code .= "\"systemcode\":$id,\"unitcode\":$unit,";}
  356. case m/pollin/ {$code .= "\"systemcode\":$id,\"unitcode\":$unit,";}
  357. case m/heitech/ {$code .= "\"systemcode\":$id,\"unitcode\":$unit,";}
  358. case m/impuls/ {$code .= "\"systemcode\":$id,\"programcode\":$unit,";}
  359. case m/rsl366/ {$code .= "\"systemcode\":$id,\"programcode\":$unit,";}
  360. case m/daycom/ { if (!defined($syscode)) {
  361. Log3 $me, 1, "$me(Write): Error protocol daycom no systemcode defined";
  362. return;
  363. }
  364. $code .= "\"id\":$id,\"systemcode\":$syscode,\"unit\":$unit,";
  365. }
  366. case m/cleverwatts/ { $code .= "\"id\":$id,";
  367. if ($unit eq "\"all\"") {
  368. $code .= "\"all\":1,";
  369. } else {
  370. $code .= "\"unit\":$unit,";
  371. }
  372. }
  373. else {$code .= "\"id\":$id,\"unit\":$unit,";}
  374. }
  375. $code .= "\"$state\":1}";
  376. }
  377. case m/dimmer/ {
  378. $code = "{\"protocol\":[\"$proto\"],\"id\":$id,\"unit\":$unit,\"$state\":1";
  379. $code .= ",\"dimlevel\":$args[0]" if (defined($args[0]));
  380. $code .= "}";
  381. }
  382. case m/raw/ {
  383. $code = "{\"protocol\":[\"$proto\"],\"code\":\"$state\"}";
  384. }
  385. else {Log3 $me, 3, "$me(Write): unsupported client ($cName) -> $cType"; return;}
  386. }
  387. return if (!defined($code));
  388. my $msg;
  389. if ($hash->{API} eq "6.0") {
  390. $msg = "{\"action\":\"send\",\"code\":$code}";
  391. } else {
  392. $msg = "{\"message\":\"send\",\"code\":$code}";
  393. }
  394. Log3 $me, 4, "$me(Write): $msg";
  395. # we can't use the same connection because the pilight-daemon close the connection after sending
  396. # we have to create a second connection for sending data
  397. # we do not update the readings - we will do this at the response message
  398. push @{$hash->{helper}->{sendQueue}}, $msg;
  399. pilight_ctrl_SendNonBlocking($hash);
  400. }
  401. #####################################
  402. sub pilight_ctrl_Send($)
  403. {
  404. my ($string) = @_;
  405. my ($me, $host,$data) = split("\\|", $string);
  406. my $hash = $defs{$me};
  407. my ($remote_ip,$remote_port) = split(":",$host);
  408. my $socket = new IO::Socket::INET (
  409. PeerHost => $remote_ip,
  410. PeerPort => $remote_port,
  411. Proto => 'tcp',
  412. );
  413. if (!$socket) {
  414. Log3 $me, 2, "$me(Send): ERROR. Can't open socket to pilight-daemon $remote_ip:$remote_port";
  415. return "$me|0";
  416. }
  417. # we only need a identification to send in 5.0 version
  418. if ($hash->{API} eq "5.0") {
  419. my $msg = "{ \"message\": \"client sender\" }";
  420. my $rcv;
  421. $socket->send($msg);
  422. $socket->recv($rcv,1024);
  423. $rcv =~ s/\n/ /g;
  424. Log3 $me, 5, "$me(Send): RCV -> $rcv";
  425. my $json = JSON->new;
  426. my $jsondata = $json->decode($rcv);
  427. if (!$jsondata)
  428. {
  429. Log3 $me, 2, "$me(Send): ERROR. no JSON response message";
  430. $socket->close();
  431. return "$me|0";
  432. }
  433. my $ret = pilight_ctrl_ClientAccepted($hash,$jsondata);
  434. if ( $ret != 1 ) {
  435. Log3 $me, 2, "$me(Send): ERROR. Connection rejected from pilight-daemon";
  436. $socket->close();
  437. return "$me|0";
  438. }
  439. }
  440. Log3 $me, 5, "$me(Send): $data";
  441. $data = $data."\n\n"; # add delimiter to signel end off stream if length > 1024
  442. $socket->send($data);
  443. #6.0 we get a response message
  444. if ($hash->{API} eq "6.0") {
  445. my $rcv;
  446. $socket->recv($rcv,1024);
  447. $rcv =~ s/\n/ /g;
  448. Log3 $me, 4, "$me(Send): RCV -> $rcv";
  449. }
  450. $socket->close();
  451. return "$me|1";
  452. }
  453. #####################################
  454. sub pilight_ctrl_addWhiteList($$)
  455. {
  456. my ($own, $dev) = @_;
  457. my $me = $own->{NAME};
  458. my $devName = $dev->{NAME};
  459. my $id = (defined($dev->{ID})) ? $dev->{ID} : return;
  460. my $protocol = (defined($dev->{PROTOCOL})) ? $dev->{PROTOCOL}: return;
  461. Log3 $me, 4, "$me(addWhiteList): add $devName to white list";
  462. my $entry = {};
  463. my %whiteHash;
  464. @whiteHash{@{$own->{helper}->{whiteList}}}=();
  465. if (!exists $whiteHash{"$protocol:$id"}) {
  466. push @{$own->{helper}->{whiteList}}, "$protocol:$id";
  467. }
  468. #spezial 2nd protocol for dimmer
  469. if (defined($dev->{PROTOCOL2})) {
  470. $protocol = $dev->{PROTOCOL2};
  471. if (!exists $whiteHash{"$protocol:$id"}) {
  472. push @{$own->{helper}->{whiteList}}, "$protocol:$id";
  473. }
  474. }
  475. }
  476. #####################################
  477. sub pilight_ctrl_createWhiteList($)
  478. {
  479. my ($own) = @_;
  480. splice(@{$own->{helper}->{whiteList}});
  481. foreach my $d (keys %defs)
  482. {
  483. my $module = $defs{$d}{TYPE};
  484. next if ($module !~ /pilight_[d|s|t|c].*/);
  485. pilight_ctrl_addWhiteList($own,$defs{$d});
  486. }
  487. }
  488. #####################################
  489. sub pilight_ctrl_Notify($$)
  490. {
  491. my ($own, $dev) = @_;
  492. my $me = $own->{NAME}; # own name / hash
  493. my $devName = $dev->{NAME}; # Device that created the events
  494. return undef if ($devName ne "global");
  495. my $max = int(@{$dev->{CHANGED}}); # number of events / changes
  496. for (my $i = 0; $i < $max; $i++) {
  497. my $s = $dev->{CHANGED}[$i];
  498. next if(!defined($s));
  499. my ($what,$who) = split(' ',$s);
  500. if ( $what =~ m/^INITIALIZED$/ ) {
  501. Log3 $me, 4, "$me(Notify): create white list for $s";
  502. pilight_ctrl_createWhiteList($own);
  503. } elsif ( $what =~ m/DEFINED/ ){
  504. my $hash = $defs{$who};
  505. next if(!$hash);
  506. my $module = $hash->{TYPE};
  507. next if ($module !~ /pilight_[d|s|t].*/);
  508. pilight_ctrl_addWhiteList($own,$hash);
  509. } elsif ( $what =~ m/DELETED/ ){
  510. Log3 $me, 4, "$me(Notify): create white list for $s";
  511. pilight_ctrl_createWhiteList($own);
  512. }
  513. }
  514. return undef;
  515. }
  516. #####################################
  517. sub pilight_ctrl_SendDone($)
  518. {
  519. my ($string) = @_;
  520. my ($me, $ok) = split("\\|", $string);
  521. my $hash = $defs{$me};
  522. Log3 $me, 4, "$me(SendDone): message successfully send" if ($ok);
  523. Log3 $me, 2, "$me(SendDone): sending message failed" if (!$ok);
  524. delete($hash->{helper}{RUNNING_PID});
  525. }
  526. #####################################
  527. sub pilight_ctrl_SendAbort($)
  528. {
  529. my ($hash) = @_;
  530. my $me = $hash->{NAME};
  531. Log3 $me, 2, "$me(SendAbort): ERROR. sending aborted";
  532. delete($hash->{helper}{RUNNING_PID});
  533. }
  534. #####################################
  535. sub pilight_ctrl_SendNonBlocking($)
  536. {
  537. my ($hash) = @_;
  538. my $me = $hash->{NAME};
  539. RemoveInternalTimer($hash);
  540. my $queueSize = @{$hash->{helper}->{sendQueue}};
  541. Log3 $me, 5, "$me(SendNonBlocking): queue size $queueSize";
  542. return if ($queueSize <=0);
  543. if (!(exists($hash->{helper}{RUNNING_PID}))) {
  544. my $data = shift @{$hash->{helper}->{sendQueue}};
  545. my $blockingFn = "pilight_ctrl_Send";
  546. my $arg = $me."|".$hash->{DeviceName}."|".$data;
  547. my $finishFn = "pilight_ctrl_SendDone";
  548. my $timeout = AttrVal($me, "SendTimeout",1);
  549. my $abortFn = "pilight_ctrl_SendAbort";
  550. $hash->{helper}{RUNNING_PID} = BlockingCall($blockingFn, $arg, $finishFn, $timeout, $abortFn, $hash);
  551. $hash->{helper}{LAST_SEND_RAW} = $data;
  552. } else {
  553. Log3 $me, 5, "$me(Write): Blocking Call running - will try it later";
  554. }
  555. $queueSize = @{$hash->{helper}->{sendQueue}};
  556. InternalTimer(gettimeofday()+0.5,"pilight_ctrl_SendNonBlocking", $hash, 0) if ($queueSize > 0);
  557. }
  558. #####################################
  559. sub pilight_ctrl_ClientAccepted(@)
  560. {
  561. my ($hash,$data) = @_;
  562. my $me = $hash->{NAME};
  563. my $ret = 0;
  564. if ($hash->{API} eq "5.0") {
  565. my $msg = (defined($data->{message})) ? $data->{message} : "";
  566. $ret = 1 if(index($msg,"accept") >= 0);
  567. $ret = -1 if(index($msg,"reject") >= 0);
  568. }
  569. else {
  570. my $status = (defined($data->{status})) ? $data->{status} : "";
  571. $ret = 1 if(index($status,"success") >= 0);
  572. $ret = -1 if(index($status,"reject") >= 0);
  573. }
  574. return $ret;
  575. }
  576. #####################################
  577. # called from the global loop, when the select for hash->{FD} reports data
  578. sub pilight_ctrl_Read($)
  579. {
  580. my ($hash) = @_;
  581. my $me = $hash->{NAME};
  582. my $buf = DevIo_SimpleRead($hash);
  583. return "" if(!defined($buf));
  584. my $recdata = $hash->{PARTIAL};
  585. #Log3 $me, 5, "$me(Read): RCV->$buf";
  586. $recdata .= $buf;
  587. while($recdata =~ m/\n/)
  588. {
  589. my $rmsg;
  590. ($rmsg,$recdata) = split("\n", $recdata, 2);
  591. $rmsg =~ s/\r//;
  592. pilight_ctrl_Parse($hash, $rmsg) if($rmsg);
  593. }
  594. $hash->{PARTIAL} = $recdata;
  595. }
  596. ###########################################
  597. sub pilight_ctrl_Parse($$)
  598. {
  599. my ($hash, $rmsg) = @_;
  600. my $me = $hash->{NAME};
  601. Log3 $me, 5, "$me(Parse): RCV -> $rmsg";
  602. next if(!$rmsg || length($rmsg) < 1);
  603. $hash->{helper}{LAST_RCV_RAW} = $rmsg;
  604. my $json = JSON->new;
  605. my $data = $json->decode($rmsg);
  606. return if (!$data);
  607. if ($hash->{helper}{CON} eq "identify") # we are in identify process
  608. {
  609. Log3 $me, 4, "$me(Parse): identify -> $rmsg";
  610. $hash->{helper}{CON} = "identify-failed";
  611. my $ret = pilight_ctrl_ClientAccepted($hash,$data);
  612. switch ($ret) {
  613. case 1 { $hash->{helper}{CON} = "connected"; }
  614. case -1 { $hash->{helper}{CON} = "identify-rejected"; }
  615. else { Log3 $me, 3, "$me(Parse): internal error"; }
  616. }
  617. pilight_ctrl_Check($hash);
  618. return;
  619. }
  620. $hash->{helper}{LAST_RCV_JSON} = $json;
  621. my $proto = (defined($data->{protocol})) ? $data->{protocol} : "";
  622. if (!$proto)
  623. {
  624. Log3 $me, 3, "$me(Parse): unknown message -> $rmsg";
  625. return;
  626. }
  627. #brands
  628. my @brands = split(",",AttrVal($me, "brands",""));
  629. foreach my $brand (@brands){
  630. my($search,$replace) = split(":",$brand);
  631. next if (!defined($search) || !defined($replace));
  632. $proto =~ s/$search/$replace/g;
  633. }
  634. $hash->{helper}{LAST_RCV_PROTOCOL} = $proto;
  635. my $s = ($hash->{API} eq "5.0") ? "code" : "message";
  636. my $state = (defined($data->{$s}{state})) ? $data->{$s}{state} : "";
  637. my $all = (defined($data->{$s}{all})) ? $data->{$s}{all} : "";
  638. my $id = "";
  639. foreach my $sid (@idList) {
  640. $id = (defined($data->{$s}{$sid})) ? $data->{$s}{$sid} : "";
  641. last if ($id ne "");
  642. }
  643. #systemcode and id for protocol daycom (needs 3 id's, systemcode, id, unit
  644. my $syscode = (defined($data->{$s}{"systemcode"})) ? $data->{$s}{"systemcode"} : "";
  645. my $unit = "";
  646. foreach my $sunit (@unitList) {
  647. $unit = (defined($data->{$s}{$sunit})) ? $data->{$s}{$sunit} : "";
  648. last if ($unit ne "");
  649. }
  650. # handling ContactAsSwitch befor white list check
  651. my $asSwitch = $attr{$me}{ContactAsSwitch};
  652. if ( defined($asSwitch) && $asSwitch =~ /$id/ && ($state =~ /opened/ || $state =~ /closed/) ) {
  653. $proto =~ s/contact/switch/g;
  654. $state =~ s/opened/on/g;
  655. $state =~ s/closed/off/g;
  656. Log3 $me, 4, "$me(Parse): contact as switch for $id";
  657. }
  658. # some protocols have no id but unit(code) e.q. ev1527, GS-iwds07
  659. $id = $unit if ($id eq "" && $unit ne "");
  660. $unit = "all" if ($unit eq "" && $all ne "");
  661. Log3 $me, 5, "$me(Parse): protocol:$proto,id:$id,unit:$unit";
  662. my @ignoreIDs = split(",",AttrVal($me, "ignoreProtocol",""));
  663. # white or ignore list
  664. if (@ignoreIDs == 1 && $ignoreIDs[0] eq "*"){ # use list
  665. my %whiteHash;
  666. @whiteHash{@{$hash->{helper}->{whiteList}}}=();
  667. if (!exists $whiteHash{"$proto:$id"}) {
  668. Log3 $me, 5, "$me(Parse): $proto:$id not in white list";
  669. return;
  670. }
  671. } else { #ignore list
  672. my %ignoreHash;
  673. @ignoreHash{@ignoreIDs}=();
  674. if (exists $ignoreHash{"$proto:$id"} || exists $ignoreHash{"$proto:*"}) {
  675. Log3 $me, 5, "$me(Parse): $proto:$id is in ignoreProtocol list";
  676. return;
  677. }
  678. }
  679. readingsBeginUpdate($hash);
  680. readingsBulkUpdate($hash,"rcv_raw",$rmsg);
  681. readingsEndUpdate($hash, 1);
  682. my $protoID = -1;
  683. switch($proto){
  684. #switch
  685. case m/switch/ {$protoID = 1;}
  686. case m/elro/ {$protoID = 1;}
  687. case m/silvercrest/ {$protoID = 1;}
  688. case m/mumbi/ {$protoID = 1;}
  689. case m/brennenstuhl/{$protoID = 1;}
  690. case m/pollin/ {$protoID = 1;}
  691. case m/daycom/ {$protoID = 1;}
  692. case m/impuls/ {$protoID = 1;}
  693. case m/rsl366/ {$protoID = 1;}
  694. case m/cleverwatts/ {$protoID = 1;}
  695. case m/intertechno_old/ {$protoID = 1;}
  696. case m/quigg_gt/ {$protoID = 1;}
  697. case m/heitech/ {$protoID = 1;}
  698. case m/dimmer/ {$protoID = 2;}
  699. #contact sensors
  700. case m/contact/ {$protoID = 3;}
  701. case m/ev1527/ {$protoID = 3;}
  702. case m/sc2262/ {$protoID = 3;}
  703. case m/GS-iwds07/ {$protoID = 3;}
  704. #Weather Stations temperature, humidity
  705. case m/alecto/ {$protoID = 4;}
  706. case m/auriol/ {$protoID = 4;}
  707. case m/ninjablocks/ {$protoID = 4;}
  708. case m/tfa/ {$protoID = 4;}
  709. case m/teknihall/ {$protoID = 4;}
  710. case m/oregon_21/ {$protoID = 4;}
  711. #handle illuminance sensor as weather station - workaround
  712. case m/bh1750/ {$protoID = 4;}
  713. #gpio temperature, humidity sensors
  714. case m/dht11/ {$protoID = 4;}
  715. case m/dht22/ {$protoID = 4;}
  716. case m/ds18b20/ {$protoID = 4;}
  717. case m/ds18s20/ {$protoID = 4;}
  718. case m/cpu_temp/ {$protoID = 4;}
  719. case m/lm75/ {$protoID = 4;}
  720. case m/lm76/ {$protoID = 4;}
  721. case m/bmp085/ {$protoID = 4;}
  722. case m/bmp180/ {$protoID = 4;}
  723. case m/screen/ {$protoID = 5;}
  724. #smoke sensors
  725. case m/secudo_smoke_sensor/ {$protoID = 6;}
  726. case m/firmware/ {return;}
  727. else {Log3 $me, 3, "$me(Parse): unknown protocol -> $proto"; return;}
  728. }
  729. if ($id eq "") {
  730. Log3 $me, 3, "$me(Parse): ERROR no or unknown id $rmsg";
  731. return;
  732. }
  733. switch($protoID){
  734. case 1 {
  735. my $msg = "PISWITCH,$proto,$id,$unit,$state";
  736. $msg .= ",$syscode" if ($syscode ne "");
  737. Log3 $me, 4, "$me(Dispatch): $msg";
  738. return Dispatch($hash, $msg,undef );
  739. }
  740. case 2 {
  741. my $dimlevel = (defined($data->{$s}{dimlevel})) ? $data->{$s}{dimlevel} : "";
  742. my $msg = "PIDIMMER,$proto,$id,$unit,$state";
  743. $msg.= ",$dimlevel" if ($dimlevel ne "");
  744. Log3 $me, 4, "$me(Dispatch): $msg";
  745. return Dispatch($hash, $msg ,undef);
  746. }
  747. case 3 {
  748. my $piTempData = "";
  749. $piTempData .= ",battery:$data->{$s}{battery}" if (defined($data->{$s}{battery}));
  750. my $msg = "PICONTACT,$proto,$id,$unit,$state$piTempData";
  751. Log3 $me, 4, "$me(Dispatch): $msg";
  752. return Dispatch($hash, $msg,undef);
  753. }
  754. case 4 {
  755. my $piTempData = "";
  756. $piTempData .= ",temperature:$data->{$s}{temperature}" if (defined($data->{$s}{temperature}));
  757. $piTempData .= ",humidity:$data->{$s}{humidity}" if (defined($data->{$s}{humidity}));
  758. $piTempData .= ",battery:$data->{$s}{battery}" if (defined($data->{$s}{battery}));
  759. $piTempData .= ",pressure:$data->{$s}{pressure}" if (defined($data->{$s}{pressure}));
  760. $piTempData .= ",windavg:$data->{$s}{windavg}" if (defined($data->{$s}{windavg}));
  761. $piTempData .= ",winddir:$data->{$s}{winddir}" if (defined($data->{$s}{winddir}));
  762. $piTempData .= ",windgust:$data->{$s}{windgust}" if (defined($data->{$s}{windgust}));
  763. #workaround illuminance sensor
  764. $piTempData .= ",illuminance:$data->{$s}{illuminance}" if (defined($data->{$s}{illuminance}));
  765. my $msg = "PITEMP,$proto,$id$piTempData";
  766. Log3 $me, 4, "$me(Dispatch): $msg";
  767. return Dispatch($hash, $msg,undef);
  768. }
  769. case 5 { return Dispatch($hash, "PISCREEN,$proto,$id,$unit,$state",undef); }
  770. case 6 { return Dispatch($hash, "PISMOKE,$proto,$id,$state",undef); }
  771. else {Log3 $me, 3, "$me(Parse): unknown protocol -> $proto"; return;}
  772. }
  773. return;
  774. }
  775. #####################################
  776. # called from gobal loop to try reconnection
  777. sub pilight_ctrl_Ready($)
  778. {
  779. my ($hash) = @_;
  780. my $me = $hash->{NAME};
  781. if($hash->{STATE} eq "disconnected")
  782. {
  783. return if(defined($hash->{helper}{NEXT_TRY}) && $hash->{helper}{NEXT_TRY} && time() < $hash->{helper}{NEXT_TRY});
  784. return pilight_ctrl_TryConnect($hash);
  785. }
  786. }
  787. #####################################
  788. sub pilight_ctrl_SimpleWrite(@)
  789. {
  790. my ($hash, $msg, $nonl) = @_;
  791. return if(!$hash);
  792. my $me = $hash->{NAME};
  793. Log3 $me, 4, "$me(SimpleWrite): snd -> $msg";
  794. $msg .= "\n" unless($nonl);
  795. DevIo_SimpleWrite($hash,$msg,0);
  796. }
  797. 1;
  798. =pod
  799. =item summary base module to comunicate with pilight
  800. =item summary_DE Basismodul zur Kommunikation mit pilight
  801. =begin html
  802. <a name="pilight_ctrl"></a>
  803. <h3>pilight_ctrl</h3>
  804. <ul>
  805. pilight_ctrl is the base device for the communication (sending and receiving) with the pilight-daemon.<br>
  806. You have to define client devices e.q. pilight_switch for switches.<br>
  807. Further information to pilight: <a href="http://www.pilight.org/">http://www.pilight.org/</a><br><br>
  808. Further information to pilight protocols: <a href="http://wiki.pilight.org/doku.php/protocols#protocols">http://wiki.pilight.org/doku.php/protocols#protocols</a><br>
  809. Currently supported: <br>
  810. <ul>
  811. <li>Switches:</li>
  812. <li>Dimmers:</li>
  813. <li>Temperature and humitity sensors</li>
  814. </ul>
  815. <br><br>
  816. <a name="pilight_ctrl_define"></a>
  817. <b>Define</b>
  818. <ul>
  819. <code>define &lt;name&gt; pilight_ctrl ip:port [api]</code>
  820. ip:port is the IP address and port of the pilight-daemon<br>
  821. api specifies the pilight api version - default 6.0<br>
  822. <br>
  823. Example:
  824. <ul>
  825. <code>define myctrl pilight_ctrl localhost:5000 5.0</code><br>
  826. <code>define myctrl pilight_ctrl 192.168.1.1:5000</code><br>
  827. </ul>
  828. </ul>
  829. <br>
  830. <a name="pilight_ctrl_set"></a>
  831. <p><b>Set</b></p>
  832. <ul>
  833. <li><b>reset</b> Reset the connection to the pilight daemon</li>
  834. <li><b>disconnect</b>Diconnect from pilight daemon and do not reconnect automatically</li>
  835. </ul>
  836. <br>
  837. <a name="pilight_ctrl_readings"></a>
  838. <p><b>Readings</b></p>
  839. <ul>
  840. <li>
  841. rcv_raw<br>
  842. The last complete received message in json format.
  843. </li>
  844. </ul>
  845. <br>
  846. <a name="pilight_ctrl_attr"></a>
  847. <b>Attributes</b>
  848. <ul>
  849. <li><a name="ignoreProtocol">ignoreProtocol</a><br>
  850. Comma separated list of protocol:id combinations to ignore.<br>
  851. protocol:* ignores the complete protocol.<br>
  852. * All incomming messages will be ignored. Only protocol id combinations from defined submodules will be accepted<br>
  853. Example:
  854. <li><code>ignoreProtocol tfa:0</code></li>
  855. <li><code>ignoreProtocol tfa:*</code></li>
  856. <li><code>ignoreProtocol *</code></li>
  857. </li>
  858. <li><a name="brands">brands</a><br>
  859. Comma separated list of <search>:<replace> combinations to rename protocol names. <br>
  860. pilight uses different protocol names for the same protocol e.q. arctech_switch and kaku_switch<br>
  861. Example: <code>brands archtech:kaku</code>
  862. </li>
  863. <li><a name="ContactAsSwitch">ContactAsSwitch</a><br>
  864. Comma separated list of ids which correspond to a contact but will be interpreted as switch. <br>
  865. In this case opened will be interpreted as on and closed as off.<br>
  866. Example: <code>ContactAsSwitch 12345</code>
  867. </li>
  868. <li><a name="SendTimeout">SendTimeout</a><br>
  869. Timeout [s] for aborting sending commands (non blocking) - default 1s
  870. </li>
  871. </ul>
  872. <br>
  873. </ul>
  874. =end html
  875. =cut