From e4fd8a1c80f18d74b13616ae95146864c6c95147 Mon Sep 17 00:00:00 2001 From: zap <> Date: Thu, 24 Feb 2022 12:10:53 +0000 Subject: [PATCH] AndroidDB: added new features git-svn-id: https://svn.fhem.de/fhem/trunk@25737 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 2 + fhem/FHEM/89_AndroidDB.pm | 549 +++++++++++++++++++++++++++++----- fhem/FHEM/89_AndroidDBHost.pm | 67 +++-- 3 files changed, 525 insertions(+), 93 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index a107b8219..488cb6b5e 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. + - feature: 89_AndroidDB: Added new features + - bugfix: 89_AndroidDBHost: Bug fixes - bugfix: 70_DENON_AVR: handled some uninitialized values - new: 72_UBUS_CALL: module to send call requests via uBus client - new: 72_UBUS_CLIENT: module to communicate with uBus devices diff --git a/fhem/FHEM/89_AndroidDB.pm b/fhem/FHEM/89_AndroidDB.pm index 86eb0fea5..e38415fb5 100644 --- a/fhem/FHEM/89_AndroidDB.pm +++ b/fhem/FHEM/89_AndroidDB.pm @@ -4,7 +4,7 @@ # # 89_AndroidDB # -# Version 0.4 +# Version 0.6 # # FHEM Integration for Android Devices # @@ -36,16 +36,19 @@ sub AndroidDB_Initialize ($) $hash->{ShutdownFn} = "AndroidDB::Shutdown"; $hash->{parseParams} = 1; - $hash->{AttrList} = 'macros:textField-long preset:MagentaTVStick,SonyTV presetFile'; + $hash->{AttrList} = 'connect:0,1 macros:textField-long preset presetFile'; + + $data{RC_layout}{MagentaTVStick} = "AndroidDB::RCLayoutMagentaTVStick"; + $data{RC_layout}{MagentaOne} = "AndroidDB::RCLayoutMagentaOne"; + $data{RC_layout}{MagentaTVExtended} = "AndroidDB::RCLayoutMagentaTVExt"; } package AndroidDB; use strict; use warnings; - use SetExtensions; - +use Storable qw(dclone); use GPUtils qw(:all); BEGIN { @@ -55,42 +58,113 @@ BEGIN { readingsBulkUpdateIfChanged readingsBeginUpdate readingsEndUpdate + setDevAttrList + CommandDefine + CommandSet + CommandAttr Log3 AttrVal ReadingsVal AssignIoPort + InternalTimer + asyncOutput + gettimeofday + parseParams defs + attr + modules + data + init_done )) }; # Remote control presets -my %PRESET = ( +my $PRESET = { 'MagentaTVStick' => { - 'APPS' => 'KEYCODE_ALL_APPS', - 'BACK' => 'KEYCODE_BACK', - 'EPG' => 'KEYCODE_TV_INPUT_HDMI_2', - 'HOME' => 'KEYCODE_HOME', - 'INFO' => 'KEYCODE_INFO', - 'MEGATHEK' => 'KEYCODE_TV_INPUT_HDMI_3', - 'MUTE' => 'KEYCODE_MUTE', - 'OK' => 'KEYCODE_DPAD_CENTER', - 'POWER' => 'KEYCODE_POWER', - 'PROG+' => 'KEYCODE_CHANNEL_UP', - 'PROG-' => 'KEYCODE_CHANNEL_DOWN', - 'RECORD' => 'KEYCODE_MEDIA_RECORD', - 'SEARCH' => 'KEYCODE_TV_INPUT_HDMI_1', - 'TV' => 'KEYCODE_TV_INPUT_HDMI_4' + 'ASSISTANT' => 'KEYCODE_ASSIST', + 'APPS' => 'KEYCODE_ALL_APPS', + 'BACK' => 'KEYCODE_BACK', + 'EPG' => 'KEYCODE_TV_INPUT_HDMI_2', + 'HOME' => 'KEYCODE_HOME', + 'LEFT' => 'KEYCODE_DPAD_LEFT', + 'RIGHT' => 'KEYCODE_DPAD_RIGHT', + 'UP' => 'KEYCODE_DPAD_UP', + 'DOWN' => 'KEYCODE_DPAD_DOWN', + 'INFO' => 'KEYCODE_INFO', + 'MEGATHEK' => 'KEYCODE_TV_INPUT_HDMI_3', + 'MUTE' => 'KEYCODE_VOLUME_MUTE', + 'OK' => 'KEYCODE_DPAD_CENTER', + 'PLAYPAUSE' => 'KEYCODE_MEDIA_PLAY_PAUSE', + 'POWER' => 'KEYCODE_POWER', + 'PROG+' => 'KEYCODE_CHANNEL_UP', + 'PROG-' => 'KEYCODE_CHANNEL_DOWN', + 'RECORD' => 'KEYCODE_MEDIA_RECORD', + 'SEARCH' => 'KEYCODE_TV_INPUT_HDMI_1', + 'STREAMINFO' => '--longpress,KEYCODE_INFO', + 'TV' => 'KEYCODE_TV_INPUT_HDMI_4', + 'VOL+' => 'KEYCODE_VOLUME_UP', + 'VOL-' => 'KEYCODE_VOLUME_DOWN' }, - 'SonyTV' => { - 'POWER' => 'KEYCODE_POWER' + 'MagentaOne' => { + 'ASSISTANT' => 'KEYCODE_ASSIST', + 'APPS' => 'KEYCODE_ALL_APPS', + 'BACK' => 'KEYCODE_BACK', + 'EPG' => 'KEYCODE_TV_INPUT_HDMI_2', + 'HOME' => 'KEYCODE_HOME', + 'LEFT' => 'KEYCODE_DPAD_LEFT', + 'RIGHT' => 'KEYCODE_DPAD_RIGHT', + 'UP' => 'KEYCODE_DPAD_UP', + 'DOWN' => 'KEYCODE_DPAD_DOWN', + 'INFO' => 'KEYCODE_INFO', + 'MEGATHEK' => 'KEYCODE_TV_INPUT_HDMI_3', + 'MUTE' => 'KEYCODE_VOLUME_MUTE', + 'OK' => 'KEYCODE_DPAD_CENTER', + 'PLAYPAUSE' => 'KEYCODE_MEDIA_PLAY_PAUSE', + 'POWER' => 'KEYCODE_POWER', + 'PROG+' => 'KEYCODE_CHANNEL_UP', + 'PROG-' => 'KEYCODE_CHANNEL_DOWN', + 'RECORD' => 'KEYCODE_MEDIA_RECORD', + 'SEARCH' => 'KEYCODE_TV_INPUT_HDMI_1', + 'STREAMINFO' => '--longpress,KEYCODE_INFO', + 'TV' => 'KEYCODE_TV_INPUT_HDMI_4', + 'VOL+' => 'KEYCODE_VOLUME_UP', + 'VOL-' => 'KEYCODE_VOLUME_DOWN' + }, + 'AndroidTV' => { + 'APPS' => 'KEYCODE_ALL_APPS', + 'BACK' => 'KEYCODE_BACK', + 'EPG' => 'KEYCODE_GUIDE', + 'HOME' => 'KEYCODE_HOME', + 'LEFT' => 'KEYCODE_DPAD_LEFT', + 'RIGHT' => 'KEYCODE_DPAD_RIGHT', + 'UP' => 'KEYCODE_DPAD_UP', + 'DOWN' => 'KEYCODE_DPAD_DOWN', + 'INFO' => 'KEYCODE_INFO', + 'MUTE' => 'KEYCODE_VOLUME_MUTE', + 'OK' => 'KEYCODE_DPAD_CENTER', + 'PLAYPAUSE' => 'KEYCODE_MEDIA_PLAY_PAUSE', + 'POWER' => 'KEYCODE_POWER', + 'PROG+' => 'KEYCODE_CHANNEL_UP', + 'PROG-' => 'KEYCODE_CHANNEL_DOWN', + 'RECORD' => 'KEYCODE_MEDIA_RECORD', + 'SEARCH' => 'KEYCODE_SEARCH', + 'VOL+' => 'KEYCODE_VOLUME_UP', + 'VOL-' => 'KEYCODE_VOLUME_DOWN', + 'RED' => 'KEYCODE_PROG_RED', + 'GREEN' => 'KEYCODE_PROG_GREEN', + 'BLUE' => 'KEYCODE_PROG_BLUE', + 'YELLOW' => 'KEYCODE_PROG_YELLOW' } -); +}; + +# Command presets +my $MACRO = { }; sub Define ($$$) { my ($hash, $a, $h) = @_; - my $usage = "define $hash->{NAME} AndroidDB {NameOrIP}"; + my $usage = "define $hash->{NAME} AndroidDB {NameOrIP[:Port]}"; return $usage if (scalar(@$a) < 3); @@ -100,9 +174,31 @@ sub Define ($$$) AssignIoPort ($hash); + $attr{$hash->{NAME}}{webCmd} = 'remoteControl'; + + # Clone predefined presets and macros + $hash->{adb}{preset} = dclone $PRESET; + $hash->{adb}{macro} = dclone $MACRO; + + InitAfterStart ($hash) if ($init_done); + return undef; } +sub InitAfterStart ($) +{ + my ($hash) = @_; + + my @presets = map { $_ eq '_custom_' ? () : $_ } keys %{$hash->{adb}{preset}}; + + if (scalar(@presets) > 0) { + my $attrPreset = 'preset:select,'.join(',',@presets); + my $attributes = $modules{AndroidDB}{AttrList}; + $attributes =~ s/preset/$attrPreset/; + setDevAttrList ($hash->{NAME}, $attributes); + } +} + sub Undef ($$) { my ($hash, $name) = @_; @@ -126,39 +222,119 @@ sub Set ($@) my $name = shift @$a; my $opt = shift @$a // return 'No set command specified'; + # # Preprare list of available commands - my $options = 'reboot sendKey shell'; - my @macroList = (); - my $preset = AttrVal ($hash->{NAME}, 'preset', ''); - my $macros = AttrVal ($hash->{NAME}, 'macros', ''); - push @macroList, sort keys %{$PRESET{$preset}} if ($preset ne '' && exists($PRESET{$preset})); - push @macroList, sort keys %{$PRESET{_custom_}} if ($macros ne '' && exists($PRESET{_custom_})); + # + + # Standard commands + my $options = 'exportPresets reboot rollHorz rollVert sendKey sendNumKeys sendText shell tap'; + + # Add remote control key presets to command remoteControl + my @presetList = (); + my $preset = AttrVal ($name, 'preset', ''); + push @presetList, sort keys %{$hash->{adb}{preset}{$preset}} if ($preset ne '' && exists($hash->{adb}{preset}{$preset})); + push @presetList, sort keys %{$hash->{adb}{preset}{_custom_}} if (exists($hash->{adb}{preset}{_custom_})); my %e; - $options .= ' remoteControl:'.join(',', sort grep { !$e{$_}++ } @macroList) if (scalar(@macroList) > 0); - $opt = lc($opt); + $options .= ' remoteControl:'.join(',', sort grep { !$e{$_}++ } @presetList) if (scalar(@presetList) > 0); + + # Add remote control layouts to command createRemote + my @layouts = keys %{$data{RC_layout}}; + $options .= ' createRemote:'.join(',', sort @layouts) if (scalar(@layouts) > 0); + + # Add command macros to command list + my @macroList = (); + push @macroList, sort keys %{$hash->{adb}{macro}{$preset}} if ($preset ne '' && exists($hash->{adb}{macro}{$preset})); + push @macroList, sort keys %{$hash->{adb}{macro}{_custom_}} if (exists($hash->{adb}{macro}{_custom_})); + my %e; + $options .= ' '.join(' ', sort grep { !$e{$_}++ } @macroList) if (scalar(@macroList) > 0); - if ($opt eq 'sendkey') { - my $key = shift @$a // return "Usage: set $name $opt KeyCode"; - my ($rc, $result, $error) = AndroidDBHost::Run ($hash, 'shell', '.*', 'input', 'keyevent', $key); + my $lcopt = lc($opt); + + if ($lcopt eq 'sendkey') { + return "Usage: set $name $opt [--longpress] KeyCode [...]" if (scalar(@$a) == 0); + return "Only one KeyCode allowed when option '--longpress' is specified" + if ($$a[0] eq '--longpress' && scalar(@$a) > 2); + my ($rc, $result, $error) = AndroidDBHost::Run ($hash, 'shell', '.*', 'input', 'keyevent', @$a); return $error if ($rc == 0); } - elsif ($opt eq 'reboot') { + elsif ($lcopt eq 'sendnumkeys') { + my $number = shift @$a // return "Usage: set $name $opt Number"; + return 'Parameter Number must be in range 1-9999' if ($number !~ /^[0-9]+$/ || $number < 1 || $number > 9999); + my ($rc, $result, $error) = AndroidDBHost::Run ($hash, 'shell', '.*', 'input', 'text', $number); + return $error if ($rc == 0); + } + elsif ($lcopt eq 'sendtext') { + return "Usage: set $name $opt Text" if (scalar(@$a) < 1); + my ($rc, $result, $error) = AndroidDBHost::Run ($hash, 'shell', '.*', 'input', 'text', join(' ',@$a)); + return $error if ($rc == 0); + } + elsif ($lcopt eq 'reboot') { my ($rc, $result, $error) = AndroidDBHost::Run ($hash, $opt); return $error if ($rc == 0); } - elsif ($opt eq 'shell') { + elsif ($lcopt eq 'shell') { return "Usage: set $name $opt ShellCommand" if (scalar(@$a) == 0); my ($rc, $result, $error) = AndroidDBHost::Run ($hash, $opt, '.*', @$a); return $result.$error, } - elsif ($opt eq 'remotecontrol') { + elsif ($lcopt eq 'remotecontrol') { my $macroName = shift @$a // return "Usage: set $name $opt MacroName"; - $preset = '_custom_' if (exists($PRESET{_custom_}) && exists($PRESET{_custom_}{$macroName})); - return "Preset and/or macro $macroName not defined" if ($preset eq '' || !exists($PRESET{$preset}{$macroName})); + $preset = '_custom_' if (exists($hash->{adb}{preset}{_custom_}) && exists($hash->{adb}{preset}{_custom_}{$macroName})); + return "Preset and/or macro $macroName not defined in preset $preset" + if ($preset eq '' || !exists($hash->{adb}{preset}{$preset}{$macroName})); my ($rc, $result, $error) = AndroidDBHost::Run ($hash, 'shell', '.*', 'input', 'keyevent', - split (',', $PRESET{$preset}{$macroName})); + split (',', $hash->{adb}{preset}{$preset}{$macroName})); return $error if ($rc == 0); } + elsif ($lcopt eq 'tap') { + my ($x, $y) = @$a; + return "Usage: set $name $opt tap X Y" if (!defined($y)); + my ($rc, $result, $error) = AndroidDBHost::Run ($hash, 'shell', '.*', 'input', 'tap', $x, $y); + return $error if ($rc == 0); + } + elsif ($lcopt eq 'rollhorz' || $lcopt eq 'rollvert') { + my $delta = shift @$a // return "Usage: set $name $opt Delta"; + my ($dx, $dy) = $opt eq 'rollhorz' ? ($delta, 0) : (0, $delta); + my ($rc, $result, $error) = AndroidDBHost::Run ($hash, 'shell', '.*', 'input', 'roll', $dx, $dy); + return $error if ($rc == 0); + } + elsif ($lcopt eq 'createremote') { + my $layout = shift @$a // return "Usage: set $name $opt LayoutName"; + my $rcName = $name.'_RC'; + return "$name: Can't create remotecontrol device $rcName" + if (CommandDefine (undef, "$rcName remotecontrol")); + Log3 $name, 2, "$name: Created remotecontrol device $rcName"; + return "$name: Can't select layout $layout for remotecontrol device $rcName" + if (CommandSet (undef, "$rcName layout $layout")); + Log3 $name, 2, "Selected layout $layout for $rcName"; + my $room = AttrVal ($name, 'room', ''); + if ($room ne '') { + Log3 $name, 2, "$name: Assigning $rcName to room $room"; + CommandAttr (undef, "$rcName room $room"); + } + CommandSet (undef, "$rcName makenotify $name"); + CommandAttr (undef, "$name group $name"); + CommandAttr (undef, "$rcName group $name"); + Log3 $name, 2, "Created notify device notify_$rcName"; + } + elsif ($lcopt eq 'exportpresets') { + my $filename = shift @$a // return "Usage: set $name $opt Filename"; + my $rc = ExportPresets ($hash, $filename); + return "Error while saving presets to file $filename" if ($rc == 0); + return "Presets saved to file $filename"; + } + elsif (exists($hash->{adb}{macro}{_custom_}) && exists($hash->{adb}{macro}{_custom_}{$opt})) { + my ($args, $pars) = parseParams ($hash->{adb}{macro}{_custom_}{$opt}); + my $cmd = shift @$args; + my ($rc, $result, $error) = AndroidDBHost::Run ($hash, $cmd, '.*', @$args); + return $rc == 0 ? $error : $result; + } + elsif ($preset ne '' && exists($hash->{adb}{macro}{$preset}) && exists($hash->{adb}{macro}{$preset}{$opt})) { + my ($args, $pars) = parseParams ($hash->{adb}{macro}{$preset}{$opt}); + my $cmd = shift @$args; + my ($rc, $result, $error) = AndroidDBHost::Run ($hash, $cmd, '.*', @$args); + return $rc == 0 ? $error : $result; + } else { return "Unknown argument $opt, choose one of $options"; } @@ -171,12 +347,35 @@ sub Get ($@) my $name = shift @$a; my $opt = shift @$a // return 'No get command specified'; - my $options = 'presets'; + my $options = 'keyPreset'; + my @presetList = sort keys %{$hash->{adb}{preset}}; + $options .= ':'.join(',', @presetList) if (scalar(@presetList) > 0); + my @macroList = sort keys %{$hash->{adb}{macro}}; + $options .= ' cmdPreset:'.join(',', @macroList) if (scalar(@macroList) > 0); - $opt = lc($opt); + my $attrPreset = AttrVal ($name, 'preset', ''); - if ($opt eq 'presets') { - return "Command not implemented"; + my $lcopt = lc($opt); + + if ($lcopt eq 'keypreset') { + my $preset = shift @$a // $attrPreset; + return "Usage: get $name $opt PresetName" if ($preset eq ''); + return "Key preset $preset not found" if (!exists($hash->{adb}{preset}{$preset})); + my $presetDef = "Definition of key preset $preset:

