From 8636edf63f4aa6ee929bec3c37f24932f6eb9592 Mon Sep 17 00:00:00 2001 From: T0RST3N Date: Mon, 27 Feb 2017 11:49:25 +0100 Subject: [PATCH 1/7] Version 0.01 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Erste Version XiaomiSmartHome Unterstützte Sensoren magnet, motion, sensortHT Nur lesend, Sensoren werden via Autocreate in FHEM angelegt --- 71_XiaomiSmartHome.pm | 281 +++++++++++++++++++++++++++++++++++ 71_XiaomiSmartHome_Device.pm | 138 +++++++++++++++++ 2 files changed, 419 insertions(+) create mode 100644 71_XiaomiSmartHome.pm create mode 100644 71_XiaomiSmartHome_Device.pm diff --git a/71_XiaomiSmartHome.pm b/71_XiaomiSmartHome.pm new file mode 100644 index 0000000..7377493 --- /dev/null +++ b/71_XiaomiSmartHome.pm @@ -0,0 +1,281 @@ +# 71_XiaomiSmartHome.pm 2017-08-02 13:07:33Z torte $ + +package main; + +use strict; +use warnings; +use strict; +use JSON qw( decode_json ); +use Data::Dumper; +use IO::Socket; +use IO::Socket::Multicast; + +my %XiaomiSmartHome_gets = ( + "getDevices" => ["get_id_list", '^.+get_id_list_ack' ], + +); + + +##################################### + +sub XiaomiSmartHome_Initialize($) { + my ($hash) = @_; + + $hash->{Clients} = "XiaomiSmartHome_Device"; + $hash->{DefFn} = 'XiaomiSmartHome_Define'; + $hash->{UndefFn} = 'XiaomiSmartHome_Undef'; + $hash->{NotifyFn} = 'XiaomiSmartHome_Notify'; + $hash->{SetFn} = 'XiaomiSmartHome_Set'; + $hash->{GetFn} = 'XiaomiSmartHome_Get'; + $hash->{AttrFn} = 'XiaomiSmartHome_Attr'; + $hash->{ReadFn} = 'XiaomiSmartHome_Read'; + $hash->{AttrList} = "disable:1,0 " + . $readingFnAttributes; + $hash->{MatchList} = { "1:XiaomiSmartHome_Device" => "^.+magnet", + "2:XiaomiSmartHome_Device" => "^.+motion", + "3:XiaomiSmartHome_Device" => "^.+sensor_ht"}; +} +##################################### + +sub XiaomiSmartHome_Read($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $buf = ""; + my $ret = sysread($hash->{CD}, $buf, 1024); + if (!defined($ret) || $ret <= 0) + { + XiaomiSmartHome_disconnect($hash); + + InternalTimer(gettimeofday() + 2, "XiaomiSmartHome_connect", $hash, 0); + return; + } + + my $json = $hash->{helper}{JSON}->incr_parse($buf); + 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'){ + 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); + } + # my @status = split('\"', $decoded->{'data'}); + # + } + } +} +##################################### + + +sub XiaomiSmartHome_getLocalIP(){ + my $socket = IO::Socket::INET->new( Proto => 'udp', + PeerAddr => '8.8.8.8:53', # google dns + ); + return '' if( !$socket ); + my $ip = $socket->sockhost; + close( $socket ); + return $ip if( $ip ); + + return ''; +} +##################################### + +sub XiaomiSmartHome_Define($$) { + my ($hash, $def) = @_; + my @param = split('[ \t]+', $def); + + if(int(@param) < 3) { + return "too few parameters: define XiaomiSmartHome "; + } + my $definition = $param[2]; + + $hash->{DEF} = $definition; + $hash->{NOTIFYDEV} = "global"; + $hash->{NAME} = $param[0]; + $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"; + + XiaomiSmartHome_connect($hash) if( $init_done); + + return undef; +} +##################################### + +sub XiaomiSmartHome_Undef($$) { + my ($hash, $arg) = @_; + + XiaomiSmartHome_disconnect($hash); + # nothing to do + return undef; +} +##################################### + +sub XiaomiSmartHome_Get($@) +{ + +} + +##################################### + +sub XiaomiSmartHome_Notify($$) +{ + my ($own_hash, $dev_hash) = @_; + my $ownName = $own_hash->{NAME}; # own name / hash + Log3 $ownName, 3, "$ownName: NotifyStart - $dev_hash->{NAME}"; + 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"; + XiaomiSmartHome_connect($own_hash); + } +} +##################################### + +sub XiaomiSmartHome_Set($@) { + +} +##################################### + +sub XiaomiSmartHome_Attr(@) { + +} +##################################### + +sub XiaomiSmartHome_connect($) +{ + my $hash = shift; + my $name = $hash->{NAME}; + Log3 $name, 3, "$name: ConnectStart"; + #return if (AttrVal($hash->{NAME}, "disable", 0)); + + XiaomiSmartHome_disconnect($hash); + + 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} + $sock->mcast_ttl(32); + $sock->mcast_loopback(1); + + if ($sock) + { + Log3 $name, 3, "$name: connected"; + + $hash->{helper}{ConnectionState} = "Connected"; + + if ($hash->{helper}{ConnectionState} ne ReadingsVal($name, "state", "" )) + { + readingsSingleUpdate($hash, "state", $hash->{helper}{ConnectionState}, 1); + } + + $hash->{FD} = $sock->fileno(); + $hash->{CD} = $sock; + + $selectlist{$name} = $hash; + + #XiaomiSmartHome_updateAllReadings($hash); + } + else + { + Log3 $name, 1, "$name: connect to $hash->{helper}{host} failed"; + } + + return undef; +} +##################################### + +sub XiaomiSmartHome_disconnect($) +{ + my $hash = shift; + my $name = $hash->{NAME}; + + RemoveInternalTimer($hash); + $hash->{helper}{ConnectionState} = "Disconnected"; + if ($hash->{helper}{ConnectionState} ne ReadingsVal($name, "state", "" )) + { + readingsSingleUpdate($hash, "state", $hash->{helper}{ConnectionState}, 1); + } + + return if (!$hash->{CD}); + Log3 $name, 3, "$name: disconnecting"; + + close($hash->{CD}); + delete($hash->{CD}); + + + return undef; +} + +1; + +=pod +=item [helper|device|command] +=item summary Module fpr XiaomiSmartHome Gateway to use with FHEM +=item summary_DE Modul um ein XiaomiSmartHome Gateyway in FHEM bekannt zu machen + +=begin html + +

