############################################## # $Id$ # # Usage # # define pilight_ctrl [5.0] # # Changelog # # V 0.10 2015-02-22 - initial beta version # V 0.20 2015-02-25 - new: dimmer # V 0.21 2015-03-01 - API 6.0 as default # V 0.22 2015-03-03 - support more switch protocols # V 0.23 2015-03-14 - fix: id isn't numeric # V 0.24 2015-03-20 - new: add cleverwatts protocol # V 0.25 2015-03-26 - new: cleverwatts unit all # - fix: unit isn't numeric # V 0.26 2015-03-29 - new: temperature and humidity sensor support (pilight_temp) # V 0.27 2015-03-30 - new: ignore complete protocols with :* in attr ignore # 2015-03-30 - new: GPIO temperature and humidity sensors # V 0.28 2015-04-09 - fix: if not connected to pilight-daemon, do not try to send messages # V 0.29 2015-04-12 - fix: identify intertechno_old as switch # V 0.50 2015-04-17 - fix: queue of sending messages # - fix: same spelling errors - thanks to pattex # V 0.51 2015-04-29 - CHG: rename attribute ignore to ignoreProtocol because with ignore the whole device is ignored in FHEMWEB # V 1.00 2015-05-09 - NEW: white list for defined submodules activating by ignoreProtocol * # V 1.01 2015-05-09 - NEW: add quigg_gt* protocol (e.q quigg_gt7000) # V 1.02 2015-05-16 - NEW: battery state for temperature sensors # V 1.03 2015-05-20 - NEW: handle screen messages (up,down) # V 1.04 2015-05-30 - FIX: StateFn # V 1.05 2015-06-07 - FIX: Reset # V 1.06 2015-06-20 - NEW: set disconnect, checking reading state # V 1.07 2015-06-23 - FIX: reading state always contains a valid value, checking reading state removed # V 1.08 2015-06-23 - FIX: clear send queue by reset # V 1.08 2015-06-23 - NEW: attribute SendTimeout for abort sending command non blocking # V 1.09 2015-07-21 - NEW: support submodule pilight_raw to send raw codes # V 1.10 2015-08-30 - NEW: support pressure, windavg, winddir, windgust from weather stations and GPIO sensors # V 1.11 2015-09-06 - FIX: pressure, windavg, winddir, windgust from weather stations without temperature # V 1.12 2015-09-11 - FIX: handling ContactAsSwitch befor white list check # V 1.13 2015-11-10 - FIX: POSIX isdigit is deprecated replaced by own isDigit # V 1.14 2016-03-20 - FIX: send delimiter to signal end of stream if length of data > 1024 # V 1.15 2016-03-28 - NEW: protocol daycom (switch) # V 1.16 2016-06-02 - NEW: protocol oregon_21 (temp) # V 1.17 2016-06-28 - FIX: Experimental splice on scalar is now forbidden - use explizit array notation # V 1.18 2016-06-28 - NEW: support smoke sensors (protocol: secudo_smoke_sensor) # V 1.19 2016-09-20 - FIX: PERL WARNING: Subroutine from Blocking.pm redefined # V 1.20 2016-10-27 - FIX: ContactAsSwitch protocol independend # V 1.21 2016-11-13 - NEW: support contact sensors # V 1.22 2017-04-08 - NEW: support contact sensor GW-iwds07 # V 1.23 2017-04-08 - NEW: support new temperature protocols bmp085 and bmp180 # V 1.24 2017-04-22 - FIX: GS-iwds07 support # V 1.25 2017-04-23 - FIX: react only of global::INITIALIZED m/^INITIALIZED$/ # V 1.26 2017-09-03 - FIX: heitech support ############################################## package main; use strict; use warnings; use Time::HiRes qw(gettimeofday); use JSON; #libjson-perl use Switch; #libswitch-perl require 'DevIo.pm'; require 'Blocking.pm'; sub pilight_ctrl_Parse($$); sub pilight_ctrl_Read($); sub pilight_ctrl_Ready($); sub pilight_ctrl_Write($@); sub pilight_ctrl_SimpleWrite(@); sub pilight_ctrl_ClientAccepted(@); sub pilight_ctrl_Send($); sub pilight_ctrl_Reset($); my %sets = ( "reset:noArg" => "", "disconnect:noArg" => ""); my %matchList = ( "1:pilight_switch" => "^PISWITCH", "2:pilight_dimmer" => "^PISWITCH|^PIDIMMER|^PISCREEN", "3:pilight_temp" => "^PITEMP", "4:pilight_raw" => "^PIRAW", "5:pilight_smoke" => "^PISMOKE", "6:pilight_contact"=> "^PICONTACT"); my @idList = ("id","systemcode","gpio"); my @unitList = ("unit","unitcode","programcode"); #ignore tfa:0,... list of : to ignore #brands arctech:kaku,... list of : protocol names #ContactAsSwitch 1234,... list of ids where contact is transformed to switch sub isDigit($) { my ($d) = @_; return $d =~ /^\d+?$/ ? 1 : 0; } sub pilight_ctrl_Initialize($) { my ($hash) = @_; $hash->{ReadFn} = "pilight_ctrl_Read"; $hash->{WriteFn} = "pilight_ctrl_Write"; $hash->{ReadyFn} = "pilight_ctrl_Ready"; $hash->{DefFn} = "pilight_ctrl_Define"; $hash->{UndefFn} = "pilight_ctrl_Undef"; $hash->{SetFn} = "pilight_ctrl_Set"; $hash->{NotifyFn}= "pilight_ctrl_Notify"; $hash->{StateFn} = "pilight_ctrl_State"; $hash->{AttrList}= "ignoreProtocol brands ContactAsSwitch SendTimeout ".$readingFnAttributes; $hash->{Clients} = ":pilight_switch:pilight_dimmer:pilight_temp:pilight_raw:pilight_smoke:pilight_contact:"; #$hash->{MatchList} = \%matchList; #only for autocreate } ##################################### sub pilight_ctrl_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); if(@a < 3) { my $msg = "wrong syntax: define pilight_ctrl hostname:port [5.0]"; Log3 undef, 2, $msg; return $msg; } DevIo_CloseDev($hash); RemoveInternalTimer($hash); my $me = $a[0]; my $dev = $a[2]; $hash->{DeviceName} = $dev; $hash->{STATE} = "defined"; $hash->{API} = "6.0"; $hash->{API} = "5.0" if (defined($a[3]) && $a[3] =~/5/); $hash->{RETRY_INTERVAL} = 60; $hash->{helper}{CON} = "define"; $hash->{helper}{CHECK} = 0; my @sendQueue = (); $hash->{helper}->{sendQueue} = \@sendQueue; my @whiteList = (); $hash->{helper}->{whiteList} = \@whiteList; #$attr{$me}{verbose} = 5; return pilight_ctrl_TryConnect($hash); } sub pilight_ctrl_setStates($$) { my ($hash, $val) = @_; $hash->{STATE} = $val; $val = "disconnected" if ($val eq "closed"); setReadingsVal($hash, "state", $val, TimeNow()); } ##################################### sub pilight_ctrl_State($$$$) { my ($hash, $time, $name, $val) = @_; my $me = $hash->{NAME}; if ($name eq "STATE" && $val eq "closed") { Log3 $me, 3, "$me(State): abort connecting because of saved STATE"; pilight_ctrl_Close($hash); return undef; } # gespeicherten Readings nicht wieder herstellen if ($name eq "state" && $hash->{STATE}) { setReadingsVal($hash, $name, "disconnected", TimeNow()); } if ($name eq "rcv_raw") { setReadingsVal($hash, $name, "empty", TimeNow()); } return undef; } sub pilight_ctrl_CheckReadingState($) { my ($hash) = @_; my $me = $hash->{NAME}; my $state = ReadingsVal($me,"state",undef); if (defined($state) && $state ne "opened" && $state ne "disconnected") { Log3 $me, 3, "$me(CheckReadingState): Unknown error: unnormal value for reading state"; $hash->{STATE} = $hash->{helper}{CON}; $hash->{STATE} = "opened" if ($hash->{helper}{CON} eq "connected"); } return undef; } sub pilight_ctrl_Close($) { my $hash = shift; my $me = $hash->{NAME}; if (exists($hash->{helper}{RUNNING_PID})) { Log3 $me, 5, "$me(Close): call BlockingKill"; BlockingKill($hash->{helper}{RUNNING_PID}); delete($hash->{helper}{RUNNING_PID}); } splice(@{$hash->{helper}->{sendQueue}}); RemoveInternalTimer($hash); Log3 $me, 5, "$me(Close): close DevIo"; DevIo_CloseDev($hash); pilight_ctrl_setStates($hash,"closed"); $hash->{helper}{CON} = "closed"; delete $hash->{DevIoJustClosed}; } ##################################### sub pilight_ctrl_Undef($$) { my ($hash, $arg) = @_; my $me = $hash->{NAME}; pilight_ctrl_Close($hash); foreach my $d (sort keys %defs) { if(defined($defs{$d}) && defined($defs{$d}{IODev}) && $defs{$d}{IODev} == $hash) { delete $defs{$d}{IODev}; } } return undef; } ##################################### sub pilight_ctrl_TryConnect($) { my $hash = shift; my $me = $hash->{NAME}; Log3 $me, 5, "$me(TryConnect): $hash->{STATE}"; $hash->{helper}{CHECK} = 0; RemoveInternalTimer($hash); delete $hash->{NEXT_OPEN}; delete $hash->{DevIoJustClosed}; my $ret = DevIo_OpenDev($hash, 0, "pilight_ctrl_DoInit"); #DevIO set state to opened setReadingsVal($hash, "state", "disconnected", TimeNow()); delete $hash->{NEXT_OPEN}; $hash->{helper}{NEXT_TRY} = time()+$hash->{RETRY_INTERVAL}; InternalTimer(gettimeofday()+1,"pilight_ctrl_Check", $hash, 0); return $ret; } ##################################### sub pilight_ctrl_Reset($) { my ($hash) = @_; pilight_ctrl_Close($hash); return pilight_ctrl_TryConnect($hash); } ##################################### sub pilight_ctrl_Set($@) { my ($hash, @a) = @_; return "set $hash->{NAME} needs at least one parameter" if(@a < 2); my $me = shift @a; my $cmd = shift @a; return join(" ", sort keys %sets) if ($cmd eq "?"); if ($cmd eq "reset") { return pilight_ctrl_Reset($hash); } if ($cmd eq "disconnect") { pilight_ctrl_Close($hash); return undef; } return "Unknown argument $cmd, choose one of ". join(" ", sort keys %sets); } ##################################### sub pilight_ctrl_Check($) { my $hash = shift; my $me = $hash->{NAME}; RemoveInternalTimer($hash); $hash->{helper}{CHECK} = 0 if (!isDigit($hash->{helper}{CHECK})); $hash->{helper}{CHECK} +=1; Log3 $me, 5, "$me(Check): $hash->{STATE}"; if($hash->{STATE} eq "disconnected") { Log3 $me, 2, "$me(Check): Could not connect to pilight-daemon $hash->{DeviceName}"; $hash->{helper}{CON} = "disconnected"; pilight_ctrl_setStates($hash,"disconnected"); } return if ($hash->{helper}{CON} eq "disconnected" || $hash->{helper}{CON} eq "closed"); if ($hash->{helper}{CON} eq "define") { Log3 $me, 2, "$me(Check): connection to $hash->{DeviceName} failed"; $hash->{helper}{CHECK} = 0; $hash->{helper}{NEXT_TRY} = time()+$hash->{RETRY_INTERVAL}; return; } if ($hash->{helper}{CON} eq "identify") { if ($hash->{helper}{CHECK} % 3 == 0 && $hash->{helper}{CHECK} < 12) { #retry pilight_ctrl_DoInit($hash); } elsif ($hash->{helper}{CHECK} >= 12) { Log3 $me, 4, "$me(Check): Could not connect to pilight-daemon $hash->{DeviceName} - maybe wrong api version or port"; DevIo_Disconnected($hash); $hash->{helper}{CHECK} = 0; $hash->{helper}{CON} = "disconnected"; pilight_ctrl_setStates($hash,"disconnected"); $hash->{helper}{NEXT_TRY} = time()+$hash->{RETRY_INTERVAL}; return; } } if ($hash->{helper}{CON} eq "identify-failed" || $hash->{helper}{CHECK} > 20) { delete $hash->{helper}{CHECK}; $hash->{helper}{CON} = "disconnected"; pilight_ctrl_setStates($hash,"disconnected"); Log3 $me, 2, "$me(Check): identification to pilight-daemon $hash->{DeviceName} failed"; $hash->{helper}{NEXT_TRY} = time()+$hash->{RETRY_INTERVAL}; return; } if ($hash->{helper}{CON} eq "identify-rejected" || $hash->{helper}{CHECK} > 20) { Log3 $me, 2, "$me(Parse): connection to pilight-daemon $hash->{DeviceName} rejected"; delete $hash->{helper}{CHECK}; $hash->{helper}{CON} = "disconnected"; pilight_ctrl_setStates($hash,"disconnected"); $hash->{helper}{NEXT_TRY} = time()+$hash->{RETRY_INTERVAL}; return; } if ($hash->{helper}{CON} eq "connected") { delete $hash->{helper}{CHECK}; delete $hash->{helper}{NEXT_TRY}; pilight_ctrl_setStates($hash,"connected"); return; } InternalTimer(gettimeofday()+1,"pilight_ctrl_Check", $hash, 0); return 1; } ##################################### sub pilight_ctrl_DoInit($) { my $hash = shift; return "No FD" if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD}))); my $me = $hash->{NAME}; my $msg; my $api; Log3 $me, 5, "$me(DoInit): $hash->{STATE}"; $hash->{helper}{CON} = "identify"; if ($hash->{API} eq "6.0") { $msg = '{"action":"identify","options":{"receiver":1},"media":"all"}'; } else { $msg = "{ \"message\": \"client receiver\" }"; } Log3 $me, 5, "$me(DoInit): send $msg"; pilight_ctrl_SimpleWrite($hash,$msg); return; } ##################################### sub pilight_ctrl_Write($@) { my ($hash,$rmsg) = @_; my $me = $hash->{NAME}; if ($hash->{helper}{CON} eq "closed") { return; } if ($hash->{helper}{CON} ne "connected") { Log3 $me, 2, "$me(Write): ERROR: no connection to pilight-daemon $hash->{DeviceName}"; return; } my ($cName,$state,@args) = split(",",$rmsg); my $cType = lc($defs{$cName}->{TYPE}); Log3 $me, 4, "$me(Write): RCV ($cType) -> $rmsg"; my $proto = $defs{$cName}->{PROTOCOL}; my $id = $defs{$cName}->{ID}; my $unit = $defs{$cName}->{UNIT}; my $syscode = undef; $syscode = $defs{$cName}->{SYSCODE} if defined($defs{$cName}->{SYSCODE}); $id = "\"".$id."\"" if (defined($id) && !isDigit($id)); $unit = "\"".$unit."\"" if (defined($unit) && !isDigit($unit)); $syscode = "\"".$syscode."\"" if (defined($syscode) && !isDigit($syscode)); my $code; switch($cType){ case m/switch/ { $code = "{\"protocol\":[\"$proto\"],"; switch ($proto) { case m/elro/ {$code .= "\"systemcode\":$id,\"unitcode\":$unit,";} case m/silvercrest/ {$code .= "\"systemcode\":$id,\"unitcode\":$unit,";} case m/mumbi/ {$code .= "\"systemcode\":$id,\"unitcode\":$unit,";} case m/brennenstuhl/ {$code .= "\"systemcode\":$id,\"unitcode\":$unit,";} case m/pollin/ {$code .= "\"systemcode\":$id,\"unitcode\":$unit,";} case m/heitech/ {$code .= "\"systemcode\":$id,\"unitcode\":$unit,";} case m/impuls/ {$code .= "\"systemcode\":$id,\"programcode\":$unit,";} case m/rsl366/ {$code .= "\"systemcode\":$id,\"programcode\":$unit,";} case m/daycom/ { if (!defined($syscode)) { Log3 $me, 1, "$me(Write): Error protocol daycom no systemcode defined"; return; } $code .= "\"id\":$id,\"systemcode\":$syscode,\"unit\":$unit,"; } case m/cleverwatts/ { $code .= "\"id\":$id,"; if ($unit eq "\"all\"") { $code .= "\"all\":1,"; } else { $code .= "\"unit\":$unit,"; } } else {$code .= "\"id\":$id,\"unit\":$unit,";} } $code .= "\"$state\":1}"; } case m/dimmer/ { $code = "{\"protocol\":[\"$proto\"],\"id\":$id,\"unit\":$unit,\"$state\":1"; $code .= ",\"dimlevel\":$args[0]" if (defined($args[0])); $code .= "}"; } case m/raw/ { $code = "{\"protocol\":[\"$proto\"],\"code\":\"$state\"}"; } else {Log3 $me, 3, "$me(Write): unsupported client ($cName) -> $cType"; return;} } return if (!defined($code)); my $msg; if ($hash->{API} eq "6.0") { $msg = "{\"action\":\"send\",\"code\":$code}"; } else { $msg = "{\"message\":\"send\",\"code\":$code}"; } Log3 $me, 4, "$me(Write): $msg"; # we can't use the same connection because the pilight-daemon close the connection after sending # we have to create a second connection for sending data # we do not update the readings - we will do this at the response message push @{$hash->{helper}->{sendQueue}}, $msg; pilight_ctrl_SendNonBlocking($hash); } ##################################### sub pilight_ctrl_Send($) { my ($string) = @_; my ($me, $host,$data) = split("\\|", $string); my $hash = $defs{$me}; my ($remote_ip,$remote_port) = split(":",$host); my $socket = new IO::Socket::INET ( PeerHost => $remote_ip, PeerPort => $remote_port, Proto => 'tcp', ); if (!$socket) { Log3 $me, 2, "$me(Send): ERROR. Can't open socket to pilight-daemon $remote_ip:$remote_port"; return "$me|0"; } # we only need a identification to send in 5.0 version if ($hash->{API} eq "5.0") { my $msg = "{ \"message\": \"client sender\" }"; my $rcv; $socket->send($msg); $socket->recv($rcv,1024); $rcv =~ s/\n/ /g; Log3 $me, 5, "$me(Send): RCV -> $rcv"; my $json = JSON->new; my $jsondata = $json->decode($rcv); if (!$jsondata) { Log3 $me, 2, "$me(Send): ERROR. no JSON response message"; $socket->close(); return "$me|0"; } my $ret = pilight_ctrl_ClientAccepted($hash,$jsondata); if ( $ret != 1 ) { Log3 $me, 2, "$me(Send): ERROR. Connection rejected from pilight-daemon"; $socket->close(); return "$me|0"; } } Log3 $me, 5, "$me(Send): $data"; $data = $data."\n\n"; # add delimiter to signel end off stream if length > 1024 $socket->send($data); #6.0 we get a response message if ($hash->{API} eq "6.0") { my $rcv; $socket->recv($rcv,1024); $rcv =~ s/\n/ /g; Log3 $me, 4, "$me(Send): RCV -> $rcv"; } $socket->close(); return "$me|1"; } ##################################### sub pilight_ctrl_addWhiteList($$) { my ($own, $dev) = @_; my $me = $own->{NAME}; my $devName = $dev->{NAME}; my $id = (defined($dev->{ID})) ? $dev->{ID} : return; my $protocol = (defined($dev->{PROTOCOL})) ? $dev->{PROTOCOL}: return; Log3 $me, 4, "$me(addWhiteList): add $devName to white list"; my $entry = {}; my %whiteHash; @whiteHash{@{$own->{helper}->{whiteList}}}=(); if (!exists $whiteHash{"$protocol:$id"}) { push @{$own->{helper}->{whiteList}}, "$protocol:$id"; } #spezial 2nd protocol for dimmer if (defined($dev->{PROTOCOL2})) { $protocol = $dev->{PROTOCOL2}; if (!exists $whiteHash{"$protocol:$id"}) { push @{$own->{helper}->{whiteList}}, "$protocol:$id"; } } } ##################################### sub pilight_ctrl_createWhiteList($) { my ($own) = @_; splice(@{$own->{helper}->{whiteList}}); foreach my $d (keys %defs) { my $module = $defs{$d}{TYPE}; next if ($module !~ /pilight_[d|s|t|c].*/); pilight_ctrl_addWhiteList($own,$defs{$d}); } } ##################################### sub pilight_ctrl_Notify($$) { my ($own, $dev) = @_; my $me = $own->{NAME}; # own name / hash my $devName = $dev->{NAME}; # Device that created the events return undef if ($devName ne "global"); my $max = int(@{$dev->{CHANGED}}); # number of events / changes for (my $i = 0; $i < $max; $i++) { my $s = $dev->{CHANGED}[$i]; next if(!defined($s)); my ($what,$who) = split(' ',$s); if ( $what =~ m/^INITIALIZED$/ ) { Log3 $me, 4, "$me(Notify): create white list for $s"; pilight_ctrl_createWhiteList($own); } elsif ( $what =~ m/DEFINED/ ){ my $hash = $defs{$who}; next if(!$hash); my $module = $hash->{TYPE}; next if ($module !~ /pilight_[d|s|t].*/); pilight_ctrl_addWhiteList($own,$hash); } elsif ( $what =~ m/DELETED/ ){ Log3 $me, 4, "$me(Notify): create white list for $s"; pilight_ctrl_createWhiteList($own); } } return undef; } ##################################### sub pilight_ctrl_SendDone($) { my ($string) = @_; my ($me, $ok) = split("\\|", $string); my $hash = $defs{$me}; Log3 $me, 4, "$me(SendDone): message successfully send" if ($ok); Log3 $me, 2, "$me(SendDone): sending message failed" if (!$ok); delete($hash->{helper}{RUNNING_PID}); } ##################################### sub pilight_ctrl_SendAbort($) { my ($hash) = @_; my $me = $hash->{NAME}; Log3 $me, 2, "$me(SendAbort): ERROR. sending aborted"; delete($hash->{helper}{RUNNING_PID}); } ##################################### sub pilight_ctrl_SendNonBlocking($) { my ($hash) = @_; my $me = $hash->{NAME}; RemoveInternalTimer($hash); my $queueSize = @{$hash->{helper}->{sendQueue}}; Log3 $me, 5, "$me(SendNonBlocking): queue size $queueSize"; return if ($queueSize <=0); if (!(exists($hash->{helper}{RUNNING_PID}))) { my $data = shift @{$hash->{helper}->{sendQueue}}; my $blockingFn = "pilight_ctrl_Send"; my $arg = $me."|".$hash->{DeviceName}."|".$data; my $finishFn = "pilight_ctrl_SendDone"; my $timeout = AttrVal($me, "SendTimeout",1); my $abortFn = "pilight_ctrl_SendAbort"; $hash->{helper}{RUNNING_PID} = BlockingCall($blockingFn, $arg, $finishFn, $timeout, $abortFn, $hash); $hash->{helper}{LAST_SEND_RAW} = $data; } else { Log3 $me, 5, "$me(Write): Blocking Call running - will try it later"; } $queueSize = @{$hash->{helper}->{sendQueue}}; InternalTimer(gettimeofday()+0.5,"pilight_ctrl_SendNonBlocking", $hash, 0) if ($queueSize > 0); } ##################################### sub pilight_ctrl_ClientAccepted(@) { my ($hash,$data) = @_; my $me = $hash->{NAME}; my $ret = 0; if ($hash->{API} eq "5.0") { my $msg = (defined($data->{message})) ? $data->{message} : ""; $ret = 1 if(index($msg,"accept") >= 0); $ret = -1 if(index($msg,"reject") >= 0); } else { my $status = (defined($data->{status})) ? $data->{status} : ""; $ret = 1 if(index($status,"success") >= 0); $ret = -1 if(index($status,"reject") >= 0); } return $ret; } ##################################### # called from the global loop, when the select for hash->{FD} reports data sub pilight_ctrl_Read($) { my ($hash) = @_; my $me = $hash->{NAME}; my $buf = DevIo_SimpleRead($hash); return "" if(!defined($buf)); my $recdata = $hash->{PARTIAL}; #Log3 $me, 5, "$me(Read): RCV->$buf"; $recdata .= $buf; while($recdata =~ m/\n/) { my $rmsg; ($rmsg,$recdata) = split("\n", $recdata, 2); $rmsg =~ s/\r//; pilight_ctrl_Parse($hash, $rmsg) if($rmsg); } $hash->{PARTIAL} = $recdata; } ########################################### sub pilight_ctrl_Parse($$) { my ($hash, $rmsg) = @_; my $me = $hash->{NAME}; Log3 $me, 5, "$me(Parse): RCV -> $rmsg"; next if(!$rmsg || length($rmsg) < 1); $hash->{helper}{LAST_RCV_RAW} = $rmsg; my $json = JSON->new; my $data = $json->decode($rmsg); return if (!$data); if ($hash->{helper}{CON} eq "identify") # we are in identify process { Log3 $me, 4, "$me(Parse): identify -> $rmsg"; $hash->{helper}{CON} = "identify-failed"; my $ret = pilight_ctrl_ClientAccepted($hash,$data); switch ($ret) { case 1 { $hash->{helper}{CON} = "connected"; } case -1 { $hash->{helper}{CON} = "identify-rejected"; } else { Log3 $me, 3, "$me(Parse): internal error"; } } pilight_ctrl_Check($hash); return; } $hash->{helper}{LAST_RCV_JSON} = $json; my $proto = (defined($data->{protocol})) ? $data->{protocol} : ""; if (!$proto) { Log3 $me, 3, "$me(Parse): unknown message -> $rmsg"; return; } #brands my @brands = split(",",AttrVal($me, "brands","")); foreach my $brand (@brands){ my($search,$replace) = split(":",$brand); next if (!defined($search) || !defined($replace)); $proto =~ s/$search/$replace/g; } $hash->{helper}{LAST_RCV_PROTOCOL} = $proto; my $s = ($hash->{API} eq "5.0") ? "code" : "message"; my $state = (defined($data->{$s}{state})) ? $data->{$s}{state} : ""; my $all = (defined($data->{$s}{all})) ? $data->{$s}{all} : ""; my $id = ""; foreach my $sid (@idList) { $id = (defined($data->{$s}{$sid})) ? $data->{$s}{$sid} : ""; last if ($id ne ""); } #systemcode and id for protocol daycom (needs 3 id's, systemcode, id, unit my $syscode = (defined($data->{$s}{"systemcode"})) ? $data->{$s}{"systemcode"} : ""; my $unit = ""; foreach my $sunit (@unitList) { $unit = (defined($data->{$s}{$sunit})) ? $data->{$s}{$sunit} : ""; last if ($unit ne ""); } # handling ContactAsSwitch befor white list check my $asSwitch = $attr{$me}{ContactAsSwitch}; if ( defined($asSwitch) && $asSwitch =~ /$id/ && ($state =~ /opened/ || $state =~ /closed/) ) { $proto =~ s/contact/switch/g; $state =~ s/opened/on/g; $state =~ s/closed/off/g; Log3 $me, 4, "$me(Parse): contact as switch for $id"; } # some protocols have no id but unit(code) e.q. ev1527, GS-iwds07 $id = $unit if ($id eq "" && $unit ne ""); $unit = "all" if ($unit eq "" && $all ne ""); Log3 $me, 5, "$me(Parse): protocol:$proto,id:$id,unit:$unit"; my @ignoreIDs = split(",",AttrVal($me, "ignoreProtocol","")); # white or ignore list if (@ignoreIDs == 1 && $ignoreIDs[0] eq "*"){ # use list my %whiteHash; @whiteHash{@{$hash->{helper}->{whiteList}}}=(); if (!exists $whiteHash{"$proto:$id"}) { Log3 $me, 5, "$me(Parse): $proto:$id not in white list"; return; } } else { #ignore list my %ignoreHash; @ignoreHash{@ignoreIDs}=(); if (exists $ignoreHash{"$proto:$id"} || exists $ignoreHash{"$proto:*"}) { Log3 $me, 5, "$me(Parse): $proto:$id is in ignoreProtocol list"; return; } } readingsBeginUpdate($hash); readingsBulkUpdate($hash,"rcv_raw",$rmsg); readingsEndUpdate($hash, 1); my $protoID = -1; switch($proto){ #switch case m/switch/ {$protoID = 1;} case m/elro/ {$protoID = 1;} case m/silvercrest/ {$protoID = 1;} case m/mumbi/ {$protoID = 1;} case m/brennenstuhl/{$protoID = 1;} case m/pollin/ {$protoID = 1;} case m/daycom/ {$protoID = 1;} case m/impuls/ {$protoID = 1;} case m/rsl366/ {$protoID = 1;} case m/cleverwatts/ {$protoID = 1;} case m/intertechno_old/ {$protoID = 1;} case m/quigg_gt/ {$protoID = 1;} case m/heitech/ {$protoID = 1;} case m/dimmer/ {$protoID = 2;} #contact sensors case m/contact/ {$protoID = 3;} case m/ev1527/ {$protoID = 3;} case m/sc2262/ {$protoID = 3;} case m/GS-iwds07/ {$protoID = 3;} #Weather Stations temperature, humidity case m/alecto/ {$protoID = 4;} case m/auriol/ {$protoID = 4;} case m/ninjablocks/ {$protoID = 4;} case m/tfa/ {$protoID = 4;} case m/teknihall/ {$protoID = 4;} case m/oregon_21/ {$protoID = 4;} #gpio temperature, humidity sensors case m/dht11/ {$protoID = 4;} case m/dht22/ {$protoID = 4;} case m/ds18b20/ {$protoID = 4;} case m/ds18s20/ {$protoID = 4;} case m/cpu_temp/ {$protoID = 4;} case m/lm75/ {$protoID = 4;} case m/lm76/ {$protoID = 4;} case m/bmp085/ {$protoID = 4;} case m/bmp180/ {$protoID = 4;} case m/screen/ {$protoID = 5;} #smoke sensors case m/secudo_smoke_sensor/ {$protoID = 6;} case m/firmware/ {return;} else {Log3 $me, 3, "$me(Parse): unknown protocol -> $proto"; return;} } if ($id eq "") { Log3 $me, 3, "$me(Parse): ERROR no or unknown id $rmsg"; return; } switch($protoID){ case 1 { my $msg = "PISWITCH,$proto,$id,$unit,$state"; $msg .= ",$syscode" if ($syscode ne ""); Log3 $me, 4, "$me(Dispatch): $msg"; return Dispatch($hash, $msg,undef ); } case 2 { my $dimlevel = (defined($data->{$s}{dimlevel})) ? $data->{$s}{dimlevel} : ""; my $msg = "PIDIMMER,$proto,$id,$unit,$state"; $msg.= ",$dimlevel" if ($dimlevel ne ""); Log3 $me, 4, "$me(Dispatch): $msg"; return Dispatch($hash, $msg ,undef); } case 3 { my $piTempData = ""; $piTempData .= ",battery:$data->{$s}{battery}" if (defined($data->{$s}{battery})); my $msg = "PICONTACT,$proto,$id,$unit,$state$piTempData"; Log3 $me, 4, "$me(Dispatch): $msg"; return Dispatch($hash, $msg,undef); } case 4 { my $piTempData = ""; $piTempData .= ",temperature:$data->{$s}{temperature}" if (defined($data->{$s}{temperature})); $piTempData .= ",humidity:$data->{$s}{humidity}" if (defined($data->{$s}{humidity})); $piTempData .= ",battery:$data->{$s}{battery}" if (defined($data->{$s}{battery})); $piTempData .= ",pressure:$data->{$s}{pressure}" if (defined($data->{$s}{pressure})); $piTempData .= ",windavg:$data->{$s}{windavg}" if (defined($data->{$s}{windavg})); $piTempData .= ",winddir:$data->{$s}{winddir}" if (defined($data->{$s}{winddir})); $piTempData .= ",windgust:$data->{$s}{windgust}" if (defined($data->{$s}{windgust})); my $msg = "PITEMP,$proto,$id$piTempData"; Log3 $me, 4, "$me(Dispatch): $msg"; return Dispatch($hash, $msg,undef); } case 5 { return Dispatch($hash, "PISCREEN,$proto,$id,$unit,$state",undef); } case 6 { return Dispatch($hash, "PISMOKE,$proto,$id,$state",undef); } else {Log3 $me, 3, "$me(Parse): unknown protocol -> $proto"; return;} } return; } ##################################### # called from gobal loop to try reconnection sub pilight_ctrl_Ready($) { my ($hash) = @_; my $me = $hash->{NAME}; if($hash->{STATE} eq "disconnected") { return if(defined($hash->{helper}{NEXT_TRY}) && $hash->{helper}{NEXT_TRY} && time() < $hash->{helper}{NEXT_TRY}); return pilight_ctrl_TryConnect($hash); } } ##################################### sub pilight_ctrl_SimpleWrite(@) { my ($hash, $msg, $nonl) = @_; return if(!$hash); my $me = $hash->{NAME}; Log3 $me, 4, "$me(SimpleWrite): snd -> $msg"; $msg .= "\n" unless($nonl); DevIo_SimpleWrite($hash,$msg,0); } 1; =pod =item summary base module to comunicate with pilight =item summary_DE Basismodul zur Kommunikation mit pilight =begin html