"; + foreach my $macro (sort keys %{$hash->{adb}{preset}{$preset}}) { + $presetDef .= "$macro = $hash->{adb}{preset}{$preset}{$macro}
"; + } + return $presetDef; + } + elsif ($lcopt eq 'cmdpreset') { + my $preset = shift @$a // $attrPreset; + return "Usage: get $name $opt PresetName" if ($preset eq ''); + return "Command preset $preset not found" if (!exists($hash->{adb}{macro}{$preset})); + my $macroDef = "Definition of command preset $preset:

"; + foreach my $macro (sort keys %{$hash->{adb}{macro}{$preset}}) { + $macroDef .= "$macro = $hash->{adb}{macro}{$preset}{$macro}
"; + } + return $macroDef; } else { return "Unknown argument $opt, choose one of $options"; @@ -190,20 +389,31 @@ sub Attr ($@) if ($cmd eq 'set') { if ($attrName eq 'macros') { - foreach my $macroDef (split /\s+/, $attrVal) { - my ($macroName, $macroKeycodes) = split (':', $macroDef); - $PRESET{_custom_}{$macroName} = $macroKeycodes; + foreach my $macroDef (split /;/, $attrVal) { + my ($macroName, $macroPar) = split (':', $macroDef); + if (!defined($macroDef)) { + Log3 $name, 2, "Missing defintion for macro $macroName"; + return "Missing definition for macro $macroName"; + } + if ($macroPar =~ /^[0-9]+/ || $macroPar =~ /^KEYCODE_/) { + $hash->{adb}{preset}{_custom_}{$macroName} = $macroPar; + } + else { + $hash->{adb}{macro}{_custom_}{$macroName} = $macroPar; + } } } elsif ($attrName eq 'presetFile') { - if (!LoadPreset ($hash, $attrVal)) { - Log3 $hash, 2, "Can't load preset from file $attrVal"; - return "Cannot load preset from file $attrVal"; + if (!LoadPresets ($hash, $attrVal)) { + return "Cannot load presets from file $attrVal"; } } + elsif ($attrName eq 'connect') { + AndroidDBHost::Connect ($hash) if (!$init_done && $attrVal eq '1'); + } } elsif ($cmd eq 'del') { - delete $PRESET{_custom_} if (exists($PRESET{_custom_})); + delete $hash->{adb}{preset}{_custom_} if (exists($hash->{adb}{preset}{_custom_})); } return undef; @@ -213,50 +423,194 @@ sub Attr ($@) # Load macro definitions from file # File format: # - Lines starting with a # are treated as comments +# - Empty lines are ignored # - Lines containing a single word are setting the preset name for the # following lines # - Lines in format Name:KeyList are defining a macro. KeyList is a comma # separated list of keycodes. +# - Lines in format Name:Command:Parameters are defining a command macro. ############################################################################## -sub LoadPreset ($$) +sub LoadPresets ($$) { my ($hash, $fileName) = @_; - my @lines; + # Read file + my @lines; if (open (PRESETFILE, "<$fileName")) { @lines = ; close (PRESETFILE); } else { + ShowMessage ($hash, 2, "Can't open file $fileName"); return 0; } - + + # Delete old presets + my @presets = keys %{$hash->{adb}{preset}}; + my @macros = keys %{$hash->{adb}{macro}}; + foreach my $e (@presets) { delete $hash->{adb}{preset}{$e} if ($e ne '_custom_'); } + foreach my $e (@macros) { delete $hash->{adb}{macro}{$e} if ($e ne '_custom_'); } + chomp @lines; my $presetName = ''; foreach my $l (@lines) { next if ($l =~ /^#/); # Comments are allowed - my ($macroName, $keyList) = split (':', $l); - if (!defined($keyList)) { + + my ($macroName, $macroPar) = split (':', $l); + if (!defined($macroPar)) { + next if (!defined($macroName) || $macroName eq ''); + if ($macroName !~ /^[a-zA-Z0-9-_]+$/) { + ShowMessage ($hash, 2, "Invalid character in macro name $macroName in file $fileName"); + return 0; + } $presetName = $macroName; - next; } - elsif (defined($keyList) && $presetName eq '') { - Log3 $hash, 2, "Preset name must be specified before first macro definition in file $fileName"; - return 0; + else { + if ($presetName eq '') { + ShowMessage ($hash, 2, "No preset name set for macro name $macroName in file $fileName"); + return 0; + } + if ($macroPar =~ /^[0-9]+/ || $macroPar =~ /^(KEYCODE_|--longpress)/) { + $hash->{adb}{preset}{$presetName}{$macroName} = $macroPar; + } + else { + $hash->{adb}{macro}{$presetName}{$macroName} = $macroPar; + } } - $PRESET{$presetName}{$macroName} = $keyList; } + # Init options of attribute 'preset' + InitAfterStart ($hash); + return 1; } +sub ExportPresets ($$) +{ + my ($hash, $filename) = @_; + + if (open (PRESETFILE, ">$filename")) { + foreach my $preset (sort keys %{$hash->{adb}{preset}}) { + next if ($preset eq '_custom_'); + print PRESETFILE "#\n# Preset $preset\n#\n$preset\n#\n"; + foreach my $macro (sort keys %{$hash->{adb}{preset}{$preset}}) { + print PRESETFILE "$macro:$hash->{adb}{preset}{$preset}{$macro}\n"; + } + if (exists($hash->{adb}{macro}{$preset})) { + foreach my $macro (sort keys %{$hash->{adb}{macro}{$preset}}) { + print PRESETFILE "$macro:$hash->{adb}{macro}{$preset}{$macro}\n"; + } + } + } + foreach my $preset (sort keys %{$hash->{adb}{macro}}) { + next if ($preset eq '_custom_' || exists($hash->{adb}{preset}{$preset})); + print PRESETFILE "#\n# Preset $preset\n#\n$preset\n#\n"; + foreach my $macro (sort keys %{$hash->{adb}{macro}{$preset}}) { + print PRESETFILE "$macro:$hash->{adb}{macro}{$preset}{$macro}\n"; + } + } + close (PRESETFILE); + return 1; + } + + return 0; +} + +sub ShowMessage ($$$) +{ + my ($hash, $level, $msg) = @_; + + Log3 $hash->{NAME}, $level, $msg; + + if ($init_done && exists($hash->{CL})) { + my $cl = $hash->{CL}; + InternalTimer (gettimeofday()+1, sub { asyncOutput ($cl, $msg) }, undef, 1); + } +} + +############################################################################## +# Remote control layout for Magenta TV Stick +############################################################################## + +sub RCLayoutMagentaTVStick () { + my @row = ( + 'sendKey KEYCODE_POWER:POWEROFF,:blank,sendKey KEYCODE_VOLUME_MUTE:MUTE', + ':blank,sendKey KEYCODE_TV_INPUT_HDMI_3:PS3Rectangle,:blank', + ':blank,sendKey KEYCODE_ALL_APPS:TOOLS,:blank', + 'sendKey KEYCODE_TV_INPUT_HDMI_4:TV,:blank,sendKey KEYCODE_TV_INPUT_HDMI_2:GUIDE', + ':blank,sendKey KEYCODE_DPAD_UP:UP,:blank', + 'sendKey KEYCODE_DPAD_LEFT:LEFT,sendKey KEYCODE_DPAD_CENTER:OK,sendKey KEYCODE_DPAD_RIGHT:RIGHT', + ':blank,sendKey KEYCODE_DPAD_DOWN:DOWN,:blank', + 'sendKey KEYCODE_BACK:BACKDroid,sendKey KEYCODE_HOME:HOMEDroid,sendKey KEYCODE_INFO:INFO', + 'sendKey KEYCODE_MEDIA_RECORD:REC,sendKey KEYCODE_TV_INPUT_HDMI_1:SEARCH,sendKey KEYCODE_VOLUME_UP:VOLUP', + 'sendKey KEYCODE_MEDIA_PLAY_PAUSE:PLAYPAUSE,sendKey KEYCODE_ASSIST:SOURCE,sendKey KEYCODE_VOLUME_DOWN:VOLDOWN', + 'attr rc_iconpath icons/remotecontrol', + 'attr rc_iconprefix black_btn_' + ); + + return @row; +} + +############################################################################## +# Remote control layout for Magenta One +############################################################################## + +sub RCLayoutMagentaOne () { + my @row = ( + 'sendKey KEYCODE_POWER:POWEROFF,:blank,sendKey KEYCODE_VOLUME_MUTE:MUTE', + ':blank,:blank,:blank', + 'sendKey KEYCODE_TV_INPUT_HDMI_1:SEARCH,sendKey KEYCODE_ASSIST:SOURCE,sendKey KEYCODE_GUIDE:GUIDE', + 'sendKey KEYCODE_BACK:BACKDroid,sendKey KEYCODE_HOME:HOMEDroid,sendKey KEYCODE_ALL_APPS:MENUDroid', + ':blank,sendKey KEYCODE_DPAD_UP:UP,:blank', + 'sendKey KEYCODE_DPAD_LEFT:LEFT,sendKey KEYCODE_DPAD_CENTER:OK,sendKey KEYCODE_DPAD_RIGHT:RIGHT', + ':blank,sendKey KEYCODE_DPAD_DOWN:DOWN,:blank', + 'sendKey KEYCODE_VOLUME_UP:VOLUP,sendKey KEYCODE_MEDIA_PLAY_PAUSE:PLAYPAUSE,sendKey KEYCODE_CHANNEL_UP:CHUP', + 'sendKey KEYCODE_VOLUME_DOWN:VOLDOWN,:blank,sendKey KEYCODE_CHANNEL_DOWN:CHDOWN', + 'sendKey KEYCODE_1:1,sendKey KEYCODE_2:2,sendKey KEYCODE_3:3', + 'sendKey KEYCODE_4:4,sendKey KEYCODE_5:5,sendKey KEYCODE_6:6', + 'sendKey KEYCODE_7:7,sendKey KEYCODE_8:8,sendKey KEYCODE_9:9', + 'sendKey KEYCODE_INFO:INFO,sendKey KEYCODE_0:0,sendKey KEYCODE_MEDIA_RECORD:REC', + 'attr rc_iconpath icons/remotecontrol', + 'attr rc_iconprefix black_btn_' + ); + + return @row; +} + +############################################################################## +# Extended remote control layout for Magenta TV +############################################################################## + +sub RCLayoutMagentaTVExt () { + my @row = ( + 'sendKey KEYCODE_POWER:POWEROFF,:blank,sendKey KEYCODE_VOLUME_MUTE:MUTE,:blank', + ':blank,:blank,:blank,:blank', + 'sendKey KEYCODE_TV_INPUT_HDMI_1:SEARCH,sendKey KEYCODE_ASSIST:SOURCE,sendKey KEYCODE_GUIDE:GUIDE,sendKey KEYCODE_TV_INPUT_HDMI_4:TV', + 'sendKey KEYCODE_BACK:BACKDroid,sendKey KEYCODE_HOME:HOMEDroid,sendKey KEYCODE_ALL_APPS:MENUDroid,:blank', + ':blank,sendKey KEYCODE_DPAD_UP:UP,:blank,:blank', + 'sendKey KEYCODE_DPAD_LEFT:LEFT,sendKey KEYCODE_DPAD_CENTER:OK,sendKey KEYCODE_DPAD_RIGHT:RIGHT,:blank', + ':blank,sendKey KEYCODE_DPAD_DOWN:DOWN,:blank,:blank', + 'sendKey KEYCODE_VOLUME_UP:VOLUP,sendKey KEYCODE_MEDIA_PLAY_PAUSE:PLAYPAUSE,sendKey KEYCODE_CHANNEL_UP:CHUP,:blank', + 'sendKey KEYCODE_VOLUME_DOWN:VOLDOWN,:blank,sendKey KEYCODE_CHANNEL_DOWN:CHDOWN,:blank', + 'sendKey KEYCODE_1:1,sendKey KEYCODE_2:2,sendKey KEYCODE_3:3,:blank', + 'sendKey KEYCODE_4:4,sendKey KEYCODE_5:5,sendKey KEYCODE_6:6,:blank', + 'sendKey KEYCODE_7:7,sendKey KEYCODE_8:8,sendKey KEYCODE_9:9,:blank', + 'sendKey KEYCODE_INFO:INFO,sendKey KEYCODE_0:0,sendKey KEYCODE_MEDIA_RECORD:REC,:blank', + 'sendKey KEYCODE_PROG_RED:RED,sendKey KEYCODE_PROG_GREEN:GREEN,sendKey KEYCODE_PROG_YELLOW:YELLOW,sendKey KEYCODE_PROG_BLUE:BLUE', + 'attr rc_iconpath icons/remotecontrol', + 'attr rc_iconprefix black_btn_' + ); + + return @row; +} + 1; =pod =item device -=item summary Allows to control an Android device via ADB +=item summary Allows to control an Android device via ADB (Android Debug Bridge) =begin html @@ -270,8 +624,10 @@ sub LoadPreset ($$) Define


