Howto: GPS to Android
Wie ich schon hier beschrieben, bastel ich im Moment etwas herum. Zur Zeit steht Android auf dem Programm. Ich habe gerade einen GPS Receiver vom Lehrstuhl hier liegen (und suche im Moment auch nach einem geeigneten Geraet fuer private Zwecke) und da kam mir heute die Frage auf: Wie zum Teufel bekomme ich die echten Daten in den Android Emulator?
Tja, das ist gar nicht so einfach. Es gibt die Möglichkeit, über die Eclipse IDE einzelne Koordinaten an den Emulator zu senden, sodass er diese als aktuelle Position annimt. Das geht auch ueber Telnet ganz wunderbar. Wie das geht ist hier beschrieben: You Are Here: Using GPS and Google Maps in Android. Das klaert aber noch nicht ganz, wie man jetzt die Daten vom echten GPS Geraet da hineinbekommt und das auch noch in Echtzeit.
Ich bin ja der Typ, der – statt endlos lang nach einer Loesung zu suchen – einfach mal ein Programm schreibt. Da der Emulator ja die aktuelle Position via Telnet annimmt, kann man das doch glatt ausnutzen. Wie das geht? So:
Erstmal ist Java alleine ja langweilig, also verwende ich C# und .NET, um mein kleines Problem zu loesen. Ich schreibe mir also eine Klasse, die das ermoeglicht:
public class LocationPipe { private SerialPort serialPort; private int port; private Socket socket; public SerialPort SerialPort { get { return this.serialPort; } set { this.serialPort = value; } } public int Port { get { return this.port; } set { this.port = value; } } public void Start() { if (this.socket == null) { this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); this.socket.Connect("localhost", this.port); } if (this.serialPort != null && !this.serialPort.IsOpen) { this.serialPort.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived); this.serialPort.Open(); } } public void Stop() { this.serialPort.Close(); this.socket.Shutdown(SocketShutdown.Both); this.socket.Close(); this.socket = null; } private void port_DataReceived(object sender, SerialDataReceivedEventArgs e) { try { string data = this.serialPort.ReadLine(); Console.WriteLine(data); if (!data.StartsWith("$GPGGA")) { return; } data = "geo nmea " + data + "\r\n"; byte[] bytes = Encoding.ASCII.GetBytes(data); this.socket.Send(bytes); } catch (Exception) { } } }
Dabei referenziert serialPort die Verbindung zum GPS Receiver und port gibt den Port zum Emulator an. Start() startet den Spass, Stop stoppt den Spass – easy as hell. Die Methode port_DataReceived() macht dann nichts anderes als eine Zeile vom COM Port zu lesen, zu pruefen, ob der Emulator damit was anfangen kann, um sie letztendlich dem Emulator zu senden.
Und so nutzt man die Kiste:
SerialPort serialPort = new SerialPort("COM8"); serialPort.BaudRate = 38400; // Haengt vom Endgeraet ab; 38400 sollte aber passen serialPort.Parity = Parity.None; serialPort.StopBits = StopBits.One; serialPort.ReadTimeout = 10; LocationPipe pipe = new LocationPipe(); pipe.SerialPort = serialPort; pipe.5554; // Haengt vom Emulator ab; 5554 sollte es aber sein, sofern nichts massiv geaendert wurde this.pipe.Start(); // Irgendwann sollte man das ganze noch stoppen!
Nunja, jetzt behaupte ich, dass das funktioniert. Damit man mir nicht widerspricht (das mag ich naemlich gar nicht), folgt jetzt noch eine Testapplikation, die im Android-Emulator die aktuelle Position stupide als Text anzeigt.
Die Activity nenne ich mal LocationViewer, demnach sollte es eine solche Datei geben, die folgendes enthaelt:
package net.visus; import android.app.Activity; import android.content.Context; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.widget.TextView; public class LocationViewer extends Activity implements LocationListener { private LocationManager locationManager; private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.textView = (TextView) this.findViewById(R.id.textBox); this.locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); for (String provider : this.locationManager.getAllProviders()) { this.locationManager .requestLocationUpdates(provider, 1000, 0, this); } } @Override public synchronized void onLocationChanged(Location location) { if (location != null) { this.textView.setText("Latitude:\t\t" + location.getLatitude() + "\nLongitude:\t" + location.getLongitude() + "\nAltitude:\t\t" + location.getAltitude() + "\nSpeed:\t\t" + location.getSpeed() + "\nAccuracy:\t\t" + location.getAccuracy() + "\nBearing:\t\t" + location.getBearing() + "\nTime:\t\t\t" + location.getTime()); } } @Override public void onProviderDisabled(String provider) { } @Override public void onProviderEnabled(String provider) { } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } }
Damit das ganze laeuft, sollte es auch eine Textbox im Layout geben (res/layout/main.xml):
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" android:id="@+id/textBox"/> </LinearLayout>
Das Programm braucht jetzt noch die Rechte, auf den Location Service zugreifen zu duerfen (ApplicationManifest.xml):
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="net.visus" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".LocationViewer" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-sdk android:minSdkVersion="4" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> </manifest>
Und dann sieht das so aus:







