diff --git a/fhem/CHANGED b/fhem/CHANGED index a54a83f38..f52adf044 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,7 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - added: New modules 10_pilight_ctrl, 30_pilight_dimmer, 30_pilight_switch + and 30_pilight_temp to support pilight (sending and receiving) - bugfix: 98_EDIPLUG: change kW/h to kWh and fix missing Id - feature: FB_CALLMONITOR: new reverse search method "textfile" for custom definitions diff --git a/fhem/FHEM/10_pilight_ctrl.pm b/fhem/FHEM/10_pilight_ctrl.pm new file mode 100644 index 000000000..4870aeef4 --- /dev/null +++ b/fhem/FHEM/10_pilight_ctrl.pm @@ -0,0 +1,761 @@ +############################################## +# $Id: 10_pilight_ctrl.pm 0.50 2015-04-17 Risiko $ +# +# 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 +############################################## +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); +use JSON; #libjson-perl +use Switch; #libswitch-perl + +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($); + +my %sets = ( "reset:noArg" => ""); +my %matchList = ( "1:pilight_switch" => "^SWITCH", + "2:pilight_dimmer" => "^SWITCH|^DIMMER", + "3:pilight_temp" => "^PITEMP") ; + +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 pilight_ctrl_Initialize($) +{ + my ($hash) = @_; + + require "$attr{global}{modpath}/FHEM/DevIo.pm"; + require "$attr{global}{modpath}/FHEM/Blocking.pm"; + + $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->{AttrList}= "ignore brands ContactAsSwitch ".$readingFnAttributes; + + $hash->{Clients} = ":pilight_switch:pilight_dimmer:pilight_temp:"; + #$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])); + $hash->{RETRY_INTERVAL} = 60; + + $hash->{helper}{CON} = "define"; + $hash->{helper}{CHECK} = 0; + + my @sendQueue = (); + $hash->{helper}->{sendQueue} = \@sendQueue; + + #$attr{$me}{verbose} = 5; + + return pilight_ctrl_TryConnect($hash); +} + +sub pilight_ctrl_Close($) +{ + my $hash = shift; + my $me = $hash->{NAME}; + + BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID})); + + RemoveInternalTimer($hash); + foreach my $d (sort keys %defs) { + if(defined($defs{$d}) && + defined($defs{$d}{IODev}) && + $defs{$d}{IODev} == $hash) + { + delete $defs{$d}{IODev}; + } + } + DevIo_CloseDev($hash); +} + +##################################### +sub pilight_ctrl_Undef($$) +{ + my ($hash, $arg) = @_; + my $me = $hash->{NAME}; + + pilight_ctrl_Close($hash); + return undef; +} + +##################################### +sub pilight_ctrl_TryConnect($) +{ + my $hash = shift; + my $me = $hash->{NAME}; + + $hash->{helper}{CHECK} = 0; + + RemoveInternalTimer($hash); + + delete $hash->{NEXT_OPEN}; + my $ret = DevIo_OpenDev($hash, 0, "pilight_ctrl_DoInit"); + + 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_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") + { + pilight_ctrl_Close($hash); + return pilight_ctrl_TryConnect($hash); + } + + 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->{helper}{CON}"; + + if($hash->{STATE} eq "disconnected" && !defined($hash->{BASE})) { + Log3 $me, 2, "$me(Check): Could not connect to pilight-daemon $hash->{DeviceName}"; + $hash->{helper}{CON} = "disconnected"; + } + + return if ($hash->{helper}{CON} eq "disconnected"); + + 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"; + $hash->{STATE} = "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"; + 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"; + $hash->{helper}{NEXT_TRY} = time()+$hash->{RETRY_INTERVAL}; + return; + } + + if ($hash->{helper}{CON} eq "connected") { + delete $hash->{helper}{CHECK}; + delete $hash->{helper}{NEXT_TRY}; + 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; + + $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} 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}; + + $id = "\"".$id."\"" if (!isdigit($id)); + $unit = "\"".$unit."\"" if (!isdigit($unit)); + + 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/impuls/ {$code .= "\"systemcode\":$id,\"programcode\":$unit,";} + case m/rsl366/ {$code .= "\"systemcode\":$id,\"programcode\":$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 .= "}"; + } + 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 return "$me|0"; + } + } + + Log3 $me, 5, "$me(Send): $data"; + $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, 5, "$me(Send): RCV -> $rcv"; + } + $socket->close(); + + return "$me|1"; +} + +##################################### +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); + + 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 = 4; + 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"; + } + + 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, 4, "$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 + { + $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 ""); + } + + my $unit = ""; + foreach my $sunit (@unitList) { + $unit = (defined($data->{$s}{$sunit})) ? $data->{$s}{$sunit} : ""; + last if ($unit ne ""); + } + + my @ignoreIDs = split(",",AttrVal($me, "ignore","")); + my %ignoreHash; + @ignoreHash{@ignoreIDs}=(); + if (exists $ignoreHash{"$proto:$id"} || exists $ignoreHash{"$proto:*"}) { + Log3 $me, 5, "$me(Parse): $proto:$id is in ignore list"; + return; + } + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"rcv_raw",$rmsg); + readingsEndUpdate($hash, 1); + + $unit = "all" if ($unit eq "" && $all ne ""); + + 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/impuls/ {$protoID = 1;} + case m/rsl366/ {$protoID = 1;} + case m/cleverwatts/ {$protoID = 1;} + case m/intertechno_old/ {$protoID = 1;} + + case m/dimmer/ {$protoID = 2;} + case m/contact/ {$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;} + + #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/screen/ {return;} + 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 { return Dispatch($hash, "SWITCH,$proto,$id,$unit,$state",undef ); } + case 2 { + my $dimlevel = (defined($data->{$s}{dimlevel})) ? $data->{$s}{dimlevel} : ""; + my $msg = "DIMMER,$proto,$id,$unit,$state"; + $msg.= ",$dimlevel" if ($dimlevel ne ""); + return Dispatch($hash, $msg ,undef); + } + case 3 { + my $asSwitch = $attr{$me}{ContactAsSwitch}; + if ( defined($asSwitch) && $asSwitch =~ /$id/) { + $proto =~ s/contact/switch/g; + $state =~ s/opened/on/g; + $state =~ s/closed/off/g; + Log3 $me, 5, "$me(Parse): contact as switch for $id"; + return Dispatch($hash, "SWITCH,$proto,$id,$unit,$state",undef); + } + return; + } + case 4 { + my $temp = (defined($data->{$s}{temperature})) ? $data->{$s}{temperature} : ""; + return if ($temp eq ""); + + my $humidity = (defined($data->{$s}{humidity})) ? $data->{$s}{humidity} : ""; + my $msg = "PITEMP,$proto,$id,$temp,$humidity"; + return Dispatch($hash, $msg,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" && !defined($hash->{BASE})) + { + 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 +=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 +
    • +
    +
    + +

    Readings

    +
      +
    • + rcv_raw
      + The last complete received message in json format. +
    • +
    +
    + + Attributes +
      +
    • ignore
      + Comma separated list of protocol:id combinations to ignore.
      + Example: ignore tfa:0 +
    • +
    • 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 +
    • +
    +
    + +