@@ -279,6 +635,14 @@ sub LoadPreset ($$) Set

+ + +Get

+ Attributes

diff --git a/fhem/FHEM/89_AndroidDBHost.pm b/fhem/FHEM/89_AndroidDBHost.pm index 3660cae32..0481b9118 100644 --- a/fhem/FHEM/89_AndroidDBHost.pm +++ b/fhem/FHEM/89_AndroidDBHost.pm @@ -4,7 +4,7 @@ # # 89_AndroidDBHost # -# Version 0.4 +# Version 0.6 # # FHEM Integration for Android Debug Bridge # @@ -19,8 +19,6 @@ # Windows/MacOSX/Linux x86: https://developer.android.com/studio/releases/platform-tools # - - package main; use strict; @@ -86,7 +84,7 @@ sub Define ($$$) $hash->{adb}{port} = $port // 5037; $hash->{adb}{cmd} = $h->{adb} // '/usr/bin/adb'; $hash->{Clients} = ':AndroidDB:'; - $hash->{NOTIFYDEV} = 'global,TYPE=(AndroidDBHost|AndroidDB)'; + $hash->{NOTIFYDEV} = 'global,TYPE=AndroidDBHost'; # Check path and rights of platform tools return "ADB command not found or is not executable in $hash->{adb}{pt}" if (! -x "$hash->{adb}{cmd}"); @@ -132,7 +130,15 @@ sub Notify ($$) return if (!$events); if ($devhash->{NAME} eq 'global' && grep (/INITIALIZED/, @$events)) { - InternalTimer (gettimeofday()+60, 'AndroidDBHost::CheckADBServerTimer', $hash, 0); + foreach my $clName (keys %defs) { + my $clHash = $defs{$clName}; + if ($clHash->{TYPE} eq 'AndroidDB') { + AndroidDB::InitAfterStart ($clHash); + } + } + + # First refresh of client connection state after 10 seconds + InternalTimer (gettimeofday()+10, 'AndroidDBHost::CheckADBServerTimer', $hash, 0); } } @@ -194,7 +200,8 @@ sub UpdateClientStates ($) foreach my $d (keys %defs) { my $clHash = $defs{$d}; - if ($clHash->{TYPE} eq 'AndroidDB') { + next if (!defined($clHash->{TYPE}) || !defined($clHash->{IODev})); + if ($clHash->{TYPE} eq 'AndroidDB' && $clHash->{IODev} == $hash) { my $clState = $device->{$clHash->{ADBDevice}} // 'disconnected'; $clState =~ s/device/connected/; readingsSingleUpdate ($clHash, 'state', $clState, 1); @@ -253,10 +260,9 @@ sub Set ($@) my ($rc, $result, $error) = Execute ($hash, $command, '.*', @$a); return $result.$error; } - elsif ($opt eq 'disconnect') { - my ($rc, $result, $error) = Execute ($hash, 'disconnect', 'disconnected'); - UpdateClientStates ($hash); - return "Disconnecting all devices failed $result $error" if ($rc == 0); + elsif ($opt eq 'disconnectall') { + my ($rc, $error) = DisconnectAll ($hash); + return "Disconnecting all devices failed: $error" if ($rc == 0); } else { return "Unknown argument $opt, choose one of $options"; @@ -311,8 +317,8 @@ sub Get ($@) # Return value: # (returncode, stdout, stderr) # Return codes: -# 0 - error -# 1 - success +# 0 = error +# 1 = success ############################################################################## sub Execute ($@) @@ -324,6 +330,8 @@ sub Execute ($@) Log3 $ioHash->{NAME}, 2, 'Execute: ADB server not running'; return (0, '', 'ADB server not running'); } + + Log3 $ioHash->{NAME}, 5, "Executing $ioHash->{adb}{cmd} $command ".join(' ',@args); # Execute ADB command local (*CHILDIN, *CHILDOUT, *CHILDERR); @@ -370,13 +378,13 @@ sub Execute ($@) sub IsConnected ($) { - my $clHash = shift // return 0; + my $clHash = shift // return -1; my $ioHash = $clHash->{IODev} // return -1; # Get active connections my $device = GetDeviceList ($ioHash) // return -1; - my $devCount = keys %$device; + my $devCount = scalar(keys %$device); if ($devCount == 1) { return exists($device->{$clHash->{ADBDevice}}) ? 1 : 2; @@ -403,13 +411,13 @@ sub Connect ($) my $ioHash = $clHash->{IODev} // return -1; my $connect = IsConnected ($clHash); +# Log3 $clHash->{NAME}, 2, "Connection state is $connect"; if ($connect == 1) { return 1; } elsif ($connect == 2) { # Disconnect all devices - my ($rc, $result, $error) = Execute ($ioHash, 'disconnect', 'disconnected'); - return -1 if ($rc == 0); + DisconnectAll ($ioHash); } elsif ($connect == -1) { Log3 $clHash->{NAME}, 2, 'Cannot detect connection state'; @@ -424,11 +432,11 @@ sub Connect ($) } ############################################################################## -# Connect to Android device +# Disconnect Android device # # Return value: -# 0 = error -# 1 = connected +# -1, 0 = error +# 1 = success ############################################################################## sub Disconnect ($) @@ -443,6 +451,21 @@ sub Disconnect ($) return $rc; } +############################################################################## +# Disconnect all Android devices +############################################################################## + +sub DisconnectAll ($) +{ + my ($hash) = @_; + + my ($rc, $result, $error) = Execute ($hash, 'disconnect', 'disconnected'); + UpdateClientStates ($hash); + return (0, $error) if ($rc == 0); + + return (1, 'ok'); +} + ############################################################################## # Get list of devices ############################################################################## @@ -482,12 +505,9 @@ sub Run ($@) my $ioHash = $clHash->{IODev} // return (0, '', 'Cannot detect IO device'); if (!Connect ($clHash)) { - readingsSingleUpdate ($clHash, 'state', 'connected', 1); return (0, '', 'Cannot connect to device'); } - readingsSingleUpdate ($clHash, 'state', 'connected', 1); - return Execute ($ioHash, $command, $succExp, @args); } @@ -546,6 +566,9 @@ sub TCPConnect ($$$)
  • set <name> command <Command> [<Args>]
    Execute ADB command.

  • +
  • set <name> disconnectAll
    + Disconnect all devices. +

  • set <name> start
    Start ADB server.