Android Remote Control

Begonnen von DerJens, 13 März 2015, 11:26:23

Vorheriges Thema - Nächstes Thema

DerJens

Hallo,

ich wollte euch kurz einen vielleicht interessanten Ansatz vorstellen. Es geht um die Fernsteuerung von Android-Tablets, die bei mir im Haus verteilt an der Wand hängen und eine Haussteuerung über den Floorplan möglich machen. Ich setze hier auf 7 Zoll Allwinner Tablets, die in der Regel um die €50,- kosten.

Bisherige Lösung
Bislang hatte ich eine Kombination aus Tasker, Motion Detector/IP Webcam und Secure Settings laufen, um die Tablets einzuschalten, wenn ich davor stehe. Das hat überwiegend funktioniert, allerdings nicht immer. Mal über Tage problemlos, mal hat sich nach einem Tag schon irgendetwas entschieden, nicht mehr zu funktionieren und dann blieb das Tablet aus. Zudem kommt, dass eine neuere Generation dieser Tablets sich nicht mehr via Secure Settings einschalten lassen.

Ich habe mich dann entschlossen, die Tablets nicht auszuschalten sondern nur die Displayhelligkeit zu regeln: Wenn ich davor stehe = hell, bei Inaktivität bitte abdunkeln. Ging ganz gut, allerdings leuchten die Dinger selbst bei komplett abgedunkelten Display immer noch so hell, dass man nachts keine Beleuchtung mehr braucht um ins Bad zu kommen. Kann man sich also sparen.

Neue Idee
Auf der Suche nach einer halbwegs sinnvollen und praktikablen Lösung habe ich nun einen neuen Ansatz:
Android kann man so einstellen, dass das Wlan selbst im Ruhemodus aktiviert bleibt. Ich habe mir nun ein kleines Mini-Programm gebaut, dass auf dem Tablet im Hintergrund mit root-Rechten läuft und auf eingehende Datenpakete hört (UDP Port). Ein zweites Programm kann dann Befehle an das Tablet schicken. Ich habe das von einem Mac getestet, später soll das FHEM machen. Der Clou ist, dass ich jetzt auf Befehl auf dem Tablet lustige Sachem machen kann, z.B. kann ich das Kommando input keyevent KEYCODE_POWER absetzen um den Powerknopf via Software zu drücken. Damit kann ich dann das Tablet an- und auch wieder ausmachen. Hier gibt es sicher noch viele Möglichkeiten. Der Vorteil ist, dass ich mir Tasker und die Bewegungserkennung auf dem Tablet sparen kann. Anschalten würde dann via FHEM mit einem Bewegungsmelder o.Ä. realisiert werden.

Bislang funktioniert das vielversprechend, allerdings gibt es noch einige Dinge zu tun. Z.B. muss ich noch einen Weg finden, mein Programm auf dem Tablet automatisch starten zu lassen. Mal schauen, wohin das noch führt...

Für die Technik-Interessierten:
Das Programm auf dem Tablet ist ein UDP Server, den ich in C mit dem Android NDK gebaut und via ADB ins /system/bin aufgespielt habe. Der Client ist ebenfalls in C, derzeit auf OSX gebaut. Dieser sollte sich problemlos auf dem FHEM-Raspberry-Pi übersetzen lassen und könnte dann mit einer angepassten Version von GenShellSwitch aufgerufen werden.

Ich wollte meine Idee mal vorstellen um ein wenig Feedback einzusammeln.

Liebe Grüße
DerJens

Martin Thomas Schrott

Hi! super Idee, freu mich schon auf erste Betas.
Hoffe, du kannst das ganze soweit entwickeln, dass es für normale Anwender d.h. aus dem store, und via fhem modul, verwendet werden kann!

Viel Erfolg,
Martin

schka17

Klingt interessant, ich nehme an die Tablets müssen gerootet sein?

Gruß, Karl


