mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-02-25 03:44:52 +00:00
802 lines
29 KiB
Perl
802 lines
29 KiB
Perl
|
|
#
|
|
# $Id$
|
|
#
|
|
# 89_AndroidDB
|
|
#
|
|
# Version 0.9
|
|
#
|
|
# 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;
|
|
use SetExtensions;
|
|
|
|
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} =
|
|
'connect:0,1 createReadings macros:textField-long preset presetFile '
|
|
. $readingFnAttributes;
|
|
|
|
$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 {
|
|
GP_Import(
|
|
qw(
|
|
readingsSingleUpdate
|
|
readingsBulkUpdate
|
|
readingsBulkUpdateIfChanged
|
|
readingsBeginUpdate
|
|
readingsEndUpdate
|
|
makeReadingName
|
|
setDevAttrList
|
|
CommandDefine
|
|
CommandSet
|
|
CommandAttr
|
|
Log3
|
|
AttrVal
|
|
ReadingsVal
|
|
AssignIoPort
|
|
InternalTimer
|
|
asyncOutput
|
|
gettimeofday
|
|
parseParams
|
|
defs
|
|
attr
|
|
modules
|
|
data
|
|
init_done
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
# Remote control presets
|
|
my $PRESET = {
|
|
'MagentaTVStick' => {
|
|
'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'
|
|
},
|
|
'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[:Port]}";
|
|
|
|
return $usage if ( scalar(@$a) < 3 );
|
|
|
|
# Set parameters
|
|
my ( $devName, $devPort ) = split( ':', $$a[2] );
|
|
$hash->{ADBDevice} = $devName . ':' . ( $devPort // '5555' );
|
|
|
|
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 ) = @_;
|
|
|
|
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
|
|
#
|
|
|
|
# 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 %e1;
|
|
$options .=
|
|
' remoteControl:' . join( ',', sort grep { !$e1{$_}++ } @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 %e2;
|
|
$options .= ' ' . join( ' ', sort grep { !$e2{$_}++ } @macroList )
|
|
if ( scalar(@macroList) > 0 );
|
|
|
|
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 ( $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 ( $lcopt eq 'shell' ) {
|
|
return "Usage: set $name $opt ShellCommand" if ( scalar(@$a) == 0 );
|
|
my $shellCommand = @$a[0];
|
|
my ( $rc, $result, $error ) = AndroidDBHost::Run( $hash, $opt, '.*', @$a );
|
|
return $error if ( $rc == 0 );
|
|
my $createReadings = AttrVal( $name, 'createReadings', '' );
|
|
return $result
|
|
if ( $createReadings eq '' || $shellCommand !~ /$createReadings/ );
|
|
UpdateReadings( $hash, $result );
|
|
}
|
|
elsif ( $lcopt eq 'remotecontrol' ) {
|
|
my $macroName = shift @$a // return "Usage: set $name $opt 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( ',', $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";
|
|
}
|
|
}
|
|
|
|
|
|
sub Get ($@)
|
|
{
|
|
my ($hash, $a, $h) = @_;
|
|
|
|
my $name = shift @$a;
|
|
my $opt = shift @$a // return 'No get command specified';
|
|
|
|
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);
|
|
|
|
my $attrPreset = AttrVal ($name, 'preset', '');
|
|
|
|
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:<br/><br/>";
|
|
foreach my $macro (sort keys %{$hash->{adb}{preset}{$preset}}) {
|
|
$presetDef .= "$macro = $hash->{adb}{preset}{$preset}{$macro}<br/>";
|
|
}
|
|
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:<br/><br/>";
|
|
foreach my $macro (sort keys %{$hash->{adb}{macro}{$preset}}) {
|
|
$macroDef .= "$macro = $hash->{adb}{macro}{$preset}{$macro}<br/>";
|
|
}
|
|
return $macroDef;
|
|
}
|
|
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') {
|
|
delete $hash->{adb}{preset}{_custom_} if (exists($hash->{adb}{preset}{_custom_}));
|
|
delete $hash->{adb}{macro}{_custom_} if (exists($hash->{adb}{macro}{_custom_}));
|
|
foreach my $macroDef (split /;/, $attrVal) {
|
|
my ($macroName, $macroPar) = split (':', $macroDef, 2);
|
|
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 (!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 $hash->{adb}{preset}{_custom_} if (exists($hash->{adb}{preset}{_custom_}));
|
|
delete $hash->{adb}{macro}{_custom_} if (exists($hash->{adb}{macro}{_custom_}));
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
|
|
##############################################################################
|
|
# 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 LoadPresets ($$)
|
|
{
|
|
my ($hash, $fileName) = @_;
|
|
|
|
# Read file
|
|
my @lines;
|
|
if (open (PRESETFILE, "<$fileName")) {
|
|
@lines = <PRESETFILE>;
|
|
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, $macroPar) = split (':', $l, 2);
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
# 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 UpdateReadings ($$)
|
|
{
|
|
my ($hash, $data) = @_;
|
|
|
|
readingsBeginUpdate ($hash);
|
|
|
|
foreach my $line (split /[\n\r]+/, $data) {
|
|
$line =~ s/^\s+//; # Remove leading whitespace characters
|
|
next if ($line eq ''); # Ignore empty lines
|
|
my @a = split('=', $line);
|
|
next if (scalar(@a) != 2);
|
|
my $r = makeReadingName ($a[0]);
|
|
readingsBulkUpdate ($hash, $r, $a[1]);
|
|
}
|
|
|
|
readingsEndUpdate ($hash, 1);
|
|
}
|
|
|
|
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 (Android Debug Bridge)
|
|
=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>[<Port>]</code>
|
|
The parameter <i>NameOrIP</i> is the hostname or the IP address of the Android device.
|
|
The parameter <i>Port</i> specifies the TCP port to be used for the device connection.
|
|
The default port is 5555.
|
|
</ul>
|
|
<br/>
|
|
</ul>
|
|
|
|
<a name="AndroidDBset"></a>
|
|
<b>Set</b><br/><br/>
|
|
<ul>
|
|
<li><b>set <Name> createRemote <Layout></b><br/>
|
|
Create a remote control device for the Android device. Create a notify device for
|
|
the remote control to send button events to the AndroidDB device.
|
|
</li>br/>
|
|
<li><b>set <Name> exportPresets <Filename></b><br/>
|
|
Export the currently loaded presets to file. This file can be modified and assigned
|
|
again by using attribute 'preset'.
|
|
</li><br/>
|
|
<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> rollHorz <DeltaX></b><br/>
|
|
Scroll display horizontally. Not supported on all types of Android devices.
|
|
</li><br/>
|
|
<li><b>set <name> rollVert <DeltaY></b><br/>
|
|
Scroll display vertically. Not supported on all types of Android devices.
|
|
</li><br/>
|
|
<li><b>set <name> sendKey [longpress] <KeyCode></b> [...]<br/>
|
|
Send a key code to the Android device. If option 'longpress' is specified, only one
|
|
<i>KeyCode</i> is allowed.
|
|
</li><br/>
|
|
<li><b>set <name> sendNumKeys <Number></b><br/>
|
|
Send digits of <i>Number</i> to the Android device. <i>Number</i> must be in range 0-9999.
|
|
</li><br/>
|
|
<li><b>set <name> sendText <Text></b><br/>
|
|
Send <i>Text</i> to the Android device.
|
|
</li><br/>
|
|
<li><b>set <name> shell <Command> [<Arguments>]</b><br/>
|
|
Execute shell command on Android device.
|
|
</li><br/>
|
|
<li><b>set <name> tap <X> <Y></b><br/>
|
|
Simulate a tap on a touchscreen (only available an devices having a touchscreen).
|
|
</li><br/>
|
|
</ul>
|
|
|
|
<a name="AndroidDBget"></a>
|
|
<b>Get</b><br/><br/>
|
|
<ul>
|
|
<li><b>set <Name> keyPreset <PresetName></b><br/>
|
|
List key preset definition.
|
|
</li>br/>
|
|
<li><b>set <Name> cmdPreset <PresetName></b><br/>
|
|
List command preset definition.
|
|
</li>br/>
|
|
</ul>
|
|
|
|
<a name="AndroidDBattr"></a>
|
|
<b>Attributes</b><br/><br/>
|
|
<ul>
|
|
<a name="connect"></a>
|
|
<li><b>connect 0|1</b><br/>
|
|
If set to 1, a connection to the Android device will be established during
|
|
FHEM start. Note: Set this attribute for one Android device only!
|
|
</li><br/>
|
|
<a name="createReadings"></a>
|
|
<li><b>createReadings <command-expression></b><br/>
|
|
Create readings for shell <i>command-expression</i>. Output must contain lines in format key=value.<br/>
|
|
Example: attr myDev createReadings dumpsys
|
|
</li><br/>
|
|
<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 or define shortcuts for remote commands.<br/>
|
|
A 'MacroDef' is using the following syntax:<br/>
|
|
MacroName:KeyCode[,...]<br/>
|
|
or<br/>
|
|
MacroName:Command<br/><br/>
|
|
Parameter <i>Command</i> is a adb command.<br/>
|
|
Example, define a command 'set listpackages':<br/>
|
|
<pre>attr myDev macros listpackages:shell pm list packages -f</pre><br/>
|
|
Several macro definitions can be specified by seperating them using a semicolon.
|
|
</li><br/>
|
|
<a name="preset"></a>
|
|
<li><b>preset <PresetName></b><br/>
|
|
Select a preset of keycode macros.
|
|
</li><br/>
|
|
<a name="presetFile"></a>
|
|
<li><b>presetFile <Filename></b><br/>
|
|
Load a set of macros from a preset defintion file.
|
|
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.<br/>
|
|
A preset defintion file is using the following format:<br/>
|
|
<pre>
|
|
# Comment
|
|
PresetName1
|
|
MacroDef1
|
|
MacroDef2
|
|
...
|
|
PresetName2
|
|
...
|
|
</pre>
|
|
A 'MacroDef' is using the following syntax:<br/>
|
|
MacroName:KeyCode[,...]<br/>
|
|
or<br/>
|
|
MacroName:Command:Parameters<br/><br/>
|
|
Usually <i>Command</i> is 'shell'.
|
|
</li><br/>
|
|
</ul>
|
|
|
|
=end html
|
|
=cut
|
|
|
|
|