xiaomismarthome

+
    + XiaomiSmartHome implements the XiaomiSmartHome Gateway and Sensors. + + Define +
      + define <name> XiaomiSmartHome <IP or Hostname> +

      + Example: define XiaomiSmartHome XiaomiSmartHome 192.168.1.xxx +

      + +
    +
    +
+=end html + +=begin html_DE + +

xiaomismarthome

+
    + XiaomiSmartHome implements the XiaomiSmartHome Gateway and Sensors. + + Define +
      + define <name> XiaomiSmartHome <IP or Hostname> +

      + Example: define XiaomiSmartHome XiaomiSmartHome 192.168.1.xxx +

      + +
    +
    +
+=end html + + +=cut + + + diff --git a/71_XiaomiSmartHome_Device.pm b/71_XiaomiSmartHome_Device.pm new file mode 100644 index 0000000..379b393 --- /dev/null +++ b/71_XiaomiSmartHome_Device.pm @@ -0,0 +1,138 @@ +# 71_XiaomiSmartHome_Device.pm 2017-08-02 13:07:33Z torte $ +package main; + +use strict; +use warnings; + +##################################### + +sub XiaomiSmartHome_Device_Initialize($) +{ + my ($hash) = @_; + + $hash->{Match} = "^.+magnet|motion|sensor_ht"; + $hash->{DefFn} = "XiaomiSmartHome_Device_Define"; + #$hash->{SetFn} = "XiaomiSmartHome_Device_Set"; + $hash->{UndefFn} = "XiaomiSmartHome_Device_Undef"; + $hash->{ParseFn} = "XiaomiSmartHome_Device_Parse"; + + $hash->{AttrList} = "IODev follow-on-for-timer:1,0 follow-on-timer ". + "do_not_notify:1,0 ignore:1,0 dummy:1,0 showtime:1,0 valueFn:textField-long motionOffTimer:1,5,10 ". + $readingFnAttributes ; +} +##################################### + +sub XiaomiSmartHome_Device_mot($$) +{ + my ($hash, $mot) = @_; + + InternalTimer(gettimeofday()+$mot, "XiaomiSmartHome_Device_on_timeout",$hash, 0); + # on-for-timer is now a on. + + +} +##################################### + +sub XiaomiSmartHome_Device_on_timeout($){ + my ($hash) = @_; + readingsSingleUpdate($hash, "state", "off", 1 ); +} +##################################### + + + +sub XiaomiSmartHome_Device_Parse($$) { + my ($io_hash, $msg) = @_; + my $decoded = decode_json($msg); + + my $sid = $decoded->{'sid'}; + my $model = $decoded->{'model'}; + + 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]; + readingsSingleUpdate($hash, "temperature", "$status[3]", 1 ); + $status[3] =~ s/(^[-+]?\d+?(?=(?>(?:\d{2})+)(?!\d))|\G\d{2}(?=\d))/$1./g; + readingsSingleUpdate($hash, "temperatureP", "$status[3]", 1 ); + } + elsif($status[1] eq 'humidity'){ + Log3 $name, 3, "$name: Sensor: " . $hash->{MODEL} . " SID: " . $sid . " Humidity: " . $status[3]; + readingsSingleUpdate($hash, "humidity", "$status[3]", 1 ); + } + XiaomiSmartHome_Device_update($hash); + return $hash->{NAME}; + } + else + { + return "UNDEFINED $sid XiaomiSmartHome_Device $model $sid"; + } + +} +##################################### + +sub XiaomiSmartHome_Device_update($){ + my ($hash) = @_; + my $model = $hash->{MODEL}; + my $name = $hash->{NAME}; + my $value_fn = AttrVal( $name, "valueFn", "" ); + my $mot = AttrVal( $name, "motionOffTimer", "" ); + if( $value_fn =~ m/^{.*}$/s ) { + + my $LASTCMD = ReadingsVal($name,"lastCmd",undef); + + my $value_fn = eval $value_fn; + Log3 $name, 3, $name .": valueFn: ". $@ if($@); + return undef if( !defined($value_fn) ); + } + if( $model eq 'motion') { + XiaomiSmartHome_Device_mot($hash, $mot) if( $mot); + } +} +##################################### + + +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; + $hash->{MODEL} = $type; + $hash->{SID} = $sid; + $hash->{NAME} = $sid; + $hash->{STATE} = "initialized"; + $modules{XiaomiSmartHome_Device}{defptr}{$sid} = $hash; + AssignIoPort($hash); + +} + +sub XiaomiSmartHome_Device_Undef($) +{ + my ($hash, $arg) = @_; + RemoveInternalTimer($hash); + delete($modules{XiaomiSmartHome_Device}{defptr}{$hash->{SID}}); + return undef; +} +1; +##################################### +=pod +=begin html + + +