Sent from my iPad using Tapatalk
M: Thinclient x64 Debian | CUL FS20, HMS100WD, HMS100TF, HMS100T, HMS100CO, S300, S555TH | OWServer DS1420, DS18B20, DS2408 | RFXCOM UVN128, THWR800, THGR228N,RTGR328, PCR800 |Jeelink PCA301 EC3000|CUNO+IR|HMLAN|HMUSB|CUL433 Somfy|mySensors|espEasy
S1:Raspberry mit BPM810, Jeelink EC3000

DerJens

Ja, irgend eine Art root muss man schon haben. Via ADB bin ich sofort root auf meinen Tablets, das ist da wohl schon voreingestellt.

Ich hab mal testweise ein zweites Tablet als Versuchsobjekt dazugenommen. Da funktioniert mein Konzept nicht, da das Tablet bei JEDEM Wifi-Connect eine andere MAC Adresse bekommt. So gibt es keine feste IP und dann auch keinen erreichbaren Server. Wer lässt sich denn sowas einfallen???

ChrisK

In der Tat, sehr interessant, was Du vorhast!
Zitat von: DerJens am 13 März 2015, 12:50:27
...Da funktioniert mein Konzept nicht, da das Tablet bei JEDEM Wifi-Connect eine andere MAC Adresse bekommt. So gibt es keine feste IP und dann auch keinen erreichbaren Server. Wer lässt sich denn sowas einfallen???
Kriegt es eine andere MAC Adresse oder eine andere IP?
Eine feste IP kannst Du direkt am Tablet einstellen. Dort wo die WLAN-Verbindungen aufgelistet werden, länger auf das verbundene WLAN drücken und dann auf "Netzwerkkonfig. ändern" gehen. Da kannst Du eine feste IP einstellen.
Zumindest ist es auf meine "Handy-Android" so, das sollte aber auf dem Tablet nicht anders sein.

DerJens

Super, ein Problem weniger. Das mit der statischen IP hat funktioniert, dann eben kein DHCP.

DerJens

Hallo,

ich wollt euch kurz den aktuellen Stand mitteilen und mein Vorgehen dokumentieren. Fertig ist es aber noch nicht, aber bislang recht stabil und vielversprechend.

Android
Das Android NDK wird benötigt, um C Programme für Android ARM Geräte zu übersetzen. Ein Download gibt es bei Google unter der Adresse https://developer.android.com/tools/sdk/ndk/index.html. Auf OSX braucht man das Paket nur entpacken:
chmod a+x android-ndk-r10c-darwin-x86_64.bin
./android-ndk-r10c-darwin-x86_64.bin

Ich habe mir im Home Verzeichnis ein Unterverzeichnis Entwicklung angelegt und darin das leicht umbenannte, entpackte NDK abgelegt.
Im Terminal braucht man einmalig zum Begin der Sitzung folgende Befehle, um die Pfadangaben zu setzen:
export NDK=~/Entwicklung/android-ndk
export SYSROOT=$NDK/platforms/android-14/arch-arm/
export CC="$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-gcc --sysroot=$SYSROOT"

Damit kann man jetzt schon ARM-kompatible Programme übersetzen, die auf Android 4.0+ laufen sollten.
echo "$CC -o foo foo.c"

Die Serverkomponente sieht bei mir so aus:
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>

int main(int argc, char**argv)
{
int sockfd,n;
struct sockaddr_in servaddr,cliaddr;
socklen_t len;
char mesg[1000];

sockfd=socket(AF_INET,SOCK_DGRAM,0);

bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(32000);
bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));

   for (;;)
   {
len = sizeof(cliaddr);
n = recvfrom(sockfd,mesg,1000,0,(struct sockaddr *)&cliaddr,&len);
sendto(sockfd,mesg,n,0,(struct sockaddr *)&cliaddr,sizeof(cliaddr));
mesg[n] = 0;
system(mesg);
   }
}

Das ist ein simpler UDP-Server, der auf Port 32000 auf eingehende Pakete hört und den darin enthaltenen Befehl ausführt. Das ist eine offene root-Konsole und natürlich ein Sicherheitsproblem. Hier: Experimenteller Prototyp

Den übersetzten Server habe ich dann via ADB auf das Tablet kopiert und gestartet:
adb remount
adb push server /system/bin/
adb remount
adb shell
server &