+ +=end html + +=cut + diff --git a/fhem/FHEM/30_pilight_dimmer.pm b/fhem/FHEM/30_pilight_dimmer.pm new file mode 100644 index 000000000..747f45eb7 --- /dev/null +++ b/fhem/FHEM/30_pilight_dimmer.pm @@ -0,0 +1,188 @@ +############################################## +# $Id: 20_pilight_dimmer.pm 2015-02-26 Risiko $ +# +# Usage +# +# define pilight_dimmer +# +# Changelog +# +# V 0.10 2015-02-26 - initial beta version +############################################## + +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); +use JSON; + +sub pilight_dimmer_Parse($$); +sub pilight_dimmer_Define($$); +sub pilight_dimmer_Fingerprint($$); + +sub pilight_dimmer_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "pilight_dimmer_Define"; + $hash->{Match} = "^SWITCH|^DIMMER"; + $hash->{ParseFn} = "pilight_dimmer_Parse"; + $hash->{SetFn} = "pilight_dimmer_Set"; + $hash->{AttrList} = "dimlevel_max ".$readingFnAttributes; +} + +##################################### +sub pilight_dimmer_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + if(@a < 5) { + my $msg = "wrong syntax: define pilight_dimmer "; + Log3 undef, 2, $msg; + return $msg; + } + + my $me = $a[0]; + my $protocol = $a[2]; + my $id = $a[3]; + my $unit = $a[4]; + + $hash->{STATE} = "defined"; + $hash->{PROTOCOL} = lc($protocol); + $hash->{ID} = $id; + $hash->{UNIT} = $unit; + + #$attr{$me}{verbose} = 5; + + $modules{pilight_dimmer}{defptr}{lc($id)}{$me} = $hash; + AssignIoPort($hash); + return undef; +} + +########################################### +sub pilight_dimmer_Parse($$) +{ + + my ($mhash, $rmsg, $rawdata) = @_; + my $backend = $mhash->{NAME}; + + Log3 $backend, 4, "pilight_dimmer_Parse: RCV -> $rmsg"; + + my ($dev,$protocol,$id,$unit,$state,$dimlevel) = split(",",$rmsg); + return () if($dev ne "SWITCH" && $dev ne "DIMMER"); + + my $chash; + foreach my $n (keys %{ $modules{pilight_dimmer}{defptr}{lc($id)} }) { + my $lh = $modules{pilight_dimmer}{defptr}{$id}{$n}; + next if ( !defined($lh->{UNIT}) ); + if ($lh->{ID} eq $id && $lh->{UNIT} eq $unit) { + $chash = $lh; + last; + } + } + + return () if (!defined($chash->{NAME})); + + readingsBeginUpdate($chash); + readingsBulkUpdate($chash,"state",$state); + readingsBulkUpdate($chash,"dimlevel",$dimlevel) if (defined($dimlevel)); + readingsEndUpdate($chash, 1); + + return $chash->{NAME}; +} + +##################################### +sub pilight_dimmer_Set($$) +{ + my ($hash, @a) = @_; + my $me = shift @a; + + return "no set value specified" if(int(@a) < 1); + my $maxlevel = $attr{$me}{dimlevel_max}; + $maxlevel = 15 if (!defined($maxlevel)); + return "Unknown argument ?, choose one of on off dimlevel:slider,0,1,$maxlevel" if($a[0] eq "?"); + + my $msg = "$me,"; + + if ($a[0] eq "dimlevel") { + $msg .= "on,".$a[1]; + } else { + $msg .= $a[0]; + } + + Log3 $me, 4, "$me(Set): $msg"; + + IOWrite($hash, $msg); +} + + +1; + +=pod +=begin html + + +

