From e899ba7886606175e81f24981391493533112e18 Mon Sep 17 00:00:00 2001 From: T0RST3N Date: Sun, 12 Mar 2017 15:48:29 +0100 Subject: [PATCH] First version with read and write to physical modul added: UpdateAll UpdateSingle After MiGateway define all connected devices to the gateway are added to FHEM with there status Cleanup logging lines [PRE] added for support for switch sensor motion, magnet, sensorht filed devStateIcon oder Stateformat --- 71_XiaomiSmartHome.pm | 184 +++++++++++++++++++++++++++-------- 71_XiaomiSmartHome_Device.pm | 156 ++++++++++++++++++++++------- 2 files changed, 266 insertions(+), 74 deletions(-) diff --git a/71_XiaomiSmartHome.pm b/71_XiaomiSmartHome.pm index bc3516b..b6c553a 100644 --- a/71_XiaomiSmartHome.pm +++ b/71_XiaomiSmartHome.pm @@ -1,4 +1,25 @@ -# 71_XiaomiSmartHome.pm 2017-08-02 13:07:33Z torte $ +############################################################################### +# +# 03.2017 torte +# All rights reserved +# +# This script is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# +############################################################################### package main; @@ -10,6 +31,10 @@ use Data::Dumper; use IO::Socket; use IO::Socket::Multicast; +sub XiaomiSmartHome_Notify($$); +sub XiaomiSmartHome_updateSingleReading($$); + +my $version = "0.02"; my %XiaomiSmartHome_gets = ( "getDevices" => ["get_id_list", '^.+get_id_list_ack' ], @@ -29,11 +54,14 @@ sub XiaomiSmartHome_Initialize($) { $hash->{GetFn} = 'XiaomiSmartHome_Get'; $hash->{AttrFn} = 'XiaomiSmartHome_Attr'; $hash->{ReadFn} = 'XiaomiSmartHome_Read'; - $hash->{AttrList} = "disable:1,0 " - . $readingFnAttributes; + $hash->{WriteFn} = "XiaomiSmartHome_Write"; + $hash->{AttrList} = "disable:1,0 " . + "Room " . + $readingFnAttributes; $hash->{MatchList} = { "1:XiaomiSmartHome_Device" => "^.+magnet", "2:XiaomiSmartHome_Device" => "^.+motion", - "3:XiaomiSmartHome_Device" => "^.+sensor_ht"}; + "3:XiaomiSmartHome_Device" => "^.+sensor_ht", + "4:XiaomiSmartHome_Device" => "^.+switch"}; } ##################################### @@ -54,28 +82,23 @@ sub XiaomiSmartHome_Read($) { my $decoded = decode_json($buf); if ($json) { - Log3 $name, 5, "$name: Read:" . $buf; - if ($decoded->{'cmd'} eq 'heartbeat'){ - readingsSingleUpdate($hash, $decoded->{'sid'}, 'heartbeat', 1 ); - } - elsif ($decoded->{'cmd'} eq 'report'){ - if ($decoded->{'model'} eq 'gateway'){ + Log3 $name, 5, "$name> Read:" . $buf; + if ($decoded->{'model'} eq 'gateway'){ + if ($decoded->{'cmd'} eq 'report'){ my @status = split('\"', $decoded->{'data'}); - if ($status[1] eq 'rgb'){ - my $t = ($status[2] =~ /([\d]+)/); - Log3 $name, 3, "$name: Gateway: " . $decoded->{'sid'} . " RGB: " . ($status[2] =~ /([\d]+)/)[0] ; - readingsSingleUpdate($hash, "RGB", ($status[2] =~ /([\d]+)/)[0] , 1 ); - } - #elsif($status[1] eq 'battery'){ - # Log3 $name, 3, "$name: MagnetSensor: " . $decoded->{'sid'} . " Battery: " . $status[3]; - # readingsSingleUpdate($hash, "Battery_" . "$decoded->{'sid'}", "$status[3]", 1 ); - #} - } - else{ - Dispatch($hash, $buf, undef); + if ($status[1] eq 'rgb'){ + Log3 $name, 4, "$name>" . " SID: " . $decoded->{'sid'} . " Type: Gateway" . " RGB: " . ($status[2] =~ /([\d]+)/)[0] ; + readingsSingleUpdate($hash, "RGB", ($status[2] =~ /([\d]+)/)[0] , 1 ); + } } - # my @status = split('\"', $decoded->{'data'}); - # + elsif ($decoded->{'cmd'} eq 'heartbeat'){ + readingsSingleUpdate($hash, $decoded->{'sid'}, 'heartbeat', 1 ); + readingsSingleUpdate($hash, 'token', $decoded->{'token'}, 1 ); + } + } + else { + Log3 $name, 4, "$name> Dispatch! " . $buf; + Dispatch($hash, $buf, undef); } } } @@ -107,12 +130,13 @@ sub XiaomiSmartHome_Define($$) { $hash->{DEF} = $definition; $hash->{NOTIFYDEV} = "global"; $hash->{NAME} = $param[0]; + $hash->{VERSION} = $version; $hash->{GATEYWAY} = $param[2]; $hash->{FHEMIP} = XiaomiSmartHome_getLocalIP(); $hash->{STATE} = "initialized"; $hash->{helper}{host} = $definition; $hash->{helper}{JSON} = JSON->new->utf8(); - Log3 $hash->{NAME}, 3, "$hash->{NAME}: $definition"; + Log3 $hash->{NAME}, 5, "$hash->{NAME}> $definition"; XiaomiSmartHome_connect($hash) if( $init_done); @@ -129,28 +153,59 @@ sub XiaomiSmartHome_Undef($$) { } ##################################### -sub XiaomiSmartHome_Get($@) +sub XiaomiSmartHome_Write($$) { + my ($hash,$cmd,$sid) = @_; + my $name = $hash->{NAME}; + my $msg = '{"cmd":"' .$cmd . '","sid":"' . $sid . '"}'; + return Log3 $name, 4, "Master ($name) - socket not connected" + unless($hash->{CD}); + + Log3 $name, 4, "$name> $msg " . $hash->{GATEYWAY}; + my $sock = $hash->{CD}; + my $MAXLEN = 1024; + $sock->mcast_send($msg,$hash->{GATEYWAY} .':9898') or die "send: $!"; + + return undef; } ##################################### + +sub XiaomiSmartHome_Get($@) +{ + my ($hash , $name, $opt, $args ) = @_; + my $name = $hash->{NAME}; + if ($opt eq "UpdateAll") + { + XiaomiSmartHome_updateAllReadings($hash); + Log3 $name, 5, "$name> UpdateALLReadings Started"; + } + elsif($opt eq "UpdateSingle") + { + XiaomiSmartHome_updateSingleReading($hash,$args); + Log3 $name, 5, "$name> UpdateSingel Started"; + } + else + { + return "unknown argument $opt choose one of UpdateAll:noArg UpdateSingle"; + } +} +##################################### + sub XiaomiSmartHome_Notify($$) { my ($own_hash, $dev_hash) = @_; my $ownName = $own_hash->{NAME}; # own name / hash - Log3 $ownName, 3, "$ownName: NotifyStart - $dev_hash->{NAME}"; + Log3 $ownName, 3, "$ownName> NotifyStart"; return "" if(IsDisabled($ownName)); # Return without any further action if the module is disabled my $devName = $dev_hash->{NAME}; # Device that created the events my $events = deviceEvents($dev_hash, 1); - Log3 $ownName, 3, "$ownName: $devName"; - print join("; ", @{$events}); - Log3 $ownName, 3, "$ownName: " . grep(m/^INITIALIZED|REREADCFG$/, @{$events}); if($devName eq "global" && grep(m/^INITIALIZED|REREADCFG$/, @{$events})) { - Log3 $ownName, 3, "$ownName: 2"; + Log3 $ownName, 3, "$ownName> Starting Connect"; XiaomiSmartHome_connect($own_hash); } } @@ -170,12 +225,11 @@ sub XiaomiSmartHome_connect($) { my $hash = shift; my $name = $hash->{NAME}; - Log3 $name, 3, "$name: ConnectStart"; - #return if (AttrVal($hash->{NAME}, "disable", 0)); + Log3 $name, 3, "$name> ConnectStart"; XiaomiSmartHome_disconnect($hash); - Log3 $name, 4, "$name: connecting"; + Log3 $name, 4, "$name> connecting"; my $sock = IO::Socket::Multicast->new( Proto => 'udp', LocalPort =>'9898', ReuseAddr => 1) or die "Creating socket: $!\n"; $sock->mcast_add('224.0.0.50', $hash->{fhemIP} ) || die "Couldn't set group: $!\n"; #$hash->{fhemIP} @@ -184,7 +238,7 @@ sub XiaomiSmartHome_connect($) if ($sock) { - Log3 $name, 3, "$name: connected"; + Log3 $name, 3, "$name> connected"; $hash->{helper}{ConnectionState} = "Connected"; @@ -198,11 +252,11 @@ sub XiaomiSmartHome_connect($) $selectlist{$name} = $hash; - #XiaomiSmartHome_updateAllReadings($hash); + XiaomiSmartHome_updateAllReadings($hash); } else { - Log3 $name, 1, "$name: connect to $hash->{helper}{host} failed"; + Log3 $name, 1, "$name> connect to $hash->{helper}{host} failed"; } return undef; @@ -222,7 +276,7 @@ sub XiaomiSmartHome_disconnect($) } return if (!$hash->{CD}); - Log3 $name, 3, "$name: disconnecting"; + Log3 $name, 3, "$name> disconnecting"; close($hash->{CD}); delete($hash->{CD}); @@ -230,6 +284,60 @@ sub XiaomiSmartHome_disconnect($) return undef; } +##################################### + +sub XiaomiSmartHome_updateSingleReading($$) +{ + my ($hash, $sensor) = @_; + my $name = $hash->{NAME}; + my $GATEYWAY = $hash->{GATEYWAY}; + my $sock = $hash->{CD}; + my $MAXLEN = 1024; + + Log3 $name, 4, "$name> PushSingelRead:" . $sensor; + XiaomiSmartHome_Write($hash, $sensor); + +} +##################################### + +sub XiaomiSmartHome_updateAllReadings($) +{ + my $hash = shift; + my $name = $hash->{NAME}; + my $GATEYWAY = $hash->{GATEYWAY}; + my $sock = $hash->{CD}; + my $MAXLEN = 1024; + + my $msg = '{"cmd" : "get_id_list"}'; + $sock->mcast_send($msg, $GATEYWAY .':9898') or die "send: $!"; + eval { + $sock->recv($msg, $MAXLEN) or die "recv: $!"; + Log3 $name, 5, "$name> " . $msg; + my $json = $hash->{helper}{JSON}->incr_parse($msg); + my $decoded = decode_json($msg); + if ($json){ + Log3 $name, 5, "$name> Read:" . $msg; + if ($decoded->{'cmd'} eq 'get_id_list_ack'){ + my @sensors = split('\"', $decoded->{'data'}); + @sensors = grep {$_ ne ',' and $_ ne ']' and $_ ne '[' } @sensors; + foreach my $sensor (@sensors) + { + $msg = '{"cmd":"read","sid":"' . $sensor . '" }'; + Log3 $name, 4, "$name> PushRead:" . $sensor; + my $msg = '{"cmd":"read","sid":"' . $sensor . '" }'; + $sock->mcast_send($msg, $GATEYWAY .':9898') or die "send: $!"; + eval { + $sock->recv($msg, $MAXLEN) or die "recv: $!"; + Log3 $name, 5, "$name> " . $msg; + Dispatch($hash, $msg, undef); + } + + } + } + } + } +} +##################################### 1; diff --git a/71_XiaomiSmartHome_Device.pm b/71_XiaomiSmartHome_Device.pm index 4961a18..5cc3663 100644 --- a/71_XiaomiSmartHome_Device.pm +++ b/71_XiaomiSmartHome_Device.pm @@ -1,16 +1,40 @@ -# 71_XiaomiSmartHome_Device.pm 2017-08-02 13:07:33Z torte $ +############################################################################### +# +# 03.2017 torte +# All rights reserved +# +# This script is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# +############################################################################### package main; use strict; use warnings; +my $version = "0.02"; +sub XiaomiSmartHome_Device_updateSReading($); + ##################################### sub XiaomiSmartHome_Device_Initialize($) { my ($hash) = @_; - $hash->{Match} = "^.+magnet|motion|sensor_ht"; + $hash->{Match} = "^.+magnet|motion|sensor_ht|switch"; $hash->{DefFn} = "XiaomiSmartHome_Device_Define"; #$hash->{SetFn} = "XiaomiSmartHome_Device_Set"; $hash->{UndefFn} = "XiaomiSmartHome_Device_Undef"; @@ -35,13 +59,48 @@ sub XiaomiSmartHome_Device_mot($$) sub XiaomiSmartHome_Device_on_timeout($){ my ($hash) = @_; + my $name = $hash->{LASTInputDev}; if ($hash->{STATE} eq 'motion') { readingsSingleUpdate($hash, "state", "off", 1 ); + Log3 $name, 3, "$name>" . " SID: " . $hash->{SID} . " Type: " . $hash->{MODEL} . " Status: off"; } } ##################################### +sub XiaomiSmartHome_Device_Read($$$){ + my ($hash, $msg, $name) = @_; + my $decoded = decode_json($msg); + + my $sid = $decoded->{'sid'}; + my $model = $decoded->{'model'}; + Log3 $name, 5, "$name: SID: " . $hash->{SID} . " " . $hash->{TYPE}; + my @status = split('\"', $decoded->{'data'}); + if ($status[1] eq 'status'){ + Log3 $name, 3, "$name>" . " SID: " . $sid . " Type: " . $hash->{MODEL} . " Status: " . $status[3]; + readingsSingleUpdate($hash, "state", "$status[3]", 1 ); + } + elsif($status[1] eq 'voltage'){ + Log3 $name, 3, "$name>" . " SID: " . $sid . " Type: " . $hash->{MODEL} . " Voltage: " . $status[3]; + readingsSingleUpdate($hash, "voltage", "$status[3]", 1 ); + } + elsif($status[1] eq 'temperature'){ + Log3 $name, 3, "$name>" . " SID: " . $sid . " Type: " . $hash->{MODEL} . " Temperature: " . $status[3]; + $status[3] =~ s/(^[-+]?\d+?(?=(?>(?:\d{2})+)(?!\d))|\G\d{2}(?=\d))/$1./g; + readingsSingleUpdate($hash, "temperature", "$status[3]", 1 ); + } + elsif($status[1] eq 'humidity'){ + Log3 $name, 3, "$name>" . " SID: " . $sid . " Type: " . $hash->{MODEL} . " Humidity: " . $status[3]; + $status[3] =~ s/(^[-+]?\d+?(?=(?>(?:\d{2})+)(?!\d))|\G\d{2}(?=\d))/$1./g; + readingsSingleUpdate($hash, "humidity", "$status[3]", 1 ); + } + elsif ($decoded->{'cmd'} eq 'heartbeat'){ + readingsSingleUpdate($hash, $decoded->{'sid'}, 'heartbeat', 1 ); + } + XiaomiSmartHome_Device_update($hash); + return $hash->{NAME}; +} +##################################### sub XiaomiSmartHome_Device_Parse($$) { my ($io_hash, $msg) = @_; @@ -49,38 +108,19 @@ sub XiaomiSmartHome_Device_Parse($$) { my $sid = $decoded->{'sid'}; my $model = $decoded->{'model'}; + my $name = $io_hash->{NAME}; if (my $hash = $modules{XiaomiSmartHome_Device}{defptr}{$sid}) { - my $name = $hash->{NAME}; - Log3 $name, 5, "$name: SID: " . $hash->{SID} . " " . $hash->{TYPE}; - my @status = split('\"', $decoded->{'data'}); - if ($status[1] eq 'status'){ - Log3 $name, 3, "$name: Sensor: " . $hash->{MODEL} . " SID: " . $sid . " Status: " . $status[3]; - readingsSingleUpdate($hash, "state", "$status[3]", 1 ); - } - elsif($status[1] eq 'voltage'){ - Log3 $name, 3, "$name: Sensor: " . $hash->{MODEL} . " SID: " . $sid . " Voltage: " . $status[3]; - readingsSingleUpdate($hash, "voltage", "$status[3]", 1 ); - } - elsif($status[1] eq 'temperature'){ - Log3 $name, 3, "$name: Sensor: " . $hash->{MODEL} . " SID: " . $sid . " Temperature: " . $status[3]; - $status[3] =~ s/(^[-+]?\d+?(?=(?>(?:\d{2})+)(?!\d))|\G\d{2}(?=\d))/$1./g; - readingsSingleUpdate($hash, "temperature", "$status[3]", 1 ); - } - elsif($status[1] eq 'humidity'){ - Log3 $name, 3, "$name: Sensor: " . $hash->{MODEL} . " SID: " . $sid . " Humidity: " . $status[3]; - $status[3] =~ s/(^[-+]?\d+?(?=(?>(?:\d{2})+)(?!\d))|\G\d{2}(?=\d))/$1./g; - readingsSingleUpdate($hash, "humidity", "$status[3]", 1 ); - } - XiaomiSmartHome_Device_update($hash); - return $hash->{NAME}; + Log3 $name, 4, "$name> IS DEFINED " . $model . " : " .$sid; + XiaomiSmartHome_Device_Read($hash, $msg, $name); } else { - return "UNDEFINED $sid XiaomiSmartHome_Device $model $sid"; - } + Log3 $name, 4, "$name> UNDEFINED " . $model . " : " .$sid; + return "UNDEFINED $sid XiaomiSmartHome_Device $model $name"; + } } ##################################### @@ -107,25 +147,69 @@ sub XiaomiSmartHome_Device_update($){ sub XiaomiSmartHome_Device_Define($$) { my ($hash, $def) = @_; - my ($name, $modul, $type, $sid) = split("[ \t]+", $def); - Log3 $name, 3, "$name: $modul $type $sid"; - $hash->{TYPE} = $modul; + my ($name, $modul, $type, $iodev) = split("[ \t]+", $def); + #Log3 "test", 3, "Define status = " . $status; + $hash->{TYPE} = $modul; $hash->{MODEL} = $type; - $hash->{SID} = $sid; - $hash->{NAME} = $sid; - $hash->{STATE} = "initialized"; - $modules{XiaomiSmartHome_Device}{defptr}{$sid} = $hash; - AssignIoPort($hash); + $hash->{SID} = $name; + $hash->{NAME} = $name; + $hash->{VERSION} = $version; + $hash->{STATE} = 'initialized'; + $modules{XiaomiSmartHome_Device}{defptr}{$name} = $hash; + AssignIoPort($hash,$iodev); + + if(defined($hash->{IODev}->{NAME})) { + my $IOname = $hash->{IODev}->{NAME}; + Log3 $name, 3, $IOname . "> " .$name. ": " . $type . " I/O device is " . $hash->{IODev}->{NAME}; + } else { + Log3 $name, 1, "$name $type - no I/O device"; + } + $iodev = $hash->{IODev}->{NAME}; + + my $d = $modules{XiaomiSmartHome_Device}{defptr}{$name}; + + return "XiaomiSmartHome device $hash->{SID} on XiaomiSmartHome $iodev already defined as $d->{NAME}." if( defined($d) && $d->{IODev} == $hash->{IODev} && $d->{NAME} ne $name ); + Log3 $name, 3, $iodev . "> " . $name . "- defined with Code: ". $hash->{DEV}; + $attr{$name}{room} = "MiSmartHome" if( !defined( $attr{$name}{room} ) ); + if( $type eq 'motion') { + $attr{$name}{devStateIcon} = 'motion:motion_detector@red off:motion_detector@green' if( !defined( $attr{$name}{devStateIcon} ) ); + } + elsif ( $type eq 'magnet') { + $attr{$name}{devStateIcon} = 'open:fts_door_open@red close:fts_door@green' if( !defined( $attr{$name}{devStateIcon} ) ); + } + elsif ( $type eq 'sensor_ht') { + $attr{$name}{stateFormat} = 'temperature °C, humidity %' if( !defined( $attr{$name}{stateFormat} ) ); + } + + if( $init_done ) { + InternalTimer( gettimeofday()+int(rand(2)), "XiaomiSmartHome_Device_updateSReading", $hash, 0 ); + Log3 $name, 4, $iodev . "> " . $name . " Init Done set InternalTimer for Update"; + } +} +##################################### +sub XiaomiSmartHome_Device_updateSReading($) { + + my $hash = shift; + #my $name = $hash->{NAME}; + #Log3 $name, 3, $name . " Updae SR"; + IOWrite($hash,'read',"$hash->{SID}"); } ##################################### +##################################### sub XiaomiSmartHome_Device_Undef($) { my ($hash, $arg) = @_; + my $name = $hash->{NAME}; RemoveInternalTimer($hash); delete($modules{XiaomiSmartHome_Device}{defptr}{$hash->{SID}}); - return undef; + my $code = $hash->{IODev}->{NAME} if( defined($hash->{IODev}->{NAME}) ); + delete($modules{HEOSPlayer}{defptr}{$code}); + + Log3 $name, 3, "$code> $name - device deleted"; + return undef; + } 1; #####################################