diff --git a/FHEM/70_PHTV.pm b/FHEM/70_PHTV.pm index 68b0fec81..d3f24bc9b 100644 --- a/FHEM/70_PHTV.pm +++ b/FHEM/70_PHTV.pm @@ -221,11 +221,13 @@ sub PHTV_GetStatus($;$) { sub PHTV_Get($@) { my ( $hash, @a ) = @_; my $name = $hash->{NAME}; + my $state = ReadingsVal( $name, "state", "Initialized" ); my $what; Log3 $name, 5, "PHTV $name: called function PHTV_Get()"; return "argument is missing" if ( int(@a) < 2 ); + return if ( $state =~ /^(pairing.*|initialized)$/i ); $what = $a[1]; @@ -346,11 +348,37 @@ sub PHTV_Set($@) { $usage .= " statusRequest:noArg"; } + $usage = "" if ( $state eq "Initialized" ); + $usage = "pin" if ( $state =~ /^pairing.*/ ); + my $cmd = ''; my $result; + # pairing grant / PIN + if ( lc( $a[1] ) eq "pin" ) { + return "Missing PIN code" unless ( defined( $a[2] ) ); + return "Not in pairing mode" + unless ( defined( $hash->{pairing} ) + && defined( $hash->{pairing}{auth_key} ) + && defined( $hash->{pairing}{timestamp} ) ); + + readingsSingleUpdate( $hash, "state", "pairing-grant", 1 ); + + $hash->{pairing}{grant} = { + auth_AppId => 1, + pin => trim( $a[2] ), + auth_timestamp => $hash->{pairing}{timestamp}, + auth_signature => PHTV_createAuthSignature( + $hash->{pairing}{timestamp}, + $a[2], +"ZmVay1EQVFOaZhwQ4Kv81ypLAZNczV9sG4KkseXWn1NEk6cXmPKO/MCa9sryslvLCFMnNe4Z4CPXzToowvhHvA==" + ), + }; + PHTV_SendCommand( $hash, "pair/grant", $hash->{pairing}{grant} ); + } + # statusRequest - if ( lc( $a[1] ) eq "statusrequest" ) { + elsif ( lc( $a[1] ) eq "statusrequest" ) { Log3 $name, 3, "PHTV set $name " . $a[1]; delete $hash->{helper}{device} @@ -1357,7 +1385,7 @@ sub PHTV_Define($$) { eval { require JSON; - import JSON qw( decode_json ); + import JSON qw( decode_json encode_json ); }; return "Please install Perl JSON to use module PHTV" if ($@); @@ -1474,13 +1502,20 @@ sub PHTV_SendCommand($$;$$$) { } # add missing port if required - if ( $address !~ m/^(.+):([0-9]+)$/ ) { + if ( $address !~ m/^.+:[0-9]+$/ ) { $address .= ":1925" if ( $protoV <= 5 ); $address .= ":1926" if ( $protoV > 5 ); } + # special authentication handling during pairing + $auth_key = undef if ( $service =~ /^pair\/.+/ ); + $auth_key = $hash->{pairing}{auth_key} + if ( $service eq "pair/grant" + && defined( $hash->{pairing} ) + && defined( $hash->{pairing}{auth_key} ) ); + $URL = "http://"; - $URL = "https://" if ( $protoV > 5 ); + $URL = "https://" if ( $protoV > 5 || $address =~ m/^.+:1926$/ ); $URL .= "$device_id:$auth_key@" if ( $device_id && $auth_key ); $URL .= $address . "/" . $protoV . "/" . $service; @@ -1508,6 +1543,9 @@ sub PHTV_SendCommand($$;$$$) { timestamp => $timestamp, httpversion => "1.1", callback => \&PHTV_ReceiveCommand, + header => { + 'Content-Type' => 'application/json', + }, } ); @@ -1521,7 +1559,10 @@ sub PHTV_ReceiveCommand($$$) { my $name = $hash->{NAME}; my $service = $param->{service}; my $cmd = $param->{cmd}; + my $code = $param->{code}; my $sequential = AttrVal( $name, "sequentialQuery", 0 ); + my $protoV = AttrVal( $name, "jsversion", 1 ); + my $device_id = AttrVal( $name, "device_id", undef ); my $state = ReadingsVal( $name, "state", "" ); my $newstate; @@ -1533,7 +1574,7 @@ sub PHTV_ReceiveCommand($$$) { readingsBeginUpdate($hash); # device not reachable - if ($err) { + if ( $err && ( !$code || $code ne "401" ) ) { if ( !defined($cmd) || ref($cmd) eq "HASH" || $cmd eq "" ) { Log3 $name, 4, "PHTV $name: RCV TIMEOUT $service"; @@ -1549,16 +1590,7 @@ sub PHTV_ReceiveCommand($$$) { || ( $service eq "input/key" && $type eq "off" ) ) { $newstate = "absent"; - - if ( - ( !defined( $hash->{helper}{AVAILABLE} ) ) - or ( defined( $hash->{helper}{AVAILABLE} ) - and $hash->{helper}{AVAILABLE} eq 1 ) - ) - { - $hash->{helper}{AVAILABLE} = 0; - readingsBulkUpdate( $hash, "presence", "absent" ); - } + readingsBulkUpdateIfChanged( $hash, "presence", "absent" ); } # device behaves naughty @@ -1577,17 +1609,117 @@ sub PHTV_ReceiveCommand($$$) { } + # pairing request reply + elsif ( $service eq "pair/request" ) { + if ( $data =~ m/^\s*([{\[][\s\S]+[}\]])\s*$/i ) { + $return = decode_json( Encode::encode_utf8($1) ) if ($1); + + if ( ref($return) eq "HASH" + && $return->{timestamp} + && $return->{auth_key} + && $return->{timeout} ) + { + Log3 $name, 3, "PHTV $name: Pairing enabled"; + readingsBulkUpdate( $hash, "state", "pairing" ); + + $hash->{pairing}{begin} = time(); + $hash->{pairing}{end} = + $hash->{pairing}{begin} + $return->{timeout}; + $hash->{pairing}{auth_key} = $return->{auth_key}; + $hash->{pairing}{timeout} = $return->{timeout}; + $hash->{pairing}{timestamp} = $return->{timestamp}; + } + else { + Log3 $name, 4, + "PHTV $name: ERROR $code - Pairing request failed"; + readingsBulkUpdate( $hash, "state", "pairing request failed" ); + } + } + else { + Log3 $name, 4, "PHTV $name: ERROR $code - Pairing not supported"; + readingsBulkUpdate( $hash, "state", "pairing not supported" ); + } + + readingsEndUpdate( $hash, 1 ); + return; + } + + # pairing grant reply + elsif ( $service eq "pair/grant" ) { + delete $hash->{pairing}; + my $interval = 10; + + if ( $code == 200 ) { + Log3 $name, 4, "PHTV $name: Pairing successful"; + readingsBulkUpdate( $hash, "state", "paired" ); + fhem 'attr ' . $name . ' auth_key ' . $hash->{pairing}{auth_key}; + $interval = 3; + } + else { + Log3 $name, 4, "PHTV $name: ERROR $code - Pairing failed"; + readingsBulkUpdate( $hash, "state", "pairing failed" ); + } + + RemoveInternalTimer($hash); + InternalTimer( gettimeofday() + $interval, "PHTV_GetStatus", $hash, 0 ); + + readingsEndUpdate( $hash, 1 ); + return; + } + + # authorization/pairing needed + elsif ( $code == 401 ) { + if ( defined( $hash->{pairing} ) + && defined( $hash->{pairing}{end} ) + && $hash->{pairing}{end} < time() ) + { + readingsEndUpdate( $hash, 1 ); + return; + } + + readingsBulkUpdateIfChanged( $hash, "presence", "present" ); + readingsBulkUpdateIfChanged( $hash, "power", "on" ); + + eval { + require Digest::SHA; + import Digest::SHA qw( hmac_sha1_base64 hmac_sha1_hex ); + }; + if ($@) { + readingsBulkUpdate( $hash, "state", + "pairing: Missing Perl module Digest::SHA" ); + readingsEndUpdate( $hash, 1 ); + return; + } + + readingsBulkUpdate( $hash, "state", "pairing-request" ); + + fhem 'attr ' . $name . ' jsversion 6' + unless ( $protoV > 1 ); + + unless ($device_id) { + $device_id = PHTV_createDeviceId(); + fhem 'attr ' . $name . ' device_id ' . $device_id; + } + + delete $hash->{pairing} if ( defined( $hash->{pairing} ) ); + $hash->{pairing}{request} = { + device_name => 'fhem', + device_os => 'Android', + app_name => 'FHEM PHTV', + type => 'native', + scope => [ "read", "write", "control" ], + app_id => 'org.fhem.PHTV', + id => $device_id, + }; + PHTV_SendCommand( $hash, "pair/request", $hash->{pairing}{request} ); + + readingsEndUpdate( $hash, 1 ); + return; + } + # data received elsif ($data) { - if ( - ( !defined( $hash->{helper}{AVAILABLE} ) ) - or ( defined( $hash->{helper}{AVAILABLE} ) - and $hash->{helper}{AVAILABLE} eq 0 ) - ) - { - $hash->{helper}{AVAILABLE} = 1; - readingsBulkUpdate( $hash, "presence", "present" ); - } + readingsBulkUpdateIfChanged( $hash, "presence", "present" ); if ( !defined($cmd) || ref($cmd) eq "HASH" | $cmd eq "" ) { Log3 $name, 4, "PHTV $name: RCV $service"; @@ -1597,10 +1729,9 @@ sub PHTV_ReceiveCommand($$$) { } if ( $data =~ -m/^\s*(([{\[].*)|(\s*
\s*