XiaomiSmartHome_Device

+ + +=end html + +=cut \ No newline at end of file From 8f98a8db635e3ebb7d39d75c93499e94c5df7ab7 Mon Sep 17 00:00:00 2001 From: T0RST3N Date: Tue, 28 Feb 2017 15:10:20 +0100 Subject: [PATCH 2/7] Fix motion off, help now displayed on motion sensor the turn off now only set when it is in motion device sprecific help replace CRLF with unix LF --- 71_XiaomiSmartHome.pm | 16 +++++++++------- 71_XiaomiSmartHome_Device.pm | 5 ++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/71_XiaomiSmartHome.pm b/71_XiaomiSmartHome.pm index 7377493..bc3516b 100644 --- a/71_XiaomiSmartHome.pm +++ b/71_XiaomiSmartHome.pm @@ -234,31 +234,33 @@ sub XiaomiSmartHome_disconnect($) 1; =pod -=item [helper|device|command] +=item device =item summary Module fpr XiaomiSmartHome Gateway to use with FHEM -=item summary_DE Modul um ein XiaomiSmartHome Gateyway in FHEM bekannt zu machen +=item summary_DE Modul um ein XiaomiSmartHome Gateyway in FHEM zu nutzen =begin html - -

xiaomismarthome

+ + +