pilight_dimmer

+
    + + pilight_dimmer represents a dimmmer controled with\from pilight
    + You have to define the base device pilight_ctrl first.
    + Further information to pilight: http://www.pilight.org/
    + Supported dimmers: http://wiki.pilight.org/doku.php/protocols#dimmers
    +

    + + + Define +
      + define <name> pilight_dimmer protocol id unit +

      + + Example: +
        + define myctrl pilight_dimmer kaku_dimmer 13483668 0
        +
      +
    +
    + +

    Set

    +
      +
    • + on +
    • +
    • + off +
    • +
    • + dimlevel +
    • +
    +
    + +

    Readings

    +
      +
    • + state
      + state of the dimmer on or off +
    • +
    • + dimlevel
      + dimlevel of the dimmer +
    • +
    +
    + + Attributes +
      +
    • dimlevel_max
      + Maximum of the dimlevel - default 15
      +
    • + +
    +
    +
+ +=end html + +=cut diff --git a/fhem/FHEM/30_pilight_switch.pm b/fhem/FHEM/30_pilight_switch.pm new file mode 100644 index 000000000..d9343f39d --- /dev/null +++ b/fhem/FHEM/30_pilight_switch.pm @@ -0,0 +1,165 @@ +############################################## +# $Id: 20_pilight_switch.pm 0.11 2015-03-29 Risiko $ +# +# Usage +# +# define pilight_switch +# +# Changelog +# +# V 0.10 2015-02-22 - initial beta version +# V 0.11 2015-03-29 - FIX: $readingFnAttributes +############################################## + +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); +use JSON; + +sub pilight_switch_Parse($$); +sub pilight_switch_Define($$); +sub pilight_switch_Fingerprint($$); + +sub pilight_switch_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "pilight_switch_Define"; + $hash->{Match} = "^SWITCH"; + $hash->{ParseFn} = "pilight_switch_Parse"; + $hash->{SetFn} = "pilight_switch_Set"; + $hash->{AttrList} = $readingFnAttributes; +} + +##################################### +sub pilight_switch_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + if(@a < 5) { + my $msg = "wrong syntax: define pilight_switch "; + Log3 undef, 2, $msg; + return $msg; + } + + my $me = $a[0]; + my $protocol = $a[2]; + my $id = $a[3]; + my $unit = $a[4]; + + $hash->{STATE} = "defined"; + $hash->{PROTOCOL} = lc($protocol); + $hash->{ID} = $id; + $hash->{UNIT} = $unit; + + #$attr{$me}{verbose} = 5; + + $modules{pilight_switch}{defptr}{lc($protocol)}{$me} = $hash; + AssignIoPort($hash); + return undef; +} + +########################################### +sub pilight_switch_Parse($$) +{ + + my ($mhash, $rmsg, $rawdata) = @_; + my $backend = $mhash->{NAME}; + + Log3 $backend, 4, "pilight_switch_Parse: RCV -> $rmsg"; + + my ($dev,$protocol,$id,$unit,$state,@args) = split(",",$rmsg); + return () if($dev ne "SWITCH"); + + my $chash; + foreach my $n (keys %{ $modules{pilight_switch}{defptr}{lc($protocol)} }) { + my $lh = $modules{pilight_switch}{defptr}{$protocol}{$n}; + next if ( !defined($lh->{ID}) || !defined($lh->{UNIT}) ); + if ($lh->{ID} eq $id && $lh->{UNIT} eq $unit) { + $chash = $lh; + last; + } + } + + return () if (!defined($chash->{NAME})); + + readingsBeginUpdate($chash); + readingsBulkUpdate($chash,"state",$state); + readingsEndUpdate($chash, 1); + + return $chash->{NAME}; +} + +##################################### +sub pilight_switch_Set($$) +{ + my ($hash, @a) = @_; + my $me = shift @a; + + return "no set value specified" if(int(@a) < 1); + return "Unknown argument ?, choose one of on off" if($a[0] eq "?"); + + my $v = join(" ", @a); + Log3 $me, 4, "$me(Set): $v"; + + #readingsSingleUpdate($hash,"state",$v,1); + + my $msg = "$me,$v"; + IOWrite($hash, $msg); +} + + +1; + +=pod +=begin html + + +

