mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-05-04 22:19:38 +00:00
AndroidDB: Initial upload
git-svn-id: https://svn.fhem.de/fhem/trunk@24426 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
7eee3b1916
commit
c34af8ca7e
273
fhem/FHEM/89_AndroidDB.pm
Normal file
273
fhem/FHEM/89_AndroidDB.pm
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
|
||||||
|
#
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# 89_AndroidDB
|
||||||
|
#
|
||||||
|
# Version 0.1
|
||||||
|
#
|
||||||
|
# FHEM Integration for Android Devices
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
#
|
||||||
|
# 89_AndroidDBHost
|
||||||
|
#
|
||||||
|
# Prerequisits:
|
||||||
|
#
|
||||||
|
# - Enable developer mode on Android device
|
||||||
|
# - Allow USB debugging on Android device
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
package main;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
sub AndroidDB_Initialize ($)
|
||||||
|
{
|
||||||
|
my ($hash) = @_;
|
||||||
|
|
||||||
|
$hash->{DefFn} = "AndroidDB::Define";
|
||||||
|
$hash->{UndefFn} = "AndroidDB::Undef";
|
||||||
|
$hash->{SetFn} = "AndroidDB::Set";
|
||||||
|
$hash->{GetFn} = "AndroidDB::Get";
|
||||||
|
$hash->{AttrFn} = "AndroidDB::Attr";
|
||||||
|
$hash->{ShutdownFn} = "AndroidDB::Shutdown";
|
||||||
|
|
||||||
|
$hash->{parseParams} = 1;
|
||||||
|
$hash->{AttrList} = 'macros:textField-long preset:MagentaTVStick,SonyTV';
|
||||||
|
}
|
||||||
|
|
||||||
|
package AndroidDB;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Data::Dumper;
|
||||||
|
|
||||||
|
use SetExtensions;
|
||||||
|
|
||||||
|
use GPUtils qw(:all);
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
GP_Import(qw(
|
||||||
|
readingsSingleUpdate
|
||||||
|
readingsBulkUpdate
|
||||||
|
readingsBulkUpdateIfChanged
|
||||||
|
readingsBeginUpdate
|
||||||
|
readingsEndUpdate
|
||||||
|
Log3
|
||||||
|
AttrVal
|
||||||
|
ReadingsVal
|
||||||
|
AssignIoPort
|
||||||
|
defs
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
# Remote control presets
|
||||||
|
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'
|
||||||
|
},
|
||||||
|
'SonyTV' => {
|
||||||
|
'POWER' => 'KEYCODE_POWER'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
sub Define ($$$)
|
||||||
|
{
|
||||||
|
my ($hash, $a, $h) = @_;
|
||||||
|
|
||||||
|
my $usage = "define $hash->{NAME} AndroidDB {NameOrIP}";
|
||||||
|
|
||||||
|
return $usage if (scalar(@$a) < 3);
|
||||||
|
|
||||||
|
# Set parameters
|
||||||
|
my ($devName, $devPort) = split (':', $$a[2]);
|
||||||
|
$hash->{ADBDevice} = $devName.':'.($devPort // '5555');
|
||||||
|
|
||||||
|
AssignIoPort ($hash);
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Undef ($$)
|
||||||
|
{
|
||||||
|
my ($hash, $name) = @_;
|
||||||
|
|
||||||
|
AndroidDBHost::Disconnect ($hash);
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Shutdown ($)
|
||||||
|
{
|
||||||
|
my ($hash) = @_;
|
||||||
|
|
||||||
|
AndroidDBHost::Disconnect ($hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Set ($@)
|
||||||
|
{
|
||||||
|
my ($hash, $a, $h) = @_;
|
||||||
|
|
||||||
|
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_}));
|
||||||
|
my %e;
|
||||||
|
$options .= ' remoteControl:'.join(',', sort grep { !$e{$_}++ } @macroList) if (scalar(@macroList) > 0);
|
||||||
|
$opt = lc($opt);
|
||||||
|
|
||||||
|
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);
|
||||||
|
return $error if ($rc == 0);
|
||||||
|
}
|
||||||
|
elsif ($opt eq 'reboot') {
|
||||||
|
my ($rc, $result, $error) = AndroidDBHost::Run ($hash, $opt);
|
||||||
|
return $error if ($rc == 0);
|
||||||
|
}
|
||||||
|
elsif ($opt 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') {
|
||||||
|
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}));
|
||||||
|
my ($rc, $result, $error) = AndroidDBHost::Run ($hash, 'shell', '.*', 'input', 'keyevent',
|
||||||
|
split (',', $PRESET{$preset}{$macroName}));
|
||||||
|
return $error if ($rc == 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "Unknown argument $opt, choose one of $options";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Get ($@)
|
||||||
|
{
|
||||||
|
my ($hash, $a, $h) = @_;
|
||||||
|
|
||||||
|
my $name = shift @$a;
|
||||||
|
my $opt = shift @$a // return 'No get command specified';
|
||||||
|
|
||||||
|
my $options = 'presets';
|
||||||
|
|
||||||
|
$opt = lc($opt);
|
||||||
|
|
||||||
|
if ($opt eq 'presets') {
|
||||||
|
return Dumper (\%PRESET);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "Unknown argument $opt, choose one of $options";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Attr ($@)
|
||||||
|
{
|
||||||
|
my ($cmd, $name, $attrName, $attrVal) = @_;
|
||||||
|
my $hash = $defs{$name};
|
||||||
|
|
||||||
|
if ($cmd eq 'set') {
|
||||||
|
if ($attrName eq 'macros') {
|
||||||
|
foreach my $macroDef (split /\s+/, $attrVal) {
|
||||||
|
my ($macroName, $macroKeycodes) = split (':', $macroDef);
|
||||||
|
$PRESET{_custom_}{$macroName} = $macroKeycodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elsif ($cmd eq 'del') {
|
||||||
|
delete $PRESET{_custom_} if (exists($PRESET{_custom_}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
||||||
|
=pod
|
||||||
|
=item device
|
||||||
|
=item summary Allows to control an Android device via ADB
|
||||||
|
=begin html
|
||||||
|
|
||||||
|
<a name="AndroidDB"></a>
|
||||||
|
<h3>AndroidDB</h3>
|
||||||
|
<ul>
|
||||||
|
The module allows to control an Android device by using the Android Debug Bridge (ADB).
|
||||||
|
Before one can define an Android device, an AndroidDBHost I/O device must exist.
|
||||||
|
<br/><br/>
|
||||||
|
Dependencies: 89_AndroidDBHost
|
||||||
|
<br/><br/>
|
||||||
|
<a name="AndroidDBdefine"></a>
|
||||||
|
<b>Define</b><br/><br/>
|
||||||
|
<ul>
|
||||||
|
<code>define <name> AndroidDB {<NameOrIP>}</code>
|
||||||
|
The parameter 'NameOrIP' is the hostname or the IP address of the Android device.
|
||||||
|
</ul>
|
||||||
|
<br/>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<a name="AndroidDBset"></a>
|
||||||
|
<b>Set</b><br/><br/>
|
||||||
|
<ul>
|
||||||
|
<li><b>set <name> reboot</b><br/>
|
||||||
|
Reboot the device.
|
||||||
|
</li><br/>
|
||||||
|
<li><b>set <name> remoteControl <MacroName></b><br/>
|
||||||
|
Send key codes associated with 'MacroName' to the Android device. Either attribute
|
||||||
|
'macros' or 'preset' must be set to make this command available. Macro names defined
|
||||||
|
in attribute 'macros' are overwriting macros with the same name in a preset selected
|
||||||
|
by attribute 'preset'.
|
||||||
|
</li><br/>
|
||||||
|
<li><b>set <name> sendKey <KeyCode></b><br/>
|
||||||
|
Send a key code to the Android device.
|
||||||
|
</li><br/>
|
||||||
|
<li><b>set <name> shell <Command> [<Arguments>]</b><br/>
|
||||||
|
Execute shell command on Android device.
|
||||||
|
</li><br/>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<a name="AndroidDBattr"></a>
|
||||||
|
<b>Attributes</b><br/><br/>
|
||||||
|
<ul>
|
||||||
|
<a name="macros"></a>
|
||||||
|
<li><b>macros <MacroDef> [...]</b><br/>
|
||||||
|
Define a list of keycode macros to be sent to an Android device with 'remoteControl'
|
||||||
|
command. A 'MacroDef' is using the following syntax:<br/>
|
||||||
|
MacroName:KeyCode[,...]<br/>
|
||||||
|
Several macro definitions can be specified by seperating them using a blank character.
|
||||||
|
</li><br/>
|
||||||
|
<a name="preset"></a>
|
||||||
|
<li><b>preset <Preset></b><br/>
|
||||||
|
Select a preset of keycode macros. If the same macro name is defined in the selected
|
||||||
|
preset and in attribute 'macros', the definition in the 'macros' attribute overwrites
|
||||||
|
the definition in the preset.
|
||||||
|
</li><br/>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
=end html
|
||||||
|
=cut
|
||||||
|
|
||||||
|
|
486
fhem/FHEM/89_AndroidDBHost.pm
Normal file
486
fhem/FHEM/89_AndroidDBHost.pm
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
|
||||||
|
#
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# 89_AndroidDBHost
|
||||||
|
#
|
||||||
|
# Version 0.1
|
||||||
|
#
|
||||||
|
# FHEM Integration for Android Debug Bridge
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
#
|
||||||
|
# - Perl Packages: IPC::Open3
|
||||||
|
# - Android Platform Tools
|
||||||
|
#
|
||||||
|
# Install Android Platform Tools:
|
||||||
|
#
|
||||||
|
# Raspbian/Debian: apt-get install android-sdk-platform-tools
|
||||||
|
# Windows/MacOSX/Linux x86: https://developer.android.com/studio/releases/platform-tools
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
package main;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
sub AndroidDBHost_Initialize ($)
|
||||||
|
{
|
||||||
|
my ($hash) = @_;
|
||||||
|
|
||||||
|
$hash->{DefFn} = "AndroidDBHost::Define";
|
||||||
|
$hash->{UndefFn} = "AndroidDBHost::Undef";
|
||||||
|
$hash->{SetFn} = "AndroidDBHost::Set";
|
||||||
|
$hash->{GetFn} = "AndroidDBHost::Get";
|
||||||
|
$hash->{NotifyFn} = "AndroidDBHost::Notify";
|
||||||
|
$hash->{ShutdownFn} = "AndroidDBHost::Shutdown";
|
||||||
|
|
||||||
|
$hash->{parseParams} = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
package AndroidDBHost;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use IPC::Open3;
|
||||||
|
|
||||||
|
use SetExtensions;
|
||||||
|
# use POSIX;
|
||||||
|
|
||||||
|
use GPUtils qw(:all);
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
GP_Import(qw(
|
||||||
|
readingsSingleUpdate
|
||||||
|
readingsBulkUpdate
|
||||||
|
readingsBulkUpdateIfChanged
|
||||||
|
readingsBeginUpdate
|
||||||
|
readingsEndUpdate
|
||||||
|
Log3
|
||||||
|
AttrVal
|
||||||
|
ReadingsVal
|
||||||
|
InternalTimer
|
||||||
|
RemoveInternalTimer
|
||||||
|
init_done
|
||||||
|
deviceEvents
|
||||||
|
gettimeofday
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
sub Define ($$$)
|
||||||
|
{
|
||||||
|
my ($hash, $a, $h) = @_;
|
||||||
|
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
my $usage = "define $name AndroidDB [server={host}[:{port}]] [adb={path}]";
|
||||||
|
|
||||||
|
# Set parameters
|
||||||
|
my ($host, $port) = split (':', $h->{ADB} // 'localhost:5037');
|
||||||
|
$hash->{adb}{host} = $host;
|
||||||
|
$hash->{adb}{port} = $port // 5037;
|
||||||
|
$hash->{adb}{cmd} = $h->{adb} // '/usr/bin/adb';
|
||||||
|
$hash->{Clients} = ':AndroidDB:';
|
||||||
|
$hash->{NOTIFYDEV} = 'global,TYPE=(AndroidDBHost|AndroidDB)';
|
||||||
|
|
||||||
|
# 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}");
|
||||||
|
|
||||||
|
# Check ADB settings, start adb server
|
||||||
|
CheckADBServer ($hash);
|
||||||
|
|
||||||
|
return "ADB server not running or cannot be started on host $hash->{adb}{host}" if ($hash->{STATE} eq 'stopped');
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Undef ($$)
|
||||||
|
{
|
||||||
|
my ($hash, $name) = @_;
|
||||||
|
|
||||||
|
Log3 $name, 2, "Stopping ADB server ...";
|
||||||
|
RemoveInternalTimer ($hash);
|
||||||
|
Execute ($hash, 'kill-server') if (IsADBServerRunning ($hash));
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Shutdown ($)
|
||||||
|
{
|
||||||
|
my $hash = shift;
|
||||||
|
|
||||||
|
RemoveInternalTimer ($hash);
|
||||||
|
Execute ($hash, 'kill-server') if (IsADBServerRunning ($hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Initialize ADB server checking timer after FHEM is initialized
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
sub Notify ($$)
|
||||||
|
{
|
||||||
|
my ($hash, $devhash) = @_;
|
||||||
|
|
||||||
|
return if (AttrVal ($hash->{NAME}, 'disable', 0) == 1);
|
||||||
|
|
||||||
|
my $events = deviceEvents ($devhash, 1);
|
||||||
|
return if (!$events);
|
||||||
|
|
||||||
|
if ($devhash->{NAME} eq 'global' && grep (/INITIALIZED/, @$events)) {
|
||||||
|
InternalTimer (gettimeofday()+60, 'AndroidDBHost::CheckADBServerTimer', $hash, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Timer function to check periodically, if ADB server is running
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
sub CheckADBServerTimer ($)
|
||||||
|
{
|
||||||
|
my $hash = shift;
|
||||||
|
|
||||||
|
CheckADBServer ($hash);
|
||||||
|
|
||||||
|
InternalTimer (gettimeofday()+60, 'AndroidDBHost::CheckADBServerTimer', $hash, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Start ADB server if it's not running
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
sub CheckADBServer ($)
|
||||||
|
{
|
||||||
|
my $hash = shift;
|
||||||
|
|
||||||
|
my $newState = 'stopped';
|
||||||
|
for (my $i=0; $i<3; $i++) {
|
||||||
|
Log3 $hash->{NAME}, 4, 'Check if ADB server is running. '.($i+1).'. attempt';
|
||||||
|
if (IsADBServerRunning ($hash)) {
|
||||||
|
$newState = 'running';
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hash->{adb}{host} eq 'localhost') {
|
||||||
|
# Start ADB server
|
||||||
|
Log3 $hash->{NAME}, 2, "Periodical check found no running ADB server. Starting ADB server ...";
|
||||||
|
Execute ($hash, 'start-server');
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
readingsSingleUpdate ($hash, 'state', $newState, 1);
|
||||||
|
|
||||||
|
return $newState eq 'running' ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Check if ADB server is running by connecting to port
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
sub IsADBServerRunning ($)
|
||||||
|
{
|
||||||
|
my $hash = shift;
|
||||||
|
|
||||||
|
return TCPConnect ($hash->{adb}{host}, $hash->{adb}{port}, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Set commands
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
sub Set ($@)
|
||||||
|
{
|
||||||
|
my ($hash, $a, $h) = @_;
|
||||||
|
|
||||||
|
my $name = shift @$a;
|
||||||
|
my $opt = shift @$a // return 'No set command specified';
|
||||||
|
|
||||||
|
# Preprare list of available commands
|
||||||
|
my $options = 'start:noArg stop:noArg';
|
||||||
|
|
||||||
|
$opt = lc($opt);
|
||||||
|
|
||||||
|
if ($opt eq 'start') {
|
||||||
|
RemoveInternalTimer ($hash, 'AndroidDBHost::CheckADBServerTimer');
|
||||||
|
CheckADBServer ($hash);
|
||||||
|
return "Cannot start server" if ($hash->{STATE} eq 'stopped');
|
||||||
|
}
|
||||||
|
elsif ($opt eq 'stop') {
|
||||||
|
my ($rc, $result, $error) = Execute ($hash, 'kill-server');
|
||||||
|
return $error if ($rc == 0);
|
||||||
|
sleep (2);
|
||||||
|
if (!IsADBServerRunning ($hash)) {
|
||||||
|
RemoveInternalTimer ($hash, 'AndroidDBHost::CheckADBServerTimer');
|
||||||
|
readingsSingleUpdate ($hash, 'state', 'stopped', 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "ADB server still running. Please try again.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "Unknown argument $opt, choose one of $options";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Get commands
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
sub Get ($@)
|
||||||
|
{
|
||||||
|
my ($hash, $a, $h) = @_;
|
||||||
|
|
||||||
|
my $name = shift @$a;
|
||||||
|
my $opt = shift @$a // return 'No get command specified';
|
||||||
|
|
||||||
|
# Prepare list of available commands
|
||||||
|
my $options = 'status:noArg';
|
||||||
|
|
||||||
|
$opt = lc($opt);
|
||||||
|
|
||||||
|
if ($opt eq 'status') {
|
||||||
|
my $status = IsADBServerRunning ($hash) ? 'running' : 'stopped';
|
||||||
|
readingsSingleUpdate ($hash, 'state', $status, 1);
|
||||||
|
return "ADB server $status";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "Unknown argument $opt, choose one of $options";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Execute adb commmand and return status code and command output
|
||||||
|
#
|
||||||
|
# Return value:
|
||||||
|
# (returncode, stdout, stderr)
|
||||||
|
# Return codes:
|
||||||
|
# 0 - error
|
||||||
|
# 1 - success
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
sub Execute ($@)
|
||||||
|
{
|
||||||
|
my ($ioHash, $command, $succExp, @args) = @_;
|
||||||
|
$succExp //= '.*';
|
||||||
|
|
||||||
|
if ($command ne 'start-server' && !IsADBServerRunning ($ioHash)) {
|
||||||
|
Log3 $ioHash->{NAME}, 2, 'Execute: ADB server not running';
|
||||||
|
return (0, '', 'ADB server not running');
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute ADB command
|
||||||
|
local (*CHILDIN, *CHILDOUT, *CHILDERR);
|
||||||
|
my $pid = open3 (*CHILDIN, *CHILDOUT, *CHILDERR, $ioHash->{adb}{cmd}, $command, @args);
|
||||||
|
close (CHILDIN);
|
||||||
|
|
||||||
|
# Read output
|
||||||
|
my $result = '';
|
||||||
|
while (my $line = <CHILDOUT>) { $result .= $line; }
|
||||||
|
my $error = '';
|
||||||
|
while (my $line = <CHILDERR>) { $error .= $line; }
|
||||||
|
|
||||||
|
close (CHILDOUT);
|
||||||
|
close (CHILDERR);
|
||||||
|
waitpid ($pid, 0);
|
||||||
|
|
||||||
|
Log3 $ioHash->{NAME}, 5, "stdout=$result";
|
||||||
|
Log3 $ioHash->{NAME}, 5, "stderr=$error";
|
||||||
|
|
||||||
|
my $rc = 0;
|
||||||
|
if ($error eq '') {
|
||||||
|
if ($result !~ /$succExp/i) {
|
||||||
|
$error = "Response doesn't match $succExp for command $command";
|
||||||
|
$rc = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$rc = 1;
|
||||||
|
$ioHash->{ADBPID} = $pid if ($command eq 'start-server');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($rc, $result, $error);
|
||||||
|
}
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Check Android device connection(s)
|
||||||
|
#
|
||||||
|
# Return value:
|
||||||
|
# -1 = Error
|
||||||
|
# 0 = No active connections
|
||||||
|
# 1 = Current device connected
|
||||||
|
# 2 = Multiple devices connected (need to disconnect)
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
sub IsConnected ($)
|
||||||
|
{
|
||||||
|
my $clHash = shift // return 0;
|
||||||
|
|
||||||
|
my $ioHash = $clHash->{IODev} // return -1;
|
||||||
|
|
||||||
|
# Get active connections
|
||||||
|
my ($rc, $result, $error) = Execute ($ioHash, 'devices', 'list');
|
||||||
|
return -1 if ($rc == 0);
|
||||||
|
|
||||||
|
my @devices = $result =~ /device$/g;
|
||||||
|
if (scalar(@devices) == 1 && $result =~ /$clHash->{ADBDevice}/) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
elsif (scalar(@devices) > 1) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Connect to Android device
|
||||||
|
#
|
||||||
|
# Return value:
|
||||||
|
# 0 = error
|
||||||
|
# 1 = connected
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
sub Connect ($)
|
||||||
|
{
|
||||||
|
my $clHash = shift // return 0;
|
||||||
|
|
||||||
|
my $ioHash = $clHash->{IODev} // return -1;
|
||||||
|
|
||||||
|
my $connect = IsConnected ($clHash);
|
||||||
|
if ($connect == 1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
elsif ($connect == 2) {
|
||||||
|
# Disconnect all devices
|
||||||
|
my ($rc, $result, $error) = Execute ($ioHash, 'disconnect', 'disconnected');
|
||||||
|
return -1 if ($rc == 0);
|
||||||
|
}
|
||||||
|
elsif ($connect == -1) {
|
||||||
|
Log3 $clHash->{NAME}, 2, 'Cannot detect connection state';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Connect
|
||||||
|
my ($rc, $state, $error) = Execute ($ioHash, 'connect', 'connected', $clHash->{ADBDevice});
|
||||||
|
readingsSingleUpdate ($clHash, 'state', 'connected', 1) if ($rc == 1);
|
||||||
|
|
||||||
|
return $rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Connect to Android device
|
||||||
|
#
|
||||||
|
# Return value:
|
||||||
|
# 0 = error
|
||||||
|
# 1 = connected
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
sub Disconnect ($)
|
||||||
|
{
|
||||||
|
my $clHash = shift // return 0;
|
||||||
|
|
||||||
|
my $ioHash = $clHash->{IODev} // return (-1, '', 'Cannot detect IO device');
|
||||||
|
|
||||||
|
my ($rc, $result, $error) = Execute ($ioHash, 'disconnect', 'disconnected', $clHash->{ADBDevice});
|
||||||
|
readingsSingleUpdate ($clHash, 'state', 'disconnected', 1) if ($rc == 1);
|
||||||
|
|
||||||
|
return $rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Execute commmand and return status code and command output
|
||||||
|
#
|
||||||
|
# Return value:
|
||||||
|
# (returncode, stdout, stderr)
|
||||||
|
# Return codes:
|
||||||
|
# 0 - error
|
||||||
|
# 1 - success
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
sub Run ($@)
|
||||||
|
{
|
||||||
|
my ($clHash, $command, $succExp, @args) = @_;
|
||||||
|
$succExp //= '.*';
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Check if TCP connection to specified host and port is possible
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
sub TCPConnect ($$$)
|
||||||
|
{
|
||||||
|
my ($addr, $port, $timeout) = @_;
|
||||||
|
|
||||||
|
my $socket = IO::Socket::INET->new (PeerAddr => $addr, PeerPort => $port, Timeout => $timeout);
|
||||||
|
if ($socket) {
|
||||||
|
close ($socket);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
||||||
|
=pod
|
||||||
|
=item device
|
||||||
|
=item summary Provides I/O device for AndroidDB devices
|
||||||
|
=begin html
|
||||||
|
|
||||||
|
<a name="AndroidDBHost"></a>
|
||||||
|
<h3>AndroidDBHost</h3>
|
||||||
|
<ul>
|
||||||
|
Provides I/O device for AndroidDB devices.
|
||||||
|
<br/><br/>
|
||||||
|
Dependencies: Perl module IPC::Open3, Android Platform Tools
|
||||||
|
<br/><br/>
|
||||||
|
Android DB Platform Tools installation:<br/>
|
||||||
|
Debian/Raspbian: apt-get install android-sdk-platform-tools<br/>
|
||||||
|
Windows/MacOSX/Linux x86: <a href="https://developer.android.com/studio/releases/platform-tools">Android Developer Portal</a>
|
||||||
|
<br/><br/>
|
||||||
|
<a name="AndroidDBHostdefine"></a>
|
||||||
|
<b>Define</b><br/><br/>
|
||||||
|
<ul>
|
||||||
|
<code>define <name> AndroidDBHost [server=<host>}[:<port>]] [adb=<path>]</code><br/><br/>
|
||||||
|
The parameter 'host' is the hostname of the system, where the ADB server is running. Default is 'localhost'.
|
||||||
|
Parameter 'adb' can be used to specify the path to the adb command (must include 'adb' or 'adb.exe').
|
||||||
|
</ul>
|
||||||
|
<br/>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<a name="AndroidDBHostset"></a>
|
||||||
|
<b>Set</b><br/><br/>
|
||||||
|
<ul>
|
||||||
|
<li><b>set <name> start</b><br/>
|
||||||
|
Start ADB server.
|
||||||
|
</li><br/>
|
||||||
|
<li><b>set <name> stop</b><br/>
|
||||||
|
Stop ADB server.
|
||||||
|
</li><br/>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<a name="AndroidDBHostget"></a>
|
||||||
|
<b>Get</b><br/><br/>
|
||||||
|
<ul>
|
||||||
|
<li><b>get <name> status</b><br/>
|
||||||
|
Get status of ADB server.
|
||||||
|
</li><br/>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
=end html
|
||||||
|
=cut
|
Loading…
x
Reference in New Issue
Block a user