From 8636edf63f4aa6ee929bec3c37f24932f6eb9592 Mon Sep 17 00:00:00 2001 From: T0RST3N Date: Mon, 27 Feb 2017 11:49:25 +0100 Subject: [PATCH] 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