10_pilight_ctrl.pm 32 KB

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