pilight_switch

+
    + + pilight_switch represents a switch controled with\from pilight
    + You have to define the base device pilight_ctrl first.
    + Further information to pilight: http://www.pilight.org/
    + Supported switches: http://wiki.pilight.org/doku.php/protocols#switches
    +
    + + Define +
      + define <name> pilight_switch protocol id unit +

      + + Example: +
        + define myctrl pilight_switch kaku_switch_old 0 0
        +
      +
    +
    + +

    Set

    +
      +
    • + on +
    • +
    • + off +
    • +
    +
    + +

    Readings

    +
      +
    • + state
      + state of the switch on or off +
    • +
    +
    +
+ +=end html + +=cut diff --git a/fhem/FHEM/30_pilight_temp.pm b/fhem/FHEM/30_pilight_temp.pm new file mode 100644 index 000000000..fa35b8ebb --- /dev/null +++ b/fhem/FHEM/30_pilight_temp.pm @@ -0,0 +1,142 @@ +############################################## +# $Id: 20_pilight_temp.pm 0.11 2015-03-29 Risiko $ +# +# Usage +# +# define pilight_temp +# +# Changelog +# +# V 0.10 2015-03-29 - initial beta version +# V 0.11 2015-03-29 - FIX: $readingFnAttributes +############################################## + +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); +use JSON; + +sub pilight_temp_Parse($$); +sub pilight_temp_Define($$); +sub pilight_temp_Fingerprint($$); + +sub pilight_temp_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "pilight_temp_Define"; + $hash->{Match} = "^PITEMP"; + $hash->{ParseFn} = "pilight_temp_Parse"; + $hash->{AttrList} = $readingFnAttributes; +} + +##################################### +sub pilight_temp_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + if(@a < 4) { + my $msg = "wrong syntax: define pilight_temp "; + Log3 undef, 2, $msg; + return $msg; + } + + my $me = $a[0]; + my $protocol = $a[2]; + my $id = $a[3]; + + $hash->{STATE} = "defined"; + $hash->{PROTOCOL} = lc($protocol); + $hash->{ID} = $id; + + #$attr{$me}{verbose} = 5; + + $modules{pilight_temp}{defptr}{lc($protocol)}{$me} = $hash; + AssignIoPort($hash); + return undef; +} + +########################################### +sub pilight_temp_Parse($$) +{ + my ($mhash, $rmsg, $rawdata) = @_; + my $backend = $mhash->{NAME}; + + Log3 $backend, 4, "pilight_temp_Parse: RCV -> $rmsg"; + + my ($dev,$protocol,$id,$temp,$humidity,@args) = split(",",$rmsg); + return () if($dev ne "PITEMP"); + + my $chash; + foreach my $n (keys %{ $modules{pilight_temp}{defptr}{lc($protocol)} }) { + my $lh = $modules{pilight_temp}{defptr}{$protocol}{$n}; + next if ( !defined($lh->{ID}) ); + if ($lh->{ID} eq $id) { + $chash = $lh; + last; + } + } + + return () if (!defined($chash->{NAME})); + + readingsBeginUpdate($chash); + readingsBulkUpdate($chash,"state",$temp); + readingsBulkUpdate($chash,"temperature",$temp); + readingsBulkUpdate($chash,"humidity",$humidity) if (defined($humidity) && $humidity ne ""); + readingsEndUpdate($chash, 1); + + return $chash->{NAME}; +} + + +1; + +=pod +=begin html + + +