Fernsteuerung
Auf OSX hab ich den passenden mini-Client gebaut:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char**argv)
{
   int sockfd,n;
   struct sockaddr_in servaddr,cliaddr;
   char sendline[1000];
   char recvline[1000];

   if (argc != 3)
   {
      printf("usage:  sysctlc <IP address> \"<command>\"\n");
      exit(1);
   }

   sockfd=socket(AF_INET,SOCK_DGRAM,0);

   bzero(&servaddr,sizeof(servaddr));
   servaddr.sin_family = AF_INET;
   servaddr.sin_addr.s_addr=inet_addr(argv[1]);
   servaddr.sin_port=htons(32000);
   
   sendto(sockfd,argv[2],strlen(argv[2]),0, (struct sockaddr *)&servaddr,sizeof(servaddr));

   n=recvfrom(sockfd,recvline,10000,0,NULL,NULL);
   recvline[n]=0;
   printf("Received \"%s\" from %s\n", recvline, argv[1]);
   //fputs(recvline,stdout);

   return 0;
}

gcc -o client client.c

Befehle
Läuft der Server kann man jetzt schon das Android-Gerät fernsteuern. Der Client braucht dazu die IP der Gegenstelle und den Befehl, der dann auf der Kommandozeile ausgeführt wird. Das folgende Beispiel simuliert einen Druck auf die Power-Taste und macht somit das Gerät an/aus:
./client <IP> "input keyevent KEYCODE_POWER"
...und so kann man die Bildschirmhelligkeit einstellen:
./client <IP> "settings put system screen_brightness <1-255>"
Der Server schickt dann den Befehl zurück, wenn er ihn denn empfangen hat. Kommt nichts zurück und hängt der Client, kann man erst mal versuchen, ob das Android-Gerät via ping erreichbar ist.

ToDo
- Sinnvolle Stelle finden, wie man den Server auf dem Android-Gerät beim booten mitstarten kann.
- Client anpassen, damit er FHEM-tauglich wird.
- Weitere brauchbare Befehle zum Fernsteuern finden.
- Ggf. wakelock setzen, damit das Gerät zwar den Bildschirm ausschaltet aber nicht einschläft.

Achtung, das ist hier experimenteller Code mit root-Rechten! Bitte nur ausprobieren, wer sich das auch zutraut! Ich bin nicht verantwortlich für nicht mehr bootende Androids!

DerJens

Neuer Stand:
Es funktioniert, ich kann jetzt mein Tablet via FHEM einschalten 8)

sysctls.c (Server für Android):
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

int main(int argc, char**argv)
{
int sockfd,n, retval;
struct sockaddr_in servaddr,cliaddr;
socklen_t len;
char mesg[1000];
char command[128];

sockfd=socket(AF_INET,SOCK_DGRAM,0);

bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(32000);
bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));

   for (;;)
   {
len = sizeof(cliaddr);
n = recvfrom(sockfd,mesg,1000,0,(struct sockaddr *)&cliaddr,&len);
sendto(sockfd,mesg,n,0,(struct sockaddr *)&cliaddr,sizeof(cliaddr));
mesg[n] = 0;

if (strcmp(mesg, "PowerOn") == 0)
{
// Check, if display is already on
strcpy(command, "dumpsys power|grep mScreenOn|grep -q true");
retval = system(command);
if (retval != 0) system("input keyevent KEYCODE_POWER");
}

else if (strcmp(mesg, "PowerToggle") == 0)
{
strcpy(command, "input keyevent KEYCODE_POWER");
system(command);
}

   }
}


Der Server versteht jetzt die Kommandos PowerOn und PowerToggle. Bei PowerOn wird geschaut, ob das Display bereits angeschaltet ist. Falls dies der Fall ist, passiert nichts, falls dies NICHT der Fall ist wird angeschaltet. Bei PowerToggle wechselt nur der Betriebszustand, identisch mit einem Druck auf den Einschaltknopf.