XiaomiSmartHome

    XiaomiSmartHome implements the XiaomiSmartHome Gateway and Sensors. +
    Define
      define <name> XiaomiSmartHome <IP or Hostname>

      Example: define XiaomiSmartHome XiaomiSmartHome 192.168.1.xxx

      -

=end html =begin html_DE - -

xiaomismarthome

+ + +

XiaomiSmartHome

    XiaomiSmartHome implements the XiaomiSmartHome Gateway and Sensors. diff --git a/71_XiaomiSmartHome_Device.pm b/71_XiaomiSmartHome_Device.pm index 379b393..8ae1d4b 100644 --- a/71_XiaomiSmartHome_Device.pm +++ b/71_XiaomiSmartHome_Device.pm @@ -35,7 +35,9 @@ sub XiaomiSmartHome_Device_mot($$) sub XiaomiSmartHome_Device_on_timeout($){ my ($hash) = @_; - readingsSingleUpdate($hash, "state", "off", 1 ); + if ($hash->{STATE} eq 'motion') { + readingsSingleUpdate($hash, "state", "off", 1 ); + } } ##################################### @@ -116,6 +118,7 @@ sub XiaomiSmartHome_Device_Define($$) { AssignIoPort($hash); } +##################################### sub XiaomiSmartHome_Device_Undef($) { From 2c35b66ac96a25db8b5714a69e027891a2603aaa Mon Sep 17 00:00:00 2001 From: T0RST3N Date: Fri, 3 Mar 2017 14:49:44 +0100 Subject: [PATCH 3/7] FIX reading temperatur& humidity obsolet reading temperatureP deleted, humidity and temperatur in user friendly format --- 71_XiaomiSmartHome_Device.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/71_XiaomiSmartHome_Device.pm b/71_XiaomiSmartHome_Device.pm index 8ae1d4b..4961a18 100644 --- a/71_XiaomiSmartHome_Device.pm +++ b/71_XiaomiSmartHome_Device.pm @@ -65,12 +65,12 @@ sub XiaomiSmartHome_Device_Parse($$) { } elsif($status[1] eq 'temperature'){ Log3 $name, 3, "$name: Sensor: " . $hash->{MODEL} . " SID: " . $sid . " Temperature: " . $status[3]; - readingsSingleUpdate($hash, "temperature", "$status[3]", 1 ); $status[3] =~ s/(^[-+]?\d+?(?=(?>(?:\d{2})+)(?!\d))|\G\d{2}(?=\d))/$1./g; - readingsSingleUpdate($hash, "temperatureP", "$status[3]", 1 ); + 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); From e899ba7886606175e81f24981391493533112e18 Mon Sep 17 00:00:00 2001 From: T0RST3N Date: Sun, 12 Mar 2017 15:48:29 +0100 Subject: [PATCH 4/7] 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; ##################################### From ac69e600760ad1cc509e2f07760e3d06e5856f0a Mon Sep 17 00:00:00 2001 From: T0RST3N Date: Sun, 12 Mar 2017 16:14:07 +0100 Subject: [PATCH 5/7] error in gutenbergs copy-paste --- 71_XiaomiSmartHome_Device.pm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/71_XiaomiSmartHome_Device.pm b/71_XiaomiSmartHome_Device.pm index 5cc3663..dfb03ab 100644 --- a/71_XiaomiSmartHome_Device.pm +++ b/71_XiaomiSmartHome_Device.pm @@ -203,9 +203,8 @@ sub XiaomiSmartHome_Device_Undef($) my ($hash, $arg) = @_; my $name = $hash->{NAME}; RemoveInternalTimer($hash); - delete($modules{XiaomiSmartHome_Device}{defptr}{$hash->{SID}}); my $code = $hash->{IODev}->{NAME} if( defined($hash->{IODev}->{NAME}) ); - delete($modules{HEOSPlayer}{defptr}{$code}); + delete($modules{XiaomiSmartHome_Device}{defptr}{$code}); Log3 $name, 3, "$code> $name - device deleted"; return undef; From e817428ae8f402fdddc35b685ea62b54b6660949 Mon Sep 17 00:00:00 2001 From: T0RST3N Date: Sun, 12 Mar 2017 20:12:12 +0100 Subject: [PATCH 6/7] error in undef --- 71_XiaomiSmartHome_Device.pm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/71_XiaomiSmartHome_Device.pm b/71_XiaomiSmartHome_Device.pm index dfb03ab..f6c736c 100644 --- a/71_XiaomiSmartHome_Device.pm +++ b/71_XiaomiSmartHome_Device.pm @@ -202,11 +202,10 @@ sub XiaomiSmartHome_Device_Undef($) { my ($hash, $arg) = @_; my $name = $hash->{NAME}; + my $iodev = $hash->{IODev}->{NAME}; RemoveInternalTimer($hash); - my $code = $hash->{IODev}->{NAME} if( defined($hash->{IODev}->{NAME}) ); - delete($modules{XiaomiSmartHome_Device}{defptr}{$code}); - - Log3 $name, 3, "$code> $name - device deleted"; + delete($modules{XiaomiSmartHome_Device}{defptr}{$hash->{SID}}); + Log3 $name, 3, "$iodev> $name - device deleted"; return undef; } From 07e41816f7986a8e0c4fd21e0effa4340d96e987 Mon Sep 17 00:00:00 2001 From: T0RST3N Date: Sat, 18 Mar 2017 23:15:27 +0100 Subject: [PATCH 7/7] write to gateway, bugfix, first time master add: write with encrypted key to change the color from the gateway. change: decode json data bugfix: error in function write error in function updatesingle --- 71_XiaomiSmartHome.pm | 156 ++++++++++++++++++++++++++++++----- 71_XiaomiSmartHome_Device.pm | 46 +++++++---- 2 files changed, 162 insertions(+), 40 deletions(-) diff --git a/71_XiaomiSmartHome.pm b/71_XiaomiSmartHome.pm index b6c553a..21204c2 100644 --- a/71_XiaomiSmartHome.pm +++ b/71_XiaomiSmartHome.pm @@ -25,21 +25,31 @@ package main; use strict; use warnings; -use strict; use JSON qw( decode_json ); use Data::Dumper; use IO::Socket; use IO::Socket::Multicast; +use Crypt::CBC; +use Color; +use SetExtensions; sub XiaomiSmartHome_Notify($$); sub XiaomiSmartHome_updateSingleReading($$); - -my $version = "0.02"; +my $iv="\x17\x99\x6d\x09\x3d\x28\xdd\xb3\xba\x69\x5a\x2e\x6f\x58\x56\x2e"; +my $version = "0.05"; my %XiaomiSmartHome_gets = ( "getDevices" => ["get_id_list", '^.+get_id_list_ack' ], ); +my %sets = ( + "password" => 1, + "rgb:colorpicker,RGB" => 1, + #"pct:colorpicker,BRI,0,300,1300" =>1, + "off" => 0, + "on" => 0, +); + ##################################### @@ -56,12 +66,14 @@ sub XiaomiSmartHome_Initialize($) { $hash->{ReadFn} = 'XiaomiSmartHome_Read'; $hash->{WriteFn} = "XiaomiSmartHome_Write"; $hash->{AttrList} = "disable:1,0 " . - "Room " . + "Room " . $readingFnAttributes; + $hash->{MatchList} = { "1:XiaomiSmartHome_Device" => "^.+magnet", "2:XiaomiSmartHome_Device" => "^.+motion", "3:XiaomiSmartHome_Device" => "^.+sensor_ht", "4:XiaomiSmartHome_Device" => "^.+switch"}; + FHEM_colorpickerInit(); } ##################################### @@ -85,14 +97,14 @@ sub XiaomiSmartHome_Read($) { 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'){ - Log3 $name, 4, "$name>" . " SID: " . $decoded->{'sid'} . " Type: Gateway" . " RGB: " . ($status[2] =~ /([\d]+)/)[0] ; - readingsSingleUpdate($hash, "RGB", ($status[2] =~ /([\d]+)/)[0] , 1 ); + my $data = decode_json($decoded->{data}); + if (defined $data->{rgb}){ + Log3 $name, 4, "$name>" . " SID: " . $decoded->{'sid'} . " Type: Gateway" . " RGB: " . $data->{rgb} ; + readingsSingleUpdate($hash, "RGB", $data->{rgb} , 1 ); } } elsif ($decoded->{'cmd'} eq 'heartbeat'){ - readingsSingleUpdate($hash, $decoded->{'sid'}, 'heartbeat', 1 ); + readingsSingleUpdate($hash, 'HEARTBEAT', $decoded->{'sid'}, 1 ); readingsSingleUpdate($hash, 'token', $decoded->{'token'}, 1 ); } } @@ -137,7 +149,10 @@ sub XiaomiSmartHome_Define($$) { $hash->{helper}{host} = $definition; $hash->{helper}{JSON} = JSON->new->utf8(); Log3 $hash->{NAME}, 5, "$hash->{NAME}> $definition"; - + $attr{$hash->{NAME}}{webCmd} = "rgb:rgb ff0000:rgb 00ff00:rgb 0000ff:on:off"; + # Define devStateIcon + $attr{$hash->{NAME}}{devStateIcon} = '{Color_devStateIcon(ReadingsVal($name,"rgb","000000"))}' if(!defined($attr{$hash->{NAME}}{devStateIcon})); + $attr{$hash->{NAME}}{room} = "MiSmartHome" if( !defined( $attr{$hash->{NAME}}{room} ) ); XiaomiSmartHome_connect($hash) if( $init_done); return undef; @@ -153,14 +168,31 @@ sub XiaomiSmartHome_Undef($$) { } ##################################### -sub XiaomiSmartHome_Write($$) +sub XiaomiSmartHome_Write($$$) { - my ($hash,$cmd,$sid) = @_; - my $name = $hash->{NAME}; - my $msg = '{"cmd":"' .$cmd . '","sid":"' . $sid . '"}'; + my ($hash,$cmd,$val) = @_; + my $name = $hash->{NAME}; + my $msg; + if ($cmd eq 'read') + { + $msg = '{"cmd":"' .$cmd . '","sid":"' . $val . '"}'; + } + elsif ($cmd eq 'rgb') + { + # TODO SID des Gateway nicht im und aus dem Reading!! + $msg = '{"cmd":"write","model":"gateway","sid":"' . $hash->{READINGS}{HEARTBEAT}{VAL} . '","short_id":0,"key":"8","data":"{\"rgb\":' . $val . ',\"key\":\"'. XiaomiSmartHome_EncryptKey($hash) .'\"}" }'; + + } + elsif ($cmd eq 'illumination') + { + # TODO SID des Gateway nicht im und aus dem Reading!! + #$msg = '{"cmd":"write","model":"gateway","sid":"' . $hash->{READINGS}{HEARTBEAT}{VAL} . '","short_id":0,"key":"8","data":"{\"illumination\":\"' . $val . '\",\"key\":\"'. XiaomiSmartHome_EncryptKey($hash) .'\"}" }'; + $msg = '{"cmd":"write","model":"gateway","sid":"' . $hash->{READINGS}{HEARTBEAT}{VAL} . '","short_id":0,"key":"8","data":"{\"mid\":\"3\",\"key\":\"'. XiaomiSmartHome_EncryptKey($hash) .'\"}" }'; + #$msg = '{"cmd":"write","model":"gateway","sid":"' . $hash->{READINGS}{HEARTBEAT}{VAL} . '","short_id":0,"key":"8","data":"{\"hue\":\"170\",\"saturation\":\"254\", \"color_temperature\":\"65279\", \"x\":\"10\", \"y\":\"10\",\"key\":\"'. XiaomiSmartHome_EncryptKey($hash) .'\"}" }'; - return Log3 $name, 4, "Master ($name) - socket not connected" - unless($hash->{CD}); + + } + return Log3 $name, 4, "Master ($name) - socket not connected" unless($hash->{CD}); Log3 $name, 4, "$name> $msg " . $hash->{GATEYWAY}; my $sock = $hash->{CD}; @@ -169,14 +201,26 @@ sub XiaomiSmartHome_Write($$) return undef; } - ##################################### +sub XiaomiSmartHome_EncryptKey($) +{ + my ($hash) = @_; + if (defined $hash->{READINGS}{password}{VAL}){ + my $key = $hash->{READINGS}{password}{VAL}; + my $cipher = Crypt::CBC->new(-key => $key, -cipher => 'Cipher::AES',-iv => $iv, -literal_key => 1, -header => "none", -keysize => 16 ); + my $encryptkey = $cipher->encrypt_hex($hash->{READINGS}{token}{VAL}); + $encryptkey = substr($encryptkey, 0, 32); + return $encryptkey; + } + return undef; +} +##################################### sub XiaomiSmartHome_Get($@) { my ($hash , $name, $opt, $args ) = @_; - my $name = $hash->{NAME}; + if ($opt eq "UpdateAll") { XiaomiSmartHome_updateAllReadings($hash); @@ -211,11 +255,80 @@ sub XiaomiSmartHome_Notify($$) } ##################################### -sub XiaomiSmartHome_Set($@) { +sub XiaomiSmartHome_Set($@) + { + my ( $hash, $name, $cmd, @args ) = @_; + my $dec_num; + my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets ); + #-- check argument + return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @args) unless @match == 1; + return "$cmd expects $sets{$match[0]} parameters" unless (@args eq $sets{$match[0]}); + + return "\"set $name\" needs at least one argument" unless(defined($cmd)); + + if($cmd eq "password") + { + if($args[0] =~ /^[a-zA-Z0-9_.-]*$/) + { + readingsSingleUpdate( $hash, $cmd, $args[0], 0 ); + return; + } + else + { + return "Unknown value $args[0] for $cmd, wrong password, it must be hex"; + } + } + elsif($cmd eq "rgb") + { + my $ownName = $hash->{NAME}; + Log3 $ownName, 4, "$ownName> Set $cmd, $args[0]"; + $dec_num = sprintf("%d", hex('ff' . $args[0])); + Log3 $ownName, 4, "$ownName> Set $cmd, $dec_num"; + readingsSingleUpdate( $hash, 'rgb', $args[0], 1 ); + readingsSingleUpdate( $hash, 'state', 'on', 1 ); + XiaomiSmartHome_Write($hash,$cmd,$dec_num); + } + elsif($cmd eq "off") + { + $hash->{helper}{prevrgbvalue} = $hash->{READINGS}{rgb}{VAL}; + readingsSingleUpdate( $hash, 'state', 'off', 1 ); + readingsSingleUpdate( $hash, 'rgb', 'off', 1 ); + XiaomiSmartHome_Write($hash,'rgb', 0); + } + elsif($cmd eq "on") + { + readingsSingleUpdate( $hash, 'state', 'on', 1 ); + if ($hash->{helper}{prevrgbvalue}) + { + $dec_num = sprintf("%d", hex('ff' . $hash->{helper}{prevrgbvalue})); + readingsSingleUpdate( $hash, 'rgb', $hash->{helper}{prevrgbvalue}, 1 ); + XiaomiSmartHome_Write($hash,'rgb', $dec_num); + } + else + { + XiaomiSmartHome_Write($hash,'rgb', 1677786880); + readingsSingleUpdate( $hash, 'rgb', '00ff00', 1 ); + } + } + elsif($cmd eq "pct") + { + my $ownName = $hash->{NAME}; + Log3 $ownName, 4, "$ownName> Set $cmd, $args[0]"; + XiaomiSmartHome_Write($hash,'illumination',$args[0]); + } + + else + { + return "Unknown argument! $cmd, $args[0], choose one of password rgb"; + } } ##################################### + +##################################### + + sub XiaomiSmartHome_Attr(@) { } @@ -295,7 +408,7 @@ sub XiaomiSmartHome_updateSingleReading($$) my $MAXLEN = 1024; Log3 $name, 4, "$name> PushSingelRead:" . $sensor; - XiaomiSmartHome_Write($hash, $sensor); + XiaomiSmartHome_Write($hash, 'read', $sensor); } ##################################### @@ -318,8 +431,7 @@ sub XiaomiSmartHome_updateAllReadings($) 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; + my @sensors = @{decode_json($decoded->{data})}; foreach my $sensor (@sensors) { $msg = '{"cmd":"read","sid":"' . $sensor . '" }'; diff --git a/71_XiaomiSmartHome_Device.pm b/71_XiaomiSmartHome_Device.pm index f6c736c..3da5309 100644 --- a/71_XiaomiSmartHome_Device.pm +++ b/71_XiaomiSmartHome_Device.pm @@ -25,7 +25,7 @@ package main; use strict; use warnings; -my $version = "0.02"; +my $version = "0.05"; sub XiaomiSmartHome_Device_updateSReading($); ##################################### @@ -73,24 +73,34 @@ sub XiaomiSmartHome_Device_Read($$$){ 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 ); + my $data = decode_json($decoded->{data}); + #my @status = split('\"', $decoded->{'data'}); + if (defined $data->{status}){ + Log3 $name, 3, "$name>" . " SID: " . $sid . " Type: " . $hash->{MODEL} . " Status: " . $data->{status}; + readingsSingleUpdate($hash, "state", "$data->{status}", 1 ); + if ($data->{status} eq 'motion' && $hash->{MODEL} eq 'motion'){ + readingsSingleUpdate($hash, "no_motion", "0", 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(defined $data->{no_motion}){ + Log3 $name, 3, "$name>" . " SID: " . $sid . " Type: " . $hash->{MODEL} . " NO_motion: " . $data->{no_motion}; + readingsSingleUpdate($hash, "no_motion", "$data->{no_motion}", 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(defined $data->{voltage}){ + Log3 $name, 3, "$name>" . " SID: " . $sid . " Type: " . $hash->{MODEL} . " Voltage: " . $data->{voltage}; + readingsSingleUpdate($hash, "voltage", "$data->{voltage}", 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(defined $data->{temperature}){ + my $temp = $data->{temperature}; + $temp =~ s/(^[-+]?\d+?(?=(?>(?:\d{2})+)(?!\d))|\G\d{2}(?=\d))/$1./g; + Log3 $name, 3, "$name>" . " SID: " . $sid . " Type: " . $hash->{MODEL} . " Temperature: " . $temp; + readingsSingleUpdate($hash, "temperature", "$temp", 1 ); + } + elsif(defined $data->{humidity}){ + my $hum = $data->{humidity}; + $hum =~ s/(^[-+]?\d+?(?=(?>(?:\d{2})+)(?!\d))|\G\d{2}(?=\d))/$1./g; + Log3 $name, 3, "$name>" . " SID: " . $sid . " Type: " . $hash->{MODEL} . " Humidity: " . $hum; + readingsSingleUpdate($hash, "humidity", "$hum", 1 ); } elsif ($decoded->{'cmd'} eq 'heartbeat'){ readingsSingleUpdate($hash, $decoded->{'sid'}, 'heartbeat', 1 ); @@ -170,10 +180,10 @@ sub XiaomiSmartHome_Device_Define($$) { 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}; + Log3 $name, 3, $iodev . "> " . $name . ": defined as ". $hash->{MODEL}; $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} ) ); + $attr{$name}{devStateIcon} = 'motion:motion_detector@red off:motion_detector@green no_motion: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} ) );