pilight_temp

+
    + + pilight_temp represents a temperature and humidity sensor receiving dat from pilight
    + You have to define the base device pilight_ctrl first.
    + Further information to pilight: http://www.pilight.org/
    + Supported Sensors: http://wiki.pilight.org/doku.php/protocols#weather_stations
    +
    + + Define +
      + define <name> pilight_temp protocol id +

      + + Example: +
        + define myctrl pilight_temp alecto_wsd17 100
        +
      +
    +
    + +

    Readings

    +
      +
    • + state
      + present the current temperature +
    • +
    • + temperature
      + present the current temperature +
    • +
    • + humidity
      + present the current humidity (if sensor support it) +
    • +
    +
    +
+ +=end html + +=cut diff --git a/fhem/HISTORY b/fhem/HISTORY index 7db2fb384..38d81cb1a 100644 --- a/fhem/HISTORY +++ b/fhem/HISTORY @@ -627,3 +627,6 @@ - Sat Mar 21 2015 (loredo) - reading residentsGuests in module 10_RESIDENTS was renamed to residentsTotalGuests - introducing new readings in module 10_RESIDENTS: residentsTotalGuestsAbsent and residentsTotalGuestsPresent + +- Sat Apr 25 2015 (risiko) + - added new modules to support pilight (sending and receiving): switch, dimmer, temperature and humidity diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index a89b2ee9f..6d5710955 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -61,6 +61,7 @@ FHEM/10_SOMFY.pm thdankert http://forum.fhem.de Sonstiges FHEM/10_UNIRoll.pm c-herrmann http://forum.fhem.de SlowRF FHEM/10_ZWave.pm rudolfkoenig http://forum.fhem.de ZWave FHEM/10_RESIDENTS.pm loredo http://forum.fhem.de Automatisierung +FHEM/10_pilight_ctrl.pm risiko http://forum.fhem.de Sonstige Systeme FHEM/11_FHT.pm rudolfkoenig http://forum.fhem.de SlowRF FHEM/11_FHT8V.pm rudolfkoenig http://forum.fhem.de SlowRF FHEM/11_OWDevice.pm borisneubert/mfr69bs http://forum.fhem.de 1Wire @@ -111,6 +112,9 @@ FHEM/30_HUEBridge.pm justme1968 http://forum.fhem.de Beleuchtu FHEM/30_LIGHTIFY.pm justme1968 http://forum.fhem.de Beleuchtung FHEM/30_MilightBridge.pm mattwire http://forum.fhem.de Beleuchtung FHEM/30_ENECSYSGW.pm akw http://forum.fhem.de Sonstige Systeme +FHEM/30_pilight_dimmer.pm risiko http://forum.fhem.de Sonstige Systeme +FHEM/30_pilight_switch.pm risiko http://forum.fhem.de Sonstige Systeme +FHEM/30_pilight_temp.pm risiko http://forum.fhem.de Sonstige Systeme FHEM/31_HUEDevice.pm justme1968 http://forum.fhem.de Beleuchtung FHEM/31_MilightDevice.pm mattwire http://forum.fhem.de Beleuchtung FHEM/31_ENECSYSINV.pm akw http://forum.fhem.de Sonstige Systeme