sysctlc.c (Client für FHEM)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char**argv)
{
   int sockfd,n;
   struct sockaddr_in servaddr,cliaddr;
   char sendline[1000];
   char recvline[1000];

   sockfd=socket(AF_INET,SOCK_DGRAM,0);

   bzero(&servaddr,sizeof(servaddr));
   servaddr.sin_family = AF_INET;
   servaddr.sin_addr.s_addr=inet_addr(argv[1]);
   servaddr.sin_port=htons(32000);
   
   sendto(sockfd,argv[2],strlen(argv[2]),0, (struct sockaddr *)&servaddr,sizeof(servaddr));

   return 0;
}


Den Client ruft man mit sysctlc <IP> <Kommando> auf, also z.B. sysctlc 192.168.1.2 PowerOn. Habe ich mir auf dem FHEM-Raspberry-Pi übersetzt und ins /usr/bin/ gelegt.

In FHEM benutze ich GenShellSwitch, zwar noch nicht schön aber funktioniert:
define Tablet1 GenShellSwitch sysctlc 192.168.1.2 PowerOn 0 0

Die beiden Nullen am Ende sind für GenShellSwitch notwendig, werden aber von sysctlc nicht ausgewertet.

Mit set Tabelt1 on geht dann das Tablet an. Das kann man jetzt z.B. mit einem Bewegungsmelder verbinden. Wenn Bewegung erkannt wird, dann schalte das Tablet ein. Das Tablet bleibt so lange an, wie das Display Timeout eingestellt ist. So lange man es bedient, geht es also auch nicht aus. Kommt in dieser Zeit eine erneute Bewegungsmeldung, würde das Tablet bei PowerToggle wieder ausgehen, bei PowerOn nicht.

Ich werde das mal testen...

chipmunk

LHi
Leicht OT: Wenn du DHCP nutzen willst, statt die IP am Tablett einzutragen, kannst du normalerweise dem DHCP-Server eine fixe Zuordnung von MAC-Adresse zu IP-Adresse vorgeben. Auf diese Weise haben bei mir alle bekannten Geräte immer die selbe IPl
Chipmunk
RasPi3, HM, HUE, div 433MHz Baumarktdosen über Sende- und Empfangsmodule von C*, Ediplug

DerJens

Update
Mein Flur-Tablet läuft jetzt wie erhofft seit ein paar Tagen stabil mit meiner weiter oben vorgestellten Lösung. Wenn einer den Flur betritt, ist durch den Bewegungsmelder nicht nur das Licht an sondern auch das Tablet. Bei Nichtbenutzung geht es dann auch wieder nach einiger Zeit aus. Ich musste allerdings etwas anpassen, da es folgende Situation gab:
Man kommt in den Flur, das Tablet geht an, man benutzt es nicht, es geht eigenständig nach 2 Minuten wieder aus weil es niemand bedient, es ist allerdings weiterhin Bewegung im Flur. Wenn man jetzt vor das Tablet geht, ist es aus und geht auch nicht an. Erst wenn der Bewegungsmelder zunächst keine Bewegung mehr meldet (dauert bei mir 1 Minute) und im Anschluss dann wieder was los ist, wird auch das Tablet wieder aktiviert.

Ich habe nun einen weiteren Befehl KeepAlive eingebaut, mit dem FHEM das Tablet aktiv halten kann. Dazu simuliere ich eine Berührung des Displays an einer Stelle, wo bei mir im Floorplan keine Schaltflächen sind. Quellcode und FHEM Beispiel hänge ich an.

sysctls.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

