10_pilight_ctrl.pm 32 KB

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