diff --git a/fhem/FHEM/89_AndroidDB.pm b/fhem/FHEM/89_AndroidDB.pm
new file mode 100644
index 000000000..bdcb2ffd8
--- /dev/null
+++ b/fhem/FHEM/89_AndroidDB.pm
@@ -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
+
+
+
AndroidDB
+
+ 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.
+
+ Dependencies: 89_AndroidDBHost
+
+
+ Define
+
+ define <name> AndroidDB {<NameOrIP>}
+ The parameter 'NameOrIP' is the hostname or the IP address of the Android device.
+
+
+
+
+
+Set
+
+ - set <name> reboot
+ Reboot the device.
+
+ - set <name> remoteControl <MacroName>
+ 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'.
+
+ - set <name> sendKey <KeyCode>
+ Send a key code to the Android device.
+
+ - set <name> shell <Command> [<Arguments>]
+ Execute shell command on Android device.
+
+
+
+
+Attributes
+
+
+ - macros <MacroDef> [...]
+ Define a list of keycode macros to be sent to an Android device with 'remoteControl'
+ command. A 'MacroDef' is using the following syntax:
+ MacroName:KeyCode[,...]
+ Several macro definitions can be specified by seperating them using a blank character.
+
+
+ - preset <Preset>
+ 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.
+
+
+
+=end html
+=cut
+
+
diff --git a/fhem/FHEM/89_AndroidDBHost.pm b/fhem/FHEM/89_AndroidDBHost.pm
new file mode 100644
index 000000000..92fbbc0b8
--- /dev/null
+++ b/fhem/FHEM/89_AndroidDBHost.pm
@@ -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 = ) { $result .= $line; }
+ my $error = '';
+ while (my $line = ) { $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
+
+
+AndroidDBHost
+
+ Provides I/O device for AndroidDB devices.
+
+ Dependencies: Perl module IPC::Open3, Android Platform Tools
+
+ Android DB Platform Tools installation:
+ Debian/Raspbian: apt-get install android-sdk-platform-tools
+ Windows/MacOSX/Linux x86: Android Developer Portal
+
+
+ Define
+
+ define <name> AndroidDBHost [server=<host>}[:<port>]] [adb=<path>]
+ 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').
+
+
+
+
+
+Set
+
+ - set <name> start
+ Start ADB server.
+
+ - set <name> stop
+ Stop ADB server.
+
+
+
+
+Get
+
+ - get <name> status
+ Get status of ADB server.
+
+
+
+=end html
+=cut