int main(int argc, char**argv)
{
int sockfd,n, retval;
struct sockaddr_in servaddr,cliaddr;
socklen_t len;
char mesg[1000];
char command[128];

sockfd=socket(AF_INET,SOCK_DGRAM,0);

bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(32000);
bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));

   for (;;)
   {
len = sizeof(cliaddr);
n = recvfrom(sockfd,mesg,1000,0,(struct sockaddr *)&cliaddr,&len);
sendto(sockfd,mesg,n,0,(struct sockaddr *)&cliaddr,sizeof(cliaddr));
mesg[n] = 0;

if (strcmp(mesg, "PowerOn") == 0)
{
// If the display is off then press the power button
strcpy(command, "dumpsys power|grep mScreenOn|grep -q true");
retval = system(command);
if (retval != 0) system("input keyevent KEYCODE_POWER");
}

else if (strcmp(mesg, "PowerToggle") == 0)
{
// Press the power button
strcpy(command, "input keyevent KEYCODE_POWER");
system(command);
}

else if (strcmp(mesg, "KeepAlive") == 0)
{
// If the display is on, simulate a tap to postpone the timeout
strcpy(command, "dumpsys power|grep mScreenOn|grep -q true");
retval = system(command);
if (retval == 0)
{
strcpy(command, "input tap 5 5");
system(command);
}
else {system("input keyevent KEYCODE_POWER");}
}
   }
}


Der Client muss nicht angepasst werden.

FHEM GenShellSwitch:
define EgEziTabletKeepAlive GenShellSwitch sysctlc 192.168.1.62 KeepAlive 0 0

FHEM Watchdog
define EgEziWatchdog4TabletKeepAlive watchdog EnO_sensor_0089847F:motion.*on 00:00:15 EnO_sensor_0089847F:motion.*off trigger EgEziWatchdog4TabletKeepAlive .;; trigger EgEziWatchdog4TabletKeepAlive .;; set EgEziTabletKeepAlive on
Der Watchdog wird bei mir durch den Bewegungsmelder aktiviert und wartet 15 Sekunden, das keine Bewegung mehr registriert wird. Das passiert allerdings frühestens in einer Minute, daher schlägt der Watchdog an und sendet ein KeepAlive an das Tablet. Es muss noch 2x der Watchdog getriggert werden, damit die 15 Sekunden wieder laufen OHNE das eine erneute Bewegungsmeldung nötig ist.

bern

Hallo,
nach meinen enttäuschenden Versuchen mein Tablet mittels eines Bewegungssensors einzuschalten wollte ich mein Vorhaben schon aufgeben bis ich hier auf deinen sehr interessanten Lösungsvorschlag gestoßen bin.
Mein bisheriger Aufbau ist wie folgt:

Wenn der Bewegungsmelder, der für das  Einschaltung des Tablets gedacht ist, auslöst ->
empfängt das Signal ein Arduino Nano mit 433Mhz Funkempfänger, ->
der schickt das Signal per serieller Schnittstelle an Eventghost ->
Eventghost schickt das Signal mittels dem Plug-In ,,Autoremote" an Tasker->
Tasker schaltet das Display mittels dem Befehl ,,Display Awake" und dem Plug-In ,,Security Settings" ein

Um alles effizienter zu machen wollte ich später noch den Arduino mit einem Bewegungssensor ausstatten und ihn über den eigenen Webserver direkt mit Tasker kommunizieren lassen.

Leider funktioniert alles nur in der Theorie so gut. Ein Problem war dass nach einer gewissen Zeit das Display nicht mehr aus ging und dass die Zeit vom Auslösen des Bewegungssensors von einer bis fast sechs Sekunden schwankt, was den ganzen Aufbau unbrauchbar macht. Problem ist hier nicht der lange Signalweg sondern Tasker bzw. Tasker und mein Tablet Typ.
Ich bin in Fhem absoluter Neuling und bin ehrlich gesagt ein bisschen überfordert mit deiner Beschreibung. Ich habe mir jetzt das Android NDK für Win64 runtergeladen und versuche deinen Code zu portieren. Ist es auch evtl. möglich dass du den fertigen Server hier zum Download bereitstellst ? Du hast geschrieben dass es sich um einen UDP Server handelt, könnte ich damit auch mit Eventghost ( UDP Plugin) darauf zugreifen? Und eine letzte Frage, könnte man so einen Server auch statt über UDP mit http  (get, post) kommunizieren lassen. Es wäre wirklich fantastisch wenn man durch einen Arduino mittels eines solchen Servers, Android direkt steuern könnte.
Auf jeden Fall vielen Dank dass du deinen Idee inkl. Code mir allen teilst.

Viele Grüße