pilight_ctrl

    pilight_ctrl is the base device for the communication (sending and receiving) with the pilight-daemon.
    You have to define client devices e.q. pilight_switch for switches.
    Further information to pilight: http://www.pilight.org/

    Further information to pilight protocols: http://wiki.pilight.org/doku.php/protocols#protocols
    Currently supported:
    • Switches:
    • Dimmers:
    • Temperature and humitity sensors


    Define
      define <name> pilight_ctrl ip:port [api] ip:port is the IP address and port of the pilight-daemon
      api specifies the pilight api version - default 6.0

      Example:
        define myctrl pilight_ctrl localhost:5000 5.0
        define myctrl pilight_ctrl 192.168.1.1:5000

    Set

    • reset Reset the connection to the pilight daemon
    • disconnectDiconnect from pilight daemon and do not reconnect automatically

    Readings

    • rcv_raw
      The last complete received message in json format.

    Attributes
    • ignoreProtocol
      Comma separated list of protocol:id combinations to ignore.
      protocol:* ignores the complete protocol.
      * All incomming messages will be ignored. Only protocol id combinations from defined submodules will be accepted
      Example:
    • ignoreProtocol tfa:0
    • ignoreProtocol tfa:*
    • ignoreProtocol *
    • brands
      Comma separated list of : combinations to rename protocol names.
      pilight uses different protocol names for the same protocol e.q. arctech_switch and kaku_switch
      Example: brands archtech:kaku
    • ContactAsSwitch
      Comma separated list of ids which correspond to a contact but will be interpreted as switch.
      In this case opened will be interpreted as on and closed as off.
      Example: ContactAsSwitch 12345
    • SendTimeout
      Timeout [s] for aborting sending commands (non blocking) - default 1s

=end html =cut