# # $Id$ # # 89_AndroidDB # # Version 0.3 # # 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 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 "Command not implemented"; } 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 ($attrName eq 'preset' && $attrVal =~ /^\@(.+)$/) { if (!LoadPreset ($hash, $1)) { Log3 $hash, 2, "Can't load preset from file $1"; } } } elsif ($cmd eq 'del') { delete $PRESET{_custom_} if (exists($PRESET{_custom_})); } return undef; } ############################################################################## # Load macro definitions from file # File format: # - Lines starting with a # are treated as comments # - 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. ############################################################################## sub LoadPreset ($$) { my ($hash, $fileName) = @_; my @lines; if (open (PRESETFILE, "<$fileName")) { @lines = ; close (PRESETFILE); } else { return 0; } chomp @lines; my $presetName = ''; foreach my $l (@lines) { next if ($l =~ /^#/); # Comments are allowed my ($macroName, $keyList) = split (':', $l); if (!defined($keyList)) { $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; } $PRESET{$presetName}{$macroName} = $keyList; } return 1; } 1; =pod =item device =item summary Allows to control an Android device via ADB =begin html

AndroidDB

Set

Attributes

=end html =cut