diff --git a/.gitignore b/.gitignore
index dc058313d..c9478a9c1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -64,13 +64,16 @@ ground/uavobjgenerator/uavobjgenerator.pro.user
ground/uavobjects/uavobjects.pro.user
ground/ground.pro.user
-# Ignore GNU global tags files
GPATH
GRTAGS
GSYMS
GTAGS
-/.cproject
-/.project
plane
quad
+
+# Ignore auto generated java files
+androidgcs/bin/
+androidgcs/gen/
+/.cproject
+/.project
diff --git a/androidgcs/.classpath b/androidgcs/.classpath
new file mode 100644
index 000000000..c88f96260
--- /dev/null
+++ b/androidgcs/.classpath
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/androidgcs/.metadata/.lock b/androidgcs/.metadata/.lock
new file mode 100644
index 000000000..e69de29bb
diff --git a/androidgcs/.metadata/.log b/androidgcs/.metadata/.log
new file mode 100644
index 000000000..8955f7bbc
--- /dev/null
+++ b/androidgcs/.metadata/.log
@@ -0,0 +1,83 @@
+!SESSION 2012-02-04 14:21:36.086 -----------------------------------------------
+eclipse.buildId=unknown
+java.version=1.6.0_29
+java.vendor=Apple Inc.
+BootLoader constants: OS=macosx, ARCH=x86_64, WS=cocoa, NL=en_US
+Framework arguments: -product org.eclipse.epp.package.cpp.product -product org.eclipse.epp.package.cpp.product -product org.eclipse.epp.package.cpp.product -keyring /Users/jcotton81/.eclipse_keyring -showlocation
+Command-line arguments: -os macosx -ws cocoa -arch x86_64 -product org.eclipse.epp.package.cpp.product -product org.eclipse.epp.package.cpp.product -data /Users/jcotton81/Documents/Programming/OpenPilot/androidgcs -product org.eclipse.epp.package.cpp.product -keyring /Users/jcotton81/.eclipse_keyring -showlocation
+
+!ENTRY org.eclipse.core.net 1 0 2012-02-04 14:21:41.864
+!MESSAGE System property http.nonProxyHosts has been set to local|*.local|169.254/16|*.169.254/16 by an external source. This value will be overwritten using the values from the preferences
+
+!ENTRY org.eclipse.ui.intro.universal 4 0 2012-02-04 14:21:44.892
+!MESSAGE /Users/jcotton81/Documents/Programming/eclipse/Eclipse.app/Contents/MacOS/introData.xml (No such file or directory)
+!STACK 0
+java.io.FileNotFoundException: /Users/jcotton81/Documents/Programming/eclipse/Eclipse.app/Contents/MacOS/introData.xml (No such file or directory)
+ at java.io.FileInputStream.open(Native Method)
+ at java.io.FileInputStream.(FileInputStream.java:120)
+ at java.io.FileInputStream.(FileInputStream.java:79)
+ at sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:70)
+ at sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:161)
+ at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:653)
+ at com.sun.org.apache.xerces.internal.impl.XMLVersionDetector.determineDocVersion(XMLVersionDetector.java:186)
+ at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:772)
+ at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737)
+ at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:119)
+ at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:235)
+ at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:284)
+ at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:180)
+ at org.eclipse.ui.internal.intro.universal.IntroData.parse(IntroData.java:159)
+ at org.eclipse.ui.internal.intro.universal.IntroData.initialize(IntroData.java:63)
+ at org.eclipse.ui.internal.intro.universal.IntroData.(IntroData.java:47)
+ at org.eclipse.ui.internal.intro.universal.CustomizationContentsArea.loadData(CustomizationContentsArea.java:624)
+ at org.eclipse.ui.internal.intro.universal.CustomizationContentsArea.addPages(CustomizationContentsArea.java:489)
+ at org.eclipse.ui.internal.intro.universal.CustomizationContentsArea.createContents(CustomizationContentsArea.java:453)
+ at org.eclipse.ui.internal.intro.universal.CustomizationDialog.createDialogArea(CustomizationDialog.java:44)
+ at org.eclipse.jface.dialogs.Dialog.createContents(Dialog.java:760)
+ at org.eclipse.jface.window.Window.create(Window.java:431)
+ at org.eclipse.jface.dialogs.Dialog.create(Dialog.java:1089)
+ at org.eclipse.jface.window.Window.open(Window.java:790)
+ at org.eclipse.ui.internal.intro.universal.CustomizeAction.run(CustomizeAction.java:35)
+ at org.eclipse.ui.internal.intro.universal.CustomizeAction.run(CustomizeAction.java:29)
+ at org.eclipse.jface.action.Action.runWithEvent(Action.java:498)
+ at org.eclipse.jface.action.ActionContributionItem.handleWidgetSelection(ActionContributionItem.java:584)
+ at org.eclipse.jface.action.ActionContributionItem.access$2(ActionContributionItem.java:501)
+ at org.eclipse.jface.action.ActionContributionItem$6.handleEvent(ActionContributionItem.java:452)
+ at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:84)
+ at org.eclipse.swt.widgets.Display.sendEvent(Display.java:3543)
+ at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1250)
+ at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1273)
+ at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1258)
+ at org.eclipse.swt.widgets.Widget.notifyListeners(Widget.java:1079)
+ at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:3441)
+ at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3100)
+ at org.eclipse.ui.internal.Workbench.runEventLoop(Workbench.java:2405)
+ at org.eclipse.ui.internal.Workbench.runUI(Workbench.java:2369)
+ at org.eclipse.ui.internal.Workbench.access$4(Workbench.java:2221)
+ at org.eclipse.ui.internal.Workbench$5.run(Workbench.java:500)
+ at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:332)
+ at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:493)
+ at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:149)
+ at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:113)
+ at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:194)
+ at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:110)
+ at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:79)
+ at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:368)
+ at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:179)
+ at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+ at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+ at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+ at java.lang.reflect.Method.invoke(Method.java:597)
+ at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:559)
+ at org.eclipse.equinox.launcher.Main.basicRun(Main.java:514)
+ at org.eclipse.equinox.launcher.Main.run(Main.java:1311)
+!SESSION 2012-02-04 15:23:30.048 -----------------------------------------------
+eclipse.buildId=unknown
+java.version=1.6.0_29
+java.vendor=Apple Inc.
+BootLoader constants: OS=macosx, ARCH=x86_64, WS=cocoa, NL=en_US
+Framework arguments: -product org.eclipse.epp.package.cpp.product -product org.eclipse.epp.package.cpp.product -product org.eclipse.epp.package.cpp.product -product org.eclipse.epp.package.cpp.product -product org.eclipse.epp.package.cpp.product -product org.eclipse.epp.package.cpp.product -keyring /Users/jcotton81/.eclipse_keyring -showlocation
+Command-line arguments: -os macosx -ws cocoa -arch x86_64 -product org.eclipse.epp.package.cpp.product -product org.eclipse.epp.package.cpp.product -product org.eclipse.epp.package.cpp.product -product org.eclipse.epp.package.cpp.product -product org.eclipse.epp.package.cpp.product -data /Users/jcotton81/Documents/Programming/OpenPilot/androidgcs -product org.eclipse.epp.package.cpp.product -keyring /Users/jcotton81/.eclipse_keyring -showlocation
+
+!ENTRY org.eclipse.core.net 1 0 2012-02-04 15:23:34.575
+!MESSAGE System property http.nonProxyHosts has been set to local|*.local|169.254/16|*.169.254/16 by an external source. This value will be overwritten using the values from the preferences
diff --git a/androidgcs/.metadata/.mylyn/repositories.xml.zip b/androidgcs/.metadata/.mylyn/repositories.xml.zip
new file mode 100644
index 000000000..e4df3bd35
Binary files /dev/null and b/androidgcs/.metadata/.mylyn/repositories.xml.zip differ
diff --git a/androidgcs/.metadata/.mylyn/tasks.xml.zip b/androidgcs/.metadata/.mylyn/tasks.xml.zip
new file mode 100644
index 000000000..2c8620af3
Binary files /dev/null and b/androidgcs/.metadata/.mylyn/tasks.xml.zip differ
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.cdt.core/.log b/androidgcs/.metadata/.plugins/org.eclipse.cdt.core/.log
new file mode 100644
index 000000000..bce672614
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.cdt.core/.log
@@ -0,0 +1 @@
+*** SESSION Feb 04, 2012 14:21:39.72 -------------------------------------------
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/history.version b/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/history.version
new file mode 100644
index 000000000..25cb955ba
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/history.version
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index b/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index
new file mode 100644
index 000000000..9c245ea2c
Binary files /dev/null and b/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index differ
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.version b/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.version
new file mode 100644
index 000000000..6b2aaa764
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.version
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.root/2.tree b/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.root/2.tree
new file mode 100644
index 000000000..a1928e3fb
Binary files /dev/null and b/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.root/2.tree differ
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources b/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources
new file mode 100644
index 000000000..f783b188f
Binary files /dev/null and b/androidgcs/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources differ
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.cdt.ui.prefs b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.cdt.ui.prefs
new file mode 100644
index 000000000..3f144b036
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.cdt.ui.prefs
@@ -0,0 +1,5 @@
+#Sat Feb 04 15:21:25 CST 2012
+spelling_locale_initialized=true
+useAnnotationsPrefPage=true
+eclipse.preferences.version=1
+useQuickDiffPrefPage=true
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.core.resources.prefs b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 000000000..360fc96b9
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Sat Feb 04 15:21:25 CST 2012
+version=1
+eclipse.preferences.version=1
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.epp.usagedata.recording.prefs b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.epp.usagedata.recording.prefs
new file mode 100644
index 000000000..87706c344
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.epp.usagedata.recording.prefs
@@ -0,0 +1,3 @@
+#Sat Feb 04 14:21:44 CST 2012
+org.eclipse.epp.usagedata.recording.last-upload=1328386904231
+eclipse.preferences.version=1
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.jdt.ui.prefs b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 000000000..bdcfb11b5
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,15 @@
+#Sat Feb 04 15:21:25 CST 2012
+useQuickDiffPrefPage=true
+proposalOrderMigrated=true
+tabWidthPropagated=true
+content_assist_proposals_background=255,255,255
+org.eclipse.jdt.ui.javadoclocations.migrated=true
+useAnnotationsPrefPage=true
+org.eclipse.jface.textfont=1|Monaco|11.0|0|COCOA|1|;
+org.eclipse.jdt.internal.ui.navigator.layout=2
+org.eclipse.jdt.ui.editor.tab.width=
+org.eclipse.jdt.ui.formatterprofiles.version=11
+spelling_locale_initialized=true
+eclipse.preferences.version=1
+content_assist_proposals_foreground=0,0,0
+fontPropagated=true
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.mylyn.context.core.prefs b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.mylyn.context.core.prefs
new file mode 100644
index 000000000..7ea301b2d
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.mylyn.context.core.prefs
@@ -0,0 +1,3 @@
+#Sat Feb 04 14:21:42 CST 2012
+eclipse.preferences.version=1
+mylyn.attention.migrated=true
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.team.cvs.ui.prefs b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.team.cvs.ui.prefs
new file mode 100644
index 000000000..62dee9f90
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.team.cvs.ui.prefs
@@ -0,0 +1,3 @@
+#Sat Feb 04 15:21:25 CST 2012
+pref_first_startup=false
+eclipse.preferences.version=1
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.team.ui.prefs b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.team.ui.prefs
new file mode 100644
index 000000000..06b301613
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.team.ui.prefs
@@ -0,0 +1,3 @@
+#Sat Feb 04 15:21:25 CST 2012
+eclipse.preferences.version=1
+org.eclipse.team.ui.first_time=false
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.editors.prefs b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.editors.prefs
new file mode 100644
index 000000000..90e1e2da9
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.editors.prefs
@@ -0,0 +1,3 @@
+#Sat Feb 04 15:21:25 CST 2012
+eclipse.preferences.version=1
+overviewRuler_migration=migrated_3.1
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.ide.prefs b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.ide.prefs
new file mode 100644
index 000000000..b945f2889
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.ide.prefs
@@ -0,0 +1,5 @@
+#Sat Feb 04 15:21:25 CST 2012
+eclipse.preferences.version=1
+tipsAndTricks=true
+platformState=1328386704250
+PROBLEMS_FILTERS_MIGRATE=true
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.prefs b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.prefs
new file mode 100644
index 000000000..a97c1c1ab
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.prefs
@@ -0,0 +1,3 @@
+#Sat Feb 04 15:21:25 CST 2012
+eclipse.preferences.version=1
+showIntro=false
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.workbench.prefs b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.workbench.prefs
new file mode 100644
index 000000000..005c5e9bc
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.workbench.prefs
@@ -0,0 +1,3 @@
+#Sat Feb 04 14:21:42 CST 2012
+eclipse.preferences.version=1
+ENABLED_DECORATORS=com.android.ide.eclipse.adt.project.FolderDecorator\:true,org.eclipse.cdt.ui.indexedFiles\:false,org.eclipse.jdt.ui.override.decorator\:true,org.eclipse.jdt.ui.interface.decorator\:false,org.eclipse.jdt.ui.buildpath.decorator\:true,org.eclipse.mylyn.context.ui.decorator.interest\:true,org.eclipse.mylyn.tasks.ui.decorators.task\:true,org.eclipse.mylyn.team.ui.changeset.decorator\:true,org.eclipse.team.cvs.ui.decorator\:true,org.eclipse.ui.LinkedResourceDecorator\:true,org.eclipse.ui.ContentTypeDecorator\:true,
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.epp.usagedata.recording/upload0.csv b/androidgcs/.metadata/.plugins/org.eclipse.epp.usagedata.recording/upload0.csv
new file mode 100644
index 000000000..bc6540615
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.epp.usagedata.recording/upload0.csv
@@ -0,0 +1,251 @@
+what,kind,bundleId,bundleVersion,description,time
+activated,perspective,org.eclipse.cdt.ui,,"org.eclipse.cdt.ui.CPerspective",1328386903139
+started,bundle,org.eclipse.osgi,3.5.2.R35x_v20100126,"org.eclipse.osgi",1328386903141
+started,bundle,org.eclipse.equinox.simpleconfigurator,1.0.101.R35x_v20090807-1100,"org.eclipse.equinox.simpleconfigurator",1328386903142
+started,bundle,com.ibm.icu,4.0.1.v20090822,"com.ibm.icu",1328386903143
+started,bundle,org.eclipse.cdt.core,5.1.2.201002161416,"org.eclipse.cdt.core",1328386903143
+started,bundle,org.eclipse.cdt.make.ui,6.0.1.201002161416,"org.eclipse.cdt.make.ui",1328386903144
+started,bundle,org.eclipse.cdt.ui,5.1.2.201002161416,"org.eclipse.cdt.ui",1328386903147
+started,bundle,org.eclipse.core.contenttype,3.4.1.R35x_v20090826-0451,"org.eclipse.core.contenttype",1328386903148
+started,bundle,org.eclipse.core.databinding.observable,1.2.0.M20090902-0800,"org.eclipse.core.databinding.observable",1328386903148
+started,bundle,org.eclipse.core.expressions,3.4.101.R35x_v20100209,"org.eclipse.core.expressions",1328386903149
+started,bundle,org.eclipse.core.filebuffers,3.5.0.v20090526-2000,"org.eclipse.core.filebuffers",1328386903149
+started,bundle,org.eclipse.core.filesystem,1.2.1.R35x_v20091203-1235,"org.eclipse.core.filesystem",1328386903150
+started,bundle,org.eclipse.core.jobs,3.4.100.v20090429-1800,"org.eclipse.core.jobs",1328386903151
+started,bundle,org.eclipse.core.net,1.2.1.r35x_20090812-1200,"org.eclipse.core.net",1328386903152
+started,bundle,org.eclipse.core.resources,3.5.2.R35x_v20091203-1235,"org.eclipse.core.resources",1328386903153
+started,bundle,org.eclipse.core.runtime,3.5.0.v20090525,"org.eclipse.core.runtime",1328386903153
+started,bundle,org.eclipse.core.runtime.compatibility,3.2.0.v20090413,"org.eclipse.core.runtime.compatibility",1328386903154
+started,bundle,org.eclipse.core.runtime.compatibility.auth,3.2.100.v20090413,"org.eclipse.core.runtime.compatibility.auth",1328386903154
+started,bundle,org.eclipse.ecf,3.0.0.v20090831-1906,"org.eclipse.ecf",1328386903155
+started,bundle,org.eclipse.ecf.filetransfer,3.0.0.v20090831-1906,"org.eclipse.ecf.filetransfer",1328386903155
+started,bundle,org.eclipse.ecf.identity,3.0.0.v20090831-1906,"org.eclipse.ecf.identity",1328386903156
+started,bundle,org.eclipse.ecf.provider.filetransfer,3.0.1.v20090831-1906,"org.eclipse.ecf.provider.filetransfer",1328386903157
+started,bundle,org.eclipse.ecf.provider.filetransfer.httpclient,3.0.1.v20090831-1906,"org.eclipse.ecf.provider.filetransfer.httpclient",1328386903158
+started,bundle,org.eclipse.epp.usagedata.gathering,1.1.1.R201001291118,"org.eclipse.epp.usagedata.gathering",1328386903159
+started,bundle,org.eclipse.epp.usagedata.recording,1.1.1.R201001291118,"org.eclipse.epp.usagedata.recording",1328386903161
+started,bundle,org.eclipse.equinox.app,1.2.1.R35x_v20091203,"org.eclipse.equinox.app",1328386903164
+started,bundle,org.eclipse.equinox.common,3.5.1.R35x_v20090807-1100,"org.eclipse.equinox.common",1328386903165
+started,bundle,org.eclipse.equinox.ds,1.1.1.R35x_v20090806,"org.eclipse.equinox.ds",1328386903166
+started,bundle,org.eclipse.equinox.frameworkadmin,1.0.100.v20090520-1905,"org.eclipse.equinox.frameworkadmin",1328386903166
+started,bundle,org.eclipse.equinox.frameworkadmin.equinox,1.0.101.R35x_v20091214,"org.eclipse.equinox.frameworkadmin.equinox",1328386903166
+started,bundle,org.eclipse.equinox.p2.core,1.0.101.R35x_v20090819,"org.eclipse.equinox.p2.core",1328386903167
+started,bundle,org.eclipse.equinox.p2.director,1.0.101.R35x_v20100112,"org.eclipse.equinox.p2.director",1328386903168
+started,bundle,org.eclipse.equinox.p2.directorywatcher,1.0.100.v20090525,"org.eclipse.equinox.p2.directorywatcher",1328386903168
+started,bundle,org.eclipse.equinox.p2.engine,1.0.102.R35x_v20091117,"org.eclipse.equinox.p2.engine",1328386903169
+started,bundle,org.eclipse.equinox.p2.exemplarysetup,1.0.100.v20090520-1905,"org.eclipse.equinox.p2.exemplarysetup",1328386903169
+started,bundle,org.eclipse.equinox.p2.garbagecollector,1.0.100.v20090520-1905,"org.eclipse.equinox.p2.garbagecollector",1328386903170
+started,bundle,org.eclipse.equinox.p2.metadata,1.0.101.R35x_v20100112,"org.eclipse.equinox.p2.metadata",1328386903170
+started,bundle,org.eclipse.equinox.p2.metadata.repository,1.0.101.R35x_v20090812,"org.eclipse.equinox.p2.metadata.repository",1328386903171
+started,bundle,org.eclipse.equinox.p2.reconciler.dropins,1.0.100.v20090520-1905,"org.eclipse.equinox.p2.reconciler.dropins",1328386903171
+started,bundle,org.eclipse.equinox.p2.repository,1.0.1.R35x_v20100105,"org.eclipse.equinox.p2.repository",1328386903172
+started,bundle,org.eclipse.equinox.p2.ui.sdk.scheduler,1.0.0.v20090520-1905,"org.eclipse.equinox.p2.ui.sdk.scheduler",1328386903172
+started,bundle,org.eclipse.equinox.p2.updatechecker,1.1.0.v20090520-1905,"org.eclipse.equinox.p2.updatechecker",1328386903172
+started,bundle,org.eclipse.equinox.preferences,3.2.301.R35x_v20091117,"org.eclipse.equinox.preferences",1328386903175
+started,bundle,org.eclipse.equinox.registry,3.4.100.v20090520-1800,"org.eclipse.equinox.registry",1328386903176
+started,bundle,org.eclipse.equinox.security,1.0.100.v20090520-1800,"org.eclipse.equinox.security",1328386903177
+started,bundle,org.eclipse.equinox.simpleconfigurator.manipulator,1.0.101.R35x_v20100209,"org.eclipse.equinox.simpleconfigurator.manipulator",1328386903177
+started,bundle,org.eclipse.equinox.util,1.0.100.v20090520-1800,"org.eclipse.equinox.util",1328386903178
+started,bundle,org.eclipse.help,3.4.1.v20090805_35x,"org.eclipse.help",1328386903179
+started,bundle,org.eclipse.jface,3.5.2.M20100120-0800,"org.eclipse.jface",1328386903179
+started,bundle,org.eclipse.jsch.core,1.1.100.I20090430-0408,"org.eclipse.jsch.core",1328386903180
+started,bundle,org.eclipse.ltk.core.refactoring,3.5.0.v20090513-2000,"org.eclipse.ltk.core.refactoring",1328386903180
+started,bundle,org.eclipse.mylyn.bugzilla.core,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.bugzilla.core",1328386903181
+started,bundle,org.eclipse.mylyn.bugzilla.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.bugzilla.ui",1328386903181
+started,bundle,org.eclipse.mylyn.commons.net,3.2.0.v20090617-0100-e3x,"org.eclipse.mylyn.commons.net",1328386903182
+started,bundle,org.eclipse.mylyn.commons.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.commons.ui",1328386903183
+started,bundle,org.eclipse.mylyn.context.core,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.context.core",1328386903183
+started,bundle,org.eclipse.mylyn.context.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.context.ui",1328386903184
+started,bundle,org.eclipse.mylyn.monitor.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.monitor.ui",1328386903185
+started,bundle,org.eclipse.mylyn.tasks.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.tasks.ui",1328386903186
+started,bundle,org.eclipse.mylyn.team.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.team.ui",1328386903186
+started,bundle,org.eclipse.team.core,3.5.1.r35x_20100113-0800,"org.eclipse.team.core",1328386903187
+started,bundle,org.eclipse.team.cvs.core,3.3.200.I20090430-0408,"org.eclipse.team.cvs.core",1328386903188
+started,bundle,org.eclipse.team.cvs.ui,3.3.202.r35x_20090930-0800,"org.eclipse.team.cvs.ui",1328386903189
+started,bundle,org.eclipse.team.ui,3.5.0.I20090430-0408,"org.eclipse.team.ui",1328386903189
+started,bundle,org.eclipse.ui,3.5.2.M20100120-0800,"org.eclipse.ui",1328386903191
+started,bundle,org.eclipse.ui.console,3.4.0.v20090513,"org.eclipse.ui.console",1328386903191
+started,bundle,org.eclipse.ui.editors,3.5.0.v20090527-2000,"org.eclipse.ui.editors",1328386903195
+started,bundle,org.eclipse.ui.forms,3.4.1.v20090714_35x,"org.eclipse.ui.forms",1328386903196
+started,bundle,org.eclipse.ui.ide,3.5.2.M20100113-0800,"org.eclipse.ui.ide",1328386903198
+started,bundle,org.eclipse.ui.intro,3.3.2.v20100111_35x,"org.eclipse.ui.intro",1328386903198
+started,bundle,org.eclipse.ui.intro.universal,3.2.300.v20090526,"org.eclipse.ui.intro.universal",1328386903198
+started,bundle,org.eclipse.ui.navigator,3.4.2.M20100120-0800,"org.eclipse.ui.navigator",1328386903199
+started,bundle,org.eclipse.ui.navigator.resources,3.4.1.M20090826-0800,"org.eclipse.ui.navigator.resources",1328386903200
+started,bundle,org.eclipse.ui.net,1.2.1.r35x_20090812-1200,"org.eclipse.ui.net",1328386903200
+started,bundle,org.eclipse.ui.views,3.4.1.M20090826-0800,"org.eclipse.ui.views",1328386903201
+started,bundle,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"org.eclipse.ui.workbench",1328386903202
+started,bundle,org.eclipse.ui.workbench.texteditor,3.5.1.r352_v20100105,"org.eclipse.ui.workbench.texteditor",1328386903203
+started,bundle,org.eclipse.update.configurator,3.3.0.v20090312,"org.eclipse.update.configurator",1328386903207
+started,bundle,org.eclipse.update.core,3.2.300.v20090525,"org.eclipse.update.core",1328386903209
+started,bundle,org.eclipse.update.scheduler,3.2.200.v20081127,"org.eclipse.update.scheduler",1328386903210
+started,bundle,org.eclipse.jdt.core,3.5.2.v_981_R35x,"org.eclipse.jdt.core",1328386903211
+started,bundle,org.eclipse.jdt.core.manipulation,1.3.0.v20090603,"org.eclipse.jdt.core.manipulation",1328386903212
+started,bundle,org.eclipse.jdt.ui,3.5.2.r352_v20100106-0800,"org.eclipse.jdt.ui",1328386903218
+os,sysinfo,,,"macosx",1328386903226
+arch,sysinfo,,,"x86_64",1328386903226
+ws,sysinfo,,,"cocoa",1328386903226
+locale,sysinfo,,,"en_US",1328386903226
+processors,sysinfo,,,"4",1328386903226
+java.runtime.name,sysinfo,,,"Java(TM) SE Runtime Environment",1328386903226
+java.runtime.version,sysinfo,,,"1.6.0_29-b11-402-11M3527",1328386903227
+java.specification.name,sysinfo,,,"Java Platform API Specification",1328386903227
+java.specification.vendor,sysinfo,,,"Sun Microsystems Inc.",1328386903227
+java.specification.version,sysinfo,,,"1.6",1328386903227
+java.vendor,sysinfo,,,"Apple Inc.",1328386903227
+java.version,sysinfo,,,"1.6.0_29",1328386903227
+java.vm.info,sysinfo,,,"mixed mode",1328386903227
+java.vm.name,sysinfo,,,"Java HotSpot(TM) 64-Bit Server VM",1328386903227
+java.vm.specification.name,sysinfo,,,"Java Virtual Machine Specification",1328386903227
+java.vm.specification.vendor,sysinfo,,,"Sun Microsystems Inc.",1328386903227
+java.vm.specification.version,sysinfo,,,"1.0",1328386903227
+java.vm.vendor,sysinfo,,,"Apple Inc.",1328386903227
+java.vm.version,sysinfo,,,"20.4-b02-402",1328386903227
+started,bundle,org.eclipse.equinox.p2.extensionlocation,1.0.100.v20090520-1905,"org.eclipse.equinox.p2.extensionlocation",1328386903389
+started,bundle,org.eclipse.equinox.p2.artifact.repository,1.0.101.R35x_v20090721,"org.eclipse.equinox.p2.artifact.repository",1328386903414
+started,bundle,org.eclipse.equinox.p2.publisher,1.0.1.R35x_20100105,"org.eclipse.equinox.p2.publisher",1328386903450
+started,bundle,org.eclipse.equinox.p2.touchpoint.eclipse,1.0.101.R35x_20090820-1821,"org.eclipse.equinox.p2.touchpoint.eclipse",1328386903464
+activated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328386903480
+activated,view,org.eclipse.ui,3.5.2.M20100120-0800,"org.eclipse.ui.internal.introview",1328386903487
+error,log,,,"/Users/jcotton81/Documents/Programming/eclipse/Eclipse.app/Contents/MacOS/introData.xml (No such file or directory)",1328386904897
+deactivated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328386905209
+activated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328386906797
+started,bundle,org.eclipse.equinox.p2.updatesite,1.0.101.R35x_20100105,"org.eclipse.equinox.p2.updatesite",1328386912464
+activated,view,org.eclipse.ui.navigator.resources,3.4.1.M20090826-0800,"org.eclipse.ui.navigator.ProjectExplorer",1328386912583
+activated,view,org.eclipse.ui.navigator.resources,3.4.1.M20090826-0800,"org.eclipse.ui.navigator.ProjectExplorer",1328386912596
+closed,view,org.eclipse.ui,3.5.2.M20100120-0800,"org.eclipse.ui.internal.introview",1328386912607
+started,bundle,org.eclipse.equinox.p2.ui,1.0.101.R35x_v20090819,"org.eclipse.equinox.p2.ui",1328386912856
+started,bundle,org.eclipse.equinox.p2.ui.sdk,1.0.100.v20090520-1905,"org.eclipse.equinox.p2.ui.sdk",1328386912895
+deactivated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328386920116
+activated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328387108087
+deactivated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328387114332
+activated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328387670491
+deactivated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328387680147
+activated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328389984496
+deactivated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328389985341
+activated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328390196855
+deactivated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328390210754
+started,bundle,com.android.ide.eclipse.ddms,10.0.1.v201103111512-110841,"com.android.ide.eclipse.ddms",1328390214192
+started,bundle,org.eclipse.wst.sse.core,1.1.402.v201001251516,"org.eclipse.wst.sse.core",1328390214246
+started,bundle,com.android.ide.eclipse.adt,10.0.1.v201103111512-110841,"com.android.ide.eclipse.adt",1328390214248
+started,bundle,org.eclipse.ltk.ui.refactoring,3.4.101.r352_v20100209,"org.eclipse.ltk.ui.refactoring",1328390214467
+activated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328390455517
+deactivated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328390462855
+activated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328390470613
+opened,view,org.eclipse.jdt.ui,3.5.2.r352_v20100106-0800,"org.eclipse.jdt.ui.PackageExplorer",1328390470944
+started,bundle,org.eclipse.search,3.5.1.r351_v20090708-0800,"org.eclipse.search",1328390471037
+opened,view,org.eclipse.mylyn.tasks.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.tasks.ui.views.tasks",1328390471127
+activated,perspective,org.eclipse.jdt.ui,3.5.2.r352_v20100106-0800,"org.eclipse.jdt.ui.JavaPerspective",1328390471151
+activated,view,org.eclipse.ui.ide,3.5.2.M20100113-0800,"org.eclipse.ui.views.ProblemView",1328390471175
+executed,command,org.eclipse.ui,3.5.2.M20100120-0800,"org.eclipse.ui.perspectives.showPerspective",1328390471213
+executed,command,org.eclipse.ui,3.5.2.M20100120-0800,"org.eclipse.ui.perspectives.showPerspective",1328390471213
+opened,view,org.eclipse.jdt.ui,3.5.2.r352_v20100106-0800,"org.eclipse.jdt.ui.JavadocView",1328390474490
+activated,view,org.eclipse.jdt.ui,3.5.2.r352_v20100106-0800,"org.eclipse.jdt.ui.JavadocView",1328390474500
+opened,view,org.eclipse.jdt.ui,3.5.2.r352_v20100106-0800,"org.eclipse.jdt.ui.SourceView",1328390475141
+activated,view,org.eclipse.jdt.ui,3.5.2.r352_v20100106-0800,"org.eclipse.jdt.ui.SourceView",1328390475161
+activated,view,org.eclipse.jdt.ui,3.5.2.r352_v20100106-0800,"org.eclipse.jdt.ui.JavadocView",1328390475451
+activated,view,org.eclipse.ui.ide,3.5.2.M20100113-0800,"org.eclipse.ui.views.ProblemView",1328390475842
+closed,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328390485206
+stopped,bundle,org.eclipse.cdt.debug.mi.ui,6.0.0.201002161416,"org.eclipse.cdt.debug.mi.ui",1328390485703
+stopped,bundle,org.eclipse.cdt.debug.gdbjtag.ui,5.0.100.201002161416,"org.eclipse.cdt.debug.gdbjtag.ui",1328390485704
+stopped,bundle,org.eclipse.cdt.debug.gdbjtag.core,5.0.100.201002161416,"org.eclipse.cdt.debug.gdbjtag.core",1328390485704
+stopped,bundle,org.eclipse.cdt.debug.mi.core,6.0.0.201002161416,"org.eclipse.cdt.debug.mi.core",1328390485705
+stopped,bundle,org.eclipse.cdt.dsf.gdb.ui,2.0.0.201002161416,"org.eclipse.cdt.dsf.gdb.ui",1328390485706
+stopped,bundle,org.eclipse.cdt.dsf.ui,2.0.1.201002161416,"org.eclipse.cdt.dsf.ui",1328390485707
+stopped,bundle,org.eclipse.cdt.launch,6.0.0.201002161416,"org.eclipse.cdt.launch",1328390485707
+stopped,bundle,org.eclipse.cdt.debug.ui,6.0.0.201002161416,"org.eclipse.cdt.debug.ui",1328390485708
+stopped,bundle,org.eclipse.cdt.dsf.gdb,2.0.0.201002161416,"org.eclipse.cdt.dsf.gdb",1328390485709
+stopped,bundle,org.eclipse.cdt.dsf,2.0.0.201002161416,"org.eclipse.cdt.dsf",1328390485709
+stopped,bundle,org.eclipse.cdt.debug.core,6.0.0.201002161416,"org.eclipse.cdt.debug.core",1328390485710
+stopped,bundle,org.eclipse.cdt.managedbuilder.ui,5.1.0.201002161416,"org.eclipse.cdt.managedbuilder.ui",1328390485710
+stopped,bundle,org.eclipse.cdt.make.ui,6.0.1.201002161416,"org.eclipse.cdt.make.ui",1328390485711
+stopped,bundle,org.eclipse.cdt.managedbuilder.gnu.ui,5.0.100.201002161416,"org.eclipse.cdt.managedbuilder.gnu.ui",1328390485711
+stopped,bundle,org.eclipse.cdt.managedbuilder.core,6.0.0.201002161416,"org.eclipse.cdt.managedbuilder.core",1328390485712
+stopped,bundle,org.eclipse.cdt.make.core,6.0.0.201002161416,"org.eclipse.cdt.make.core",1328390485713
+stopped,bundle,org.eclipse.cdt.mylyn.ui,1.0.100.201002161416,"org.eclipse.cdt.mylyn.ui",1328390485713
+stopped,bundle,org.eclipse.cdt.ui,5.1.2.201002161416,"org.eclipse.cdt.ui",1328390485713
+stopped,bundle,org.eclipse.cdt.core,5.1.2.201002161416,"org.eclipse.cdt.core",1328390485714
+stopped,bundle,org.eclipse.mylyn.bugzilla.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.bugzilla.ui",1328390485714
+stopped,bundle,org.eclipse.mylyn.ide.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.ide.ui",1328390485714
+stopped,bundle,org.eclipse.mylyn.team.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.team.ui",1328390485715
+stopped,bundle,org.eclipse.mylyn.resources.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.resources.ui",1328390485715
+stopped,bundle,org.eclipse.mylyn.wikitext.tasks.ui,1.1.3.v20100217-0100-e3x,"org.eclipse.mylyn.wikitext.tasks.ui",1328390485716
+stopped,bundle,org.eclipse.mylyn.context.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.context.ui",1328390485716
+stopped,bundle,org.eclipse.mylyn.help.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.help.ui",1328390485716
+stopped,bundle,org.eclipse.mylyn.tasks.bugs,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.tasks.bugs",1328390485717
+stopped,bundle,org.eclipse.mylyn.tasks.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.tasks.ui",1328390485719
+stopped,bundle,org.eclipse.ant.ui,3.4.2.v20091204_r352,"org.eclipse.ant.ui",1328390485719
+stopped,bundle,com.android.ide.eclipse.adt,10.0.1.v201103111512-110841,"com.android.ide.eclipse.adt",1328390485719
+stopped,bundle,org.eclipse.jdt.junit,3.5.2.r352_v20100113-0800,"org.eclipse.jdt.junit",1328390485720
+stopped,bundle,org.eclipse.jdt.debug.ui,3.4.1.v20090811_r351,"org.eclipse.jdt.debug.ui",1328390485721
+stopped,bundle,org.eclipse.jdt.apt.ui,3.3.200.v20090930-2100_R35x,"org.eclipse.jdt.apt.ui",1328390485722
+stopped,bundle,org.eclipse.jdt.ui,3.5.2.r352_v20100106-0800,"org.eclipse.jdt.ui",1328390485726
+stopped,bundle,org.eclipse.wst.dtd.ui,1.0.400.v200904300717,"org.eclipse.wst.dtd.ui",1328390485730
+stopped,bundle,org.eclipse.wst.xsd.ui,1.2.204.v200909021537,"org.eclipse.wst.xsd.ui",1328390485747
+stopped,bundle,org.eclipse.wst.xml.ui,1.1.2.v201001222130,"org.eclipse.wst.xml.ui",1328390485747
+stopped,bundle,org.eclipse.wst.common.ui,1.1.402.v200901262305,"org.eclipse.wst.common.ui",1328390485748
+stopped,bundle,org.eclipse.wst.sse.ui,1.1.102.v200910200227,"org.eclipse.wst.sse.ui",1328390485749
+stopped,bundle,org.eclipse.search,3.5.1.r351_v20090708-0800,"org.eclipse.search",1328390485750
+stopped,bundle,org.eclipse.ltk.ui.refactoring,3.4.101.r352_v20100209,"org.eclipse.ltk.ui.refactoring",1328390485750
+stopped,bundle,org.eclipse.team.cvs.ui,3.3.202.r35x_20090930-0800,"org.eclipse.team.cvs.ui",1328390485751
+stopped,bundle,org.eclipse.team.ui,3.5.0.I20090430-0408,"org.eclipse.team.ui",1328390485751
+stopped,bundle,org.eclipse.compare,3.5.2.r35x_20100113-0800,"org.eclipse.compare",1328390485752
+stopped,bundle,org.eclipse.ui.externaltools,3.2.0.v20090504,"org.eclipse.ui.externaltools",1328390485753
+stopped,bundle,org.eclipse.debug.ui,3.5.2.v20091028_r352,"org.eclipse.debug.ui",1328390485756
+stopped,bundle,org.eclipse.mylyn.wikitext.ui,1.1.3.v20100217-0100-e3x,"org.eclipse.mylyn.wikitext.ui",1328390485757
+stopped,bundle,com.android.ide.eclipse.hierarchyviewer,10.0.1.v201103111512-110841,"com.android.ide.eclipse.hierarchyviewer",1328390485757
+stopped,bundle,com.android.ide.eclipse.ddms,10.0.1.v201103111512-110841,"com.android.ide.eclipse.ddms",1328390485757
+stopped,bundle,org.eclipse.ui.console,3.4.0.v20090513,"org.eclipse.ui.console",1328390485757
+stopped,bundle,org.eclipse.mylyn.commons.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.commons.ui",1328390485757
+stopped,bundle,org.eclipse.ui.editors,3.5.0.v20090527-2000,"org.eclipse.ui.editors",1328390485758
+stopped,bundle,org.eclipse.ui.navigator.resources,3.4.1.M20090826-0800,"org.eclipse.ui.navigator.resources",1328390485758
+activated,perspective,org.eclipse.jdt.ui,,"org.eclipse.jdt.ui.JavaPerspective",1328390616647
+started,bundle,org.eclipse.osgi,3.5.2.R35x_v20100126,"org.eclipse.osgi",1328390616648
+started,bundle,org.eclipse.equinox.simpleconfigurator,1.0.101.R35x_v20090807-1100,"org.eclipse.equinox.simpleconfigurator",1328390616653
+started,bundle,com.ibm.icu,4.0.1.v20090822,"com.ibm.icu",1328390616654
+started,bundle,org.eclipse.core.contenttype,3.4.1.R35x_v20090826-0451,"org.eclipse.core.contenttype",1328390616654
+started,bundle,org.eclipse.core.databinding.observable,1.2.0.M20090902-0800,"org.eclipse.core.databinding.observable",1328390616655
+started,bundle,org.eclipse.core.expressions,3.4.101.R35x_v20100209,"org.eclipse.core.expressions",1328390616656
+started,bundle,org.eclipse.core.filebuffers,3.5.0.v20090526-2000,"org.eclipse.core.filebuffers",1328390616656
+started,bundle,org.eclipse.core.jobs,3.4.100.v20090429-1800,"org.eclipse.core.jobs",1328390616656
+started,bundle,org.eclipse.core.net,1.2.1.r35x_20090812-1200,"org.eclipse.core.net",1328390616657
+started,bundle,org.eclipse.core.resources,3.5.2.R35x_v20091203-1235,"org.eclipse.core.resources",1328390616658
+started,bundle,org.eclipse.core.runtime,3.5.0.v20090525,"org.eclipse.core.runtime",1328390616659
+started,bundle,org.eclipse.core.runtime.compatibility,3.2.0.v20090413,"org.eclipse.core.runtime.compatibility",1328390616659
+started,bundle,org.eclipse.core.runtime.compatibility.auth,3.2.100.v20090413,"org.eclipse.core.runtime.compatibility.auth",1328390616660
+started,bundle,org.eclipse.ecf,3.0.0.v20090831-1906,"org.eclipse.ecf",1328390616660
+started,bundle,org.eclipse.ecf.filetransfer,3.0.0.v20090831-1906,"org.eclipse.ecf.filetransfer",1328390616661
+started,bundle,org.eclipse.ecf.identity,3.0.0.v20090831-1906,"org.eclipse.ecf.identity",1328390616662
+started,bundle,org.eclipse.ecf.provider.filetransfer,3.0.1.v20090831-1906,"org.eclipse.ecf.provider.filetransfer",1328390616663
+started,bundle,org.eclipse.ecf.provider.filetransfer.httpclient,3.0.1.v20090831-1906,"org.eclipse.ecf.provider.filetransfer.httpclient",1328390616663
+started,bundle,org.eclipse.epp.usagedata.gathering,1.1.1.R201001291118,"org.eclipse.epp.usagedata.gathering",1328390616664
+started,bundle,org.eclipse.epp.usagedata.recording,1.1.1.R201001291118,"org.eclipse.epp.usagedata.recording",1328390616668
+started,bundle,org.eclipse.equinox.app,1.2.1.R35x_v20091203,"org.eclipse.equinox.app",1328390616676
+started,bundle,org.eclipse.equinox.common,3.5.1.R35x_v20090807-1100,"org.eclipse.equinox.common",1328390616676
+started,bundle,org.eclipse.equinox.ds,1.1.1.R35x_v20090806,"org.eclipse.equinox.ds",1328390616677
+started,bundle,org.eclipse.equinox.frameworkadmin,1.0.100.v20090520-1905,"org.eclipse.equinox.frameworkadmin",1328390616679
+started,bundle,org.eclipse.equinox.frameworkadmin.equinox,1.0.101.R35x_v20091214,"org.eclipse.equinox.frameworkadmin.equinox",1328390616680
+started,bundle,org.eclipse.equinox.p2.core,1.0.101.R35x_v20090819,"org.eclipse.equinox.p2.core",1328390616681
+started,bundle,org.eclipse.equinox.p2.director,1.0.101.R35x_v20100112,"org.eclipse.equinox.p2.director",1328390616682
+started,bundle,org.eclipse.equinox.p2.directorywatcher,1.0.100.v20090525,"org.eclipse.equinox.p2.directorywatcher",1328390616688
+started,bundle,org.eclipse.equinox.p2.engine,1.0.102.R35x_v20091117,"org.eclipse.equinox.p2.engine",1328390616689
+started,bundle,org.eclipse.equinox.p2.exemplarysetup,1.0.100.v20090520-1905,"org.eclipse.equinox.p2.exemplarysetup",1328390616690
+started,bundle,org.eclipse.equinox.p2.garbagecollector,1.0.100.v20090520-1905,"org.eclipse.equinox.p2.garbagecollector",1328390616691
+started,bundle,org.eclipse.equinox.p2.metadata,1.0.101.R35x_v20100112,"org.eclipse.equinox.p2.metadata",1328390616692
+started,bundle,org.eclipse.equinox.p2.metadata.repository,1.0.101.R35x_v20090812,"org.eclipse.equinox.p2.metadata.repository",1328390616692
+started,bundle,org.eclipse.equinox.p2.reconciler.dropins,1.0.100.v20090520-1905,"org.eclipse.equinox.p2.reconciler.dropins",1328390616693
+started,bundle,org.eclipse.equinox.p2.repository,1.0.1.R35x_v20100105,"org.eclipse.equinox.p2.repository",1328390616693
+started,bundle,org.eclipse.equinox.p2.ui.sdk.scheduler,1.0.0.v20090520-1905,"org.eclipse.equinox.p2.ui.sdk.scheduler",1328390616694
+started,bundle,org.eclipse.equinox.p2.updatechecker,1.1.0.v20090520-1905,"org.eclipse.equinox.p2.updatechecker",1328390616695
+started,bundle,org.eclipse.equinox.preferences,3.2.301.R35x_v20091117,"org.eclipse.equinox.preferences",1328390616695
+started,bundle,org.eclipse.equinox.registry,3.4.100.v20090520-1800,"org.eclipse.equinox.registry",1328390616696
+started,bundle,org.eclipse.equinox.security,1.0.100.v20090520-1800,"org.eclipse.equinox.security",1328390616697
+started,bundle,org.eclipse.equinox.simpleconfigurator.manipulator,1.0.101.R35x_v20100209,"org.eclipse.equinox.simpleconfigurator.manipulator",1328390616697
+started,bundle,org.eclipse.equinox.util,1.0.100.v20090520-1800,"org.eclipse.equinox.util",1328390616698
+started,bundle,org.eclipse.help,3.4.1.v20090805_35x,"org.eclipse.help",1328390616698
+started,bundle,org.eclipse.jface,3.5.2.M20100120-0800,"org.eclipse.jface",1328390616699
+started,bundle,org.eclipse.jsch.core,1.1.100.I20090430-0408,"org.eclipse.jsch.core",1328390616705
+started,bundle,org.eclipse.mylyn.bugzilla.core,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.bugzilla.core",1328390616705
+started,bundle,org.eclipse.mylyn.bugzilla.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.bugzilla.ui",1328390616706
+started,bundle,org.eclipse.mylyn.commons.net,3.2.0.v20090617-0100-e3x,"org.eclipse.mylyn.commons.net",1328390616706
+started,bundle,org.eclipse.mylyn.commons.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.commons.ui",1328390616707
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.epp.usagedata.recording/usagedata.csv b/androidgcs/.metadata/.plugins/org.eclipse.epp.usagedata.recording/usagedata.csv
new file mode 100644
index 000000000..510c72478
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.epp.usagedata.recording/usagedata.csv
@@ -0,0 +1,83 @@
+what,kind,bundleId,bundleVersion,description,time
+started,bundle,org.eclipse.mylyn.context.core,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.context.core",1328390616707
+started,bundle,org.eclipse.mylyn.context.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.context.ui",1328390616708
+started,bundle,org.eclipse.mylyn.monitor.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.monitor.ui",1328390616708
+started,bundle,org.eclipse.mylyn.tasks.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.tasks.ui",1328390616709
+started,bundle,org.eclipse.mylyn.team.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.team.ui",1328390616710
+started,bundle,org.eclipse.search,3.5.1.r351_v20090708-0800,"org.eclipse.search",1328390616711
+started,bundle,org.eclipse.team.core,3.5.1.r35x_20100113-0800,"org.eclipse.team.core",1328390616712
+started,bundle,org.eclipse.team.cvs.core,3.3.200.I20090430-0408,"org.eclipse.team.cvs.core",1328390616712
+started,bundle,org.eclipse.team.cvs.ui,3.3.202.r35x_20090930-0800,"org.eclipse.team.cvs.ui",1328390616713
+started,bundle,org.eclipse.team.ui,3.5.0.I20090430-0408,"org.eclipse.team.ui",1328390616714
+started,bundle,org.eclipse.ui,3.5.2.M20100120-0800,"org.eclipse.ui",1328390616715
+started,bundle,org.eclipse.ui.console,3.4.0.v20090513,"org.eclipse.ui.console",1328390616716
+started,bundle,org.eclipse.ui.editors,3.5.0.v20090527-2000,"org.eclipse.ui.editors",1328390616717
+started,bundle,org.eclipse.ui.forms,3.4.1.v20090714_35x,"org.eclipse.ui.forms",1328390616717
+started,bundle,org.eclipse.ui.ide,3.5.2.M20100113-0800,"org.eclipse.ui.ide",1328390616718
+started,bundle,org.eclipse.ui.net,1.2.1.r35x_20090812-1200,"org.eclipse.ui.net",1328390616719
+started,bundle,org.eclipse.ui.views,3.4.1.M20090826-0800,"org.eclipse.ui.views",1328390616720
+started,bundle,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"org.eclipse.ui.workbench",1328390616720
+started,bundle,org.eclipse.ui.workbench.texteditor,3.5.1.r352_v20100105,"org.eclipse.ui.workbench.texteditor",1328390616721
+started,bundle,org.eclipse.update.configurator,3.3.0.v20090312,"org.eclipse.update.configurator",1328390616722
+started,bundle,org.eclipse.update.core,3.2.300.v20090525,"org.eclipse.update.core",1328390616722
+started,bundle,org.eclipse.update.scheduler,3.2.200.v20081127,"org.eclipse.update.scheduler",1328390616723
+started,bundle,org.eclipse.jdt.core,3.5.2.v_981_R35x,"org.eclipse.jdt.core",1328390616724
+started,bundle,org.eclipse.jdt.core.manipulation,1.3.0.v20090603,"org.eclipse.jdt.core.manipulation",1328390616725
+started,bundle,org.eclipse.jdt.ui,3.5.2.r352_v20100106-0800,"org.eclipse.jdt.ui",1328390616730
+os,sysinfo,,,"macosx",1328390616740
+arch,sysinfo,,,"x86_64",1328390616740
+ws,sysinfo,,,"cocoa",1328390616740
+locale,sysinfo,,,"en_US",1328390616740
+processors,sysinfo,,,"4",1328390616740
+java.runtime.name,sysinfo,,,"Java(TM) SE Runtime Environment",1328390616740
+java.runtime.version,sysinfo,,,"1.6.0_29-b11-402-11M3527",1328390616740
+java.specification.name,sysinfo,,,"Java Platform API Specification",1328390616740
+java.specification.vendor,sysinfo,,,"Sun Microsystems Inc.",1328390616740
+java.specification.version,sysinfo,,,"1.6",1328390616740
+java.vendor,sysinfo,,,"Apple Inc.",1328390616740
+java.version,sysinfo,,,"1.6.0_29",1328390616740
+java.vm.info,sysinfo,,,"mixed mode",1328390616740
+java.vm.name,sysinfo,,,"Java HotSpot(TM) 64-Bit Server VM",1328390616740
+java.vm.specification.name,sysinfo,,,"Java Virtual Machine Specification",1328390616740
+java.vm.specification.vendor,sysinfo,,,"Sun Microsystems Inc.",1328390616740
+java.vm.specification.version,sysinfo,,,"1.0",1328390616740
+java.vm.vendor,sysinfo,,,"Apple Inc.",1328390616740
+java.vm.version,sysinfo,,,"20.4-b02-402",1328390616740
+started,bundle,org.eclipse.equinox.p2.extensionlocation,1.0.100.v20090520-1905,"org.eclipse.equinox.p2.extensionlocation",1328390616768
+started,bundle,org.eclipse.equinox.p2.artifact.repository,1.0.101.R35x_v20090721,"org.eclipse.equinox.p2.artifact.repository",1328390616783
+started,bundle,org.eclipse.equinox.p2.publisher,1.0.1.R35x_20100105,"org.eclipse.equinox.p2.publisher",1328390616808
+started,bundle,org.eclipse.equinox.p2.touchpoint.eclipse,1.0.101.R35x_20090820-1821,"org.eclipse.equinox.p2.touchpoint.eclipse",1328390616820
+deactivated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328390619481
+started,bundle,org.eclipse.equinox.p2.updatesite,1.0.101.R35x_20100105,"org.eclipse.equinox.p2.updatesite",1328390623001
+started,bundle,org.eclipse.equinox.p2.ui,1.0.101.R35x_v20090819,"org.eclipse.equinox.p2.ui",1328390623371
+started,bundle,org.eclipse.equinox.p2.ui.sdk,1.0.100.v20090520-1905,"org.eclipse.equinox.p2.ui.sdk",1328390623409
+started,bundle,org.eclipse.core.filesystem,1.2.1.R35x_v20091203-1235,"org.eclipse.core.filesystem",1328390630622
+activated,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328390659248
+executed,command,org.eclipse.ui,3.5.2.M20100120-0800,"org.eclipse.ui.file.import",1328390659277
+closed,workbench,org.eclipse.ui.workbench,3.5.2.M20100113-0800,"",1328390663881
+stopped,bundle,org.eclipse.cdt.debug.mi.ui,6.0.0.201002161416,"org.eclipse.cdt.debug.mi.ui",1328390664441
+stopped,bundle,org.eclipse.cdt.debug.gdbjtag.ui,5.0.100.201002161416,"org.eclipse.cdt.debug.gdbjtag.ui",1328390664442
+stopped,bundle,org.eclipse.cdt.debug.gdbjtag.core,5.0.100.201002161416,"org.eclipse.cdt.debug.gdbjtag.core",1328390664442
+stopped,bundle,org.eclipse.cdt.debug.mi.core,6.0.0.201002161416,"org.eclipse.cdt.debug.mi.core",1328390664443
+stopped,bundle,org.eclipse.cdt.dsf.gdb.ui,2.0.0.201002161416,"org.eclipse.cdt.dsf.gdb.ui",1328390664443
+stopped,bundle,org.eclipse.cdt.dsf.ui,2.0.1.201002161416,"org.eclipse.cdt.dsf.ui",1328390664444
+stopped,bundle,org.eclipse.cdt.launch,6.0.0.201002161416,"org.eclipse.cdt.launch",1328390664445
+stopped,bundle,org.eclipse.cdt.debug.ui,6.0.0.201002161416,"org.eclipse.cdt.debug.ui",1328390664446
+stopped,bundle,org.eclipse.cdt.dsf.gdb,2.0.0.201002161416,"org.eclipse.cdt.dsf.gdb",1328390664446
+stopped,bundle,org.eclipse.cdt.dsf,2.0.0.201002161416,"org.eclipse.cdt.dsf",1328390664447
+stopped,bundle,org.eclipse.cdt.debug.core,6.0.0.201002161416,"org.eclipse.cdt.debug.core",1328390664448
+stopped,bundle,org.eclipse.cdt.managedbuilder.ui,5.1.0.201002161416,"org.eclipse.cdt.managedbuilder.ui",1328390664449
+stopped,bundle,org.eclipse.cdt.make.ui,6.0.1.201002161416,"org.eclipse.cdt.make.ui",1328390664452
+stopped,bundle,org.eclipse.cdt.managedbuilder.gnu.ui,5.0.100.201002161416,"org.eclipse.cdt.managedbuilder.gnu.ui",1328390664453
+stopped,bundle,org.eclipse.cdt.managedbuilder.core,6.0.0.201002161416,"org.eclipse.cdt.managedbuilder.core",1328390664454
+stopped,bundle,org.eclipse.cdt.make.core,6.0.0.201002161416,"org.eclipse.cdt.make.core",1328390664455
+stopped,bundle,org.eclipse.cdt.mylyn.ui,1.0.100.201002161416,"org.eclipse.cdt.mylyn.ui",1328390664455
+stopped,bundle,org.eclipse.cdt.ui,5.1.2.201002161416,"org.eclipse.cdt.ui",1328390664458
+stopped,bundle,org.eclipse.cdt.core,5.1.2.201002161416,"org.eclipse.cdt.core",1328390664459
+stopped,bundle,org.eclipse.mylyn.bugzilla.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.bugzilla.ui",1328390664460
+stopped,bundle,org.eclipse.mylyn.ide.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.ide.ui",1328390664460
+stopped,bundle,org.eclipse.mylyn.team.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.team.ui",1328390664460
+stopped,bundle,org.eclipse.mylyn.resources.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.resources.ui",1328390664461
+stopped,bundle,org.eclipse.mylyn.wikitext.tasks.ui,1.1.3.v20100217-0100-e3x,"org.eclipse.mylyn.wikitext.tasks.ui",1328390664461
+stopped,bundle,org.eclipse.mylyn.context.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.context.ui",1328390664462
+stopped,bundle,org.eclipse.mylyn.help.ui,3.2.3.v20100217-0100-e3x,"org.eclipse.mylyn.help.ui",1328390664462
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.jdt.core/nonChainingJarsCache b/androidgcs/.metadata/.plugins/org.eclipse.jdt.core/nonChainingJarsCache
new file mode 100644
index 000000000..593f4708d
Binary files /dev/null and b/androidgcs/.metadata/.plugins/org.eclipse.jdt.core/nonChainingJarsCache differ
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.jdt.core/variablesAndContainers.dat b/androidgcs/.metadata/.plugins/org.eclipse.jdt.core/variablesAndContainers.dat
new file mode 100644
index 000000000..3aea61cb8
Binary files /dev/null and b/androidgcs/.metadata/.plugins/org.eclipse.jdt.core/variablesAndContainers.dat differ
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml b/androidgcs/.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml
new file mode 100644
index 000000000..a4ee3cbc9
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.jdt.ui/OpenTypeHistory.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml b/androidgcs/.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml
new file mode 100644
index 000000000..9e390f501
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml b/androidgcs/.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml
new file mode 100644
index 000000000..8bc608925
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml
@@ -0,0 +1,10 @@
+
+
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.ui.ide/dialog_settings.xml b/androidgcs/.metadata/.plugins/org.eclipse.ui.ide/dialog_settings.xml
new file mode 100644
index 000000000..ab9bf177c
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.ui.ide/dialog_settings.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.ui.intro/dialog_settings.xml b/androidgcs/.metadata/.plugins/org.eclipse.ui.intro/dialog_settings.xml
new file mode 100644
index 000000000..f118f0213
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.ui.intro/dialog_settings.xml
@@ -0,0 +1,4 @@
+
+
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml b/androidgcs/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml
new file mode 100644
index 000000000..5b583c4be
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.ui.workbench/workbench.xml b/androidgcs/.metadata/.plugins/org.eclipse.ui.workbench/workbench.xml
new file mode 100644
index 000000000..931fd0157
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.ui.workbench/workbench.xml
@@ -0,0 +1,355 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidgcs/.metadata/.plugins/org.eclipse.ui.workbench/workingsets.xml b/androidgcs/.metadata/.plugins/org.eclipse.ui.workbench/workingsets.xml
new file mode 100644
index 000000000..8daaf65a1
--- /dev/null
+++ b/androidgcs/.metadata/.plugins/org.eclipse.ui.workbench/workingsets.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/androidgcs/.metadata/version.ini b/androidgcs/.metadata/version.ini
new file mode 100644
index 000000000..c51ff745b
--- /dev/null
+++ b/androidgcs/.metadata/version.ini
@@ -0,0 +1 @@
+org.eclipse.core.runtime=1
\ No newline at end of file
diff --git a/androidgcs/.project b/androidgcs/.project
new file mode 100644
index 000000000..c607dd2bd
--- /dev/null
+++ b/androidgcs/.project
@@ -0,0 +1,33 @@
+
+
+ androidgcs
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/androidgcs/.settings/org.eclipse.jdt.core.prefs b/androidgcs/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..a149e8e84
--- /dev/null
+++ b/androidgcs/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,12 @@
+#Sat Feb 04 16:05:48 CST 2012
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/androidgcs/AndroidManifest.xml b/androidgcs/AndroidManifest.xml
new file mode 100644
index 000000000..c367886bd
--- /dev/null
+++ b/androidgcs/AndroidManifest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidgcs/Doc/AndroidArchitecture.txt b/androidgcs/Doc/AndroidArchitecture.txt
new file mode 100644
index 000000000..0a17a8dfa
--- /dev/null
+++ b/androidgcs/Doc/AndroidArchitecture.txt
@@ -0,0 +1,70 @@
+------- TELEMETRY ---------
+The Telemetry system has been implemented, and is composed of a few
+major components:
+
+Telemetry.java - receives command to transmit objects through telemetry and
+also emits notification when transactions are completed
+
+TelemetryMonitor.java - monitors the FlightTelemetryStats and GCSTelemetryStats
+to establish when a working connection is in place. Also initiates downloading
+all the objects on a new connection.
+
+UAVObjectManager.java - the central data store. The data is actually stored
+within objects, but this maintains the handles to all of them.
+
+UAVTalk.java - the actual communication layer. Can packetize an object and
+insert into stream and process the incoming stream and update objects
+accordingly.
+
+** Threading
+Currently object updates run within the thread of the function that called
+update. This should be changed so that it adds a message to the Telemetry
+thread which will then send on its own time.
+
+---- MESSAGE PASSING ----
+The current implementation/analog to the slots/sockets in QT are Observers
+which are registered as added to an Observable. This is used extensibly within
+the telemetry system. I will continue to use this _within_ the Telemetry
+module so it doesn't depend on any android features.
+
+In android there is a constraint that UI operations should all be done from the
+UI thread. The most common way to do this is for the UI object (such as
+Activity) to instantiate a Handler to which messages or runnables are posted.
+
+So for external objects they will register a runnable somehow...
+
+--- TELEMETRY SERVICE ---
+The telemetry connection will be maintained by a service separate from the the
+main activity(s). Although it is a bit unusual, the service will support being
+started and stopped, as well as being bound. Binding will be required to get
+access to the Object Manager.
+
+In addition, to make it forward looking, the start intent can specify a
+connection to open. This will allow the service in future to monitor multiple
+connections.
+
+The service will destroy itself only when all active connections disappear and
+all activies are unbound.
+
+It will also handle any logging desired (I think).
+
+There will be a primary message handler thread. This thread will separately
+launch telemetry threads when required.
+
+The service should send broadcast intents whenever a connection is estabilished
+or dropped.
+
+I dont think the service should have the options about which UAVs are
+supported.
+
+** Telemetry IBinder
+*** Give handle to the ObjectManager for each UAV
+*** Call disconnect
+*** Query conncetion status
+
+--- TELEMETRY WIDGET ---
+Listens for conncet/disconnect intents
+
+Also show if service is running?
+
+Ability to launch service?
diff --git a/androidgcs/lint.xml b/androidgcs/lint.xml
new file mode 100644
index 000000000..ee0eead5b
--- /dev/null
+++ b/androidgcs/lint.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/androidgcs/proguard.cfg b/androidgcs/proguard.cfg
new file mode 100644
index 000000000..ec78e7622
--- /dev/null
+++ b/androidgcs/proguard.cfg
@@ -0,0 +1,36 @@
+-optimizationpasses 5
+-dontusemixedcaseclassnames
+-dontskipnonpubliclibraryclasses
+-dontpreverify
+-verbose
+-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
+
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Application
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+-keep public class * extends android.content.ContentProvider
+-keep public class * extends android.app.backup.BackupAgentHelper
+-keep public class * extends android.preference.Preference
+-keep public class com.android.vending.licensing.ILicensingService
+
+-keepclasseswithmembers class * {
+ native ;
+}
+
+-keepclasseswithmembernames class * {
+ public (android.content.Context, android.util.AttributeSet);
+}
+
+-keepclasseswithmembernames class * {
+ public (android.content.Context, android.util.AttributeSet, int);
+}
+
+-keepclassmembers enum * {
+ public static **[] values();
+ public static ** valueOf(java.lang.String);
+}
+
+-keep class * implements android.os.Parcelable {
+ public static final android.os.Parcelable$Creator *;
+}
diff --git a/androidgcs/project.properties b/androidgcs/project.properties
new file mode 100644
index 000000000..a43ea8cdc
--- /dev/null
+++ b/androidgcs/project.properties
@@ -0,0 +1,11 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=Google Inc.:Google APIs:16
diff --git a/androidgcs/res/drawable-hdpi/browser_icon.png b/androidgcs/res/drawable-hdpi/browser_icon.png
new file mode 100644
index 000000000..3fcee4852
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/browser_icon.png differ
diff --git a/androidgcs/res/drawable-hdpi/ic_alarms.png b/androidgcs/res/drawable-hdpi/ic_alarms.png
new file mode 100644
index 000000000..b83c17e6d
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/ic_alarms.png differ
diff --git a/androidgcs/res/drawable-hdpi/ic_browser.png b/androidgcs/res/drawable-hdpi/ic_browser.png
new file mode 100644
index 000000000..d0f14656a
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/ic_browser.png differ
diff --git a/androidgcs/res/drawable-hdpi/ic_controller.png b/androidgcs/res/drawable-hdpi/ic_controller.png
new file mode 100644
index 000000000..cd497f925
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/ic_controller.png differ
diff --git a/androidgcs/res/drawable-hdpi/ic_home.png b/androidgcs/res/drawable-hdpi/ic_home.png
new file mode 100644
index 000000000..5afd63f17
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/ic_home.png differ
diff --git a/androidgcs/res/drawable-hdpi/ic_logging.png b/androidgcs/res/drawable-hdpi/ic_logging.png
new file mode 100644
index 000000000..4791e4293
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/ic_logging.png differ
diff --git a/androidgcs/res/drawable-hdpi/ic_logo.png b/androidgcs/res/drawable-hdpi/ic_logo.png
new file mode 100644
index 000000000..eab1fc68f
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/ic_logo.png differ
diff --git a/androidgcs/res/drawable-hdpi/ic_map.png b/androidgcs/res/drawable-hdpi/ic_map.png
new file mode 100644
index 000000000..eb148224a
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/ic_map.png differ
diff --git a/androidgcs/res/drawable-hdpi/ic_pfd.png b/androidgcs/res/drawable-hdpi/ic_pfd.png
new file mode 100644
index 000000000..80bb1d92a
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/ic_pfd.png differ
diff --git a/androidgcs/res/drawable-hdpi/ic_telemetry.png b/androidgcs/res/drawable-hdpi/ic_telemetry.png
new file mode 100644
index 000000000..e3842a6cb
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/ic_telemetry.png differ
diff --git a/androidgcs/res/drawable-hdpi/ic_uav.png b/androidgcs/res/drawable-hdpi/ic_uav.png
new file mode 100644
index 000000000..169c54f82
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/ic_uav.png differ
diff --git a/androidgcs/res/drawable-hdpi/im_pfd_horizon.png b/androidgcs/res/drawable-hdpi/im_pfd_horizon.png
new file mode 100644
index 000000000..f5a5c2157
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/im_pfd_horizon.png differ
diff --git a/androidgcs/res/drawable-hdpi/map_positioner_background.9.png b/androidgcs/res/drawable-hdpi/map_positioner_background.9.png
new file mode 100644
index 000000000..3828ebb95
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/map_positioner_background.9.png differ
diff --git a/androidgcs/res/drawable-hdpi/map_positioner_poi.png b/androidgcs/res/drawable-hdpi/map_positioner_poi.png
new file mode 100644
index 000000000..3b6915301
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/map_positioner_poi.png differ
diff --git a/androidgcs/res/drawable-hdpi/map_positioner_uav.png b/androidgcs/res/drawable-hdpi/map_positioner_uav.png
new file mode 100644
index 000000000..887204cd5
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/map_positioner_uav.png differ
diff --git a/androidgcs/res/drawable-hdpi/map_positioner_waypoint.png b/androidgcs/res/drawable-hdpi/map_positioner_waypoint.png
new file mode 100644
index 000000000..bc4a5b36c
Binary files /dev/null and b/androidgcs/res/drawable-hdpi/map_positioner_waypoint.png differ
diff --git a/androidgcs/res/drawable-ldpi/browser_icon.png b/androidgcs/res/drawable-ldpi/browser_icon.png
new file mode 100644
index 000000000..3fcee4852
Binary files /dev/null and b/androidgcs/res/drawable-ldpi/browser_icon.png differ
diff --git a/androidgcs/res/drawable-ldpi/icon.png b/androidgcs/res/drawable-ldpi/icon.png
new file mode 100644
index 000000000..eab1fc68f
Binary files /dev/null and b/androidgcs/res/drawable-ldpi/icon.png differ
diff --git a/androidgcs/res/drawable-mdpi/browser_icon.png b/androidgcs/res/drawable-mdpi/browser_icon.png
new file mode 100644
index 000000000..3fcee4852
Binary files /dev/null and b/androidgcs/res/drawable-mdpi/browser_icon.png differ
diff --git a/androidgcs/res/drawable-mdpi/icon.png b/androidgcs/res/drawable-mdpi/icon.png
new file mode 100644
index 000000000..eab1fc68f
Binary files /dev/null and b/androidgcs/res/drawable-mdpi/icon.png differ
diff --git a/androidgcs/res/layout/controller.xml b/androidgcs/res/layout/controller.xml
new file mode 100644
index 000000000..d81cd0e09
--- /dev/null
+++ b/androidgcs/res/layout/controller.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidgcs/res/layout/dualjoystick.xml b/androidgcs/res/layout/dualjoystick.xml
new file mode 100644
index 000000000..3bbaba520
--- /dev/null
+++ b/androidgcs/res/layout/dualjoystick.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidgcs/res/layout/gcs_home.xml b/androidgcs/res/layout/gcs_home.xml
new file mode 100644
index 000000000..2819cf29a
--- /dev/null
+++ b/androidgcs/res/layout/gcs_home.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidgcs/res/layout/logger.xml b/androidgcs/res/layout/logger.xml
new file mode 100644
index 000000000..52d92ec6e
--- /dev/null
+++ b/androidgcs/res/layout/logger.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidgcs/res/layout/map_layout.xml b/androidgcs/res/layout/map_layout.xml
new file mode 100644
index 000000000..a0cea291f
--- /dev/null
+++ b/androidgcs/res/layout/map_layout.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidgcs/res/layout/map_positioner.xml b/androidgcs/res/layout/map_positioner.xml
new file mode 100644
index 000000000..7cbce13ac
--- /dev/null
+++ b/androidgcs/res/layout/map_positioner.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidgcs/res/layout/object_browser.xml b/androidgcs/res/layout/object_browser.xml
new file mode 100644
index 000000000..8a7221fb6
--- /dev/null
+++ b/androidgcs/res/layout/object_browser.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/androidgcs/res/layout/object_editor.xml b/androidgcs/res/layout/object_editor.xml
new file mode 100644
index 000000000..062f64260
--- /dev/null
+++ b/androidgcs/res/layout/object_editor.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidgcs/res/layout/object_view.xml b/androidgcs/res/layout/object_view.xml
new file mode 100644
index 000000000..fdf5a0bae
--- /dev/null
+++ b/androidgcs/res/layout/object_view.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/androidgcs/res/layout/pfd.xml b/androidgcs/res/layout/pfd.xml
new file mode 100644
index 000000000..3c6895f1e
--- /dev/null
+++ b/androidgcs/res/layout/pfd.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/androidgcs/res/layout/system_alarms.xml b/androidgcs/res/layout/system_alarms.xml
new file mode 100644
index 000000000..dbf427536
--- /dev/null
+++ b/androidgcs/res/layout/system_alarms.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/androidgcs/res/layout/system_alarms_fragment.xml b/androidgcs/res/layout/system_alarms_fragment.xml
new file mode 100644
index 000000000..f6f39e0ba
--- /dev/null
+++ b/androidgcs/res/layout/system_alarms_fragment.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidgcs/res/layout/telemetry_stats.xml b/androidgcs/res/layout/telemetry_stats.xml
new file mode 100644
index 000000000..442609c1e
--- /dev/null
+++ b/androidgcs/res/layout/telemetry_stats.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidgcs/res/layout/telemetry_widget.xml b/androidgcs/res/layout/telemetry_widget.xml
new file mode 100644
index 000000000..6af378104
--- /dev/null
+++ b/androidgcs/res/layout/telemetry_widget.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/androidgcs/res/menu/options_menu.xml b/androidgcs/res/menu/options_menu.xml
new file mode 100644
index 000000000..db86aa670
--- /dev/null
+++ b/androidgcs/res/menu/options_menu.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/androidgcs/res/menu/status_menu.xml b/androidgcs/res/menu/status_menu.xml
new file mode 100644
index 000000000..b9865e891
--- /dev/null
+++ b/androidgcs/res/menu/status_menu.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/androidgcs/res/values/arrays.xml b/androidgcs/res/values/arrays.xml
new file mode 100644
index 000000000..4461c31d8
--- /dev/null
+++ b/androidgcs/res/values/arrays.xml
@@ -0,0 +1,17 @@
+
+
+
+ - None
+ - Fake
+ - Bluetooth
+ - Network
+ - HID
+
+
+ - 0
+ - 1
+ - 2
+ - 3
+ - 4
+
+
\ No newline at end of file
diff --git a/androidgcs/res/values/colors.xml b/androidgcs/res/values/colors.xml
new file mode 100644
index 000000000..dc8eaf315
--- /dev/null
+++ b/androidgcs/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #F555
+ #AFFF
+ #AFFF
+
diff --git a/androidgcs/res/values/strings.xml b/androidgcs/res/values/strings.xml
new file mode 100644
index 000000000..b7fd43909
--- /dev/null
+++ b/androidgcs/res/values/strings.xml
@@ -0,0 +1,36 @@
+
+
+ OpenPilot GCS Home
+ Browser
+ Controller
+ PFD
+ Map
+ Logging
+
+ Settings
+ Connect
+ Disconnect
+
+ Settings
+ Automatically Connect
+ Connection Type
+ Bluetooth
+ Select the connection method
+
+ Compass
+ N
+ E
+ S
+ W
+ Connected
+ Update
+ Save
+ Send
+ Manual control values:
+ Number of objects
+ Number of bytes
+ Alarms
+ TxRate:
+ RxRate:
+ Tester
+
diff --git a/androidgcs/res/xml/device_filter.xml b/androidgcs/res/xml/device_filter.xml
new file mode 100644
index 000000000..8347ef270
--- /dev/null
+++ b/androidgcs/res/xml/device_filter.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidgcs/res/xml/preferences.xml b/androidgcs/res/xml/preferences.xml
new file mode 100644
index 000000000..3a9103b49
--- /dev/null
+++ b/androidgcs/res/xml/preferences.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/androidgcs/res/xml/telemetry_widget_info.xml b/androidgcs/res/xml/telemetry_widget_info.xml
new file mode 100644
index 000000000..f2ced6645
--- /dev/null
+++ b/androidgcs/res/xml/telemetry_widget_info.xml
@@ -0,0 +1,9 @@
+
+
+ android:minWidth="294dp"
+ android:minHeight="72dp"
+ android:updatePeriodMillis="86400000"
+ android:initialLayout="@layout/telemetry_widget">
+
+
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/DockPanel/DockPanel.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DockPanel/DockPanel.java
new file mode 100644
index 000000000..bbc6dd276
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DockPanel/DockPanel.java
@@ -0,0 +1,319 @@
+package com.MobileAnarchy.Android.Widgets.DockPanel;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.TranslateAnimation;
+import android.view.animation.Animation.AnimationListener;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+public class DockPanel extends LinearLayout {
+
+ // =========================================
+ // Private members
+ // =========================================
+
+ private static final String TAG = "DockPanel";
+ private DockPosition position;
+ private int contentLayoutId;
+ private int handleButtonDrawableId;
+ private Boolean isOpen;
+ private Boolean animationRunning;
+ private FrameLayout contentPlaceHolder;
+ private ImageButton toggleButton;
+ private int animationDuration;
+
+ // =========================================
+ // Constructors
+ // =========================================
+
+ public DockPanel(Context context, int contentLayoutId,
+ int handleButtonDrawableId, Boolean isOpen) {
+ super(context);
+
+ this.contentLayoutId = contentLayoutId;
+ this.handleButtonDrawableId = handleButtonDrawableId;
+ this.isOpen = isOpen;
+
+ Init(null);
+ }
+
+ public DockPanel(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ // to prevent from crashing the designer
+ try {
+ Init(attrs);
+ } catch (Exception ex) {
+ }
+ }
+
+ // =========================================
+ // Initialization
+ // =========================================
+
+ private void Init(AttributeSet attrs) {
+ setDefaultValues(attrs);
+
+ createHandleToggleButton();
+
+ // create the handle container
+ FrameLayout handleContainer = new FrameLayout(getContext());
+ handleContainer.addView(toggleButton);
+
+ // create and populate the panel's container, and inflate it
+ contentPlaceHolder = new FrameLayout(getContext());
+ String infService = Context.LAYOUT_INFLATER_SERVICE;
+ LayoutInflater li = (LayoutInflater) getContext().getSystemService(
+ infService);
+ li.inflate(contentLayoutId, contentPlaceHolder, true);
+
+ // setting the layout of the panel parameters according to the docking
+ // position
+ if (position == DockPosition.LEFT || position == DockPosition.RIGHT) {
+ handleContainer.setLayoutParams(new LayoutParams(
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT, 1));
+ contentPlaceHolder.setLayoutParams(new LayoutParams(
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT, 1));
+ } else {
+ handleContainer.setLayoutParams(new LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT, 1));
+ contentPlaceHolder.setLayoutParams(new LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT, 1));
+ }
+
+ // adding the view to the parent layout according to docking position
+ if (position == DockPosition.RIGHT || position == DockPosition.BOTTOM) {
+ this.addView(handleContainer);
+ this.addView(contentPlaceHolder);
+ } else {
+ this.addView(contentPlaceHolder);
+ this.addView(handleContainer);
+ }
+
+ if (!isOpen) {
+ contentPlaceHolder.setVisibility(GONE);
+ }
+ }
+
+ private void setDefaultValues(AttributeSet attrs) {
+ // set default values
+ isOpen = true;
+ animationRunning = false;
+ animationDuration = 500;
+ setPosition(DockPosition.RIGHT);
+
+ // Try to load values set by xml markup
+ if (attrs != null) {
+ String namespace = "http://com.MobileAnarchy.Android.Widgets";
+
+ animationDuration = attrs.getAttributeIntValue(namespace,
+ "animationDuration", 500);
+ contentLayoutId = attrs.getAttributeResourceValue(namespace,
+ "contentLayoutId", 0);
+ handleButtonDrawableId = attrs.getAttributeResourceValue(
+ namespace, "handleButtonDrawableResourceId", 0);
+ isOpen = attrs.getAttributeBooleanValue(namespace, "isOpen", true);
+
+ // Enums are a bit trickier (needs to be parsed)
+ try {
+ position = DockPosition.valueOf(attrs.getAttributeValue(
+ namespace, "dockPosition").toUpperCase());
+ setPosition(position);
+ } catch (Exception ex) {
+ // Docking to the left is the default behavior
+ setPosition(DockPosition.LEFT);
+ }
+ }
+ }
+
+ private void createHandleToggleButton() {
+ toggleButton = new ImageButton(getContext());
+ toggleButton.setPadding(0, 0, 0, 0);
+ toggleButton.setLayoutParams(new FrameLayout.LayoutParams(
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER));
+ toggleButton.setBackgroundColor(Color.TRANSPARENT);
+ toggleButton.setImageResource(handleButtonDrawableId);
+ toggleButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ toggle();
+ }
+ });
+ }
+
+ private void setPosition(DockPosition position) {
+ this.position = position;
+ switch (position) {
+ case TOP:
+ setOrientation(LinearLayout.VERTICAL);
+ setGravity(Gravity.TOP);
+ break;
+ case RIGHT:
+ setOrientation(LinearLayout.HORIZONTAL);
+ setGravity(Gravity.RIGHT);
+ break;
+ case BOTTOM:
+ setOrientation(LinearLayout.VERTICAL);
+ setGravity(Gravity.BOTTOM);
+ break;
+ case LEFT:
+ setOrientation(LinearLayout.HORIZONTAL);
+ setGravity(Gravity.LEFT);
+ break;
+ }
+ }
+
+ // =========================================
+ // Public methods
+ // =========================================
+
+ public int getAnimationDuration() {
+ return animationDuration;
+ }
+
+ public void setAnimationDuration(int milliseconds) {
+ animationDuration = milliseconds;
+ }
+
+ public Boolean getIsRunning() {
+ return animationRunning;
+ }
+
+ public void open() {
+ if (!animationRunning) {
+ Log.d(TAG, "Opening...");
+
+ Animation animation = createShowAnimation();
+ this.setAnimation(animation);
+ animation.start();
+
+ isOpen = true;
+ }
+ }
+
+ public void close() {
+ if (!animationRunning) {
+ Log.d(TAG, "Closing...");
+
+ Animation animation = createHideAnimation();
+ this.setAnimation(animation);
+ animation.start();
+ isOpen = false;
+ }
+ }
+
+ public void toggle() {
+ if (isOpen) {
+ close();
+ } else {
+ open();
+ }
+ }
+
+ // =========================================
+ // Private methods
+ // =========================================
+
+ private Animation createHideAnimation() {
+ Animation animation = null;
+ switch (position) {
+ case TOP:
+ animation = new TranslateAnimation(0, 0, 0, -contentPlaceHolder
+ .getHeight());
+ break;
+ case RIGHT:
+ animation = new TranslateAnimation(0, contentPlaceHolder
+ .getWidth(), 0, 0);
+ break;
+ case BOTTOM:
+ animation = new TranslateAnimation(0, 0, 0, contentPlaceHolder
+ .getHeight());
+ break;
+ case LEFT:
+ animation = new TranslateAnimation(0, -contentPlaceHolder
+ .getWidth(), 0, 0);
+ break;
+ }
+
+ animation.setDuration(animationDuration);
+ animation.setInterpolator(new AccelerateInterpolator());
+ animation.setAnimationListener(new AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ animationRunning = true;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ contentPlaceHolder.setVisibility(View.GONE);
+ animationRunning = false;
+ }
+ });
+ return animation;
+ }
+
+ private Animation createShowAnimation() {
+ Animation animation = null;
+ switch (position) {
+ case TOP:
+ animation = new TranslateAnimation(0, 0, -contentPlaceHolder
+ .getHeight(), 0);
+ break;
+ case RIGHT:
+ animation = new TranslateAnimation(contentPlaceHolder.getWidth(),
+ 0, 0, 0);
+ break;
+ case BOTTOM:
+ animation = new TranslateAnimation(0, 0, contentPlaceHolder
+ .getHeight(), 0);
+ break;
+ case LEFT:
+ animation = new TranslateAnimation(-contentPlaceHolder.getWidth(),
+ 0, 0, 0);
+ break;
+ }
+ Log.d(TAG, "Animation duration: " + animationDuration);
+ animation.setDuration(animationDuration);
+ animation.setInterpolator(new DecelerateInterpolator());
+ animation.setAnimationListener(new AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ animationRunning = true;
+ contentPlaceHolder.setVisibility(View.VISIBLE);
+ Log.d(TAG, "\"Show\" Animation started");
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ animationRunning = false;
+ Log.d(TAG, "\"Show\" Animation ended");
+ }
+ });
+ return animation;
+ }
+
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/DockPanel/DockPosition.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DockPanel/DockPosition.java
new file mode 100644
index 000000000..59643dfc6
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DockPanel/DockPosition.java
@@ -0,0 +1,5 @@
+package com.MobileAnarchy.Android.Widgets.DockPanel;
+
+public enum DockPosition {
+ TOP, BOTTOM, LEFT, RIGHT
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DragAndDropManager.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DragAndDropManager.java
new file mode 100644
index 000000000..d7e0c5e44
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DragAndDropManager.java
@@ -0,0 +1,124 @@
+package com.MobileAnarchy.Android.Widgets.DragAndDrop;
+
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+
+public class DragAndDropManager {
+
+ // =========================================
+ // Private members
+ // =========================================
+
+ protected static final String TAG = "DragAndDropManager";
+ private static DragAndDropManager instance = null;
+ private ArrayList dropZones;
+ private OnTouchListener originalTouchListener;
+ private DragSurface dragSurface;
+ private DraggableItem draggedItem;
+ private DropZone activeDropZone;
+
+ // =========================================
+ // Protected Constructor
+ // =========================================
+
+ protected DragAndDropManager() {
+ // Exists only to defeat instantiation.
+ dropZones = new ArrayList();
+ }
+
+ // =========================================
+ // Public Properties
+ // =========================================
+
+ public static DragAndDropManager getInstance() {
+ if (instance == null) {
+ instance = new DragAndDropManager();
+ }
+ return instance;
+ }
+
+ public Context getContext() {
+ if (dragSurface == null)
+ return null;
+
+ return dragSurface.getContext();
+ }
+
+ // =========================================
+ // Public Methods
+ // =========================================
+
+ public void init(DragSurface surface) {
+ dragSurface = surface;
+ clearDropZones();
+ }
+
+ public void clearDropZones() {
+ dropZones.clear();
+ }
+
+ public void addDropZone(DropZone dropZone) {
+ dropZones.add(dropZone);
+ }
+
+
+ public void startDragging(OnTouchListener originalListener, DraggableItem draggedItem) {
+ originalTouchListener = originalListener;
+ this.draggedItem = draggedItem;
+ draggedItem.getSource().setOnTouchListener(new OnTouchListener() {
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ int[] location = new int[2];
+ v.getLocationOnScreen(location);
+ event.offsetLocation(location[0], location[1]);
+ invalidateDropZones((int)event.getX(), (int)event.getY());
+ return dragSurface.onTouchEvent(event);
+ }
+ });
+
+ dragSurface.startDragging(draggedItem);
+ }
+
+
+ // =========================================
+ // Protected Methods
+ // =========================================
+
+ protected void invalidateDropZones(int x, int y) {
+ if (activeDropZone != null) {
+ if (!activeDropZone.isOver(x, y)) {
+ activeDropZone.getListener().OnDragZoneLeft(activeDropZone, draggedItem);
+ activeDropZone = null;
+ }
+ else {
+ // we are still over the same drop zone, no need to check other drop zones
+ return;
+ }
+ }
+
+ for (DropZone dropZone : dropZones) {
+ if (dropZone.isOver(x, y)) {
+ activeDropZone = dropZone;
+ dropZone.getListener().OnDragZoneEntered(activeDropZone, draggedItem);
+ break;
+ }
+ }
+ }
+
+ protected void stoppedDragging() {
+ if (activeDropZone != null) {
+ activeDropZone.getListener().OnDropped(activeDropZone, draggedItem);
+ }
+
+ // Registering the "old" listener to the view that initiated this drag session
+ draggedItem.getSource().setOnTouchListener(originalTouchListener);
+ draggedItem = null;
+ activeDropZone = null;
+ }
+
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DragSurface.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DragSurface.java
new file mode 100644
index 000000000..aebb9d481
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DragSurface.java
@@ -0,0 +1,94 @@
+package com.MobileAnarchy.Android.Widgets.DragAndDrop;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.widget.FrameLayout;
+
+public class DragSurface extends FrameLayout {
+
+ private float draggedViewHalfHeight;
+ private float draggedViewHalfWidth;
+ private int framesCount;
+
+ private Boolean isDragging;
+ private DraggableItem draggedItem;
+
+ public DragSurface(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ isDragging = false;
+ }
+
+ // =========================================
+ // Touch Events Listener
+ // =========================================
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (isDragging && event.getAction() == MotionEvent.ACTION_UP) {
+ // Dragging ended
+ removeAllViews();
+ isDragging = false;
+
+ DragAndDropManager.getInstance().stoppedDragging();
+ }
+
+ if (isDragging && event.getAction() == MotionEvent.ACTION_MOVE) {
+ // Move the dragged view to it's new position
+ repositionView(event.getX(), event.getY());
+
+ // Mark this event as handled (so that other UI elements will not intercept it)
+ return true;
+ }
+
+ return false;
+ }
+
+ public void startDragging(DraggableItem draggableItem) {
+ this.draggedItem = draggableItem;
+ this.draggedItem.getDraggedView().setVisibility(INVISIBLE);
+ isDragging = true;
+ addView(this.draggedItem.getDraggedView());
+ //repositionView(x, y);
+ framesCount = 0;
+ }
+
+ private void repositionView(float x, float y) {
+ draggedViewHalfHeight = draggedItem.getDraggedView().getHeight() / 2f;
+ draggedViewHalfWidth = draggedItem.getDraggedView().getWidth() / 2f;
+
+ // If the dragged view was not drawn yet, skip this phase
+ if (draggedViewHalfHeight == 0 || draggedViewHalfWidth == 0)
+ return;
+
+ framesCount++;
+
+ //Log.d(TAG, "Original = (x=" + x + ", y=" + y + ")");
+ //Log.d(TAG, "Size (W=" + draggedViewHalfWidth + ", H=" + draggedViewHalfHeight + ")");
+
+ x = x - draggedViewHalfWidth;
+ y = y - draggedViewHalfHeight;
+
+ x = Math.max(x, 0);
+ x = Math.min(x, getWidth() - draggedViewHalfWidth * 2);
+
+ y = Math.max(y, 0);
+ y = Math.min(y, getHeight() - draggedViewHalfHeight * 2);
+
+ //Log.d(TAG, "Moving view to (x=" + x + ", y=" + y + ")");
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT, Gravity.TOP + Gravity.LEFT);
+
+ lp.setMargins((int)x, (int)y, 0, 0);
+ draggedItem.getDraggedView().setLayoutParams(lp);
+
+ // hte first couple of dragged frame's positions are not calculated correctly,
+ // so we have a threshold before making the dragged view visible again
+ if (framesCount < 2)
+ return;
+
+ draggedItem.getDraggedView().setVisibility(VISIBLE);
+ }
+
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DraggableItem.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DraggableItem.java
new file mode 100644
index 000000000..d0c8b7e25
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DraggableItem.java
@@ -0,0 +1,44 @@
+package com.MobileAnarchy.Android.Widgets.DragAndDrop;
+
+import android.view.View;
+
+public class DraggableItem {
+
+ // =========================================
+ // Private members
+ // =========================================
+
+ private View source;
+ private View draggedView;
+ private Object tag;
+
+ // =========================================
+ // Constructor
+ // =========================================
+
+ public DraggableItem(View source, View draggedItem) {
+ this.source = source;
+ this.draggedView = draggedItem;
+ }
+
+ // =========================================
+ // Public properties
+ // =========================================
+
+ public Object getTag() {
+ return tag;
+ }
+
+ public void setTag(Object tag) {
+ this.tag = tag;
+ }
+
+ public View getSource() {
+ return source;
+ }
+
+ public View getDraggedView() {
+ return draggedView;
+ }
+
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DraggableViewsFactory.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DraggableViewsFactory.java
new file mode 100644
index 000000000..246b2392f
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DraggableViewsFactory.java
@@ -0,0 +1,19 @@
+package com.MobileAnarchy.Android.Widgets.DragAndDrop;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.TableLayout.LayoutParams;
+
+public class DraggableViewsFactory {
+
+ public static View getLabel(String text) {
+ Context context = DragAndDropManager.getInstance().getContext();
+ TextView textView = new TextView(context);
+ textView.setText(text);
+ textView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ //textView.setGravity(Gravity.TOP + Gravity.LEFT);
+ return textView;
+ }
+
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DropZone.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DropZone.java
new file mode 100644
index 000000000..06a8caa17
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DropZone.java
@@ -0,0 +1,67 @@
+package com.MobileAnarchy.Android.Widgets.DragAndDrop;
+
+import android.view.View;
+
+public class DropZone {
+
+ // =========================================
+ // Private members
+ // =========================================
+
+ private View view;
+ private DropZoneEventsListener listener;
+ private int left, top, width, height;
+ private Boolean dimansionsCalculated;
+
+ // =========================================
+ // Constructor
+ // =========================================
+
+ public DropZone(View view, DropZoneEventsListener listener) {
+ this.view = view;
+ this.listener = listener;
+ dimansionsCalculated = false;
+ }
+
+ // =========================================
+ // Public properties
+ // =========================================
+
+ public View getView() {
+ return view;
+ }
+
+ // =========================================
+ // Public methods
+ // =========================================
+
+ public Boolean isOver(int x, int y) {
+ if (!dimansionsCalculated)
+ calculateDimensions();
+
+ Boolean isOver = (x >= left && x <= (left + width)) &&
+ (y >= top && y <= (top + height));
+
+ //Log.d("DragZone", "x=" +x + ", left=" + left + ", y=" + y + ", top=" + top + " width=" + width + ", height=" + height + ", isover=" + isOver);
+
+ return isOver;
+ }
+
+ // =========================================
+ // Protected & Private methods
+ // =========================================
+
+ protected DropZoneEventsListener getListener() {
+ return listener;
+ }
+
+ private void calculateDimensions() {
+ int[] location = new int[2];
+ view.getLocationOnScreen(location);
+ left = location[0];
+ top = location[1];
+ width = view.getWidth();
+ height = view.getHeight();
+ dimansionsCalculated = true;
+ }
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DropZoneEventsListener.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DropZoneEventsListener.java
new file mode 100644
index 000000000..828a1cbf2
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/DragAndDrop/DropZoneEventsListener.java
@@ -0,0 +1,9 @@
+package com.MobileAnarchy.Android.Widgets.DragAndDrop;
+
+public interface DropZoneEventsListener {
+
+ void OnDragZoneEntered(DropZone zone, DraggableItem item);
+ void OnDragZoneLeft(DropZone zone, DraggableItem item);
+ void OnDropped(DropZone zone, DraggableItem item);
+
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/Joystick/DualJoystickView.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/Joystick/DualJoystickView.java
new file mode 100644
index 000000000..bb419d404
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/Joystick/DualJoystickView.java
@@ -0,0 +1,147 @@
+package com.MobileAnarchy.Android.Widgets.Joystick;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+public class DualJoystickView extends LinearLayout {
+ @SuppressWarnings("unused")
+ private static final String TAG = DualJoystickView.class.getSimpleName();
+
+ private final boolean D = false;
+ private Paint dbgPaint1;
+
+ private JoystickView stickL;
+ private JoystickView stickR;
+
+ private View pad;
+
+ public DualJoystickView(Context context) {
+ super(context);
+ stickL = new JoystickView(context);
+ stickR = new JoystickView(context);
+ initDualJoystickView();
+ }
+
+ public DualJoystickView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ stickL = new JoystickView(context, attrs);
+ stickR = new JoystickView(context, attrs);
+ initDualJoystickView();
+ }
+
+ private void initDualJoystickView() {
+ setOrientation(LinearLayout.HORIZONTAL);
+
+ if ( D ) {
+ dbgPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
+ dbgPaint1.setColor(Color.CYAN);
+ dbgPaint1.setStrokeWidth(1);
+ dbgPaint1.setStyle(Paint.Style.STROKE);
+ }
+
+ pad = new View(getContext());
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ removeView(stickL);
+ removeView(stickR);
+
+ float padW = getMeasuredWidth()-(getMeasuredHeight()*2);
+ int joyWidth = (int) ((getMeasuredWidth()-padW)/2);
+ LayoutParams joyLParams = new LayoutParams(joyWidth,getMeasuredHeight());
+
+ stickL.setLayoutParams(joyLParams);
+ stickR.setLayoutParams(joyLParams);
+
+ stickL.TAG = "L";
+ stickR.TAG = "R";
+ stickL.setPointerId(JoystickView.INVALID_POINTER_ID);
+ stickR.setPointerId(JoystickView.INVALID_POINTER_ID);
+
+ addView(stickL);
+
+ ViewGroup.LayoutParams padLParams = new ViewGroup.LayoutParams((int) padW,getMeasuredHeight());
+ removeView(pad);
+ pad.setLayoutParams(padLParams);
+ addView(pad);
+
+ addView(stickR);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ stickR.setTouchOffset(stickR.getLeft(), stickR.getTop());
+ }
+
+ public void setAutoReturnToCenter(boolean left, boolean right) {
+ stickL.setAutoReturnToCenter(left);
+ stickR.setAutoReturnToCenter(right);
+ }
+
+ public void setOnJostickMovedListener(JoystickMovedListener left, JoystickMovedListener right) {
+ stickL.setOnJostickMovedListener(left);
+ stickR.setOnJostickMovedListener(right);
+ }
+
+ public void setOnJostickClickedListener(JoystickClickedListener left, JoystickClickedListener right) {
+ stickL.setOnJostickClickedListener(left);
+ stickR.setOnJostickClickedListener(right);
+ }
+
+ public void setYAxisInverted(boolean leftYAxisInverted, boolean rightYAxisInverted) {
+ stickL.setYAxisInverted(leftYAxisInverted);
+ stickL.setYAxisInverted(rightYAxisInverted);
+ }
+
+ public void setMovementConstraint(int movementConstraint) {
+ stickL.setMovementConstraint(movementConstraint);
+ stickR.setMovementConstraint(movementConstraint);
+ }
+
+ public void setMovementRange(float movementRangeLeft, float movementRangeRight) {
+ stickL.setMovementRange(movementRangeLeft);
+ stickR.setMovementRange(movementRangeRight);
+ }
+
+ public void setMoveResolution(float leftMoveResolution, float rightMoveResolution) {
+ stickL.setMoveResolution(leftMoveResolution);
+ stickR.setMoveResolution(rightMoveResolution);
+ }
+
+ public void setUserCoordinateSystem(int leftCoordinateSystem, int rightCoordinateSystem) {
+ stickL.setUserCoordinateSystem(leftCoordinateSystem);
+ stickR.setUserCoordinateSystem(rightCoordinateSystem);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ if (D) {
+ canvas.drawRect(1, 1, getMeasuredWidth()-1, getMeasuredHeight()-1, dbgPaint1);
+ }
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ boolean l = stickL.dispatchTouchEvent(ev);
+ boolean r = stickR.dispatchTouchEvent(ev);
+ return l || r;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ boolean l = stickL.onTouchEvent(ev);
+ boolean r = stickR.onTouchEvent(ev);
+ return l || r;
+ }
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickClickedListener.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickClickedListener.java
new file mode 100644
index 000000000..128828980
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickClickedListener.java
@@ -0,0 +1,6 @@
+package com.MobileAnarchy.Android.Widgets.Joystick;
+
+public interface JoystickClickedListener {
+ public void OnClicked();
+ public void OnReleased();
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickMovedListener.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickMovedListener.java
new file mode 100644
index 000000000..346f2efed
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickMovedListener.java
@@ -0,0 +1,7 @@
+package com.MobileAnarchy.Android.Widgets.Joystick;
+
+public interface JoystickMovedListener {
+ public void OnMoved(int pan, int tilt);
+ public void OnReleased();
+ public void OnReturnedToCenter();
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickView.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickView.java
new file mode 100644
index 000000000..a61fdfd1a
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/Joystick/JoystickView.java
@@ -0,0 +1,520 @@
+package com.MobileAnarchy.Android.Widgets.Joystick;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+import android.view.View;
+
+public class JoystickView extends View {
+ public static final int INVALID_POINTER_ID = -1;
+
+ // =========================================
+ // Private Members
+ // =========================================
+ private final boolean D = false;
+ String TAG = "JoystickView";
+
+ private Paint dbgPaint1;
+ private Paint dbgPaint2;
+
+ private Paint bgPaint;
+ private Paint handlePaint;
+
+ private int innerPadding;
+ private int bgRadius;
+ private int handleRadius;
+ private int movementRadius;
+ private int handleInnerBoundaries;
+
+ private JoystickMovedListener moveListener;
+ private JoystickClickedListener clickListener;
+
+ //# of pixels movement required between reporting to the listener
+ private float moveResolution;
+
+ private boolean yAxisInverted;
+ private boolean autoReturnToCenter;
+
+ //Max range of movement in user coordinate system
+ public final static int CONSTRAIN_BOX = 0;
+ public final static int CONSTRAIN_CIRCLE = 1;
+ private int movementConstraint;
+ private float movementRange;
+
+ public final static int COORDINATE_CARTESIAN = 0; //Regular cartesian coordinates
+ public final static int COORDINATE_DIFFERENTIAL = 1; //Uses polar rotation of 45 degrees to calc differential drive paramaters
+ private int userCoordinateSystem;
+
+ //Records touch pressure for click handling
+ private float touchPressure;
+ private boolean clicked;
+ private float clickThreshold;
+
+ //Last touch point in view coordinates
+ private int pointerId = INVALID_POINTER_ID;
+ private float touchX, touchY;
+
+ //Last reported position in view coordinates (allows different reporting sensitivities)
+ private float reportX, reportY;
+
+ //Handle center in view coordinates
+ private float handleX, handleY;
+
+ //Center of the view in view coordinates
+ private int cX, cY;
+
+ //Size of the view in view coordinates
+ private int dimX;
+
+ //Cartesian coordinates of last touch point - joystick center is (0,0)
+ private int cartX, cartY;
+
+ //Polar coordinates of the touch point from joystick center
+ private double radial;
+ private double angle;
+
+ //User coordinates of last touch point
+ private int userX, userY;
+
+ //Offset co-ordinates (used when touch events are received from parent's coordinate origin)
+ private int offsetX;
+ private int offsetY;
+
+ // =========================================
+ // Constructors
+ // =========================================
+
+ public JoystickView(Context context) {
+ super(context);
+ initJoystickView();
+ }
+
+ public JoystickView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initJoystickView();
+ }
+
+ public JoystickView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initJoystickView();
+ }
+
+ // =========================================
+ // Initialization
+ // =========================================
+
+ private void initJoystickView() {
+ setFocusable(true);
+
+ dbgPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
+ dbgPaint1.setColor(Color.RED);
+ dbgPaint1.setStrokeWidth(1);
+ dbgPaint1.setStyle(Paint.Style.STROKE);
+
+ dbgPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
+ dbgPaint2.setColor(Color.GREEN);
+ dbgPaint2.setStrokeWidth(1);
+ dbgPaint2.setStyle(Paint.Style.STROKE);
+
+ bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ bgPaint.setColor(Color.GRAY);
+ bgPaint.setStrokeWidth(1);
+ bgPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+ handlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ handlePaint.setColor(Color.DKGRAY);
+ handlePaint.setStrokeWidth(1);
+ handlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+ innerPadding = 10;
+
+ setMovementRange(10);
+ setMoveResolution(1.0f);
+ setClickThreshold(0.4f);
+ setYAxisInverted(true);
+ setUserCoordinateSystem(COORDINATE_CARTESIAN);
+ setAutoReturnToCenter(true);
+ }
+
+ public void setAutoReturnToCenter(boolean autoReturnToCenter) {
+ this.autoReturnToCenter = autoReturnToCenter;
+ }
+
+ public boolean isAutoReturnToCenter() {
+ return autoReturnToCenter;
+ }
+
+ public void setUserCoordinateSystem(int userCoordinateSystem) {
+ if (userCoordinateSystem < COORDINATE_CARTESIAN || movementConstraint > COORDINATE_DIFFERENTIAL)
+ Log.e(TAG, "invalid value for userCoordinateSystem");
+ else
+ this.userCoordinateSystem = userCoordinateSystem;
+ }
+
+ public int getUserCoordinateSystem() {
+ return userCoordinateSystem;
+ }
+
+ public void setMovementConstraint(int movementConstraint) {
+ if (movementConstraint < CONSTRAIN_BOX || movementConstraint > CONSTRAIN_CIRCLE)
+ Log.e(TAG, "invalid value for movementConstraint");
+ else
+ this.movementConstraint = movementConstraint;
+ }
+
+ public int getMovementConstraint() {
+ return movementConstraint;
+ }
+
+ public boolean isYAxisInverted() {
+ return yAxisInverted;
+ }
+
+ public void setYAxisInverted(boolean yAxisInverted) {
+ this.yAxisInverted = yAxisInverted;
+ }
+
+ /**
+ * Set the pressure sensitivity for registering a click
+ * @param clickThreshold threshold 0...1.0f inclusive. 0 will cause clicks to never be reported, 1.0 is a very hard click
+ */
+ public void setClickThreshold(float clickThreshold) {
+ if (clickThreshold < 0 || clickThreshold > 1.0f)
+ Log.e(TAG, "clickThreshold must range from 0...1.0f inclusive");
+ else
+ this.clickThreshold = clickThreshold;
+ }
+
+ public float getClickThreshold() {
+ return clickThreshold;
+ }
+
+ public void setMovementRange(float movementRange) {
+ this.movementRange = movementRange;
+ }
+
+ public float getMovementRange() {
+ return movementRange;
+ }
+
+ public void setMoveResolution(float moveResolution) {
+ this.moveResolution = moveResolution;
+ }
+
+ public float getMoveResolution() {
+ return moveResolution;
+ }
+
+ // =========================================
+ // Public Methods
+ // =========================================
+
+ public void setOnJostickMovedListener(JoystickMovedListener listener) {
+ this.moveListener = listener;
+ }
+
+ public void setOnJostickClickedListener(JoystickClickedListener listener) {
+ this.clickListener = listener;
+ }
+
+ // =========================================
+ // Drawing Functionality
+ // =========================================
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Here we make sure that we have a perfect circle
+ int measuredWidth = measure(widthMeasureSpec);
+ int measuredHeight = measure(heightMeasureSpec);
+ setMeasuredDimension(measuredWidth, measuredHeight);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ int d = Math.min(getMeasuredWidth(), getMeasuredHeight());
+
+ dimX = d;
+ cX = d / 2;
+ cY = d / 2;
+
+ bgRadius = dimX/2 - innerPadding;
+ handleRadius = (int)(d * 0.25);
+ handleInnerBoundaries = handleRadius;
+ movementRadius = Math.min(cX, cY) - handleInnerBoundaries;
+ }
+
+ private int measure(int measureSpec) {
+ int result = 0;
+ // Decode the measurement specifications.
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+ if (specMode == MeasureSpec.UNSPECIFIED) {
+ // Return a default size of 200 if no bounds are specified.
+ result = 200;
+ } else {
+ // As you want to fill the available space
+ // always return the full available bounds.
+ result = specSize;
+ }
+ return result;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.save();
+ // Draw the background
+ canvas.drawCircle(cX, cY, bgRadius, bgPaint);
+
+ // Draw the handle
+ handleX = touchX + cX;
+ handleY = touchY + cY;
+ canvas.drawCircle(handleX, handleY, handleRadius, handlePaint);
+
+ if (D) {
+ canvas.drawRect(1, 1, getMeasuredWidth()-1, getMeasuredHeight()-1, dbgPaint1);
+
+ canvas.drawCircle(handleX, handleY, 3, dbgPaint1);
+
+ if ( movementConstraint == CONSTRAIN_CIRCLE ) {
+ canvas.drawCircle(cX, cY, this.movementRadius, dbgPaint1);
+ }
+ else {
+ canvas.drawRect(cX-movementRadius, cY-movementRadius, cX+movementRadius, cY+movementRadius, dbgPaint1);
+ }
+
+ //Origin to touch point
+ canvas.drawLine(cX, cY, handleX, handleY, dbgPaint2);
+
+ int baseY = (int) (touchY < 0 ? cY + handleRadius : cY - handleRadius);
+ canvas.drawText(String.format("%s (%.0f,%.0f)", TAG, touchX, touchY), handleX-20, baseY-7, dbgPaint2);
+ canvas.drawText("("+ String.format("%.0f, %.1f", radial, angle * 57.2957795) + (char) 0x00B0 + ")", handleX-20, baseY+15, dbgPaint2);
+ }
+
+// Log.d(TAG, String.format("touch(%f,%f)", touchX, touchY));
+// Log.d(TAG, String.format("onDraw(%.1f,%.1f)\n\n", handleX, handleY));
+ canvas.restore();
+ }
+
+ // Constrain touch within a box
+ private void constrainBox() {
+ touchX = Math.max(Math.min(touchX, movementRadius), -movementRadius);
+ touchY = Math.max(Math.min(touchY, movementRadius), -movementRadius);
+ }
+
+ // Constrain touch within a circle
+ private void constrainCircle() {
+ float diffX = touchX;
+ float diffY = touchY;
+ double radial = FloatMath.sqrt((diffX*diffX) + (diffY*diffY));
+ if ( radial > movementRadius ) {
+ touchX = (int)((diffX / radial) * movementRadius);
+ touchY = (int)((diffY / radial) * movementRadius);
+ }
+ }
+
+ public void setPointerId(int id) {
+ this.pointerId = id;
+ }
+
+ public int getPointerId() {
+ return pointerId;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ final int action = ev.getAction();
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_MOVE: {
+ return processMoveEvent(ev);
+ }
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP: {
+ if ( pointerId != INVALID_POINTER_ID ) {
+// Log.d(TAG, "ACTION_UP");
+ returnHandleToCenter();
+ setPointerId(INVALID_POINTER_ID);
+ }
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_UP: {
+ if ( pointerId != INVALID_POINTER_ID ) {
+ final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if ( pointerId == this.pointerId ) {
+// Log.d(TAG, "ACTION_POINTER_UP: " + pointerId);
+ returnHandleToCenter();
+ setPointerId(INVALID_POINTER_ID);
+ return true;
+ }
+ }
+ break;
+ }
+ case MotionEvent.ACTION_DOWN: {
+ if ( pointerId == INVALID_POINTER_ID ) {
+ int x = (int) ev.getX();
+ if ( x >= offsetX && x < offsetX + dimX ) {
+ setPointerId(ev.getPointerId(0));
+// Log.d(TAG, "ACTION_DOWN: " + getPointerId());
+ return true;
+ }
+ }
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ if ( pointerId == INVALID_POINTER_ID ) {
+ final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerId = ev.getPointerId(pointerIndex);
+ int x = (int) ev.getX(pointerId);
+ if ( x >= offsetX && x < offsetX + dimX ) {
+// Log.d(TAG, "ACTION_POINTER_DOWN: " + pointerId);
+ setPointerId(pointerId);
+ return true;
+ }
+ }
+ break;
+ }
+ }
+ return false;
+ }
+
+ private boolean processMoveEvent(MotionEvent ev) {
+ if ( pointerId != INVALID_POINTER_ID ) {
+ final int pointerIndex = ev.findPointerIndex(pointerId);
+
+ // Translate touch position to center of view
+ float x = ev.getX(pointerIndex);
+ touchX = x - cX - offsetX;
+ float y = ev.getY(pointerIndex);
+ touchY = y - cY - offsetY;
+
+// Log.d(TAG, String.format("ACTION_MOVE: (%03.0f, %03.0f) => (%03.0f, %03.0f)", x, y, touchX, touchY));
+
+ reportOnMoved();
+ invalidate();
+
+ touchPressure = ev.getPressure(pointerIndex);
+ reportOnPressure();
+
+ return true;
+ }
+ return false;
+ }
+
+ private void reportOnMoved() {
+ if ( movementConstraint == CONSTRAIN_CIRCLE )
+ constrainCircle();
+ else
+ constrainBox();
+
+ calcUserCoordinates();
+
+ if (moveListener != null) {
+ boolean rx = Math.abs(touchX - reportX) >= moveResolution;
+ boolean ry = Math.abs(touchY - reportY) >= moveResolution;
+ if (rx || ry) {
+ this.reportX = touchX;
+ this.reportY = touchY;
+
+// Log.d(TAG, String.format("moveListener.OnMoved(%d,%d)", (int)userX, (int)userY));
+ moveListener.OnMoved(userX, userY);
+ }
+ }
+ }
+
+ private void calcUserCoordinates() {
+ //First convert to cartesian coordinates
+ cartX = (int)(touchX / movementRadius * movementRange);
+ cartY = (int)(touchY / movementRadius * movementRange);
+
+ radial = Math.sqrt((cartX*cartX) + (cartY*cartY));
+ angle = Math.atan2(cartY, cartX);
+
+ //Invert Y axis if requested
+ if ( !yAxisInverted )
+ cartY *= -1;
+
+ if ( userCoordinateSystem == COORDINATE_CARTESIAN ) {
+ userX = cartX;
+ userY = cartY;
+ }
+ else if ( userCoordinateSystem == COORDINATE_DIFFERENTIAL ) {
+ userX = cartY + cartX / 4;
+ userY = cartY - cartX / 4;
+
+ if ( userX < -movementRange )
+ userX = (int)-movementRange;
+ if ( userX > movementRange )
+ userX = (int)movementRange;
+
+ if ( userY < -movementRange )
+ userY = (int)-movementRange;
+ if ( userY > movementRange )
+ userY = (int)movementRange;
+ }
+
+ }
+
+ //Simple pressure click
+ private void reportOnPressure() {
+// Log.d(TAG, String.format("touchPressure=%.2f", this.touchPressure));
+ if ( clickListener != null ) {
+ if ( clicked && touchPressure < clickThreshold ) {
+ clickListener.OnReleased();
+ this.clicked = false;
+// Log.d(TAG, "reset click");
+ invalidate();
+ }
+ else if ( !clicked && touchPressure >= clickThreshold ) {
+ clicked = true;
+ clickListener.OnClicked();
+// Log.d(TAG, "click");
+ invalidate();
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ }
+ }
+ }
+
+ private void returnHandleToCenter() {
+ if ( autoReturnToCenter ) {
+ final int numberOfFrames = 5;
+ final double intervalsX = (0 - touchX) / numberOfFrames;
+ final double intervalsY = (0 - touchY) / numberOfFrames;
+
+ for (int i = 0; i < numberOfFrames; i++) {
+ final int j = i;
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ touchX += intervalsX;
+ touchY += intervalsY;
+
+ // No longer reportOnMoved() in this method because we only
+ // want to receive inputs from the user
+ invalidate();
+
+ if (moveListener != null && j == numberOfFrames - 1) {
+ moveListener.OnReturnedToCenter();
+ }
+ }
+ }, i * 40);
+ }
+
+ if (moveListener != null) {
+ moveListener.OnReleased();
+ }
+ }
+ }
+
+ public void setTouchOffset(int x, int y) {
+ offsetX = x;
+ offsetY = y;
+ }
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/ThresholdEditText/ThresholdEditText.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/ThresholdEditText/ThresholdEditText.java
new file mode 100644
index 000000000..5fbd76f46
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/ThresholdEditText/ThresholdEditText.java
@@ -0,0 +1,169 @@
+package com.MobileAnarchy.Android.Widgets.ThresholdEditText;
+
+import android.content.Context;
+import android.os.Handler;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.widget.EditText;
+
+public class ThresholdEditText extends EditText {
+
+ // =========================================
+ // Private members
+ // =========================================
+
+ private int threshold;
+ private ThresholdTextChanged thresholdTextChanged;
+ private Handler handler;
+ private Runnable invoker;
+ private boolean thresholdDisabledOnEmptyInput;
+
+
+ // =========================================
+ // Constructors
+ // =========================================
+
+ public ThresholdEditText(Context context) {
+ super(context);
+ initAttributes(null);
+ init();
+ }
+
+ public ThresholdEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initAttributes(attrs);
+ init();
+ }
+
+
+ // =========================================
+ // Public properties
+ // =========================================
+
+ /**
+ * Get the current threshold value
+ */
+ public int getThreshold() {
+ return threshold;
+ }
+
+ /**
+ * Set the threshold value (in milliseconds)
+ *
+ * @param threshold
+ * Threshold value
+ */
+ public void setThreshold(int threshold) {
+ this.threshold = threshold;
+ }
+
+ /**
+ * @return True = the callback will fire immediately when the content of the
+ * EditText is emptied False = The threshold will be used even on
+ * empty input
+ */
+ public boolean getThresholdDisabledOnEmptyInput() {
+ return thresholdDisabledOnEmptyInput;
+ }
+
+ /**
+ * @param thresholdDisabledOnEmptyInput
+ * Set to true if you want the callback to fire immediately when
+ * the content of the EditText is emptied
+ */
+ public void setThresholdDisabledOnEmptyInput(
+ boolean thresholdDisabledOnEmptyInput) {
+ this.thresholdDisabledOnEmptyInput = thresholdDisabledOnEmptyInput;
+ }
+
+ /**
+ * Set the callback to the OnThresholdTextChanged event
+ *
+ * @param listener
+ */
+ public void setOnThresholdTextChanged(ThresholdTextChanged listener) {
+ this.thresholdTextChanged = listener;
+ }
+
+ // =========================================
+ // Private / Protected methods
+ // =========================================
+
+ /**
+ * Load properties values from xml layout
+ */
+ private void initAttributes(AttributeSet attrs) {
+ if (attrs != null) {
+ String namespace = "http://com.MobileAnarchy.Android.Widgets";
+
+ // Load values to local members
+ this.threshold = attrs.getAttributeIntValue(namespace, "threshold",
+ 500);
+ this.thresholdDisabledOnEmptyInput = attrs.getAttributeBooleanValue(
+ namespace, "disableThresholdOnEmptyInput", true);
+ } else {
+ // Default threshold value is 0.5 seconds
+ threshold = 500;
+
+ // Default behaviour on emptied text - no threshold
+ thresholdDisabledOnEmptyInput = true;
+ }
+ }
+
+ /**
+ * Initialize the private members with default values
+ */
+ private void init() {
+
+ handler = new Handler();
+
+ invoker = new Runnable() {
+
+ @Override
+ public void run() {
+ invokeCallback();
+ }
+
+ };
+
+ this.addTextChangedListener(new TextWatcher() {
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count,
+ int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before,
+ int count) {
+
+ // Remove any existing pending callbacks
+ handler.removeCallbacks(invoker);
+
+ if (s.length() == 0 && thresholdDisabledOnEmptyInput) {
+ // The text is empty, so invoke the callback immediately
+ invoker.run();
+ } else {
+ // Post a new delayed callback
+ handler.postDelayed(invoker, threshold);
+ }
+ }
+
+ });
+ }
+
+ /**
+ * Invoking the callback on the listener provided (if provided)
+ */
+ private void invokeCallback() {
+ if (thresholdTextChanged != null) {
+ thresholdTextChanged.onThersholdTextChanged(this.getText());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/ThresholdEditText/ThresholdTextChanged.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/ThresholdEditText/ThresholdTextChanged.java
new file mode 100644
index 000000000..145f6547b
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/ThresholdEditText/ThresholdTextChanged.java
@@ -0,0 +1,7 @@
+package com.MobileAnarchy.Android.Widgets.ThresholdEditText;
+
+import android.text.Editable;
+
+public interface ThresholdTextChanged {
+ void onThersholdTextChanged(Editable text);
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/TilesLayout/SingleTileLayout.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/TilesLayout/SingleTileLayout.java
new file mode 100644
index 000000000..d2f3be2d3
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/TilesLayout/SingleTileLayout.java
@@ -0,0 +1,68 @@
+package com.MobileAnarchy.Android.Widgets.TilesLayout;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+public class SingleTileLayout extends FrameLayout {
+
+ // =========================================
+ // Private members
+ // =========================================
+
+ private TilePosition position;
+ private long timestamp;
+
+ // =========================================
+ // Constructors
+ // =========================================
+
+ public SingleTileLayout(Context context) {
+ super(context);
+ }
+
+ public SingleTileLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+
+ // =========================================
+ // Public Methods
+ // =========================================
+
+ public TilePosition getPosition() {
+ return position;
+ }
+
+ public void setPosition(TilePosition position) {
+ this.position = position;
+ }
+
+ public long getTimestamp() {
+ return this.timestamp;
+ }
+
+ // =========================================
+ // Overrides
+ // =========================================
+
+ @Override
+ public void addView(View child) {
+ super.addView(child);
+ timestamp = java.lang.System.currentTimeMillis();
+ }
+
+ @Override
+ public void removeAllViews() {
+ super.removeAllViews();
+ timestamp = 0;
+ }
+
+ @Override
+ public void removeView(View view) {
+ super.removeView(view);
+ timestamp = 0;
+ }
+
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/TilesLayout/TilePosition.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/TilesLayout/TilePosition.java
new file mode 100644
index 000000000..04bf0c1d4
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/TilesLayout/TilePosition.java
@@ -0,0 +1,63 @@
+package com.MobileAnarchy.Android.Widgets.TilesLayout;
+
+
+public class TilePosition {
+
+ // =========================================
+ // Private Members
+ // =========================================
+
+ private float x, y, height, width;
+
+ // =========================================
+ // Constructors
+ // =========================================
+
+ public TilePosition(float x, float y, float width, float height) {
+ this.x = x;
+ this.y = y;
+ this.height = height;
+ this.width = width;
+ }
+
+ // =========================================
+ // Public Properties
+ // =========================================
+
+ public float getX() {
+ return x;
+ }
+
+ public float getY() {
+ return y;
+ }
+
+ public float getHeight() {
+ return height;
+ }
+
+ public float getWidth() {
+ return width;
+ }
+
+
+ // =========================================
+ // Public Methods
+ // =========================================
+
+ public Boolean equals(TilePosition position) {
+ if (position == null)
+ return false;
+
+ return this.x == position.x &&
+ this.y == position.y &&
+ this.height == position.height &&
+ this.width == position.width;
+ }
+
+ @Override
+ public String toString() {
+ return "TilePosition = [X: " + x + ", Y: " + y + ", Height: " + height + ", Width: " + width + "]";
+ }
+
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/TilesLayout/TilesLayout.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/TilesLayout/TilesLayout.java
new file mode 100644
index 000000000..b6086e0ca
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/TilesLayout/TilesLayout.java
@@ -0,0 +1,296 @@
+package com.MobileAnarchy.Android.Widgets.TilesLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
+import android.view.animation.Animation.AnimationListener;
+import android.widget.FrameLayout;
+
+public class TilesLayout extends FrameLayout {
+
+ // =========================================
+ // Private members
+ // =========================================
+
+ private static final String TAG = "TilesLayout";
+ private int animatedTransitionDuration;
+ private List tiles;
+ private TilesLayoutPreset preset;
+ private int tileBackgroundResourceId;
+
+ // =========================================
+ // Constructors
+ // =========================================
+
+ public TilesLayout(Context context) {
+ super(context);
+ Init(null);
+ }
+
+ public TilesLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ Init(attrs);
+ }
+
+
+ // =========================================
+ // Public Properties
+ // =========================================
+
+ public void setPreset(TilesLayoutPreset preset) {
+ try {
+ rebuildLayout(preset);
+ this.preset = preset;
+ }
+ catch (Exception ex) {
+ Log.e(TAG, "Failed to set layout preset", ex);
+ }
+ }
+
+ public TilesLayoutPreset getPreset() {
+ return this.preset;
+ }
+
+ public int getAnimatedTransitionDuration() {
+ return animatedTransitionDuration;
+ }
+
+ public void setAnimatedTransitionDuration(int animatedTransitionDuration) {
+ this.animatedTransitionDuration = animatedTransitionDuration;
+ }
+
+ public int getTileBackgroundResourceId() {
+ return tileBackgroundResourceId;
+ }
+
+ public void setTileBackgroundResourceId(int tileBackgroundResourceId) {
+ this.tileBackgroundResourceId = tileBackgroundResourceId;
+ }
+
+ // =========================================
+ // Public Methods
+ // =========================================
+
+ public void addContent(View view) {
+ for (int i = 0; i < tiles.size(); i++) {
+ if (tiles.get(i).getChildCount() == 0) {
+ tiles.get(i).addView(view);
+ return;
+ }
+ }
+ // No available space for the new view...
+ // TODO: Take the tile with the smallest time stamp and place the new view in it
+ }
+
+ public void clearView(int tileId) {
+ if (tiles.size() < tileId) {
+ tiles.get(tileId).removeAllViews();
+ }
+ }
+
+ // =========================================
+ // Private Methods
+ // =========================================
+
+ private void Init(AttributeSet attrs) {
+ animatedTransitionDuration = 750;
+ tileBackgroundResourceId = android.R.drawable.edit_text;
+ tiles = new ArrayList();
+ }
+
+
+ private void rebuildLayout(TilesLayoutPreset preset) {
+ ArrayList positions = buildViewsPositions(preset);
+
+ // We need to transform the current layout, to the new layout
+ int extraViews = tiles.size() - positions.size();
+ if (extraViews > 0) {
+ // Remove the extra views
+ while(tiles.size() - positions.size() > 0) {
+ int lastViewPosition = tiles.size() - 1;
+ removeView(tiles.get(lastViewPosition));
+ tiles.remove(lastViewPosition);
+ }
+ } else {
+ // Add the extra views
+ for (int i = tiles.size(); i< positions.size(); i++) {
+ TilePosition newTilePosition = positions.get(i);
+ SingleTileLayout tile = new SingleTileLayout(getContext());
+ tile.setBackgroundResource(tileBackgroundResourceId);
+
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+ (int)newTilePosition.getWidth(),
+ (int)newTilePosition.getHeight(),
+ Gravity.TOP + Gravity.LEFT);
+ lp.setMargins((int)newTilePosition.getX(),
+ (int)newTilePosition.getY(), 0, 0);
+
+ tile.setLayoutParams(lp);
+
+ tiles.add(tile);
+ addView(tile);
+ }
+ }
+ // There is a bug in the animation-set, so we'll not animate
+ animateChange(positions);
+
+ // Regular repositioning (no animation)
+ //processChange(positions);
+ }
+
+
+
+ private ArrayList buildViewsPositions(TilesLayoutPreset preset) {
+ int width = getWidth();
+ int height = getHeight();
+
+ Log.d(TAG, "Container's Dimensions = Width: " + width + ", Height: " + height);
+ ArrayList actualPositions = new ArrayList();
+ for (TilePosition position : preset.getTilePositions()) {
+
+ int tileX = (int) Math.round(width * ((float)position.getX() / 100.0));
+ int tileY = (int) Math.round(height * ((float)position.getY() / 100.0));
+ int tileWidth = (int) Math.round(width * ((float)position.getWidth() / 100.0));
+ int tileHeight = (int) Math.round(height * ((float)position.getHeight() / 100.0));
+
+ TilePosition actualPosition = new TilePosition(tileX, tileY, tileWidth, tileHeight);
+ actualPositions.add(actualPosition);
+
+ Log.d(TAG, "New tile created - X: " + tileX + ", Y: " + tileY + ", Width: " + tileWidth + ", Height: " + tileHeight);
+ }
+ return actualPositions;
+ }
+
+
+ @SuppressWarnings("unused")
+ private void processChange(ArrayList positions) {
+ for (int i = 0; i < tiles.size(); i++) {
+ final SingleTileLayout currentTile = tiles.get(i);
+ final TilePosition targetPosition = positions.get(i);
+ currentTile.setPosition(targetPosition);
+
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+ (int)targetPosition.getWidth(),
+ (int)targetPosition.getHeight(),
+ Gravity.TOP + Gravity.LEFT);
+ lp.setMargins((int)targetPosition.getX(),
+ (int)targetPosition.getY(), 0, 0);
+
+ currentTile.setLayoutParams(lp);
+ }
+ }
+
+
+ private void animateChange(ArrayList positions) {
+ AnimationSet animationSet = new AnimationSet(true);
+ DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator();
+
+ for (int i = 0; i < tiles.size(); i++) {
+ AnimationSet scaleAndMove = new AnimationSet(true);
+ scaleAndMove.setFillAfter(true);
+
+ final SingleTileLayout currentTile = tiles.get(i);
+ TilePosition currentPosition = currentTile.getPosition();
+ final TilePosition targetPosition = positions.get(i);
+
+ if (currentPosition == null) {
+ AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
+ alphaAnimation.setDuration(animatedTransitionDuration);
+ alphaAnimation.setStartOffset(0);
+ currentTile.setAnimation(alphaAnimation);
+
+ scaleAndMove.addAnimation(alphaAnimation);
+ }
+
+ currentTile.setPosition(targetPosition);
+
+ if (!targetPosition.equals(currentPosition)) {
+ float toXDelta = 0, toYDelta = 0;
+ if (currentPosition != null) {
+ // Calculate new position
+ toXDelta = targetPosition.getX() - currentPosition.getX();
+ toYDelta = targetPosition.getY() - currentPosition.getY();
+
+ // Factor in the scaling animation
+ toXDelta = toXDelta / (targetPosition.getWidth() / currentPosition.getWidth());
+ toYDelta = toYDelta / (targetPosition.getHeight() / currentPosition.getHeight());
+ }
+
+ // Move
+ TranslateAnimation moveAnimation = new TranslateAnimation(0, toXDelta, 0, toYDelta);
+ moveAnimation.setDuration(animatedTransitionDuration);
+ moveAnimation.setStartOffset(0);
+ moveAnimation.setFillAfter(true);
+ moveAnimation.setInterpolator(decelerateInterpolator);
+ scaleAndMove.addAnimation(moveAnimation);
+
+ // Physically move the tile when the animation ends
+ scaleAndMove.setAnimationListener(new AnimationListener() {
+
+ @Override
+ public void onAnimationStart(Animation animation) { }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) { }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+ (int)targetPosition.getWidth(),
+ (int)targetPosition.getHeight(),
+ Gravity.TOP + Gravity.LEFT);
+ lp.setMargins((int)targetPosition.getX(),
+ (int)targetPosition.getY(), 0, 0);
+
+ currentTile.setLayoutParams(lp);
+
+ // The following null animation just gets rid of screen flicker
+ animation = new TranslateAnimation(0.0f, 0.0f, 0.0f, 0.0f);
+ animation.setDuration(1);
+ currentTile.startAnimation(animation);
+ }
+ });
+
+ // Scale
+ if (currentPosition != null) {
+ ScaleAnimation scaleAnimation =
+ new ScaleAnimation(1,
+ targetPosition.getWidth() / currentPosition.getWidth(),
+ 1,
+ targetPosition.getHeight() / currentPosition.getHeight(),
+ Animation.ABSOLUTE, 0,
+ Animation.ABSOLUTE, 0);
+
+ scaleAnimation.setDuration(animatedTransitionDuration);
+ scaleAnimation.setStartOffset(0);
+ scaleAnimation.setFillAfter(true);
+ scaleAndMove.addAnimation(scaleAnimation);
+ }
+
+ // Set animation to the tile
+ currentTile.setAnimation(scaleAndMove);
+ }
+ // Add to the total animation set
+ animationSet.addAnimation(scaleAndMove);
+ }
+
+ if (animationSet.getAnimations().size() > 0) {
+ Log.d(TAG, "Starting animation");
+ animationSet.setFillAfter(true);
+ animationSet.start();
+ }
+ }
+
+
+}
diff --git a/androidgcs/src/com/MobileAnarchy/Android/Widgets/TilesLayout/TilesLayoutPreset.java b/androidgcs/src/com/MobileAnarchy/Android/Widgets/TilesLayout/TilesLayoutPreset.java
new file mode 100644
index 000000000..27c5514c4
--- /dev/null
+++ b/androidgcs/src/com/MobileAnarchy/Android/Widgets/TilesLayout/TilesLayoutPreset.java
@@ -0,0 +1,155 @@
+package com.MobileAnarchy.Android.Widgets.TilesLayout;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Describes the positioning of a tiles in a 100x100 environment
+ */
+public class TilesLayoutPreset {
+
+ // =========================================
+ // Private Members
+ // =========================================
+
+ private List _positions;
+
+ // =========================================
+ // Constructors
+ // =========================================
+
+ public TilesLayoutPreset(String name) {
+ _positions = new LinkedList();
+ }
+
+ // =========================================
+ // Public Methods
+ // =========================================
+
+ public void add(float x, float y, float width, float height) {
+ TilePosition tilePosition = new TilePosition(x, y, height, width);
+ add(tilePosition);
+ }
+
+ public void add(TilePosition tilePosition) {
+ _positions.add(tilePosition);
+ }
+
+ public Iterable getTilePositions() {
+ return _positions;
+ }
+
+ public int getCount() {
+ return _positions.size();
+ }
+
+ // =========================================
+ // Static Presets Factories
+ // =========================================
+
+ public static TilesLayoutPreset get1x1() {
+ TilesLayoutPreset preset = new TilesLayoutPreset("Default 1X1");
+ preset.add(0, 0, 100, 100);
+ return preset;
+ }
+
+
+ public static TilesLayoutPreset get1x2() {
+ TilesLayoutPreset preset = new TilesLayoutPreset("Default 1X2");
+ preset.add(0, 0, 50, 100);
+ preset.add(0, 50, 50, 100);
+ return preset;
+ }
+
+
+ public static TilesLayoutPreset get2x1() {
+ TilesLayoutPreset preset = new TilesLayoutPreset("Default 2X1");
+ preset.add(0, 0, 100, 50);
+ preset.add(50, 0, 100, 50);
+ return preset;
+ }
+
+
+ public static TilesLayoutPreset get2x2() {
+ TilesLayoutPreset preset = new TilesLayoutPreset("Default 2X2");
+ preset.add(0, 0, 50, 50);
+ preset.add(50, 0, 50, 50);
+ preset.add(0, 50, 50, 50);
+ preset.add(50, 50, 50, 50);
+ return preset;
+ }
+
+ public static TilesLayoutPreset get3x3() {
+ TilesLayoutPreset preset = new TilesLayoutPreset("Default 3X3");
+ preset.add(0, 0, 100/3f, 100/3f);
+ preset.add(100/3f, 0, 100/3f, 100/3f);
+ preset.add(200/3f, 0, 100/3f, 100/3f);
+ preset.add(0, 100/3f, 100/3f, 100/3f);
+ preset.add(100/3f, 100/3f, 100/3f, 100/3f);
+ preset.add(200/3f, 100/3f, 100/3f, 100/3f);
+ preset.add(0, 200/3f, 100/3f, 100/3f);
+ preset.add(100/3f, 200/3f, 100/3f, 100/3f);
+ preset.add(200/3f, 200/3f, 100/3f, 100/3f);
+ return preset;
+ }
+
+ public static TilesLayoutPreset get3x2() {
+ TilesLayoutPreset preset = new TilesLayoutPreset("Default 3X2");
+ preset.add(0, 0, 50, 100/3f);
+ preset.add(100/3f, 0, 50, 100/3f);
+ preset.add(200/3f, 0, 50, 100/3f);
+ preset.add(0, 50, 50, 100/3f);
+ preset.add(100/3f, 50, 50, 100/3f);
+ preset.add(200/3f, 50, 50, 100/3f);
+ return preset;
+ }
+
+ public static TilesLayoutPreset get2x3() {
+ TilesLayoutPreset preset = new TilesLayoutPreset("Default 2X3");
+ preset.add(0, 0, 100/3f, 50);
+ preset.add(50, 0, 100/3f, 50);
+ preset.add(0, 100/3f, 100/3f, 50);
+ preset.add(50, 100/3f, 100/3f, 50);
+ preset.add(0, 200/3f, 100/3f, 50);
+ preset.add(50, 200/3f, 100/3f, 50);
+ return preset;
+ }
+
+
+ public static TilesLayoutPreset get4x4() {
+ TilesLayoutPreset preset = new TilesLayoutPreset("Default 2X2");
+ preset.add(0, 0, 25, 25);
+ preset.add(25, 0, 25, 25);
+ preset.add(50, 0, 25, 25);
+ preset.add(75, 0, 25, 25);
+ preset.add(0, 25, 25, 25);
+ preset.add(25, 25, 25, 25);
+ preset.add(50, 25, 25, 25);
+ preset.add(75, 25, 25, 25);
+ preset.add(0, 50, 25, 25);
+ preset.add(25, 50, 25, 25);
+ preset.add(50, 50, 25, 25);
+ preset.add(75, 50, 25, 25);
+ preset.add(0, 75, 25, 25);
+ preset.add(25, 75, 25, 25);
+ preset.add(50, 75, 25, 25);
+ preset.add(75, 75, 25, 25);
+ return preset;
+ }
+
+ public static TilesLayoutPreset get2x3x3() {
+ TilesLayoutPreset preset = new TilesLayoutPreset("Custom 2X4X4");
+ preset.add(0, 0, 50, 50);
+ preset.add(50, 0, 50, 50);
+ preset.add(0, 50, 50, 50);
+
+ preset.add(50, 50, 25, 25);
+ preset.add(75, 50, 25, 25);
+ preset.add(50, 75, 25, 25);
+ preset.add(75, 75, 25, 25);
+
+ return preset;
+ }
+
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/AttitudeView.java b/androidgcs/src/org/openpilot/androidgcs/AttitudeView.java
new file mode 100644
index 000000000..d8aecc5e1
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/AttitudeView.java
@@ -0,0 +1,117 @@
+/**
+ ******************************************************************************
+ * @file AttitudeView.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief A view for UAV attitude.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.androidgcs;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class AttitudeView extends View {
+
+ private Paint markerPaint;
+ public AttitudeView(Context context) {
+ super(context);
+ initAttitudeView();
+ }
+
+ public AttitudeView(Context context, AttributeSet ats, int defaultStyle) {
+ super(context, ats, defaultStyle);
+ initAttitudeView();
+ }
+
+ public AttitudeView(Context context, AttributeSet ats) {
+ super(context, ats);
+ initAttitudeView();
+ }
+
+ protected void initAttitudeView() {
+ setFocusable(true);
+ markerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ markerPaint.setColor(getContext().getResources().getColor(
+ R.color.marker_color));
+
+ }
+
+ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int measuredWidth = measure(widthMeasureSpec);
+ int measuredHeight = measure(heightMeasureSpec);
+ int d = Math.min(measuredWidth, measuredHeight);
+ setMeasuredDimension(d/2, d/2);
+ }
+
+ private int measure(int measureSpec) {
+ int result = 0;
+ // Decode the measurement specifications.
+
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+
+ if (specMode == MeasureSpec.UNSPECIFIED) { // Return a default size of 200 if no bounds are specified.
+ result = 200;
+ } else {
+ // As you want to fill the available space
+ // always return the full available bounds.
+ result = specSize;
+ }
+ return result;
+ }
+
+ private float roll;
+ public void setRoll(double roll) {
+ this.roll = (float) roll;
+ }
+
+ private float pitch;
+ public void setPitch(double d) {
+ this.pitch = (float) d;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+
+ final int PX = getMeasuredWidth() / 2;
+ final int PY = getMeasuredHeight() / 2;
+
+ // TODO: Figure out why these magic numbers are needed to center it
+ final int WIDTH = 600;
+ final int HEIGHT = 600;
+ final int DEG_TO_PX = 10; // Magic number for how to scale pitch
+
+ canvas.save(0);
+ canvas.rotate(-roll, PX, PY);
+ canvas.translate(0, pitch * DEG_TO_PX);
+ Drawable horizon = getContext().getResources().getDrawable(
+ R.drawable.im_pfd_horizon);
+ // This puts the image at the center of the PFD canvas (after it was
+ // translated)
+ horizon.setBounds(-(WIDTH - PX) / 2, -(HEIGHT - PY) / 2, WIDTH, HEIGHT);
+ horizon.draw(canvas);
+ canvas.restore();
+
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/BluetoothDevicePreference.java b/androidgcs/src/org/openpilot/androidgcs/BluetoothDevicePreference.java
new file mode 100644
index 000000000..80f8970c6
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/BluetoothDevicePreference.java
@@ -0,0 +1,66 @@
+/**
+ ******************************************************************************
+ * @file BluetoothDevicePreference.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief A dialog in the preferences options that shows the paired BT
+ * devices.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.openpilot.androidgcs;
+
+import java.util.Set;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.preference.ListPreference;
+import android.util.AttributeSet;
+
+public class BluetoothDevicePreference extends ListPreference {
+
+ public BluetoothDevicePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ BluetoothAdapter bta = BluetoothAdapter.getDefaultAdapter();
+ if (bta == null)
+ return; // BT not supported
+ Set pairedDevices = bta.getBondedDevices();
+ CharSequence[] entries = new CharSequence[pairedDevices.size()];
+ CharSequence[] entryValues = new CharSequence[pairedDevices.size()];
+ if (pairedDevices.size() == 0) {
+ entries[0] = "No Devices";
+ entryValues[0] = "";
+ } else {
+ int i = 0;
+ for (BluetoothDevice dev : pairedDevices) {
+ entries[i] = dev.getName();
+ entryValues[i] = dev.getAddress();
+ i++;
+ }
+ }
+ setEntries(entries);
+ setEntryValues(entryValues);
+ }
+
+ public BluetoothDevicePreference(Context context) {
+ this(context, null);
+ }
+
+}
\ No newline at end of file
diff --git a/androidgcs/src/org/openpilot/androidgcs/BluetoothUAVTalk.java b/androidgcs/src/org/openpilot/androidgcs/BluetoothUAVTalk.java
new file mode 100644
index 000000000..b2c622238
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/BluetoothUAVTalk.java
@@ -0,0 +1,154 @@
+package org.openpilot.androidgcs;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.UUID;
+
+import org.openpilot.uavtalk.UAVObjectManager;
+import org.openpilot.uavtalk.UAVTalk;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+@TargetApi(10) public class BluetoothUAVTalk {
+ private final String TAG = "BluetoothUAVTalk";
+ public static int LOGLEVEL = 2;
+ public static boolean WARN = LOGLEVEL > 1;
+ public static boolean DEBUG = LOGLEVEL > 0;
+
+ // Temporarily define fixed device name
+ private String device_name = "RN42-222D";
+ private final static UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
+
+ private BluetoothAdapter mBluetoothAdapter;
+ private BluetoothSocket socket;
+ private BluetoothDevice device;
+ private UAVTalk uavTalk;
+ private boolean connected;
+
+ public BluetoothUAVTalk(Context caller) {
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(caller);
+ device_name = prefs.getString("bluetooth_mac","");
+
+ if (DEBUG) Log.d(TAG, "Trying to open UAVTalk with " + device_name);
+
+ connected = false;
+ device = null;
+
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (mBluetoothAdapter == null) {
+ // Device does not support Bluetooth
+ Log.e(TAG, "Device does not support Bluetooth");
+ return;
+ }
+
+ if (!mBluetoothAdapter.isEnabled()) {
+ // Enable bluetooth if it isn't already
+ Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ caller.sendOrderedBroadcast(enableBtIntent, "android.permission.BLUETOOTH_ADMIN", new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.e(TAG,"Received " + context + intent);
+ //TODO: some logic here to see if it worked
+ queryDevices();
+ }
+ }, null, Activity.RESULT_OK, null, null);
+ } else {
+ queryDevices();
+ }
+ }
+
+ public boolean connect(UAVObjectManager objMngr) {
+ if( getConnected() )
+ return true;
+ if( !getFoundDevice() )
+ return false;
+ if( !openTelemetryBluetooth(objMngr) )
+ return false;
+ return true;
+ }
+
+ public boolean getConnected() {
+ return connected;
+ }
+
+ public boolean getFoundDevice() {
+ return (device != null);
+ }
+
+ public UAVTalk getUavtalk() {
+ return uavTalk;
+ }
+
+ private void queryDevices() {
+ Log.d(TAG, "Searching for devices");
+ Set pairedDevices = mBluetoothAdapter.getBondedDevices();
+ // If there are paired devices
+ if (pairedDevices.size() > 0) {
+ // Loop through paired devices
+ for (BluetoothDevice device : pairedDevices) {
+ // Add the name and address to an array adapter to show in a ListView
+ //mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
+ Log.d(TAG, "Paired device: " + device.getAddress() + " compared to " + device_name);
+ if(device.getAddress().compareTo(device_name) == 0) {
+ Log.d(TAG, "Found device: " + device.getName());
+ this.device = device;
+ return;
+ }
+ }
+ }
+
+ }
+
+ private boolean openTelemetryBluetooth(UAVObjectManager objMngr) {
+ Log.d(TAG, "Opening connection to " + device.getName());
+ socket = null;
+ connected = false;
+ try {
+ socket = device.createInsecureRfcommSocketToServiceRecord(MY_UUID);
+ } catch (IOException e) {
+ Log.e(TAG,"Unable to create Rfcomm socket");
+ return false;
+ //e.printStackTrace();
+ }
+
+ mBluetoothAdapter.cancelDiscovery();
+
+ try {
+ socket.connect();
+ }
+ catch (IOException e) {
+ Log.e(TAG,"Unable to connect to requested device", e);
+ try {
+ socket.close();
+ } catch (IOException e2) {
+ Log.e(TAG, "unable to close() socket during connection failure", e2);
+ }
+ return false;
+ }
+
+ connected = true;
+
+ try {
+ uavTalk = new UAVTalk(socket.getInputStream(), socket.getOutputStream(), objMngr);
+ } catch (IOException e) {
+ Log.e(TAG,"Error starting UAVTalk");
+ // TODO Auto-generated catch block
+ //e.printStackTrace();
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/CompassView.java b/androidgcs/src/org/openpilot/androidgcs/CompassView.java
new file mode 100644
index 000000000..b6b348588
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/CompassView.java
@@ -0,0 +1,163 @@
+/**
+ ******************************************************************************
+ * @file CompassView.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief A view of the compass heading.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.openpilot.androidgcs;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class CompassView extends View {
+
+ public CompassView(Context context) {
+ super(context);
+ initCompassView();
+ }
+
+ public CompassView(Context context, AttributeSet ats, int defaultStyle) {
+ super(context, ats, defaultStyle);
+ initCompassView();
+ }
+
+ public CompassView(Context context, AttributeSet ats) {
+ super(context, ats);
+ initCompassView();
+ }
+
+ protected void initCompassView() {
+ setFocusable(true);
+
+ circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ circlePaint.setColor(getResources().getColor(R.color.background_color));
+ circlePaint.setStrokeWidth(1);
+ circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ Resources r = this.getResources();
+ northString = r.getString(R.string.cardinal_north);
+ eastString = r.getString(R.string.cardinal_east);
+ southString = r.getString(R.string.cardinal_south);
+ westString = r.getString(R.string.cardinal_west);
+ textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ textPaint.setColor(r.getColor(R.color.text_color));
+ textHeight = (int)textPaint.measureText("yY");
+ markerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ markerPaint.setColor(r.getColor(R.color.marker_color));
+ }
+
+ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int measuredWidth = measure(widthMeasureSpec);
+ int measuredHeight = measure(heightMeasureSpec);
+ int d = Math.min(measuredWidth, measuredHeight);
+ setMeasuredDimension(d/2, d/2);
+ }
+
+ private int measure(int measureSpec) {
+ int result = 0;
+ // Decode the measurement specifications.
+
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+
+ if (specMode == MeasureSpec.UNSPECIFIED) { // Return a default size of 200 if no bounds are specified.
+ result = 200;
+ } else {
+ // As you want to fill the available space
+ // always return the full available bounds.
+ result = specSize;
+ }
+ return result;
+ }
+
+ private double bearing;
+ public void setBearing(double bearing) {
+ this.bearing = bearing;
+ }
+
+ // Drawing related code
+ private Paint markerPaint;
+ private Paint textPaint;
+ private Paint circlePaint;
+ private String northString;
+ private String eastString;
+ private String southString;
+ private String westString;
+ private int textHeight;
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ int px = getMeasuredWidth() / 2;
+ int py = getMeasuredHeight() /2 ;
+ int radius = Math.min(px, py);
+ // Draw the background
+ canvas.drawCircle(px, py, radius, circlePaint);
+
+ // Rotate our perspective so that the top is
+ // facing the current bearing.
+ canvas.save();
+ canvas.rotate((float) -bearing, px, py);
+
+ int textWidth = (int)textPaint.measureText("W");
+ int cardinalX = px-textWidth/2;
+ int cardinalY = py-radius+textHeight;
+
+ // Draw the marker every 15 degrees and text every 45.
+ for (int i = 0; i < 24; i++) {
+ // Draw a marker.
+ canvas.drawLine(px, py-radius, px, py-radius+10, markerPaint);
+ canvas.save();
+ canvas.translate(0, textHeight);
+ // Draw the cardinal points
+ if (i % 6 == 0) {
+ String dirString = null;
+ switch (i) {
+ case 0 : {
+ dirString = northString;
+ int arrowY = 2*textHeight;
+ canvas.drawLine(px, arrowY, px-5, 3*textHeight, markerPaint);
+ canvas.drawLine(px, arrowY, px+5, 3*textHeight, markerPaint);
+ break;
+ }
+ case 6: dirString = eastString; break;
+ case 12: dirString = southString; break;
+ case 18: dirString = westString; break;
+ }
+ canvas.drawText(dirString, cardinalX, cardinalY, textPaint);
+ }
+ else if (i % 3 == 0) {
+ // Draw the text every alternate 45deg
+ String angle = String.valueOf(i*15);
+ float angleTextWidth = textPaint.measureText(angle);
+
+ int angleTextX = (int)(px-angleTextWidth/2);
+ int angleTextY = py-radius+textHeight;
+ canvas.drawText(angle, angleTextX, angleTextY, textPaint);
+ }
+ canvas.restore();
+ canvas.rotate(15, px, py);
+ }
+ canvas.restore();
+ }
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/Controller.java b/androidgcs/src/org/openpilot/androidgcs/Controller.java
new file mode 100644
index 000000000..3ef291d4d
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/Controller.java
@@ -0,0 +1,252 @@
+/**
+ ******************************************************************************
+ * @file Controller.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief Allows controlling the UAV over telemetry. This activity
+ * pushes the appropriate settings to the remote device for it to
+ * listen to the GCSReceiver.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.androidgcs;
+
+import java.util.Observable;
+import java.util.Observer;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.openpilot.uavtalk.UAVDataObject;
+import org.openpilot.uavtalk.UAVObject;
+import org.openpilot.uavtalk.UAVObjectField;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.MobileAnarchy.Android.Widgets.Joystick.DualJoystickView;
+import com.MobileAnarchy.Android.Widgets.Joystick.JoystickMovedListener;
+import com.MobileAnarchy.Android.Widgets.Joystick.JoystickView;
+
+public class Controller extends ObjectManagerActivity {
+ private final String TAG = "Controller";
+
+ private final boolean DEBUG = false;
+
+ private final int THROTTLE_CHANNEL = 0;
+ private final int ROLL_CHANNEL = 1;
+ private final int PITCH_CHANNEL = 2;
+ private final int YAW_CHANNEL = 3;
+ private final int FLIGHTMODE_CHANNEL = 4;
+
+ private final int CHANNEL_MIN = 1000;
+ private final int CHANNEL_MAX = 2000;
+ private final int CHANNEL_NEUTRAL = 1500;
+ private final int CHANNEL_NEUTRAL_THROTTLE = 1100;
+
+ private double throttle = 0.1, roll = 0.1, pitch = -0.1, yaw = 0;
+ private boolean updated;
+ private boolean leftJoystickHeld, rightJoystickHeld;
+
+ Timer sendTimer = new Timer();
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.controller);
+ TextView manualView = (TextView) findViewById(R.id.manualControlValues);
+ }
+
+ Observer settingsUpdated = new Observer() {
+ @Override
+ public void update(Observable observable, Object data) {
+ // Once we have updated settings we can active the GCS receiver mode
+ Log.d(TAG,"Got update from settings");
+ activateGcsReceiver();
+ UAVDataObject manualControlSettings = (UAVDataObject) objMngr.getObject("ManualControlSettings");
+ if(manualControlSettings != null) {
+ manualControlSettings.removeUpdatedObserver(this);
+ }
+ }
+ };
+
+ @Override
+ void onOPConnected() {
+ super.onOPConnected();
+
+ Log.d(TAG, "onOPConnected()");
+
+ // Subscribe to updates from ManualControlCommand and show the values for crude feedback
+ UAVDataObject manualControl = (UAVDataObject) objMngr.getObject("ManualControlCommand");
+ registerObjectUpdates(manualControl);
+
+ // Request a one time update before configuring for GCS control mode
+ UAVDataObject manualSettings = (UAVDataObject) objMngr.getObject("ManualControlSettings");
+ manualSettings.addUpdatedObserver(settingsUpdated);
+ manualSettings.updateRequested();
+
+ final double MOVEMENT_RANGE = 50.0;
+ DualJoystickView joystick = (DualJoystickView) findViewById(R.id.dualjoystickView);
+ joystick.setMovementConstraint(JoystickView.CONSTRAIN_BOX);
+ joystick.setMovementRange((int)MOVEMENT_RANGE, (int)MOVEMENT_RANGE);
+
+ // Hardcode a Mode 1 listener for now
+ joystick.setOnJostickMovedListener(new JoystickMovedListener() {
+ @Override
+ public void OnMoved(int pan, int tilt) {
+ pitch = tilt / MOVEMENT_RANGE;
+ yaw = pan / MOVEMENT_RANGE;
+ leftJoystickHeld = true;
+ }
+ @Override
+ public void OnReleased() { leftJoystickHeld = false; throttle = -1; updated = true; }
+ @Override
+ public void OnReturnedToCenter() { }
+ }, new JoystickMovedListener() {
+ @Override
+ public void OnMoved(int pan, int tilt) {
+ throttle = (-tilt + (MOVEMENT_RANGE -5)) / (MOVEMENT_RANGE - 5);
+ throttle *= 0.5;
+ if (throttle < 0)
+ throttle = -1;
+ roll = pan / MOVEMENT_RANGE;
+ rightJoystickHeld = true;
+ }
+ @Override
+ public void OnReleased() { rightJoystickHeld = false; throttle = -1; updated = true; }
+ @Override
+ public void OnReturnedToCenter() { }
+ });
+
+ //! This timer task actually periodically sends updates to the UAV
+ TimerTask controllerTask = new TimerTask() {
+ @Override
+ public void run() {
+ uavobjHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if ((leftJoystickHeld && rightJoystickHeld) || updated) {
+ UAVObject gcsReceiver = objMngr.getObject("GCSReceiver");
+ if (gcsReceiver == null) {
+ Log.e(TAG, "No GCS Receiver object found");
+ return;
+ }
+
+ UAVObjectField channels = gcsReceiver.getField("Channel");
+ if(channels == null) {
+ Log.e(TAG, "GCS Receiver object ill formatted");
+ return;
+ }
+
+ channels.setValue(scaleChannel(throttle, CHANNEL_NEUTRAL_THROTTLE), THROTTLE_CHANNEL);
+ channels.setValue(scaleChannel(roll, CHANNEL_NEUTRAL), ROLL_CHANNEL);
+ channels.setValue(scaleChannel(pitch, CHANNEL_NEUTRAL), PITCH_CHANNEL);
+ channels.setValue(scaleChannel(yaw, CHANNEL_NEUTRAL), YAW_CHANNEL);
+ channels.setValue(scaleChannel(0, CHANNEL_NEUTRAL), FLIGHTMODE_CHANNEL);
+
+ gcsReceiver.updated();
+
+ updated = false;
+
+ if (DEBUG) Log.d(TAG, "Send update" + gcsReceiver.toStringData());
+ }
+ updated = false;
+ }
+ });
+ }
+ };
+ sendTimer.schedule(controllerTask, 500, 10);
+ }
+
+ /**
+ * Show the string description of manual control command
+ */
+ @Override
+ protected void objectUpdated(UAVObject obj) {
+ if (obj.getName().compareTo("ManualControlCommand") == 0) {
+ TextView manualView = (TextView) findViewById(R.id.manualControlValues);
+ if (manualView != null)
+ manualView.setText(obj.toStringData());
+ }
+ }
+
+ /**
+ * Active GCS receiver mode
+ */
+ private void activateGcsReceiver() {
+ UAVObject manualControlSettings = objMngr.getObject("ManualControlSettings");
+
+ if (manualControlSettings == null) {
+ Toast.makeText(this, "Failed to get manual control settings", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ UAVObjectField channelGroups = manualControlSettings.getField("ChannelGroups");
+ UAVObjectField channelNumber = manualControlSettings.getField("ChannelNumber");
+ UAVObjectField channelMax = manualControlSettings.getField("ChannelMax");
+ UAVObjectField channelNeutral = manualControlSettings.getField("ChannelNeutral");
+ UAVObjectField channelMin = manualControlSettings.getField("ChannelMin");
+ if (channelGroups == null || channelMax == null || channelNeutral == null ||
+ channelMin == null || channelNumber == null) {
+ Toast.makeText(this, "Manual control settings not formatted correctly", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ /* Configure the manual control module how the GCS controller expects
+ * This order MUST correspond to the enumeration order of ChannelNumber in
+ * ManualControlSettings.
+ */
+ int channels[] = { THROTTLE_CHANNEL, ROLL_CHANNEL, PITCH_CHANNEL, YAW_CHANNEL, FLIGHTMODE_CHANNEL };
+ for (int i = 0; i < channels.length; i++) {
+ channelGroups.setValue("GCS", channels[i]);
+ channelNumber.setValue(1 + channels[i], i); // Add 1 because this uses 0 for "NONE"
+ channelMin.setValue(CHANNEL_MIN, channels[i]);
+ channelMax.setValue(CHANNEL_MAX, channels[i]);
+ switch(channels[i]) {
+ case THROTTLE_CHANNEL:
+ channelNeutral.setValue(CHANNEL_NEUTRAL_THROTTLE, channels[i]);
+ break;
+ default:
+ channelNeutral.setValue(CHANNEL_NEUTRAL, channels[i]);
+ break;
+ }
+ }
+
+ // Send settings to the UAV
+ manualControlSettings.updated();
+
+
+ }
+
+ /**
+ * Scale the channels to the output range the flight controller expects
+ */
+ private float scaleChannel(double in, double neutral) {
+ // Check bounds
+ if (in > 1)
+ in = 1;
+ if (in < -1)
+ in = -1;
+
+ if (in >= 0)
+ return (float) (neutral + (CHANNEL_MAX - neutral) * in);
+ return (float) (neutral + (neutral - CHANNEL_MIN) * in);
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/FragmentTester.java b/androidgcs/src/org/openpilot/androidgcs/FragmentTester.java
new file mode 100644
index 000000000..1ed1f6c7e
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/FragmentTester.java
@@ -0,0 +1,57 @@
+/**
+ ******************************************************************************
+ * @file FragmentTester.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief An activity that displays the SystemAlarmsFragment.
+ * @see The GNU Public License (GPL) Version 3
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.androidgcs;
+
+import org.openpilot.androidgcs.fragments.MapPositioner;
+
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AbsListView;
+import android.widget.LinearLayout;
+
+public class FragmentTester extends ObjectManagerActivity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(createUI());
+ }
+
+ private View createUI() {
+ LinearLayout layout = new LinearLayout(this);
+
+ layout.setOrientation(LinearLayout.HORIZONTAL);
+ layout.setLayoutParams(new LinearLayout.LayoutParams(
+ AbsListView.LayoutParams.MATCH_PARENT,
+ AbsListView.LayoutParams.MATCH_PARENT));
+ layout.setId(0x101);
+ {
+ FragmentTransaction fragmentTransaction = getFragmentManager()
+ .beginTransaction();
+ fragmentTransaction.add(0x101, new MapPositioner());
+ fragmentTransaction.commit();
+ }
+ return layout;
+ }
+}
+
diff --git a/androidgcs/src/org/openpilot/androidgcs/HomePage.java b/androidgcs/src/org/openpilot/androidgcs/HomePage.java
new file mode 100644
index 000000000..44d50b3b3
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/HomePage.java
@@ -0,0 +1,96 @@
+/**
+ ******************************************************************************
+ * @file HomePage.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief Main launch page for the Android GCS actitivies
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.androidgcs;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+public class HomePage extends ObjectManagerActivity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.gcs_home);
+
+ Button objectBrowser = (Button) findViewById(R.id.launch_object_browser);
+ objectBrowser.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ startActivity(new Intent(HomePage.this, ObjectBrowser.class));
+ }
+ });
+
+ Button pfd = (Button) findViewById(R.id.launch_pfd);
+ pfd.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ startActivity(new Intent(HomePage.this, PfdActivity.class));
+ }
+ });
+
+ Button location = (Button) findViewById(R.id.launch_location);
+ location.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ startActivity(new Intent(HomePage.this, UAVLocation.class));
+ }
+ });
+
+ Button controller = (Button) findViewById(R.id.launch_controller);
+ controller.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ startActivity(new Intent(HomePage.this, Controller.class));
+ }
+ });
+
+ Button logger = (Button) findViewById(R.id.launch_logger);
+ logger.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ startActivity(new Intent(HomePage.this, Logger.class));
+ }
+ });
+
+ Button alarms = (Button) findViewById(R.id.launch_alarms);
+ alarms.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ startActivity(new Intent(HomePage.this, SystemAlarmActivity.class));
+ }
+ });
+
+ Button tester = (Button) findViewById(R.id.launch_tester);
+ tester.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ startActivity(new Intent(HomePage.this, FragmentTester.class));
+ }
+ });
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/Logger.java b/androidgcs/src/org/openpilot/androidgcs/Logger.java
new file mode 100644
index 000000000..0760e3e83
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/Logger.java
@@ -0,0 +1,167 @@
+/**
+ ******************************************************************************
+ * @file Logger.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief Controller for logging data as well as interface for getting that
+ * data on and off the tablet.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.androidgcs;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.openpilot.uavtalk.UAVObject;
+import org.openpilot.uavtalk.UAVTalk;
+
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+import android.widget.TextView;
+
+
+public class Logger extends ObjectManagerActivity {
+
+ final String TAG = "Logger";
+
+ final boolean VERBOSE = false;
+ final boolean DEBUG = true;
+
+ private File file;
+ private boolean logging;
+ private FileOutputStream fileStream;
+ private UAVTalk uavTalk;
+
+ private int writtenBytes;
+ private int writtenObjects;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.logger);
+ }
+
+ private void onStartLogging() {
+
+ File root = Environment.getExternalStorageDirectory();
+
+ Date d = new Date();
+ String date = (new SimpleDateFormat("yyyyMMdd_hhmmss")).format(d);
+ String fileName = "/logs/logs_" + date + ".opl";
+
+ file = new File(root, fileName);
+ if (DEBUG) Log.d(TAG, "Trying for file: " + file.getAbsolutePath());
+ try {
+ if (root.canWrite()){
+ fileStream = new FileOutputStream(file);
+ uavTalk = new UAVTalk(null, fileStream, objMngr);
+ logging = true;
+ writtenBytes = 0;
+ writtenObjects = 0;
+ } else {
+ Log.e(TAG, "Unwriteable address");
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Could not write file " + e.getMessage());
+ }
+
+ // TODO: if logging succeeded then retrieve all settings
+ }
+
+ private void onStopLogging() {
+ if (DEBUG) Log.d(TAG, "Stop logging");
+ logging = false;
+ try {
+ fileStream.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ void onOPConnected() {
+ super.onOPConnected();
+
+ if (DEBUG) Log.d(TAG, "onOPConnected()");
+ onStartLogging();
+ registerObjectUpdates(objMngr.getObjects());
+ }
+
+ @Override
+ void onOPDisconnected() {
+ if (DEBUG) Log.d(TAG, "onOPDisconnected()");
+ onStopLogging();
+ }
+
+ @Override
+ public void onPause()
+ {
+ super.onPause();
+ onStopLogging();
+ }
+
+ @Override
+ public void onResume()
+ {
+ super.onResume();
+ onStartLogging();
+ }
+ /**
+ * Called whenever any objects subscribed to via registerObjects
+ */
+ @Override
+ protected void objectUpdated(UAVObject obj) {
+ if (logging) {
+ if (VERBOSE) Log.v(TAG,"Updated: " + obj.toString());
+ try {
+ long time = System.currentTimeMillis();
+ fileStream.write((byte)(time & 0xff));
+ fileStream.write((byte)((time & 0x0000ff00) >> 8));
+ fileStream.write((byte)((time & 0x00ff0000) >> 16));
+ fileStream.write((byte)((time & 0xff000000) >> 24));
+
+ long size = obj.getNumBytes();
+ fileStream.write((byte)(size & 0x00000000000000ffl) >> 0);
+ fileStream.write((byte)(size & 0x000000000000ff00l) >> 8);
+ fileStream.write((byte)(size & 0x0000000000ff0000l) >> 16);
+ fileStream.write((byte)(size & 0x00000000ff000000l) >> 24);
+ fileStream.write((byte)(size & 0x000000ff00000000l) >> 32);
+ fileStream.write((byte)(size & 0x0000ff0000000000l) >> 40);
+ fileStream.write((byte)(size & 0x00ff000000000000l) >> 48);
+ fileStream.write((byte)(size & 0xff00000000000000l) >> 56);
+
+ uavTalk.sendObject(obj, false, false);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ writtenBytes += obj.getNumBytes();
+ writtenObjects ++;
+
+ ((TextView) findViewById(R.id.logger_number_of_bytes)).setText(Integer.valueOf(writtenBytes).toString());
+ ((TextView) findViewById(R.id.logger_number_of_objects)).setText(Integer.valueOf(writtenObjects).toString());
+ }
+ }
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/ObjectBrowser.java b/androidgcs/src/org/openpilot/androidgcs/ObjectBrowser.java
new file mode 100644
index 000000000..2c04ed248
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/ObjectBrowser.java
@@ -0,0 +1,197 @@
+/**
+ ******************************************************************************
+ * @file ObjectBrowser.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief A simple object browser for UAVOs that allows viewing, editing,
+ * loading and saving.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.androidgcs;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Observable;
+import java.util.Observer;
+
+import org.openpilot.uavtalk.UAVDataObject;
+import org.openpilot.uavtalk.UAVObject;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Bundle;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class ObjectBrowser extends ObjectManagerActivity implements OnSharedPreferenceChangeListener {
+
+ private final String TAG = "ObjectBrower";
+ int selected_index = -1;
+ boolean connected;
+ SharedPreferences prefs;
+ ArrayAdapter adapter;
+ List allObjects;
+
+ final Handler uavobjHandler = new Handler();
+ final Runnable updateText = new Runnable() {
+ @Override
+ public void run() {
+ updateObject();
+ }
+ };
+
+ private final Observer updatedObserver = new Observer() {
+ @Override
+ public void update(Observable observable, Object data) {
+ uavobjHandler.post(updateText);
+ }
+ };
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ setContentView(R.layout.object_browser);
+ prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ prefs.registerOnSharedPreferenceChangeListener(this);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ void onOPConnected() {
+ super.onOPConnected();
+ Log.d(TAG, "onOPConnected()");
+
+ OnCheckedChangeListener checkListener = new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView,
+ boolean isChecked) {
+ updateList();
+ }
+ };
+
+ ((CheckBox) findViewById(R.id.dataCheck)).setOnCheckedChangeListener(checkListener);
+ ((CheckBox) findViewById(R.id.settingsCheck)).setOnCheckedChangeListener(checkListener);
+
+ ((Button) findViewById(R.id.editButton)).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (selected_index > 0) {
+ Intent intent = new Intent(ObjectBrowser.this, ObjectEditor.class);
+ intent.putExtra("org.openpilot.androidgcs.ObjectName", allObjects.get(selected_index).getName());
+ intent.putExtra("org.openpilot.androidgcs.ObjectId", allObjects.get(selected_index).getObjID());
+ intent.putExtra("org.openpilot.androidgcs.InstId", allObjects.get(selected_index).getInstID());
+ startActivity(intent);
+ }
+ }
+ });
+
+ ((Button) findViewById(R.id.object_load_button)).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ UAVObject objPer = objMngr.getObject("ObjectPersistence");
+
+ if (selected_index > 0 && objPer != null) {
+ objPer.getField("Operation").setValue("Load");
+ objPer.getField("Selection").setValue("SingleObject");
+ Log.d(TAG,"Loading with object id: " + allObjects.get(selected_index).getObjID());
+ objPer.getField("ObjectID").setValue(allObjects.get(selected_index).getObjID());
+ objPer.getField("InstanceID").setValue(0);
+ objPer.updated();
+
+ allObjects.get(selected_index).updateRequested();
+ }
+ }
+ });
+
+ updateList();
+ }
+
+ /**
+ * Populate the list of UAVO objects based on the selected filter
+ */
+ private void updateList() {
+ // Disconnect any previous signals
+ if (selected_index > 0)
+ allObjects.get(selected_index).removeUpdatedObserver(updatedObserver);
+ selected_index = -1;
+
+ boolean includeData = ((CheckBox) findViewById(R.id.dataCheck)).isChecked();
+ boolean includeSettings = ((CheckBox) findViewById(R.id.settingsCheck)).isChecked();
+
+ List> allobjects = objMngr.getDataObjects();
+ allObjects = new ArrayList();
+ ListIterator> li = allobjects.listIterator();
+ while(li.hasNext()) {
+ List objects = li.next();
+ if(includeSettings && objects.get(0).isSettings())
+ allObjects.addAll(objects);
+ else if (includeData && !objects.get(0).isSettings())
+ allObjects.addAll(objects);
+ }
+
+ adapter = new ArrayAdapter(this,R.layout.object_view, allObjects);
+ ListView objects = (ListView) findViewById(R.id.object_list);
+ objects.setAdapter(adapter);
+
+ objects.setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view,
+ int position, long id) {
+
+ if (selected_index > 0)
+ allObjects.get(selected_index).removeUpdatedObserver(updatedObserver);
+
+ selected_index = position;
+ allObjects.get(position).addUpdatedObserver(updatedObserver);
+ allObjects.get(position).updateRequested();
+ updateObject();
+ }
+ });
+
+ }
+
+ private void updateObject() {
+ //adapter.notifyDataSetChanged();
+ TextView text = (TextView) findViewById(R.id.object_information);
+ if (selected_index >= 0 && selected_index < allObjects.size())
+ text.setText(allObjects.get(selected_index).toStringData());
+ else
+ Log.d(TAG,"Update called but invalid index: " + selected_index);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+ String key) {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/ObjectEditView.java b/androidgcs/src/org/openpilot/androidgcs/ObjectEditView.java
new file mode 100644
index 000000000..7946a357b
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/ObjectEditView.java
@@ -0,0 +1,102 @@
+package org.openpilot.androidgcs;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openpilot.uavtalk.UAVObjectField;
+
+import android.content.Context;
+import android.text.InputType;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.GridLayout;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+public class ObjectEditView extends GridLayout {
+
+ String objectName;
+ public List fields;
+
+ public ObjectEditView(Context context) {
+ super(context);
+ initObjectEditView();
+ }
+
+ public ObjectEditView(Context context, AttributeSet ats, int defaultStyle) {
+ super(context, ats);
+ initObjectEditView();
+ }
+
+ public ObjectEditView(Context context, AttributeSet ats) {
+ super(context, ats);
+ initObjectEditView();
+ }
+
+ public void initObjectEditView() {
+ // Set orientation of layout to vertical
+ setOrientation(LinearLayout.VERTICAL);
+ setColumnCount(2);
+ fields = new ArrayList();
+ }
+
+ public void setName(String name) {
+ objectName = name;
+ }
+
+ public void addField(UAVObjectField field) {
+ for (int i = 0; i < field.getNumElements(); i++)
+ addRow(getContext(), field, i);
+ }
+
+
+ public void addRow(Context context, UAVObjectField field, int idx) {
+ int row = getRowCount();
+
+ TextView fieldName = new TextView(context);
+ if(field.getNumElements() == 1) {
+ fieldName.setText(field.getName());
+ } else {
+ fieldName.setText(field.getName() + "-" + field.getElementNames().get(idx));
+ }
+ addView(fieldName, new GridLayout.LayoutParams(spec(row), spec(0)));
+
+ View fieldValue = null;
+ switch(field.getType())
+ {
+ case FLOAT32:
+ fieldValue = new EditText(context);
+ ((EditText)fieldValue).setText(field.getValue(idx).toString());
+ ((EditText)fieldValue).setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED | InputType.TYPE_NUMBER_FLAG_DECIMAL);
+ break;
+ case INT8:
+ case INT16:
+ case INT32:
+ fieldValue = new EditText(context);
+ ((EditText)fieldValue).setText(field.getValue(idx).toString());
+ ((EditText)fieldValue).setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
+ break;
+ case UINT8:
+ case UINT16:
+ case UINT32:
+ fieldValue = new EditText(context);
+ ((EditText)fieldValue).setText(field.getValue(idx).toString());
+ ((EditText)fieldValue).setInputType(InputType.TYPE_CLASS_NUMBER);
+ break;
+ case ENUM:
+ fieldValue = new Spinner(context);
+ ArrayAdapter adapter = new ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item);
+ adapter.addAll(field.getOptions());
+ ((Spinner) fieldValue).setAdapter(adapter);
+ ((Spinner) fieldValue).setSelection((int) field.getDouble(idx));
+ break;
+ }
+
+ addView(fieldValue, new GridLayout.LayoutParams(spec(row), spec(1)));
+ fields.add(fieldValue);
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/ObjectEditor.java b/androidgcs/src/org/openpilot/androidgcs/ObjectEditor.java
new file mode 100644
index 000000000..10614e1de
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/ObjectEditor.java
@@ -0,0 +1,166 @@
+/**
+ ******************************************************************************
+ * @file ObjectEditor.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief A popup dialog for editing the contents of a UAVO.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.androidgcs;
+
+import java.util.List;
+import java.util.ListIterator;
+
+import org.openpilot.uavtalk.UAVObject;
+import org.openpilot.uavtalk.UAVObjectField;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.Toast;
+
+public class ObjectEditor extends ObjectManagerActivity {
+
+ static final String TAG = "ObjectEditor";
+ String objectName;
+ long objectID;
+ long instID;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.object_editor);
+
+ // TODO: Figure out why this line is required so it doesn't
+ // have to be set programmatically
+ setTheme(android.R.style.Theme_Holo);
+
+ Bundle extras = getIntent().getExtras();
+ if (extras == null)
+ return;
+
+ objectName = extras.getString("org.openpilot.androidgcs.ObjectName");
+ objectID = extras.getLong("org.openpilot.androidgcs.ObjectId");
+ instID = extras.getLong("org.openpilot.androidgcs.InstId");
+
+ setTitle(objectName);
+
+ Button sendButton = (Button) findViewById(R.id.object_edit_send_button);
+ sendButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ updateObject();
+ }
+ });
+
+ Button saveButton = (Button) findViewById(R.id.object_edit_save_button);
+ saveButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ saveObject();
+ }
+ });
+ }
+
+ @Override
+ public void onOPConnected() {
+ UAVObject obj = objMngr.getObject(objectID, instID);
+ if (obj == null) {
+ Log.d(TAG, "Object not found:" + objectID);
+ return;
+ }
+
+ ObjectEditView editView = (ObjectEditView) findViewById(R.id.object_edit_view);
+ editView.setName(obj.getName());
+
+ List fields = obj.getFields();
+ ListIterator li = fields.listIterator();
+ while (li.hasNext()) {
+ editView.addField(li.next());
+ }
+ }
+
+ /**
+ * Fetch the data back from the view and then send it to the UAV
+ */
+ private void saveObject() {
+
+ UAVObject objPer = objMngr.getObject("ObjectPersistence");
+
+ if( !updateObject() || objPer == null) {
+ Toast.makeText(this, "Save failed", Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ long thisId = objectID < 0 ? 0x100000000l + objectID : objectID;
+ objPer.getField("Operation").setValue("Save");
+ objPer.getField("Selection").setValue("SingleObject");
+ Log.d(TAG,"Saving with object id: " + objectID + " swapped to " + thisId);
+ objPer.getField("ObjectID").setValue(thisId);
+ objPer.getField("InstanceID").setValue(instID);
+ objPer.updated();
+
+ Toast.makeText(this, "Save succeeded", Toast.LENGTH_LONG).show();
+ }
+
+ /**
+ * Fetch the data back from the view and then send it to the UAV
+ */
+ private boolean updateObject() {
+ UAVObject obj = objMngr.getObject(objectID, instID);
+ if (obj == null)
+ return false;
+
+ Log.d(TAG, "Updating object id " + obj.getObjID());
+ ObjectEditView editView = (ObjectEditView) findViewById(R.id.object_edit_view);
+
+ int field_idx = 0;
+
+ List fields = obj.getFields();
+ ListIterator li = fields.listIterator();
+ while (li.hasNext()) {
+ UAVObjectField field = li.next();
+ int num_fields = field.getNumElements();
+ for (int i = 0; i < num_fields; i++) {
+ switch (field.getType()) {
+ case ENUM:
+ int selected = ((Spinner)editView.fields.get(field_idx)).getSelectedItemPosition();
+ field.setValue(selected, i);
+ break;
+ default:
+ String val = ((EditText) editView.fields.get(field_idx)).getText().toString();
+ Double num = Double.parseDouble(val);
+
+ Log.e(TAG, "Updating field: " + field.getName() + " value: " + num);
+ field.setValue(num, i);
+ break;
+ }
+
+ field_idx++;
+ }
+ }
+ obj.updated();
+
+ return true;
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/ObjectManagerActivity.java b/androidgcs/src/org/openpilot/androidgcs/ObjectManagerActivity.java
new file mode 100644
index 000000000..8cd89e8d8
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/ObjectManagerActivity.java
@@ -0,0 +1,511 @@
+/**
+ ******************************************************************************
+ * @file ObjectManagerActivity.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief Base object for all activies that use the UAVObjectManager.
+ * This class takes care of binding to the service and getting the
+ * object manager as well as setting up callbacks to the objects of
+ * interest that run on the UI thread.
+ * Implements a new Android lifecycle: onOPConnected() / onOPDisconnected()
+ * which indicates when a valid telemetry is established as well as a
+ * valid object manager handle.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.androidgcs;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Observable;
+import java.util.Observer;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.openpilot.androidgcs.fragments.ObjectManagerFragment;
+import org.openpilot.androidgcs.telemetry.OPTelemetryService;
+import org.openpilot.androidgcs.telemetry.OPTelemetryService.LocalBinder;
+import org.openpilot.androidgcs.telemetry.OPTelemetryService.TelemTask;
+import org.openpilot.uavtalk.UAVObject;
+import org.openpilot.uavtalk.UAVObjectManager;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.TextView;
+
+public abstract class ObjectManagerActivity extends Activity {
+
+ private final String TAG = "ObjectManagerActivity";
+ private static int LOGLEVEL = 0;
+// private static boolean WARN = LOGLEVEL > 1;
+ private static boolean DEBUG = LOGLEVEL > 0;
+
+ //! Object manager, populated by parent for the children to use
+ UAVObjectManager objMngr;
+ //! Indicates if the activity is bound to the service
+ boolean mBound = false;
+ //! Indicates if telemetry is connected
+ boolean mConnected = false;
+ //! The binder to access the telemetry task, and thus the object manager
+ LocalBinder binder;
+ //! Store the broadcast receiver to unregister it
+ BroadcastReceiver connectedReceiver;
+ //! Indicate if this activity has already connected it's telemetry callbacks
+ private boolean telemetryStatsConnected = false;
+ //! Maintain a list of all the UAVObject listeners for this activity
+ private HashMap listeners;
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ /**
+ * Called whenever any objects subscribed to via registerObjects
+ * whenever this Activity is not paused
+ */
+ protected void objectUpdated(UAVObject obj) {
+
+ }
+
+ private void updateStats() {
+ UAVObject stats = objMngr.getObject("GCSTelemetryStats");
+ TextView rxRate = (TextView) findViewById(R.id.telemetry_stats_rx_rate);
+ TextView txRate = (TextView) findViewById(R.id.telemetry_stats_tx_rate);
+ if (rxRate != null)
+ rxRate.setText(Integer.valueOf((int) stats.getField("RxDataRate").getDouble()).toString());
+ if (rxRate != null)
+ txRate.setText(Integer.valueOf((int) stats.getField("TxDataRate").getDouble()).toString());
+
+ }
+
+ final Observer telemetryObserver = new Observer() {
+ @Override
+ public void update(Observable observable, Object data) {
+ uavobjHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ updateStats();
+ }
+ });
+ }
+ };
+
+ /**
+ * Called when either the telemetry establishes a connection or
+ * if it already has on creation of this activity
+ *
+ * This should be called by all inherited classes if they want the telemetry bar etc
+ */
+ void onOPConnected() {
+ if ( telemetryStatsConnected )
+ return;
+
+ // Create a map for all the object updates register for this activity. If anyone
+ // tries to register an object update before this a null exception will occur
+ listeners = new HashMap();
+
+ // We are not using the objectUpdated mechanism in place so that all the children
+ // don't have to sort through the messages.
+
+ if (!telemetryStatsConnected) {
+ UAVObject stats = objMngr.getObject("GCSTelemetryStats");
+ if (stats == null)
+ return;
+
+ stats.addUpdatedObserver(telemetryObserver);
+ telemetryStatsConnected = true;
+ }
+ updateStats();
+
+ if (DEBUG) Log.d(TAG, "Notifying listeners about connection. There are " + connectionListeners.countObservers());
+ connectionListeners.connected();
+ }
+
+ /**
+ * Called when telemetry drops the connection
+ *
+ * This should be called by all inherited classes if they want the telemetry bar etc
+ */
+ void onOPDisconnected() {
+ // Indicate no connection
+ TextView rxRate = (TextView) findViewById(R.id.telemetry_stats_rx_rate);
+ TextView txRate = (TextView) findViewById(R.id.telemetry_stats_tx_rate);
+ rxRate.setText("");
+ txRate.setText("");
+
+ // Providing a null update triggers a disconnect on fragments
+ connectionListeners.disconnected();
+
+ if (objMngr == null) {
+ Log.d(TAG, "onOPDisconnected(): Object manager already went away");
+ return;
+ }
+
+ if (telemetryStatsConnected) {
+ UAVObject stats = objMngr.getObject("GCSTelemetryStats");
+ if (stats != null) {
+ stats.removeUpdatedObserver(telemetryObserver);
+ }
+ telemetryStatsConnected = false;
+ }
+
+ // Disconnect from any UAVO updates
+ if (DEBUG) Log.d(TAG, "onOpDisconnected(): Pausing the listeners and deleting the list");
+ pauseObjectUpdates();
+ listeners = null;
+ }
+
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (mConnected && !telemetryStatsConnected) {
+ UAVObject stats = objMngr.getObject("GCSTelemetryStats");
+ if (stats == null)
+ return;
+
+ stats.addUpdatedObserver(telemetryObserver);
+ telemetryStatsConnected = true;
+ }
+
+ resumeObjectUpdates();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ if (telemetryStatsConnected) {
+ UAVObject stats = objMngr.getObject("GCSTelemetryStats");
+ Assert.assertNotNull(stats); // Should not be null if we connected
+
+ stats.removeUpdatedObserver(telemetryObserver);
+ telemetryStatsConnected = false;
+ }
+
+ pauseObjectUpdates();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (DEBUG) Log.d(TAG, "onStart()");
+
+ // Register a receiver to get connected/disconnected signals from the telemetry
+ // service
+ connectedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG)
+ Log.d(TAG, "Received intent");
+ TelemTask task;
+ if(intent.getAction().compareTo(OPTelemetryService.INTENT_ACTION_CONNECTED) == 0) {
+ if(binder == null)
+ return;
+ if((task = binder.getTelemTask(0)) == null)
+ return;
+ objMngr = task.getObjectManager();
+ mConnected = true;
+ onOPConnected();
+ Log.d(TAG, "Connected()");
+ } else if (intent.getAction().compareTo(OPTelemetryService.INTENT_ACTION_DISCONNECTED) == 0) {
+ onOPDisconnected();
+ objMngr = null;
+ mConnected = false;
+ Log.d(TAG, "Disonnected()");
+ }
+ }
+ };
+
+ // Set up the filters
+ IntentFilter filter = new IntentFilter();
+ filter.addCategory(OPTelemetryService.INTENT_CATEGORY_GCS);
+ filter.addAction(OPTelemetryService.INTENT_ACTION_CONNECTED);
+ filter.addAction(OPTelemetryService.INTENT_ACTION_DISCONNECTED);
+ registerReceiver(connectedReceiver, filter);
+
+ // Bind to the telemetry service (which will start it)
+ Intent intent = new Intent(getApplicationContext(),
+ org.openpilot.androidgcs.telemetry.OPTelemetryService.class);
+ if (DEBUG)
+ Log.d(TAG, "Attempting to bind: " + intent);
+ bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+
+ }
+
+ /**
+ * When stopping disconnect form the service and the broadcast receiver
+ */
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (DEBUG) Log.d(TAG, "onStop()");
+ unbindService(mConnection);
+ unregisterReceiver(connectedReceiver);
+ connectedReceiver = null;
+
+ // Disconnect from any UAVO updates
+ if (DEBUG) Log.d(TAG, "onStop(): Pausing the listeners and deleting the list");
+ pauseObjectUpdates();
+ listeners = null;
+ }
+
+ /*********** This provides the object update messaging service ************/
+
+ /**
+ * A message handler and a custom Observer to use it which calls
+ * objectUpdated with the right object type
+ */
+ final Handler uavobjHandler = new Handler();
+ private class ActivityUpdatedObserver implements Observer {
+ UAVObject obj;
+ ActivityUpdatedObserver(UAVObject obj) { this.obj = obj; };
+ @Override
+ public void update(Observable observable, Object data) {
+ uavobjHandler.post(new Runnable() {
+ @Override
+ public void run() { objectUpdated(obj); }
+ });
+ }
+ };
+ private class FragmentUpdatedObserver implements Observer {
+ UAVObject obj;
+ ObjectManagerFragment frag;
+ FragmentUpdatedObserver(UAVObject obj, ObjectManagerFragment frag) {
+ this.obj = obj;
+ this.frag = frag;
+ };
+ @Override
+ public void update(Observable observable, Object data) {
+ uavobjHandler.post(new Runnable() {
+ @Override
+ public void run() { frag.objectUpdated(obj); }
+ });
+ }
+ };
+
+ /**
+ * Unregister all the objects connected to this activity
+ */
+ private boolean paused = false;
+
+ /**
+ * When an activity is paused, disconnect from all
+ * updates to ensure we don't draw to an invalid view
+ */
+ protected void pauseObjectUpdates()
+ {
+ // When listeners is null then a pause occurred after
+ // disconnecting from the service
+ if (listeners == null)
+ return;
+
+ Set s = listeners.keySet();
+ Iterator i = s.iterator();
+ while (i.hasNext()) {
+ Observer o = i.next();
+ UAVObject obj = listeners.get(o);
+ obj.removeUpdatedObserver(o);
+ }
+ paused = true;
+
+ }
+
+ /**
+ * When an activity is resumed, reconnect all now the view
+ * is valid again
+ */
+ protected void resumeObjectUpdates()
+ {
+ // When listeners is null this is the resume at the beginning
+ // before connecting to the telemetry service
+ if(listeners == null)
+ return;
+
+ Set s = listeners.keySet();
+ Iterator i = s.iterator();
+ while (i.hasNext()) {
+ Observer o = i.next();
+ UAVObject obj = listeners.get(o);
+ obj.addUpdatedObserver(o);
+ }
+ paused = false;
+ }
+
+ /**
+ * Register to listen to a single object from a fragment
+ * @param object The object to listen to updates from
+ * @param frag The fragment who should be notified
+ * the objectUpdated() method will be called in the original UI thread
+ */
+ public void registerObjectUpdates(UAVObject object,
+ ObjectManagerFragment frag) {
+ Observer o = new FragmentUpdatedObserver(object, frag);
+ listeners.put(o, object);
+ if (!paused)
+ object.addUpdatedObserver(o);
+ }
+
+ /**
+ * Register an activity to receive updates from this object
+ * @param object The object the activity should listen to updates from
+ * the objectUpdated() method will be called in the original UI thread
+ */
+ protected void registerObjectUpdates(UAVObject object) {
+ Observer o = new ActivityUpdatedObserver(object);
+ listeners.put(o, object);
+ if (!paused)
+ object.addUpdatedObserver(o);
+ }
+
+ /**
+ * Helper method to register array of objects
+ */
+ protected void registerObjectUpdates(List> objects) {
+ for (int i = 0; i < objects.size(); i++) {
+ List inner = objects.get(i);
+ for (int j = 0; j < inner.size(); j++)
+ registerObjectUpdates(inner.get(j));
+ }
+ }
+
+ /*********** Deals with fragments listening for connections ***************/
+
+ /**
+ * Callbacks so ObjectManagerFragments get the onOPConnected and onOPDisconnected signals
+ */
+ class ConnectionObserver extends Observable {
+ public void disconnected() {
+ synchronized(this) {
+ setChanged();
+ notifyObservers();
+ }
+ }
+ public void connected() {
+ synchronized(this) {
+ setChanged();
+ notifyObservers(objMngr);
+ }
+ }
+ };
+ private final ConnectionObserver connectionListeners = new ConnectionObserver();
+ public class OnConnectionListener implements Observer {
+
+ // Local reference of the fragment to notify, store in constructor
+ ObjectManagerFragment fragment;
+ OnConnectionListener(ObjectManagerFragment fragment) { this.fragment = fragment; };
+
+ // Whenever the observer is updated either conenct or disconnect based on the data
+ @Override
+ public void update(Observable observable, Object data) {
+ Log.d(TAG, "onConnectionListener called");
+ if (data == null)
+ fragment.onOPDisconnected();
+ else
+ fragment.onOPConnected(objMngr);
+ }
+
+ } ;
+ public void addOnConnectionListenerFragment(ObjectManagerFragment frag) {
+ connectionListeners.addObserver(new OnConnectionListener(frag));
+ if (DEBUG) Log.d(TAG, "Connecting " + frag + " there are now " + connectionListeners.countObservers());
+ if (mConnected)
+ frag.onOPConnected(objMngr);
+ }
+
+
+ /*********** Deals with (dis)connection to telemetry service ***************/
+
+ /** Defines callbacks for service binding, passed to bindService() */
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName arg0, IBinder service) {
+ // We've bound to LocalService, cast the IBinder and attempt to open a connection
+ if (DEBUG) Log.d(TAG,"Service bound");
+ mBound = true;
+ binder = (LocalBinder) service;
+
+ if(binder.isConnected()) {
+ TelemTask task;
+ if((task = binder.getTelemTask(0)) != null) {
+ objMngr = task.getObjectManager();
+ mConnected = true;
+ onOPConnected();
+ }
+
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ onOPDisconnected();
+ mBound = false;
+ binder = null;
+ mConnected = false;
+ objMngr = null;
+ objMngr = null;
+ mConnected = false;
+ }
+ };
+
+ /************* Deals with menus *****************/
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (!mBound || binder == null) {
+ Log.e(TAG, "Unable to connect to service");
+ return super.onOptionsItemSelected(item);
+ }
+ switch(item.getItemId()) {
+ case R.id.menu_connect:
+ binder.openConnection();
+ return true;
+ case R.id.menu_disconnect:
+ binder.stopConnection();
+ return true;
+ case R.id.menu_settings:
+ startActivity(new Intent(this, Preferences.class));
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.status_menu, menu);
+ inflater.inflate(R.menu.options_menu, menu);
+ return true;
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/PfdActivity.java b/androidgcs/src/org/openpilot/androidgcs/PfdActivity.java
new file mode 100644
index 000000000..6374c7b82
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/PfdActivity.java
@@ -0,0 +1,58 @@
+/**
+ ******************************************************************************
+ * @file PfdActivity.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief Shows the PFD activity.
+ * @see The GNU Public License (GPL) Version 3
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.openpilot.androidgcs;
+
+import org.openpilot.androidgcs.fragments.PFD;
+
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AbsListView;
+import android.widget.LinearLayout;
+
+public class PfdActivity extends ObjectManagerActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(createUI());
+ }
+
+ private View createUI() {
+ LinearLayout layout = new LinearLayout(this);
+
+ layout.setOrientation(LinearLayout.HORIZONTAL);
+ layout.setLayoutParams(new LinearLayout.LayoutParams(
+ AbsListView.LayoutParams.MATCH_PARENT,
+ AbsListView.LayoutParams.MATCH_PARENT));
+ layout.setId(0x101);
+ {
+ FragmentTransaction fragmentTransaction = getFragmentManager()
+ .beginTransaction();
+ fragmentTransaction.add(0x101, new PFD());
+ fragmentTransaction.commit();
+ }
+ return layout;
+ }
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/Preferences.java b/androidgcs/src/org/openpilot/androidgcs/Preferences.java
new file mode 100644
index 000000000..4b99eadcd
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/Preferences.java
@@ -0,0 +1,47 @@
+/**
+ ******************************************************************************
+ * @file Preferences.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief Displays the preferences dialog.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.openpilot.androidgcs;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceFragment;
+
+public class Preferences extends PreferenceActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Display the fragment as the main content.
+ getFragmentManager().beginTransaction().replace(android.R.id.content,
+ new PrefsFragment()).commit();
+ }
+ public static class PrefsFragment extends PreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.preferences);
+ }
+ }
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/SystemAlarmActivity.java b/androidgcs/src/org/openpilot/androidgcs/SystemAlarmActivity.java
new file mode 100644
index 000000000..51a95004c
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/SystemAlarmActivity.java
@@ -0,0 +1,37 @@
+/**
+ ******************************************************************************
+ * @file SystemAlarmActivity.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief An activity that displays the SystemAlarmsFragment.
+ * @see The GNU Public License (GPL) Version 3
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.openpilot.androidgcs;
+
+import android.os.Bundle;
+
+/**
+ * All the work for this activity is performed by it's fragment
+ */
+public class SystemAlarmActivity extends ObjectManagerActivity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.system_alarms);
+ }
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/TcpUAVTalk.java b/androidgcs/src/org/openpilot/androidgcs/TcpUAVTalk.java
new file mode 100644
index 000000000..cb21abb94
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/TcpUAVTalk.java
@@ -0,0 +1,114 @@
+package org.openpilot.androidgcs;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import org.openpilot.uavtalk.UAVObjectManager;
+import org.openpilot.uavtalk.UAVTalk;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+public class TcpUAVTalk {
+ private final String TAG = "TcpUAVTalk";
+ public static int LOGLEVEL = 2;
+ public static boolean WARN = LOGLEVEL > 1;
+ public static boolean DEBUG = LOGLEVEL > 0;
+
+ // Temporarily define fixed device name
+ private String ip_address = "1";
+ private int port = 9001;
+
+ private UAVTalk uavTalk;
+ private boolean connected;
+ private Socket socket;
+
+ /**
+ * Construct a TcpUAVTalk object attached to the OPTelemetryService. Gets the
+ * connection settings from the preferences.
+ */
+ public TcpUAVTalk(Context caller) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(caller);
+ ip_address = prefs.getString("ip_address","127.0.0.1");
+ try {
+ port = Integer.decode(prefs.getString("port", ""));
+ } catch (NumberFormatException e) {
+ //TODO: Handle this exception
+ }
+
+ if (DEBUG) Log.d(TAG, "Trying to open UAVTalk with " + ip_address);
+
+ connected = false;
+ }
+
+ /**
+ * Connect a TCP object to an object manager. Returns true if already
+ * connected, otherwise returns true if managed a successful socket.
+ */
+ public boolean connect(UAVObjectManager objMngr) {
+ if( getConnected() )
+ return true;
+ if( !openTelemetryTcp(objMngr) )
+ return false;
+ return true;
+ }
+
+ public void disconnect() {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ socket = null;
+ }
+
+ public boolean getConnected() {
+ return connected;
+ }
+
+ public UAVTalk getUavtalk() {
+ return uavTalk;
+ }
+
+ /**
+ * Opens a TCP socket to the address determined on construction. If successful
+ * creates a UAVTalk stream connection this socket to the passed in object manager
+ */
+ private boolean openTelemetryTcp(UAVObjectManager objMngr) {
+ Log.d(TAG, "Opening connection to " + ip_address + " at address " + port);
+
+ InetAddress serverAddr = null;
+ try {
+ serverAddr = InetAddress.getByName(ip_address);
+ } catch (UnknownHostException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ return false;
+ }
+
+ socket = null;
+ try {
+ socket = new Socket(serverAddr,port);
+ } catch (IOException e1) {
+ return false;
+ }
+
+ connected = true;
+
+ try {
+ uavTalk = new UAVTalk(socket.getInputStream(), socket.getOutputStream(), objMngr);
+ } catch (IOException e) {
+ Log.e(TAG,"Error starting UAVTalk");
+ // TODO Auto-generated catch block
+ //e.printStackTrace();
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/TelemetryWidget.java b/androidgcs/src/org/openpilot/androidgcs/TelemetryWidget.java
new file mode 100644
index 000000000..d874c9b12
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/TelemetryWidget.java
@@ -0,0 +1,74 @@
+/**
+ ******************************************************************************
+ * @file TelemetryWidget.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief A widget that shows the status of telemetry.
+ * @see The GNU Public License (GPL) Version 3
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.androidgcs;
+
+import org.openpilot.androidgcs.telemetry.OPTelemetryService;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.widget.RemoteViews;
+
+public class TelemetryWidget extends AppWidgetProvider {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if(intent.getAction().equals(OPTelemetryService.INTENT_ACTION_CONNECTED)) {
+ changeStatus(context, true);
+ }
+ if(intent.getAction().equals(OPTelemetryService.INTENT_ACTION_DISCONNECTED)) {
+ changeStatus(context, false);
+ }
+
+ super.onReceive(context, intent);
+ }
+
+ public void changeStatus(Context context, boolean status) {
+ RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.telemetry_widget);
+ updateViews.setTextViewText(R.id.telemetryWidgetStatus, "Connection status: " + status);
+ ComponentName thisWidget = new ComponentName(context, TelemetryWidget.class);
+ AppWidgetManager manager = AppWidgetManager.getInstance(context);
+ manager.updateAppWidget(thisWidget, updateViews);
+
+ }
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ final int N = appWidgetIds.length;
+
+ // Perform this loop procedure for each App Widget that belongs to this provider
+ for (int i=0; i 1;
+ private static boolean DEBUG = LOGLEVEL > 0;
+
+ private MapView mapView;
+ private MapController mapController;
+
+ UAVObjectManager objMngr;
+ boolean mBound = false;
+ boolean mConnected = false;
+ BroadcastReceiver connectedReceiver;
+ org.openpilot.androidgcs.telemetry.OPTelemetryService.LocalBinder binder;
+
+ GeoPoint homeLocation;
+ GeoPoint uavLocation;
+
+ @Override public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.map_layout);
+ mapView = (MapView)findViewById(R.id.map_view);
+ mapController = mapView.getController();
+
+ mapView.displayZoomControls(true);
+ Double lat = 37.422006*1E6;
+ Double lng = -122.084095*1E6;
+ homeLocation = new GeoPoint(lat.intValue(), lng.intValue());
+ uavLocation = homeLocation;
+ mapController.setCenter(homeLocation);
+ mapController.setZoom(18);
+
+ List overlays = mapView.getOverlays();
+ UAVOverlay myOverlay = new UAVOverlay();
+ overlays.add(myOverlay);
+
+ MyLocationOverlay myLocationOverlay = new MyLocationOverlay(this, mapView);
+ myLocationOverlay.enableMyLocation();
+ myLocationOverlay.enableCompass();
+ overlays.add(myLocationOverlay);
+
+ mapView.postInvalidate();
+
+ }
+
+ //@Override
+ @Override
+ protected boolean isRouteDisplayed() {
+ // IMPORTANT: This method must return true if your Activity // is displaying driving directions. Otherwise return false.
+ return false;
+ }
+
+ public class UAVOverlay extends Overlay {
+ Bitmap homeSymbol = BitmapFactory.decodeResource(getResources(), R.drawable.ic_home);
+ Bitmap uavSymbol = BitmapFactory.decodeResource(getResources(), R.drawable.ic_uav);
+ @Override
+ public void draw(Canvas canvas, MapView mapView, boolean shadow) {
+
+ Projection projection = mapView.getProjection();
+
+ if (shadow == false) {
+
+ Point myPoint = new Point();
+ projection.toPixels(uavLocation, myPoint);
+
+ //// Draw UAV
+ // Create and setup your paint brush
+ Paint paint = new Paint();
+ paint.setARGB(250, 255, 0, 0);
+ paint.setAntiAlias(true);
+ paint.setFakeBoldText(true);
+
+ // Draw on the canvas
+ canvas.drawBitmap(uavSymbol, myPoint.x - uavSymbol.getWidth() / 2,
+ myPoint.y - uavSymbol.getHeight() / 2, paint);
+ canvas.drawText("UAV", myPoint.x+uavSymbol.getWidth() / 2, myPoint.y, paint);
+
+ //// Draw Home
+ myPoint = new Point();
+ projection.toPixels(homeLocation, myPoint);
+
+ // Create and setup your paint brush
+ paint.setARGB(250, 0, 0, 0);
+ paint.setAntiAlias(true);
+ paint.setFakeBoldText(true);
+
+ canvas.drawBitmap(homeSymbol, myPoint.x - homeSymbol.getWidth() / 2,
+ myPoint.y - homeSymbol.getHeight() / 2, paint);
+ canvas.drawText("Home", myPoint.x+homeSymbol.getWidth(), myPoint.y, paint);
+
+ }
+ }
+
+ @Override
+ public boolean onTap(GeoPoint point, MapView mapView1) {
+ // Return true if screen tap is handled by this overlay
+ return false;
+ }
+ }
+
+ void onOPConnected() {
+ UAVObject obj = objMngr.getObject("HomeLocation");
+ registerObjectUpdates(obj);
+
+ obj = objMngr.getObject("PositionActual");
+ registerObjectUpdates(obj);
+ }
+
+ private GeoPoint getUavLocation() {
+ UAVObject pos = objMngr.getObject("PositionActual");
+ if (pos == null)
+ return new GeoPoint(0,0);
+
+ UAVObject home = objMngr.getObject("HomeLocation");
+ if (home == null)
+ return new GeoPoint(0,0);
+
+ double lat, lon, alt;
+ lat = home.getField("Latitude").getDouble() / 10.0e6;
+ lon = home.getField("Longitude").getDouble() / 10.0e6;
+ alt = home.getField("Altitude").getDouble();
+
+ // Get the home coordinates
+ double T0, T1;
+ T0 = alt+6.378137E6;
+ T1 = Math.cos(lat * Math.PI / 180.0)*(alt+6.378137E6);
+
+ // Get the NED coordinates
+ double NED0, NED1;
+ NED0 = pos.getField("North").getDouble();
+ NED1 = pos.getField("East").getDouble();
+
+ // Compute the LLA coordinates
+ lat = lat + (NED0 / T0) * 180.0 / Math.PI;
+ lon = lon + (NED1 / T1) * 180.0 / Math.PI;
+
+ return new GeoPoint((int) (lat * 1e6), (int) (lon * 1e6));
+ }
+
+ void onOPDisconnected() {
+ unregisterObjectUpdates();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case R.id.menu_connect:
+ binder.openConnection();
+ return true;
+ case R.id.menu_disconnect:
+ binder.stopConnection();
+ return true;
+ case R.id.menu_settings:
+ startActivity(new Intent(this, Preferences.class));
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.options_menu, menu);
+ return true;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ // ObjectManager related stuff (can't inherit standard class)
+ connectedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "Received intent");
+ TelemTask task;
+ if(intent.getAction().compareTo(OPTelemetryService.INTENT_ACTION_CONNECTED) == 0) {
+
+ if(binder == null)
+ return;
+ if((task = binder.getTelemTask(0)) == null)
+ return;
+ objMngr = task.getObjectManager();
+ mConnected = true;
+ onOPConnected();
+ Log.d(TAG, "Connected()");
+ } else if (intent.getAction().compareTo(OPTelemetryService.INTENT_ACTION_DISCONNECTED) == 0) {
+ objMngr = null;
+ mConnected = false;
+ onOPDisconnected();
+ Log.d(TAG, "Disonnected()");
+ }
+ }
+ };
+
+ IntentFilter filter = new IntentFilter();
+ filter.addCategory(OPTelemetryService.INTENT_CATEGORY_GCS);
+ filter.addAction(OPTelemetryService.INTENT_ACTION_CONNECTED);
+ filter.addAction(OPTelemetryService.INTENT_ACTION_DISCONNECTED);
+ registerReceiver(connectedReceiver, filter);
+
+ Intent intent = new Intent(this, OPTelemetryService.class);
+ bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ /**
+ * When stopping disconnect form the service and the broadcast receiver
+ */
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (DEBUG) Log.d(TAG, "onStop()");
+ unbindService(mConnection);
+ unregisterReceiver(connectedReceiver);
+ connectedReceiver = null;
+
+ // TODO: The register and unregister probably should move to onPause / onResume
+ unregisterObjectUpdates();
+ }
+
+ public void onBind() {
+
+ }
+
+ /** Defines callbacks for service binding, passed to bindService() */
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName arg0, IBinder service) {
+ // We've bound to LocalService, cast the IBinder and attempt to open a connection
+ if (DEBUG) Log.d(TAG,"Service bound");
+ mBound = true;
+ binder = (LocalBinder) service;
+
+ if(binder.isConnected()) {
+ TelemTask task;
+ if((task = binder.getTelemTask(0)) != null) {
+ objMngr = task.getObjectManager();
+ mConnected = true;
+ onOPConnected();
+ }
+
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mBound = false;
+ binder = null;
+ mConnected = false;
+ objMngr = null;
+ objMngr = null;
+ mConnected = false;
+ onOPDisconnected();
+ }
+ };
+
+/******* STRAIGHT COPY PASTE FROM ObjectManagerActivity *************/
+ /**
+ * Called whenever any objects subscribed to via registerObjects
+ */
+ protected void objectUpdated(UAVObject obj) {
+ if (obj == null)
+ return;
+ if (obj.getName().compareTo("HomeLocation") == 0) {
+ Double lat = obj.getField("Latitude").getDouble() / 10;
+ Double lon = obj.getField("Longitude").getDouble() / 10;
+ homeLocation = new GeoPoint(lat.intValue(), lon.intValue());
+ mapController.setCenter(homeLocation);
+ } else if (obj.getName().compareTo("PositionActual") == 0) {
+ uavLocation = getUavLocation();
+ mapView.invalidate();
+ }
+ }
+
+ /**
+ * A message handler and a custom Observer to use it which calls
+ * objectUpdated with the right object type
+ */
+ final Handler uavobjHandler = new Handler();
+ private class ActivityUpdatedObserver implements Observer {
+ UAVObject obj;
+ ActivityUpdatedObserver(UAVObject obj) { this.obj = obj; };
+ @Override
+ public void update(Observable observable, Object data) {
+ uavobjHandler.post(new Runnable() {
+ @Override
+ public void run() { objectUpdated(obj); }
+ });
+ }
+ };
+ private class FragmentUpdatedObserver implements Observer {
+ UAVObject obj;
+ ObjectManagerFragment frag;
+ FragmentUpdatedObserver(UAVObject obj, ObjectManagerFragment frag) {
+ this.obj = obj;
+ this.frag = frag;
+ };
+ @Override
+ public void update(Observable observable, Object data) {
+ uavobjHandler.post(new Runnable() {
+ @Override
+ public void run() { frag.objectUpdated(obj); }
+ });
+ }
+ };
+
+
+ /**
+ * Register an activity to receive updates from this object
+ *
+ * the objectUpdated() method will be called in the original UI thread
+ */
+ HashMap listeners = new HashMap();
+ protected void registerObjectUpdates(UAVObject object) {
+ Observer o = new ActivityUpdatedObserver(object);
+ object.addUpdatedObserver(o);
+ listeners.put(o, object);
+ }
+ /**
+ * Unregister all the objects connected to this activity
+ */
+ protected void unregisterObjectUpdates()
+ {
+ Set s = listeners.keySet();
+ Iterator i = s.iterator();
+ while (i.hasNext()) {
+ Observer o = i.next();
+ UAVObject obj = listeners.get(o);
+ obj.removeUpdatedObserver(o);
+ }
+ listeners.clear();
+ }
+ public void registerObjectUpdates(UAVObject object,
+ ObjectManagerFragment frag) {
+ Observer o = new FragmentUpdatedObserver(object, frag);
+ object.addUpdatedObserver(o);
+ listeners.put(o, object);
+ }
+ protected void registerObjectUpdates(List> objects) {
+ ListIterator> li = objects.listIterator();
+ while(li.hasNext()) {
+ ListIterator li2 = li.next().listIterator();
+ while(li2.hasNext())
+ registerObjectUpdates(li2.next());
+ }
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/fragments/MapPositioner.java b/androidgcs/src/org/openpilot/androidgcs/fragments/MapPositioner.java
new file mode 100644
index 000000000..e4d2b266e
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/fragments/MapPositioner.java
@@ -0,0 +1,229 @@
+/**
+ ******************************************************************************
+ * @file MapPositioner.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief A fragmenet to move the UAV around by dragging
+ * @see The GNU Public License (GPL) Version 3
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+ package org.openpilot.androidgcs.fragments;
+
+import org.openpilot.androidgcs.R;
+import org.openpilot.uavtalk.UAVObject;
+import org.openpilot.uavtalk.UAVObjectManager;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.SeekBar;
+
+public class MapPositioner extends ObjectManagerFragment {
+
+ private static final String TAG = ObjectManagerFragment.class
+ .getSimpleName();
+ private static final int LOGLEVEL = 0;
+ private static final boolean DEBUG = LOGLEVEL > 0;
+
+ private final float MAX_DISTANCE_M = 50.0f;
+
+ float px_to_m(View v, float px) {
+ final float MAX_DISTANCE_PX = Math.max(v.getMeasuredHeight(),
+ v.getMeasuredWidth());
+ float scale = MAX_DISTANCE_M / MAX_DISTANCE_PX;
+ return px * scale;
+ }
+
+ float m_to_px(View v, float m) {
+ final float MAX_DISTANCE_PX = Math.max(v.getMeasuredHeight(),
+ v.getMeasuredWidth());
+ float scale = MAX_DISTANCE_M / MAX_DISTANCE_PX;
+ return m / scale;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+ return inflater.inflate(R.layout.map_positioner, container, false);
+ }
+
+ private float pos_x = 0, pos_y = 0;
+ private float desired_x = 0, desired_y = 0, desired_z = 0;
+ //private final float poi_x = 1, poi_y = 1;
+ private UAVOverlay uav = null;
+
+ public class UAVOverlay extends View {
+
+ Paint paint = null;
+
+ public UAVOverlay(Context context) {
+ super(context);
+ paint = new Paint();
+ paint.setColor(Color.WHITE);
+ paint.setTextSize(20);
+ }
+
+ private void draw(Canvas canvas, int resource, float x, float y) {
+ Drawable icon = getContext().getResources().getDrawable(resource);
+ final int offset_x = canvas.getWidth() / 2;
+ final int offset_y = canvas.getHeight() / 2;
+ final int SIZE = 50 / 2;
+ icon.setBounds((int) x - SIZE + offset_x, (int) y - SIZE + offset_y,
+ (int) x + SIZE + offset_x, (int) y + SIZE + offset_y);
+ icon.draw(canvas);
+
+ }
+
+ @Override
+ /**
+ * Draw the UAV, the position desired, and the camera POI
+ */
+ protected void onDraw(Canvas canvas) {
+ draw(canvas, R.drawable.map_positioner_uav, (int) m_to_px(this, pos_x), (int) m_to_px(this, pos_y));
+ draw(canvas, R.drawable.map_positioner_waypoint, (int) m_to_px(this, desired_x), (int) -m_to_px(this, desired_y));
+ //draw(canvas, R.drawable.map_positioner_poi, (int) m_to_px(this, poi_x), (int) -m_to_px(this, poi_y));
+
+ // Annotate the desired position
+ canvas.drawText("(" + String.format("%.1f",desired_x) + "," + String.format("%.1f",desired_y) + "," + String.format("%.1f",desired_z) + ")",
+ m_to_px(this, desired_x) + canvas.getWidth() / 2 + 25,
+ -m_to_px(this, desired_y) + canvas.getHeight() / 2, paint);
+ }
+
+ };
+
+ public void connectTouch() {
+ if (DEBUG)
+ Log.d(TAG, "Connected touch listener()");
+ View v = getActivity().findViewById(R.id.map_view);
+ if (v != null)
+ v.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ UAVObject desired = objMngr.getObject("PathDesired");
+ if (desired != null) {
+ if (DEBUG) Log.d(TAG, "Updating path desired");
+ desired.getField("End").setDouble(px_to_m(v, (int) event.getX() - v.getMeasuredWidth() / 2), 1);
+ desired.getField("End").setDouble(px_to_m(v, -(event.getY() - v.getMeasuredHeight() / 2)), 0);
+ desired.updated();
+ if (uav != null)
+ uav.invalidate();
+ }
+ break;
+ }
+ return true;
+ }
+ });
+ else
+ if (DEBUG)
+ Log.d(TAG, "No view");
+
+ FrameLayout f = (FrameLayout) getActivity().findViewById(R.id.map_view);
+ uav = new UAVOverlay(getActivity().getBaseContext());
+ f.addView(uav);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (DEBUG)
+ Log.d(TAG, "onStart()");
+ connectTouch();
+
+ SeekBar altitudeBar = (SeekBar) getActivity().findViewById(R.id.altitude_slider);
+ if(altitudeBar != null)
+ altitudeBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress,
+ boolean fromUser) {
+ UAVObject desired = objMngr.getObject("PathDesired");
+ if (desired != null) {
+ if (DEBUG) Log.d(TAG, "Updating path desired");
+ desired.getField("End").setDouble(-progress, 2);
+ desired.updated();
+ if (uav != null)
+ uav.invalidate();
+ }
+
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // TODO Auto-generated method stub
+
+ }
+
+ });
+ }
+
+ @Override
+ public void onOPConnected(UAVObjectManager objMngr) {
+ super.onOPConnected(objMngr);
+ if (DEBUG)
+ Log.d(TAG, "On connected");
+
+ UAVObject obj = objMngr.getObject("PositionActual");
+ if (obj != null)
+ registerObjectUpdates(obj);
+ objectUpdated(obj);
+
+ obj = objMngr.getObject("PathDesired");
+ if (obj != null)
+ registerObjectUpdates(obj);
+ objectUpdated(obj);
+ }
+
+ /**
+ * Called whenever any objects subscribed to via registerObjects. Store the
+ * local copy of the information.
+ */
+ @Override
+ public void objectUpdated(UAVObject obj) {
+ if (obj.getName().compareTo("PositionActual") == 0) {
+ pos_x = (int) obj.getField("East").getDouble();
+ pos_y = (int) obj.getField("North").getDouble();
+ uav.invalidate();
+ }
+ if (obj.getName().compareTo("PathDesired") == 0) {
+ desired_x = (float) obj.getField("End").getDouble(1);
+ desired_y = (float) obj.getField("End").getDouble(0);
+ desired_z = (float) obj.getField("End").getDouble(2);
+ uav.invalidate();
+ }
+ }
+
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/fragments/ObjectManagerFragment.java b/androidgcs/src/org/openpilot/androidgcs/fragments/ObjectManagerFragment.java
new file mode 100644
index 000000000..0ddd7a97e
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/fragments/ObjectManagerFragment.java
@@ -0,0 +1,104 @@
+/**
+ ******************************************************************************
+ * @file ObjectManagerFragment.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief Base class for all fragments that use the UAVObjectManager. This
+ * supports all the extensions the ObjectManagerActivity does, namely
+ * access to the UAVObjectManager and callbacks in the UI thread for
+ * object updates.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.androidgcs.fragments;
+
+import org.openpilot.androidgcs.ObjectManagerActivity;
+import org.openpilot.uavtalk.UAVObject;
+import org.openpilot.uavtalk.UAVObjectManager;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.os.Bundle;
+import android.util.Log;
+
+public class ObjectManagerFragment extends Fragment {
+
+ private static final String TAG = ObjectManagerFragment.class.getSimpleName();
+ private static final int LOGLEVEL = 0;
+// private static boolean WARN = LOGLEVEL > 1;
+ private static final boolean DEBUG = LOGLEVEL > 0;
+
+ UAVObjectManager objMngr;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (DEBUG) Log.d(TAG, "Created an ObjectManagerFragment");
+ // For an activity this registers against the telemetry service intents. Fragments must be notified by their
+ // parent activity
+ }
+
+ /**
+ * Attach to the parent activity so it can notify us when the connection
+ * changed
+ */
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ if (DEBUG) Log.d(TAG,"onAttach");
+
+ ObjectManagerActivity castActivity = null;
+ try {
+ castActivity = (ObjectManagerActivity)activity;
+ } catch (ClassCastException e) {
+ throw new android.app.Fragment.InstantiationException(
+ "Attaching a ObjectManagerFragment to an activity failed because the parent activity is not a ObjectManagerActivity",
+ e);
+ }
+ castActivity.addOnConnectionListenerFragment(this);
+ }
+
+
+ // The below methods should all be called by the parent activity at the appropriate times
+ public void onOPConnected(UAVObjectManager objMngr) {
+ this.objMngr = objMngr;
+ if (DEBUG) Log.d(TAG,"onOPConnected");
+ }
+
+ public void onOPDisconnected() {
+ objMngr = null;
+ if (DEBUG) Log.d(TAG,"onOPDisconnected");
+ }
+
+ /**
+ * Called whenever any objects subscribed to via registerObjects
+ */
+ public void objectUpdated(UAVObject obj) {
+
+ }
+
+ /**
+ * Register on the activities object monitor handler so that updates
+ * occur within that UI thread. No need to maintain a handler for
+ * each fragment.
+ */
+ protected void registerObjectUpdates(UAVObject object) {
+ ((ObjectManagerActivity) getActivity()).registerObjectUpdates(object, this);
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/fragments/PFD.java b/androidgcs/src/org/openpilot/androidgcs/fragments/PFD.java
new file mode 100644
index 000000000..ec265fe6e
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/fragments/PFD.java
@@ -0,0 +1,93 @@
+/**
+ ******************************************************************************
+ * @file PFD.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief The PFD display fragment
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.openpilot.androidgcs.fragments;
+
+import org.openpilot.androidgcs.AttitudeView;
+import org.openpilot.androidgcs.R;
+import org.openpilot.uavtalk.UAVObject;
+import org.openpilot.uavtalk.UAVObjectManager;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class PFD extends ObjectManagerFragment {
+
+ private static final String TAG = ObjectManagerFragment.class
+ .getSimpleName();
+ private static final int LOGLEVEL = 0;
+ // private static boolean WARN = LOGLEVEL > 1;
+ private static final boolean DEBUG = LOGLEVEL > 0;
+
+ // @Override
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.pfd, container, false);
+ }
+
+ @Override
+ public void onOPConnected(UAVObjectManager objMngr) {
+ super.onOPConnected(objMngr);
+ if (DEBUG)
+ Log.d(TAG, "On connected");
+
+ UAVObject obj = objMngr.getObject("AttitudeActual");
+ if (obj != null)
+ registerObjectUpdates(obj);
+ objectUpdated(obj);
+ }
+
+ /**
+ * Called whenever any objects subscribed to via registerObjects
+ */
+ @Override
+ public void objectUpdated(UAVObject obj) {
+ if (DEBUG)
+ Log.d(TAG, "Updated");
+
+ double pitch = obj.getField("Pitch").getDouble();
+ double roll = obj.getField("Roll").getDouble();
+
+ // TODO: These checks, while sensible, are necessary because the
+ // callbacks aren't
+ // removed when we switch to different activities sharing this fragment
+ Activity parent = getActivity();
+ AttitudeView attitude = null;
+ if (parent != null)
+ attitude = (AttitudeView) parent.findViewById(R.id.attitude_view);
+ if (attitude != null) {
+ attitude.setRoll(roll);
+ attitude.setPitch(pitch);
+ attitude.invalidate();
+ }
+
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/fragments/SystemAlarmsFragment.java b/androidgcs/src/org/openpilot/androidgcs/fragments/SystemAlarmsFragment.java
new file mode 100644
index 000000000..e311e7249
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/fragments/SystemAlarmsFragment.java
@@ -0,0 +1,93 @@
+/**
+ ******************************************************************************
+ * @file SystemAlarmsFragment.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief A fragment that will connect to the SystemAlarms and visualize them.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.androidgcs.fragments;
+
+import java.util.List;
+
+import org.openpilot.androidgcs.R;
+import org.openpilot.uavtalk.UAVObject;
+import org.openpilot.uavtalk.UAVObjectField;
+import org.openpilot.uavtalk.UAVObjectManager;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class SystemAlarmsFragment extends ObjectManagerFragment {
+
+ private static final String TAG = SystemAlarmsFragment.class.getSimpleName();
+ private static final int LOGLEVEL = 0;
+ // private static boolean WARN = LOGLEVEL > 1;
+ private static final boolean DEBUG = LOGLEVEL > 0;
+
+ //@Override
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.system_alarms_fragment, container, false);
+ }
+
+ @Override
+ public void onOPConnected(UAVObjectManager objMngr) {
+ super.onOPConnected(objMngr);
+ if (DEBUG)
+ Log.d(TAG, "On connected");
+
+ UAVObject obj = objMngr.getObject("SystemAlarms");
+ if (obj != null)
+ registerObjectUpdates(obj);
+ objectUpdated(obj);
+ }
+
+ /**
+ * Called whenever any objects subscribed to via registerObjects
+ */
+ @Override
+ public void objectUpdated(UAVObject obj) {
+ if (DEBUG)
+ Log.d(TAG, "Updated");
+ if (obj.getName().compareTo("SystemAlarms") == 0) {
+ TextView alarms = (TextView) getActivity().findViewById(R.id.system_alarms_fragment_field);
+ UAVObjectField a = obj.getField("Alarm");
+ List names = a.getElementNames();
+ String contents = new String();
+ List options = a.getOptions();
+
+ // Rank the alarms by order of severity, skip uninitialized
+ for (int j = options.size() - 1; j > 0; j--) {
+ for (int i = 0; i < names.size(); i++) {
+ if(a.getDouble(i) == j)
+ contents += names.get(i) + " : " + a.getValue(i).toString() + "\n";
+ }
+ }
+ alarms.setText(contents);
+ }
+ }
+
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/BluetoothUAVTalk.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/BluetoothUAVTalk.java
new file mode 100644
index 000000000..db3d44134
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/BluetoothUAVTalk.java
@@ -0,0 +1,178 @@
+/**
+ ******************************************************************************
+ * @file BluetoothUAVTalk.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief Telemetry over bluetooth.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.openpilot.androidgcs.telemetry;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.UUID;
+
+import org.openpilot.uavtalk.UAVObjectManager;
+import org.openpilot.uavtalk.UAVTalk;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+@TargetApi(10) public class BluetoothUAVTalk {
+ private final String TAG = "BluetoothUAVTalk";
+ public static int LOGLEVEL = 2;
+ public static boolean WARN = LOGLEVEL > 1;
+ public static boolean DEBUG = LOGLEVEL > 0;
+
+ // Temporarily define fixed device name
+ private String device_name = "RN42-222D";
+ private final static UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
+
+ private BluetoothAdapter mBluetoothAdapter;
+ private BluetoothSocket socket;
+ private BluetoothDevice device;
+ private UAVTalk uavTalk;
+ private boolean connected;
+
+ public BluetoothUAVTalk(Context caller) {
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(caller);
+ device_name = prefs.getString("bluetooth_mac","");
+
+ if (DEBUG) Log.d(TAG, "Trying to open UAVTalk with " + device_name);
+
+ connected = false;
+ device = null;
+
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (mBluetoothAdapter == null) {
+ // Device does not support Bluetooth
+ Log.e(TAG, "Device does not support Bluetooth");
+ return;
+ }
+
+ if (!mBluetoothAdapter.isEnabled()) {
+ // Enable bluetooth if it isn't already
+ Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ caller.sendOrderedBroadcast(enableBtIntent, "android.permission.BLUETOOTH_ADMIN", new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.e(TAG,"Received " + context + intent);
+ //TODO: some logic here to see if it worked
+ queryDevices();
+ }
+ }, null, Activity.RESULT_OK, null, null);
+ } else {
+ queryDevices();
+ }
+ }
+
+ public boolean connect(UAVObjectManager objMngr) {
+ if( getConnected() )
+ return true;
+ if( !getFoundDevice() )
+ return false;
+ if( !openTelemetryBluetooth(objMngr) )
+ return false;
+ return true;
+ }
+
+ public boolean getConnected() {
+ return connected;
+ }
+
+ public boolean getFoundDevice() {
+ return (device != null);
+ }
+
+ public UAVTalk getUavtalk() {
+ return uavTalk;
+ }
+
+ private void queryDevices() {
+ Log.d(TAG, "Searching for devices");
+ Set pairedDevices = mBluetoothAdapter.getBondedDevices();
+ // If there are paired devices
+ if (pairedDevices.size() > 0) {
+ // Loop through paired devices
+ for (BluetoothDevice device : pairedDevices) {
+ // Add the name and address to an array adapter to show in a ListView
+ //mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
+ Log.d(TAG, "Paired device: " + device.getAddress() + " compared to " + device_name);
+ if(device.getAddress().compareTo(device_name) == 0) {
+ Log.d(TAG, "Found device: " + device.getName());
+ this.device = device;
+ return;
+ }
+ }
+ }
+
+ }
+
+ private boolean openTelemetryBluetooth(UAVObjectManager objMngr) {
+ Log.d(TAG, "Opening connection to " + device.getName());
+ socket = null;
+ connected = false;
+ try {
+ socket = device.createInsecureRfcommSocketToServiceRecord(MY_UUID);
+ } catch (IOException e) {
+ Log.e(TAG,"Unable to create Rfcomm socket");
+ return false;
+ //e.printStackTrace();
+ }
+
+ mBluetoothAdapter.cancelDiscovery();
+
+ try {
+ socket.connect();
+ }
+ catch (IOException e) {
+ Log.e(TAG,"Unable to connect to requested device", e);
+ try {
+ socket.close();
+ } catch (IOException e2) {
+ Log.e(TAG, "unable to close() socket during connection failure", e2);
+ }
+ return false;
+ }
+
+ connected = true;
+
+ try {
+ uavTalk = new UAVTalk(socket.getInputStream(), socket.getOutputStream(), objMngr);
+ } catch (IOException e) {
+ Log.e(TAG,"Error starting UAVTalk");
+ // TODO Auto-generated catch block
+ //e.printStackTrace();
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java
new file mode 100644
index 000000000..991e83871
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java
@@ -0,0 +1,590 @@
+package org.openpilot.androidgcs.telemetry;
+
+// Code based on notes from http://torvafirmus-android.blogspot.com/2011/09/implementing-usb-hid-interface-in.html
+// Taken 2012-08-10
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import junit.framework.Assert;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbRequest;
+import android.util.Log;
+
+public class HidUAVTalk extends TelemetryTask {
+
+ private static final String TAG = HidUAVTalk.class.getSimpleName();
+ public static int LOGLEVEL = 0;
+ public static boolean WARN = LOGLEVEL > 1;
+ public static boolean DEBUG = LOGLEVEL > 0;
+
+ //! USB constants
+ private static final int MAX_HID_PACKET_SIZE = 64;
+ static final int OPENPILOT_VENDOR_ID = 0x20A0;
+
+ static final int USB_PRODUCT_ID_OPENPILOT_MAIN = 0x415A;
+ static final int USB_PRODUCT_ID_COPTERCONTROL = 0x415B;
+ static final int USB_PRODUCT_ID_PIPXTREME = 0x415C;
+ static final int USB_PRODUCT_ID_CC3D = 0x415D;
+ static final int USB_PRODUCT_ID_REVOLUTION = 0x415E;
+ static final int USB_PRODUCT_ID_OSD = 0x4194;
+ static final int USB_PRODUCT_ID_SPARE = 0x4195;
+
+ private static final String ACTION_USB_PERMISSION = "com.access.device.USB_PERMISSION";
+
+ UsbDevice currentDevice;
+
+ public HidUAVTalk(OPTelemetryService service) {
+ super(service);
+ }
+
+ @Override
+ public void disconnect() {
+
+ CleanUpAndClose();
+ //hostDisplayActivity.unregisterReceiver(usbReceiver);
+ telemService.unregisterReceiver(usbPermissionReceiver);
+
+ super.disconnect();
+
+ try {
+ if(readWriteThread != null) {
+ readWriteThread.join();
+ readWriteThread = null;
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ if (readRequest != null) {
+ readRequest.cancel();
+ readRequest.close();
+ readRequest = null;
+ }
+
+ if (writeRequest != null) {
+ writeRequest.cancel();
+ writeRequest.close();
+ writeRequest = null;
+ }
+
+ }
+
+ @Override
+ boolean attemptConnection() {
+ if (DEBUG) Log.d(TAG, "connect()");
+
+ // Register to get permission requested dialog
+ usbManager = (UsbManager) telemService.getSystemService(Context.USB_SERVICE);
+ permissionIntent = PendingIntent.getBroadcast(telemService, 0, new Intent(ACTION_USB_PERMISSION), 0);
+ permissionFilter = new IntentFilter(ACTION_USB_PERMISSION);
+ telemService.registerReceiver(usbPermissionReceiver, permissionFilter);
+
+ // Go through all the devices plugged in
+ HashMap deviceList = usbManager.getDeviceList();
+ if (DEBUG) Log.d(TAG, "Found " + deviceList.size() + " devices");
+ Iterator deviceIterator = deviceList.values().iterator();
+ while(deviceIterator.hasNext()){
+ UsbDevice dev = deviceIterator.next();
+ if (DEBUG) Log.d(TAG, "Testing device: " + dev);
+ usbManager.requestPermission(dev, permissionIntent);
+ }
+
+ if (DEBUG) Log.d(TAG, "Registered the deviceAttachedFilter");
+
+ return deviceList.size() > 0;
+ }
+
+ /*
+ * Receives a requested broadcast from the operating system.
+ * In this case the following actions are handled:
+ * USB_PERMISSION
+ * UsbManager.ACTION_USB_DEVICE_DETACHED
+ * UsbManager.ACTION_USB_DEVICE_ATTACHED
+ */
+ private final BroadcastReceiver usbPermissionReceiver = new BroadcastReceiver()
+ {
+ @Override
+ public void onReceive(Context context, Intent intent)
+ {
+ if (DEBUG) Log.d(TAG,"Broadcast receiver caught intent: " + intent);
+ String action = intent.getAction();
+ // Validate the action against the actions registered
+ if (ACTION_USB_PERMISSION.equals(action))
+ {
+ // A permission response has been received, validate if the user has
+ // GRANTED, or DENIED permission
+ synchronized (this)
+ {
+ UsbDevice deviceConnected = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+
+ if (DEBUG) Log.d(TAG, "Device Permission requested" + deviceConnected);
+ if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false))
+ {
+ // Permission has been granted, so connect to the device
+ // If this fails, then keep looking
+ if (deviceConnected != null)
+ {
+ // call method to setup device communication
+ currentDevice = deviceConnected;
+ if (DEBUG) Log.d(TAG, "Device Permission Acquired" + currentDevice);
+ if (!ConnectToDeviceInterface(currentDevice))
+ {
+ if (DEBUG) Log.d(TAG, "Unable to connect to device");
+ }
+ }
+ }
+ else
+ {
+ // Permission has not been granted, so keep looking for another
+ // device to be attached....
+ if (DEBUG) Log.d(TAG, "Device Permission Denied" + deviceConnected);
+ currentDevice = null;
+ }
+ }
+ }
+ }
+ };
+
+ /* TODO: Detect dettached events and close the connection
+ private final BroadcastReceiver usbReceiver = new BroadcastReceiver()
+ {
+ @Override
+ public void onReceive(Context context, Intent intent)
+ {
+ if (DEBUG) Log.d(TAG,"Broadcast receiver taught intent: " + intent);
+ String action = intent.getAction();
+ // Validate the action against the actions registered
+
+ if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action))
+ {
+ // A device has been detached from the device, so close all the connections
+ // and restart the search for a new device being attached
+ UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ if ((device != null) && (currentDevice != null))
+ {
+ if (device.equals(currentDevice))
+ {
+ // call your method that cleans up and closes communication with the device
+ CleanUpAndClose();
+ }
+ }
+ }
+ else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action))
+ {
+ // A device has been attached. If not already connected to a device,
+ // validate if this device is supported
+ UsbDevice searchDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ if (DEBUG) Log.d(TAG, "Device found" + searchDevice);
+ if ((searchDevice != null) && (currentDevice == null))
+ {
+ // call your method that cleans up and closes communication with the device
+ ValidateFoundDevice(searchDevice);
+ }
+ }
+ }
+ }; */
+
+ private UsbEndpoint usbEndpointRead;
+
+ private UsbEndpoint usbEndpointWrite;
+
+ private UsbManager usbManager;
+
+ private PendingIntent permissionIntent;
+
+ private UsbDeviceConnection connectionRead;
+
+ private UsbDeviceConnection connectionWrite;
+
+ private IntentFilter permissionFilter;
+
+ protected void CleanUpAndClose() {
+ if (UsingSingleInterface) {
+ if(connectionRead != null && usbInterfaceRead != null)
+ connectionRead.releaseInterface(usbInterfaceRead);
+ usbInterfaceRead = null;
+ }
+ else {
+ if(connectionRead != null && usbInterfaceRead != null)
+ connectionRead.releaseInterface(usbInterfaceRead);
+ if(connectionWrite != null && usbInterfaceWrite != null)
+ connectionWrite.releaseInterface(usbInterfaceWrite);
+ usbInterfaceWrite = null;
+ usbInterfaceRead = null;
+ }
+ }
+
+ //Validating the Connected Device - Before asking for permission to connect to the device, it is essential that you ensure that this is a device that you support or expect to connect to. This can be done by validating the devices Vendor ID and Product ID.
+ boolean ValidateFoundDevice(UsbDevice searchDevice) {
+ //A vendor id is a global identifier for the manufacturer. A product id refers to the product itself, and is unique to the manufacturer. The vendor id, product id combination refers to a particular product manufactured by a vendor.
+ if (DEBUG) Log.d(TAG, "ValidateFoundDevice: " + searchDevice );
+
+ if ( searchDevice.getVendorId() == OPENPILOT_VENDOR_ID ) {
+ //Requesting permission
+ if (DEBUG) Log.d(TAG, "Device: " + searchDevice );
+ usbManager.requestPermission(searchDevice, permissionIntent);
+ return true;
+ }
+ else
+ return false;
+ }
+
+ private UsbInterface usbInterfaceRead = null;
+ private UsbInterface usbInterfaceWrite = null;
+ private final boolean UsingSingleInterface = true;
+
+ private TalkInputStream inTalkStream;
+ private TalkOutputStream outTalkStream;
+ UsbRequest writeRequest = null;
+
+ boolean ConnectToDeviceInterface(UsbDevice connectDevice) {
+ // Connecting to the Device - If you are reading and writing, then the device
+ // can either have two end points on a single interface, or two interfaces
+ // each with a single end point. Either way, it is best if you know which interface
+ // you need to use and which end points
+
+ if (DEBUG) Log.d(TAG, "ConnectToDeviceInterface:");
+ UsbEndpoint ep1 = null;
+ UsbEndpoint ep2 = null;
+
+
+ if (UsingSingleInterface)
+ {
+ // Using the same interface for reading and writing
+ usbInterfaceRead = connectDevice.getInterface(0x2);
+ usbInterfaceWrite = usbInterfaceRead;
+ if (usbInterfaceRead.getEndpointCount() == 2)
+ {
+ ep1 = usbInterfaceRead.getEndpoint(0);
+ ep2 = usbInterfaceRead.getEndpoint(1);
+ }
+ }
+ else // if (!UsingSingleInterface)
+ {
+ usbInterfaceRead = connectDevice.getInterface(0x01);
+ usbInterfaceWrite = connectDevice.getInterface(0x02);
+ if ((usbInterfaceRead.getEndpointCount() == 1) && (usbInterfaceWrite.getEndpointCount() == 1))
+ {
+ ep1 = usbInterfaceRead.getEndpoint(0);
+ ep2 = usbInterfaceWrite.getEndpoint(0);
+ }
+ }
+
+
+ if ((ep1 == null) || (ep2 == null))
+ {
+ if (DEBUG) Log.d(TAG, "Null endpoints");
+ return false;
+ }
+
+ // Determine which endpoint is the read, and which is the write
+
+ if (ep1.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)
+ {
+ if (ep1.getDirection() == UsbConstants.USB_DIR_IN)
+ {
+ usbEndpointRead = ep1;
+ }
+ else if (ep1.getDirection() == UsbConstants.USB_DIR_OUT)
+ {
+ usbEndpointWrite = ep1;
+ }
+ }
+ if (ep2.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)
+ {
+ if (ep2.getDirection() == UsbConstants.USB_DIR_IN)
+ {
+ usbEndpointRead = ep2;
+ }
+ else if (ep2.getDirection() == UsbConstants.USB_DIR_OUT)
+ {
+ usbEndpointWrite = ep2;
+ }
+ }
+ if ((usbEndpointRead == null) || (usbEndpointWrite == null))
+ {
+ if (DEBUG) Log.d(TAG, "Endpoints wrong way around");
+ return false;
+ }
+ connectionRead = usbManager.openDevice(connectDevice);
+ connectionRead.claimInterface(usbInterfaceRead, true);
+
+
+ if (UsingSingleInterface)
+ {
+ connectionWrite = connectionRead;
+ }
+ else // if (!UsingSingleInterface)
+ {
+ connectionWrite = usbManager.openDevice(connectDevice);
+ connectionWrite.claimInterface(usbInterfaceWrite, true);
+ }
+
+ if (DEBUG) Log.d(TAG, "Opened endpoints");
+
+ // Create the USB requests
+ readRequest = new UsbRequest();
+ readRequest.initialize(connectionRead, usbEndpointRead);
+
+ writeRequest = new UsbRequest();
+ writeRequest.initialize(connectionWrite, usbEndpointWrite);
+
+ inTalkStream = new TalkInputStream();
+ outTalkStream = new TalkOutputStream();
+ inStream = inTalkStream;
+ outStream = outTalkStream;
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ attemptSucceeded();
+ }
+ });
+
+ readWriteThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (!shutdown) {
+ readData();
+ sendData();
+ }
+ }
+
+ }, "HID Read Write");
+ readWriteThread.start();
+
+ return true;
+ }
+
+ Thread readWriteThread;
+
+ void displayBuffer(String msg, byte[] buf) {
+ msg += " (";
+ for (int i = 0; i < buf.length; i++) {
+ msg += buf[i] + " ";
+ }
+ msg += ")";
+ Log.d(TAG, msg);
+ }
+ /**
+ * Gets a report from HID, extract the meaningful data and push
+ * it to the input stream
+ */
+ UsbRequest readRequest = null;
+ public int readData() {
+ int bufferDataLength = usbEndpointRead.getMaxPacketSize();
+ ByteBuffer buffer = ByteBuffer.allocate(bufferDataLength + 1);
+
+ // queue a request on the interrupt endpoint
+ if(!readRequest.queue(buffer, bufferDataLength)) {
+ if (DEBUG) Log.d(TAG, "Failed to queue request");
+ return 0;
+ }
+
+ if (DEBUG) Log.d(TAG, "Request queued");
+
+ int dataSize;
+ // wait for status event
+ if (connectionRead.requestWait() == readRequest) {
+ // Packet format:
+ // 0: Report ID (1)
+ // 1: Number of valid bytes
+ // 2:63: Data
+
+ dataSize = buffer.get(1); // Data size
+ //Assert.assertEquals(1, buffer.get()); // Report ID
+ //Assert.assertTrue(dataSize < buffer.capacity());
+
+ if (buffer.get(0) != 1 || buffer.get(1) < 0 || buffer.get(1) > (buffer.capacity() - 2)) {
+ if (DEBUG) Log.d(TAG, "Badly formatted HID packet");
+ } else {
+ byte[] dst = new byte[dataSize];
+ buffer.position(2);
+ buffer.get(dst, 0, dataSize);
+ if (DEBUG) Log.d(TAG, "Entered read");
+ inTalkStream.write(dst);
+ if (DEBUG) Log.d(TAG, "Got read: " + dataSize + " bytes");
+ }
+ } else
+ return 0;
+
+ return dataSize;
+ }
+
+ /**
+ * Send a packet if data is available
+ */
+ public void sendData() {
+ ByteBuffer packet = null;
+ do { // Send all the data available to prevent sending backlog
+ packet = outTalkStream.getHIDpacket();
+ if (packet != null) {
+ if (DEBUG) Log.d(TAG, "Writing to device()");
+
+ int bufferDataLength = usbEndpointWrite.getMaxPacketSize();
+ Assert.assertTrue(packet.capacity() <= bufferDataLength);
+
+ writeRequest.queue(packet, bufferDataLength);
+ try
+ {
+ if (!writeRequest.equals(connectionWrite.requestWait()))
+ Log.e(TAG, "writeRequest failed");
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+ } while (packet != null);
+ }
+
+ /*********** Helper classes for telemetry streams ************/
+
+ class TalkOutputStream extends OutputStream {
+ // Uses ByteFifo.getByteBlocking()
+ // and ByteFifo.put(byte [])
+ ByteFifo data = new ByteFifo();
+
+ public ByteBuffer getHIDpacket() {
+ ByteBuffer packet = null;
+ synchronized(data) {
+ // Determine how much data to put in the packet
+ int size = Math.min(data.remaining(), MAX_HID_PACKET_SIZE - 2);
+ if (size <= 0)
+ return packet;
+
+ // Format into a HID packet
+ packet = ByteBuffer.allocate(MAX_HID_PACKET_SIZE);
+ packet.put(0,(byte) 2); // Report ID
+ packet.put(1,(byte) size); // The number of useful bytes
+ data.get(packet.array(), 2, size);
+
+ if (DEBUG) Log.d(TAG, "packetizeData(): size="+size);
+ }
+ return packet;
+ }
+ @Override
+ public void write(int oneByte) throws IOException {
+ // Throw exception when try and read after shutdown
+ if (shutdown)
+ throw new IOException();
+
+ synchronized(data) {
+ data.put((byte) oneByte);
+ data.notify();
+ }
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ if (shutdown)
+ throw new IOException();
+
+ synchronized(data) {
+ data.put(b);
+ data.notify();
+ }
+ }
+
+ };
+
+ private class TalkInputStream extends InputStream {
+ // Uses ByteFifo.getByteBlocking()
+ // Uses ByteFifo.put(byte[])
+ ByteFifo data = new ByteFifo();
+
+ TalkInputStream() {
+ }
+
+ @Override
+ public int read() {
+ try {
+ return data.getByteBlocking();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Timed out");
+ e.printStackTrace();
+ }
+ return -1;
+ }
+
+ public void write(byte[] b) {
+ data.put(b);
+ }
+ };
+
+ private class ByteFifo {
+
+ //! The maximum size of the fifo
+ private final int MAX_SIZE = 256;
+ //! The number of bytes in the buffer
+ private int size = 0;
+ //! Internal buffer
+ private final ByteBuffer buf;
+
+ ByteFifo() {
+ buf = ByteBuffer.allocate(MAX_SIZE);
+ size = 0;
+ }
+
+ private int byteToInt(byte b) { return b & 0x000000ff; }
+
+ final int remaining() { return size; };
+
+ public boolean put(byte b) {
+ byte[] a = {b};
+ return put(a);
+ }
+
+ public boolean put(byte[] dat) {
+ if ((size + dat.length) > MAX_SIZE) {
+ Log.e(TAG, "Dropped data. Size:" + size + " data length: " + dat.length);
+ return false;
+ }
+
+ // Place data at the end of the buffer
+ synchronized(buf) {
+ buf.position(size);
+ buf.put(dat);
+ size = size + dat.length;
+ buf.notify();
+ }
+ return true;
+ }
+
+ public ByteBuffer get(byte[] dst, int offset, int size) {
+ synchronized(buf) {
+ buf.flip();
+ buf.get(dst, offset, size);
+ buf.compact();
+ this.size -= size;
+ }
+ return buf;
+ }
+
+ public int getByteBlocking() throws InterruptedException {
+ synchronized(buf) {
+ if (size == 0) {
+ buf.wait();
+ }
+ int val = byteToInt(buf.get(0));
+ buf.position(1);
+ buf.compact();
+ size--;
+ return val;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/OPTelemetryService.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/OPTelemetryService.java
new file mode 100644
index 000000000..4eeb57abe
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/OPTelemetryService.java
@@ -0,0 +1,428 @@
+/**
+ ******************************************************************************
+ * @file OPTelemetryService.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief Provides UAVTalk telemetry over multiple physical links. The
+ * details of each of these are in their respective connection
+ * classes. This mostly creates those threads based on the selected
+ * preferences.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.androidgcs.telemetry;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.Observable;
+import java.util.Observer;
+
+import org.openpilot.uavtalk.Telemetry;
+import org.openpilot.uavtalk.TelemetryMonitor;
+import org.openpilot.uavtalk.UAVDataObject;
+import org.openpilot.uavtalk.UAVObjectManager;
+import org.openpilot.uavtalk.UAVTalk;
+import org.openpilot.uavtalk.uavobjects.UAVObjectsInitialize;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.widget.Toast;
+
+public class OPTelemetryService extends Service {
+
+ // Logging settings
+ private final String TAG = OPTelemetryService.class.getSimpleName();
+ public static int LOGLEVEL = 2;
+ public static boolean DEBUG = LOGLEVEL > 1;
+ public static boolean WARN = LOGLEVEL > 0;
+
+ // Intent category
+ public final static String INTENT_CATEGORY_GCS = "org.openpilot.intent.category.GCS";
+
+ // Intent actions
+ public final static String INTENT_ACTION_CONNECTED = "org.openpilot.intent.action.CONNECTED";
+ public final static String INTENT_ACTION_DISCONNECTED = "org.openpilot.intent.action.DISCONNECTED";
+
+ // Variables for local message handler thread
+ private Looper mServiceLooper;
+ private ServiceHandler mServiceHandler;
+ private static HandlerThread thread;
+
+ // Message ids
+ static final int MSG_START = 0;
+ static final int MSG_CONNECT = 1;
+ static final int MSG_DISCONNECT = 3;
+ static final int MSG_TOAST = 100;
+
+ private boolean terminate = false;
+
+ private Thread activeTelem;
+ private TelemetryTask telemTask;
+
+ private final IBinder mBinder = new LocalBinder();
+
+ static class ServiceHandler extends Handler {
+ private final WeakReference mService;
+
+ ServiceHandler(OPTelemetryService service, Looper looper) {
+ super(looper);
+ mService = new WeakReference(service);
+ }
+
+ @Override
+ public void handleMessage(Message msg)
+ {
+ OPTelemetryService service = mService.get();
+ if (service != null) {
+ service.handleMessage(msg);
+ }
+ }
+ }
+
+ void handleMessage(Message msg) {
+ switch(msg.arg1) {
+ case MSG_START:
+ stopSelf(msg.arg2);
+ break;
+ case MSG_CONNECT:
+ terminate = false;
+ int connection_type;
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(OPTelemetryService.this);
+ try {
+ connection_type = Integer.decode(prefs.getString("connection_type", ""));
+ } catch (NumberFormatException e) {
+ connection_type = 0;
+ }
+
+ switch(connection_type) {
+ case 0: // No connection
+ return;
+ case 1:
+ Toast.makeText(getApplicationContext(), "Attempting fake connection", Toast.LENGTH_SHORT).show();
+ activeTelem = new FakeTelemetryThread();
+ break;
+ case 2:
+ Toast.makeText(getApplicationContext(), "Attempting BT connection", Toast.LENGTH_SHORT).show();
+ activeTelem = new BTTelemetryThread();
+ break;
+ case 3:
+ Toast.makeText(getApplicationContext(), "Attempting TCP connection", Toast.LENGTH_SHORT).show();
+ telemTask = new TcpUAVTalk(this);
+ activeTelem = new Thread(telemTask, "Tcp telemetry thread");
+ break;
+ case 4:
+ Toast.makeText(getApplicationContext(), "Attempting USB HID connection", Toast.LENGTH_SHORT).show();
+ telemTask = new HidUAVTalk(this);
+ activeTelem = new Thread(telemTask, "Hid telemetry thread");
+ break;
+ default:
+ throw new Error("Unsupported");
+ }
+ activeTelem.start();
+ break;
+ case MSG_DISCONNECT:
+ Toast.makeText(getApplicationContext(), "Disconnect requested", Toast.LENGTH_SHORT).show();
+ if (DEBUG) Log.d(TAG, "Calling disconnect");
+ terminate = true;
+ if (telemTask != null) {
+ telemTask.disconnect();
+ telemTask = null;
+
+ try {
+ activeTelem.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ else if (activeTelem != null) {
+ activeTelem.interrupt();
+ try {
+ activeTelem.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ activeTelem = null;
+ }
+ if (DEBUG) Log.d(TAG, "Telemetry thread terminated");
+ Intent intent = new Intent();
+ intent.setAction(INTENT_ACTION_DISCONNECTED);
+ sendBroadcast(intent,null);
+
+ stopSelf();
+
+ break;
+ case MSG_TOAST:
+ Toast.makeText(this, (String) msg.obj, Toast.LENGTH_SHORT).show();
+ break;
+ default:
+ System.out.println(msg.toString());
+ throw new Error("Invalid message");
+ }
+ }
+
+ /**
+ * Called when the service starts. It creates a thread to handle messages (e.g. connect and disconnect)
+ * and based on the stored preference will send itself a connect signal if needed.
+ */
+ public void startup() {
+ Toast.makeText(getApplicationContext(), "Telemetry service starting", Toast.LENGTH_SHORT).show();
+
+ thread = new HandlerThread("TelemetryServiceHandler", Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+
+ // Get the HandlerThread's Looper and use it for our Handler
+ mServiceLooper = thread.getLooper();
+ mServiceHandler = new ServiceHandler(this, mServiceLooper);
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(OPTelemetryService.this);
+ if(prefs.getBoolean("autoconnect", false)) {
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = MSG_CONNECT;
+ msg.arg2 = 0;
+ mServiceHandler.sendMessage(msg);
+ }
+
+ }
+
+ @Override
+ public void onCreate() {
+ if (DEBUG)
+ Log.d(TAG, "Telemetry service created");
+ startup();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ // Currently only using as bound service
+
+ // If we get killed, after returning from here, restart
+ return START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ @Override
+ public void onDestroy() {
+
+ if (telemTask != null) {
+ Log.d(TAG, "onDestory() shutting down telemetry task");
+ telemTask.disconnect();
+ telemTask = null;
+
+ try {
+ activeTelem.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ Log.d(TAG, "onDestory() shut down telemetry task");
+ Toast.makeText(this, "Telemetry service done", Toast.LENGTH_SHORT).show();
+ }
+
+ public class LocalBinder extends Binder {
+ public TelemTask getTelemTask(int id) {
+ if (telemTask != null)
+ return telemTask.getTelemTaskIface();
+ return null;
+ }
+ public void openConnection() {
+ Toast.makeText(getApplicationContext(), "Requested open connection", Toast.LENGTH_SHORT).show();
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = MSG_CONNECT;
+ mServiceHandler.sendMessage(msg);
+ }
+ public void stopConnection() {
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = MSG_DISCONNECT;
+ mServiceHandler.sendMessage(msg);
+ }
+ public boolean isConnected() {
+ return activeTelem != null;
+ }
+ };
+
+ public void toastMessage(String msgText) {
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = MSG_TOAST;
+ msg.obj = msgText;
+ mServiceHandler.sendMessage(msg);
+ }
+
+ /**
+ * This is used by other processes to get a handle to the object manager
+ */
+ public interface TelemTask {
+ public UAVObjectManager getObjectManager();
+ };
+
+ // Fake class for testing, simply emits periodic updates on
+ private class FakeTelemetryThread extends Thread implements TelemTask {
+ private final UAVObjectManager objMngr;
+ @Override
+ public UAVObjectManager getObjectManager() { return objMngr; };
+
+ FakeTelemetryThread() {
+ objMngr = new UAVObjectManager();
+ UAVObjectsInitialize.register(objMngr);
+ }
+
+ @Override
+ public void run() {
+ System.out.println("Running fake thread");
+
+ Intent intent = new Intent();
+ intent.setAction(INTENT_ACTION_CONNECTED);
+ sendBroadcast(intent,null);
+
+ //toastMessage("Started fake telemetry thread");
+ UAVDataObject systemStats = (UAVDataObject) objMngr.getObject("SystemStats");
+ UAVDataObject attitudeActual = (UAVDataObject) objMngr.getObject("AttitudeActual");
+ UAVDataObject homeLocation = (UAVDataObject) objMngr.getObject("HomeLocation");
+ UAVDataObject positionActual = (UAVDataObject) objMngr.getObject("PositionActual");
+ UAVDataObject systemAlarms = (UAVDataObject) objMngr.getObject("SystemAlarms");
+
+ systemAlarms.getField("Alarm").setValue("Warning",0);
+ systemAlarms.getField("Alarm").setValue("OK",1);
+ systemAlarms.getField("Alarm").setValue("Critical",2);
+ systemAlarms.getField("Alarm").setValue("Error",3);
+ systemAlarms.updated();
+
+ homeLocation.getField("Latitude").setDouble(379420315);
+ homeLocation.getField("Longitude").setDouble(-88330078);
+ homeLocation.getField("Be").setDouble(26702.78710938,0);
+ homeLocation.getField("Be").setDouble(-1468.33605957,1);
+ homeLocation.getField("Be").setDouble(34181.78515625,2);
+
+
+ double roll = 0;
+ double pitch = 0;
+ double yaw = 0;
+ double north = 0;
+ double east = 0;
+ while( !terminate ) {
+ attitudeActual.getField("Roll").setDouble(roll);
+ attitudeActual.getField("Pitch").setDouble(pitch);
+ attitudeActual.getField("Yaw").setDouble(yaw);
+ positionActual.getField("North").setDouble(north += 100);
+ positionActual.getField("East").setDouble(east += 100);
+ roll = (roll + 10) % 180;
+ pitch = (pitch + 10) % 180;
+ yaw = (yaw + 10) % 360;
+
+ systemStats.updated();
+ attitudeActual.updated();
+ positionActual.updated();
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ break;
+ }
+ }
+ }
+ }
+ private class BTTelemetryThread extends Thread implements TelemTask {
+
+ private final UAVObjectManager objMngr;
+ private UAVTalk uavTalk;
+ private Telemetry tel;
+ private TelemetryMonitor mon;
+
+ @Override
+ public UAVObjectManager getObjectManager() { return objMngr; };
+
+ BTTelemetryThread() {
+ objMngr = new UAVObjectManager();
+ UAVObjectsInitialize.register(objMngr);
+ }
+
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Telemetry Thread started");
+
+ Looper.prepare();
+
+ BluetoothUAVTalk bt = new BluetoothUAVTalk(OPTelemetryService.this);
+ for( int i = 0; i < 10; i++ ) {
+ if (DEBUG) Log.d(TAG, "Attempting Bluetooth Connection");
+
+ bt.connect(objMngr);
+
+ if (DEBUG) Log.d(TAG, "Done attempting connection");
+ if( bt.getConnected() )
+ break;
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Thread interrupted while trying to connect");
+ e.printStackTrace();
+ return;
+ }
+ }
+ if( ! bt.getConnected() ) {
+ toastMessage("BT connection failed");
+ return;
+ }
+ toastMessage("BT Connected");
+
+ if (DEBUG) Log.d(TAG, "Connected via bluetooth");
+
+ uavTalk = bt.getUavtalk();
+ tel = new Telemetry(uavTalk, objMngr);
+ mon = new TelemetryMonitor(objMngr,tel);
+ mon.addObserver(new Observer() {
+ @Override
+ public void update(Observable arg0, Object arg1) {
+ if (DEBUG) Log.d(TAG, "Mon updated. Connected: " + mon.getConnected() + " objects updated: " + mon.getObjectsUpdated());
+ if(mon.getConnected() /*&& mon.getObjectsUpdated()*/) {
+ Intent intent = new Intent();
+ intent.setAction(INTENT_ACTION_CONNECTED);
+ sendBroadcast(intent,null);
+ }
+ }
+ });
+
+
+ if (DEBUG) Log.d(TAG, "Entering UAVTalk processing loop");
+ while( !terminate ) {
+ try {
+ if( !uavTalk.processInputStream() )
+ break;
+ } catch (IOException e) {
+ // This occurs when they communication stream fails
+ toastMessage("Connection dropped");
+ break;
+ }
+ }
+ if (DEBUG) Log.d(TAG, "UAVTalk stream disconnected");
+ }
+
+ };
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/TcpUAVTalk.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/TcpUAVTalk.java
new file mode 100644
index 000000000..b51ee8b13
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/TcpUAVTalk.java
@@ -0,0 +1,128 @@
+/**
+ ******************************************************************************
+ * @file TcpUAVTalk.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief UAVTalk over TCP.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.androidgcs.telemetry;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+public class TcpUAVTalk extends TelemetryTask {
+ private final String TAG = "TcpUAVTalk";
+ public static int LOGLEVEL = 2;
+ public static boolean WARN = LOGLEVEL > 1;
+ public static boolean DEBUG = LOGLEVEL > 0;
+
+ // Temporarily define fixed device name
+ private String ip_address = "1";
+ private int port = 9001;
+
+ private Socket socket;
+
+ /**
+ * Construct a TcpUAVTalk object attached to the OPTelemetryService. Gets the
+ * connection settings from the preferences.
+ */
+ public TcpUAVTalk(OPTelemetryService caller) {
+ super(caller);
+ }
+
+ @Override
+ boolean attemptConnection() {
+
+ if( getConnected() )
+ return true;
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(telemService);
+ ip_address = prefs.getString("ip_address","127.0.0.1");
+ try {
+ port = Integer.decode(prefs.getString("port", ""));
+ } catch (NumberFormatException e) {
+ //TODO: Handle this exception
+ }
+
+ if (DEBUG) Log.d(TAG, "Trying to open UAVTalk with " + ip_address);
+
+ Log.d(TAG, "Opening connection to " + ip_address + " at address " + port);
+
+ InetAddress serverAddr = null;
+ try {
+ serverAddr = InetAddress.getByName(ip_address);
+ } catch (UnknownHostException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ return false;
+ }
+
+ socket = null;
+ try {
+ socket = new Socket(serverAddr,port);
+ } catch (IOException e1) {
+ return false;
+ }
+
+ try {
+ inStream = socket.getInputStream();
+ outStream = socket.getOutputStream();
+ } catch (IOException e) {
+ try {
+ socket.close();
+ } catch (IOException e2) {
+
+ }
+ return false;
+ }
+
+
+ // Post message to call attempt succeeded on the parent class
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ TcpUAVTalk.this.attemptSucceeded();
+ }
+ });
+
+ return true;
+ }
+
+
+ @Override
+ public void disconnect() {
+ super.disconnect();
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ socket = null;
+ }
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java
new file mode 100644
index 000000000..87196bc1e
--- /dev/null
+++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java
@@ -0,0 +1,235 @@
+package org.openpilot.androidgcs.telemetry;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Observable;
+import java.util.Observer;
+
+import org.openpilot.uavtalk.Telemetry;
+import org.openpilot.uavtalk.TelemetryMonitor;
+import org.openpilot.uavtalk.UAVObjectManager;
+import org.openpilot.uavtalk.UAVTalk;
+import org.openpilot.uavtalk.uavobjects.UAVObjectsInitialize;
+
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+public abstract class TelemetryTask implements Runnable {
+
+ // Logging settings
+ private final String TAG = TelemetryTask.class.getSimpleName();
+ public static final int LOGLEVEL = 2;
+ public static final boolean WARN = LOGLEVEL > 1;
+ public static final boolean DEBUG = LOGLEVEL > 0;
+
+ /*
+ * This is a self contained runnable that will establish (if possible)
+ * a telemetry connection and provides a listener interface to be
+ * notified of a set of events
+ *
+ * 1. attempt to establish connection
+ * 2. callback when it succeeds (or fails) which notifies listener
+ * 3. once physical connection is established instantiate uavtalk / objectmanager
+ * 4. notify listener they are connected
+ * 5. detect physical link failure and notify listener about that
+ * 6. provide "close link" method
+ *
+ * There are essentially four tasks that need to occur here
+ * 1. Transfer data from the outputStream to the physical link (some protocols do this automatically)
+ * 2. Transfer data from the physical link to the inputStream (again some protocols do this automatically)
+ * 3. Transfer data from the inputStream to UAVTalk (uavTalk.processInputByte)
+ * 4. Transfer data from objects via UAVTalk to output stream (occurs from Telemetry object)
+ */
+
+ //! Private variables
+ protected Handler handler;
+
+ //! Handle to the parent service
+ protected final OPTelemetryService telemService;
+
+ //! The object manager that will be used for this telemetry task
+ protected UAVObjectManager objMngr;
+
+ //! The UAVTalk connected to the below streams
+ private UAVTalk uavTalk;
+
+ //! The input stream for the telemetry channel
+ protected InputStream inStream;
+
+ //! The output stream for the telemetry channel
+ protected OutputStream outStream;
+
+ //! The telemetry object which takes care of higher level transactions
+ private Telemetry tel;
+
+ //! The telemetry monitor which takes care of high level connects / disconnects
+ private TelemetryMonitor mon;
+
+ //! Flag to indicate a shut down was requested. Derived classes should take care to respect this.
+ boolean shutdown;
+
+ //! Indicate a physical connection is established
+ private boolean connected;
+
+ TelemetryTask(OPTelemetryService s) {
+ telemService = s;
+ shutdown = false;
+ connected = false;
+ }
+
+ /**
+ * Attempt a connection. This method may return before the results are
+ * known.
+ * @return False if the attempt failed and no connection will be established
+ * @return True if the attempt succeeded but does not guarantee success
+ */
+ abstract boolean attemptConnection();
+
+ /**
+ * Called when a physical channel is opened
+ *
+ * When this method is called the derived class must have
+ * created a valid inStream and outStream
+ */
+ boolean attemptSucceeded() {
+ // Create a new object manager and register all objects
+ // in the future the particular register method should
+ // be dependent on what is connected (e.g. board and
+ // version number).
+ objMngr = new UAVObjectManager();
+ UAVObjectsInitialize.register(objMngr);
+
+ // Create the required telemetry objects attached to this
+ // data stream
+ uavTalk = new UAVTalk(inStream, outStream, objMngr);
+ tel = new Telemetry(uavTalk, objMngr);
+ mon = new TelemetryMonitor(objMngr,tel);
+
+ // Create an observer to notify system of connection
+ mon.addObserver(connectionObserver);
+
+ // Create a new thread that processes the input bytes
+ startInputProcessing();
+
+ connected = true;
+ return connected;
+ }
+
+ boolean attemptedFailed() {
+ connected = false;
+ return connected;
+ }
+
+ void disconnect() {
+ // Make the default input procesing loop stop
+ shutdown = true;
+
+ // Shut down all the attached
+ if (mon != null) {
+ mon.stopMonitor();
+ mon.deleteObserver(connectionObserver);
+ mon = null;
+ }
+ if (tel != null) {
+ tel.stopTelemetry();
+ tel = null;
+ }
+
+ // Stop the master telemetry thread
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ Looper.myLooper().quit();
+ }
+ });
+
+ // TODO: Make sure the input and output stream is closed
+
+ // TODO: Make sure any threads for input and output are closed
+ }
+
+ /**
+ * Default implementation for processing input stream
+ * which creates a new thread that keeps attempting
+ * to read from the input stream.
+ */
+ private void startInputProcessing() {
+ new Thread(new processUavTalk(), "Process UAV talk").start();
+ }
+
+ //! Runnable to process input stream
+ class processUavTalk implements Runnable {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Entering UAVTalk processing loop");
+ while (!shutdown) {
+ try {
+ if( !uavTalk.processInputStream() )
+ break;
+ } catch (IOException e) {
+ e.printStackTrace();
+ telemService.toastMessage("Telemetry input stream interrupted");
+ break;
+ }
+ }
+ if (DEBUG) Log.d(TAG, "UAVTalk processing loop finished");
+ }
+ };
+
+ @Override
+ public void run() {
+ try {
+
+ Looper.prepare();
+ handler = new Handler();
+
+ if (DEBUG) Log.d(TAG, "Attempting connection");
+ if( attemptConnection() == false )
+ return; // Attempt failed
+
+ Looper.loop();
+
+ if (DEBUG) Log.d(TAG, "TelemetryTask runnable finished");
+
+ } catch (Throwable t) {
+ Log.e(TAG, "halted due to an error", t);
+ }
+
+ telemService.toastMessage("Telemetry Thread finished");
+ }
+
+ private final Observer connectionObserver = new Observer() {
+ @Override
+ public void update(Observable arg0, Object arg1) {
+ if (DEBUG) Log.d(TAG, "Mon updated. Connected: " + mon.getConnected() + " objects updated: " + mon.getObjectsUpdated());
+ if(mon.getConnected()) {
+ Intent intent = new Intent();
+ intent.setAction(OPTelemetryService.INTENT_ACTION_CONNECTED);
+ telemService.sendBroadcast(intent,null);
+ }
+ }
+ };
+
+ /**** General accessors ****/
+
+ public boolean getConnected() {
+ return connected;
+ }
+
+ public UAVTalk getUavtalk() {
+ return uavTalk;
+ }
+
+ public OPTelemetryService.TelemTask getTelemTaskIface() {
+ return new OPTelemetryService.TelemTask() {
+ @Override
+ public UAVObjectManager getObjectManager() {
+ return objMngr;
+ }
+ };
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/uavtalk/Telemetry.java b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java
new file mode 100644
index 000000000..21525ea14
--- /dev/null
+++ b/androidgcs/src/org/openpilot/uavtalk/Telemetry.java
@@ -0,0 +1,772 @@
+/**
+ ******************************************************************************
+ * @file Telemetry.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief Port of Telemetry.cpp from the GCS. Handles transactions on the
+ * UAVTalk channel.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.uavtalk;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Observable;
+import java.util.Observer;
+import java.util.Queue;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import android.util.Log;
+
+public class Telemetry {
+
+ private final String TAG = "Telemetry";
+ public static int LOGLEVEL = 1;
+ public static boolean WARN = LOGLEVEL > 2;
+ public static boolean DEBUG = LOGLEVEL > 1;
+ public static boolean ERROR = LOGLEVEL > 0;
+ public class TelemetryStats {
+ public int txBytes;
+ public int rxBytes;
+ public int txObjectBytes;
+ public int rxObjectBytes;
+ public int rxObjects;
+ public int txObjects;
+ public int txErrors;
+ public int rxErrors;
+ public int txRetries;
+ } ;
+
+ class ObjectTimeInfo {
+ UAVObject obj;
+ int updatePeriodMs; /** Update period in ms or 0 if no periodic updates are needed */
+ int timeToNextUpdateMs; /** Time delay to the next update */
+ };
+
+ class ObjectQueueInfo {
+ UAVObject obj;
+ int event;
+ boolean allInstances;
+
+ @Override
+ public boolean equals(Object e) {
+ try {
+ ObjectQueueInfo o = (ObjectQueueInfo) e;
+ return o.obj.getObjID() == obj.getObjID() && o.event == event && o.allInstances == allInstances;
+ } catch (Exception err) {
+
+ };
+ return false;
+ }
+ };
+
+ class ObjectTransactionInfo {
+ UAVObject obj;
+ boolean allInstances;
+ boolean objRequest;
+ int retriesRemaining;
+ boolean acked;
+ } ;
+
+ /**
+ * Events generated by objects. Not enum because used in mask.
+ */
+ private static final int EV_UNPACKED = 0x01; /** Object data updated by unpacking */
+ private static final int EV_UPDATED = 0x02; /** Object data updated by changing the data structure */
+ private static final int EV_UPDATED_MANUAL = 0x04; /** Object update event manually generated */
+ private static final int EV_UPDATE_REQ = 0x08; /** Request to update object data */
+
+ /**
+ * Constructor
+ */
+ public Telemetry(UAVTalk utalkIn, UAVObjectManager objMngr)
+ {
+ this.utalk = utalkIn;
+ this.objMngr = objMngr;
+
+ // Process all objects in the list
+ List< List > objs = objMngr.getObjects();
+ ListIterator> li = objs.listIterator();
+ while(li.hasNext())
+ registerObject(li.next().get(0)); // we only need to register one instance per object type
+
+ // Listen to new object creations
+ objMngr.addNewInstanceObserver(new Observer() {
+ @Override
+ public void update(Observable observable, Object data) {
+ newInstance((UAVObject) data);
+ }
+ });
+ objMngr.addNewObjectObserver(new Observer() {
+ @Override
+ public void update(Observable observable, Object data) {
+ newObject((UAVObject) data);
+ }
+ });
+
+ // Listen to transaction completions from uavtalk
+ utalk.setOnTransactionCompletedListener(
+ utalk.new OnTransactionCompletedListener() {
+ @Override
+ void TransactionSucceeded(UAVObject data) {
+ try {
+ transactionCompleted(data, true);
+ } catch (IOException e) {
+ // Disconnect when stream fails
+ utalk.setOnTransactionCompletedListener(null);
+ }
+ }
+ @Override
+ void TransactionFailed(UAVObject data) {
+ try {
+ if (DEBUG) Log.d(TAG, "TransactionFailed(" + data.getName() + ")");
+
+ transactionCompleted(data, false);
+ } catch (IOException e) {
+ // Disconnect when stream fails
+ utalk.setOnTransactionCompletedListener(null);
+ }
+ }
+
+ });
+
+ // Get GCS stats object
+ gcsStatsObj = objMngr.getObject("GCSTelemetryStats");
+
+ // Setup transaction timer
+ transPending = false;
+ // Setup and start the periodic timer
+ timeToNextUpdateMs = 0;
+ updateTimerSetPeriod(1000);
+ // Setup and start the stats timer
+ txErrors = 0;
+ txRetries = 0;
+ }
+
+ synchronized void transTimerSetPeriod(int periodMs) {
+ if(transTimerTask != null)
+ transTimerTask.cancel();
+
+ if(transTimer != null)
+ transTimer.purge();
+
+ transTimer = new Timer();
+
+ transTimerTask = new TimerTask() {
+ @Override
+ public void run() {
+ try {
+ transactionTimeout();
+ } catch (IOException e) {
+ cancel();
+ }
+ }
+ };
+ transTimer.schedule(transTimerTask, periodMs, periodMs);
+ }
+
+ synchronized void updateTimerSetPeriod(int periodMs) {
+ if (updateTimer != null) {
+ updateTimer.cancel();
+ updateTimer = null;
+ }
+ if (updateTimerTask != null) {
+ updateTimerTask.cancel();
+ updateTimerTask = null;
+ }
+ updateTimer = new Timer();
+ updateTimerTask = new TimerTask() {
+ @Override
+ public void run() {
+ try {
+ processPeriodicUpdates();
+ } catch (IOException e) {
+ updateTimerTask.cancel();
+ updateTimer.cancel();
+ }
+ }
+ };
+ updateTimer.schedule(updateTimerTask, periodMs, periodMs);
+
+ }
+
+ /**
+ * Register a new object for periodic updates (if enabled)
+ */
+ private synchronized void registerObject(UAVObject obj)
+ {
+ // Setup object for periodic updates
+ addObject(obj);
+
+ // Setup object for telemetry updates
+ updateObject(obj);
+ }
+
+ /**
+ * Add an object in the list used for periodic updates
+ */
+ private synchronized void addObject(UAVObject obj)
+ {
+ // Check if object type is already in the list
+ ListIterator li = objList.listIterator();
+ while(li.hasNext()) {
+ ObjectTimeInfo n = li.next();
+ if( n.obj.getObjID() == obj.getObjID() )
+ {
+ // Object type (not instance!) is already in the list, do nothing
+ return;
+ }
+ }
+
+ // If this point is reached, then the object type is new, let's add it
+ ObjectTimeInfo timeInfo = new ObjectTimeInfo();
+ timeInfo.obj = obj;
+ timeInfo.timeToNextUpdateMs = 0;
+ timeInfo.updatePeriodMs = 0;
+ objList.add(timeInfo);
+ }
+
+ /**
+ * Update the object's timers
+ */
+ private synchronized void setUpdatePeriod(UAVObject obj, int periodMs)
+ {
+ // Find object type (not instance!) and update its period
+ ListIterator li = objList.listIterator();
+ while(li.hasNext()) {
+ ObjectTimeInfo n = li.next();
+ if ( n.obj.getObjID() == obj.getObjID() )
+ {
+ n.updatePeriodMs = periodMs;
+ n.timeToNextUpdateMs = (int) (periodMs * (new java.util.Random()).nextDouble()); // avoid bunching of updates
+ }
+ }
+ }
+
+ final Observer unpackedObserver = new Observer() {
+ @Override
+ public void update(Observable observable, Object data) {
+ try {
+ enqueueObjectUpdates((UAVObject) data, EV_UNPACKED, false, true);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ };
+
+ final Observer updatedAutoObserver = new Observer() {
+ @Override
+ public void update(Observable observable, Object data) {
+ try {
+ enqueueObjectUpdates((UAVObject) data, EV_UPDATED, false, true);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ };
+
+ final Observer updatedManualObserver = new Observer() {
+ @Override
+ public void update(Observable observable, Object data) {
+ try {
+ enqueueObjectUpdates((UAVObject) data, EV_UPDATED_MANUAL, false, true);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ };
+
+ final Observer updatedRequestedObserver = new Observer() {
+ @Override
+ public void update(Observable observable, Object data) {
+ try {
+ enqueueObjectUpdates((UAVObject) data, EV_UPDATE_REQ, false, true);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ };
+
+ /**
+ * Connect to all instances of an object depending on the event mask specified
+ */
+ private synchronized void connectToObjectInstances(UAVObject obj, int eventMask)
+ {
+ List objs = objMngr.getObjectInstances(obj.getObjID());
+ ListIterator li = objs.listIterator();
+ while(li.hasNext())
+ {
+ obj = li.next();
+
+ // Disconnect all previous observers from telemetry. This is imortant as this can
+ // be called multiple times
+ obj.removeUnpackedObserver(unpackedObserver);
+ obj.removeUpdatedAutoObserver(updatedAutoObserver);
+ obj.removeUpdatedManualObserver(updatedManualObserver);
+ obj.removeUpdateRequestedObserver(updatedRequestedObserver);
+
+ // Connect only the selected events
+ if ( (eventMask&EV_UNPACKED) != 0)
+ obj.addUnpackedObserver(unpackedObserver);
+ if ( (eventMask&EV_UPDATED) != 0)
+ obj.addUpdatedAutoObserver(updatedAutoObserver);
+ if ( (eventMask&EV_UPDATED_MANUAL) != 0)
+ obj.addUpdatedManualObserver(updatedManualObserver);
+ if ( (eventMask&EV_UPDATE_REQ) != 0)
+ obj.addUpdateRequestedObserver(updatedRequestedObserver);
+ }
+ }
+
+ /**
+ * Update an object based on its metadata properties
+ */
+ private synchronized void updateObject(UAVObject obj)
+ {
+ // Get metadata
+ UAVObject.Metadata metadata = obj.getMetadata();
+
+ // Setup object depending on update mode
+ int eventMask;
+ if ( metadata.GetGcsTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_PERIODIC )
+ {
+ // Set update period
+ setUpdatePeriod(obj, metadata.gcsTelemetryUpdatePeriod);
+ // Connect signals for all instances
+ eventMask = EV_UPDATED_MANUAL | EV_UPDATE_REQ;
+ if(obj.isMetadata())
+ eventMask |= EV_UNPACKED; // we also need to act on remote updates (unpack events)
+
+ connectToObjectInstances(obj, eventMask);
+ }
+ else if ( metadata.GetGcsTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_ONCHANGE )
+ {
+ // Set update period
+ setUpdatePeriod(obj, 0);
+ // Connect signals for all instances
+ eventMask = EV_UPDATED | EV_UPDATED_MANUAL | EV_UPDATE_REQ;
+ if(obj.isMetadata())
+ eventMask |= EV_UNPACKED; // we also need to act on remote updates (unpack events)
+
+ connectToObjectInstances(obj, eventMask);
+ }
+ else if ( metadata.GetGcsTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_THROTTLED )
+ {
+ // TODO
+ }
+ else if ( metadata.GetGcsTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_MANUAL )
+ {
+ // Set update period
+ setUpdatePeriod(obj, 0);
+ // Connect signals for all instances
+ eventMask = EV_UPDATED_MANUAL | EV_UPDATE_REQ;
+ if(obj.isMetadata())
+ eventMask |= EV_UNPACKED; // we also need to act on remote updates (unpack events)
+
+ connectToObjectInstances(obj, eventMask);
+ }
+ }
+
+ /**
+ * Called when a transaction is successfully completed (uavtalk event)
+ * @throws IOException
+ */
+ private synchronized void transactionCompleted(UAVObject obj, boolean result) throws IOException
+ {
+ if (DEBUG) Log.d(TAG,"UAVTalk transactionCompleted");
+ // Check if there is a pending transaction and the objects match
+ if ( transPending && transInfo.obj.getObjID() == obj.getObjID() )
+ {
+ if (DEBUG) Log.d(TAG,"Telemetry: transaction completed for " + obj.getName());
+ // Complete transaction
+ transTimer.cancel();
+ transPending = false;
+
+ //Send signal
+ obj.transactionCompleted(result);
+ // Process new object updates from queue
+ processObjectQueue();
+ } else
+ {
+ if (ERROR) Log.e(TAG,"Error: received a transaction completed when did not expect it.");
+ transPending = false;
+ }
+ }
+
+ /**
+ * Called when a transaction is not completed within the timeout period (timer event)
+ * @throws IOException
+ */
+ private synchronized void transactionTimeout() throws IOException
+ {
+ if (DEBUG) Log.d(TAG,"Telemetry: transaction timeout.");
+ transTimer.cancel();
+ // Proceed only if there is a pending transaction
+ if ( transPending )
+ {
+ // Check if more retries are pending
+ if (transInfo.retriesRemaining > 0)
+ {
+ --transInfo.retriesRemaining;
+ processObjectTransaction();
+ ++txRetries;
+ }
+ else
+ {
+ if (ERROR) Log.e(TAG, "Transaction failed for: " + transInfo.obj.getName());
+
+ // Terminate transaction. This triggers UAVTalk to send a transaction
+ // failed signal which will make the next queue entry be processed
+ // Note this is UAVTalk listener TransactionFailed function and not the
+ // object specific transaction failed.
+ utalk.cancelPendingTransaction(transInfo.obj);
+ ++txErrors;
+ }
+ }
+ }
+
+ /**
+ * Start an object transaction with UAVTalk, all information is stored in transInfo
+ * @throws IOException
+ */
+ private synchronized void processObjectTransaction() throws IOException
+ {
+ if (transPending)
+ {
+ if (DEBUG) Log.d(TAG, "Process Object transaction for " + transInfo.obj.getName());
+ // Initiate transaction
+ if (transInfo.objRequest)
+ {
+ utalk.sendObjectRequest(transInfo.obj, transInfo.allInstances);
+ }
+ else
+ {
+ utalk.sendObject(transInfo.obj, transInfo.acked, transInfo.allInstances);
+ }
+ // Start timer if a response is expected
+ if ( transInfo.objRequest || transInfo.acked )
+ {
+ transTimerSetPeriod(REQ_TIMEOUT_MS);
+ }
+ else
+ {
+ transTimer.cancel();
+ transPending = false;
+ }
+ } else
+ {
+ if (ERROR) Log.e(TAG,"Error: inside of processObjectTransaction with no transPending");
+ }
+ }
+
+ /**
+ * Enqueue the event received from an object. This is the main method that handles all the callbacks
+ * from UAVObjects (due to updates, or update requests)
+ */
+ private void enqueueObjectUpdates(UAVObject obj, int event, boolean allInstances, boolean priority) throws IOException
+ {
+ // Push event into queue
+ if (DEBUG) Log.d(TAG, "Push event into queue for obj " + obj.getName() + " event " + event);
+ if(event == 8 && obj.getName().compareTo("GCSTelemetryStats") == 0)
+ Thread.dumpStack();
+ ObjectQueueInfo objInfo = new ObjectQueueInfo();
+ objInfo.obj = obj;
+ objInfo.event = event;
+ objInfo.allInstances = allInstances;
+ if (priority)
+ {
+ // Only enqueue if an identical transaction does not already exist
+ if(!objPriorityQueue.contains(objInfo)) {
+ if ( objPriorityQueue.size() < MAX_QUEUE_SIZE )
+ {
+ objPriorityQueue.add(objInfo);
+ }
+ else
+ {
+ ++txErrors;
+ obj.transactionCompleted(false);
+ Log.w(TAG,"Telemetry: priority event queue is full, event lost " + obj.getName());
+ }
+ }
+ }
+ else
+ {
+ // Only enqueue if an identical transaction does not already exist
+ if(!objQueue.contains(objInfo)) {
+ if ( objQueue.size() < MAX_QUEUE_SIZE )
+ {
+ objQueue.add(objInfo);
+ }
+ else
+ {
+ ++txErrors;
+ obj.transactionCompleted(false);
+ }
+ }
+ }
+
+ // If there is no transaction in progress then process event
+ if (!transPending)
+ {
+ processObjectQueue();
+ }
+ }
+
+ /**
+ * Process events from the object queue
+ * @throws IOException
+ */
+ private void processObjectQueue() throws IOException
+ {
+ if (DEBUG) Log.d(TAG, "Process object queue - Depth " + objQueue.size() + " priority " + objPriorityQueue.size());
+
+ // Don nothing if a transaction is already in progress (should not happen)
+ if (transPending)
+ {
+ if (WARN) Log.e(TAG,"Dequeue while a transaction pending");
+ return;
+ }
+
+ // Get object information from queue (first the priority and then the regular queue)
+ ObjectQueueInfo objInfo;
+ if ( !objPriorityQueue.isEmpty() )
+ {
+ objInfo = objPriorityQueue.remove();
+ }
+ else if ( !objQueue.isEmpty() )
+ {
+ objInfo = objQueue.remove();
+ }
+ else
+ {
+ return;
+ }
+
+ // Check if a connection has been established, only process GCSTelemetryStats updates
+ // (used to establish the connection)
+ gcsStatsObj = objMngr.getObject("GCSTelemetryStats");
+ if ( ((String) gcsStatsObj.getField("Status").getValue()).compareTo("Connected") != 0 )
+ {
+ objQueue.clear();
+ if ( objInfo.obj.getObjID() != objMngr.getObject("GCSTelemetryStats").getObjID() )
+ {
+ if (DEBUG) Log.d(TAG,"transactionCompleted(false) due to receiving object not GCSTelemetryStats while not connected.");
+ objInfo.obj.transactionCompleted(false);
+ return;
+ }
+ }
+
+ // Setup transaction (skip if unpack event)
+ if ( objInfo.event != EV_UNPACKED )
+ {
+ UAVObject.Metadata metadata = objInfo.obj.getMetadata();
+ transInfo.obj = objInfo.obj;
+ transInfo.allInstances = objInfo.allInstances;
+ transInfo.retriesRemaining = MAX_RETRIES;
+ transInfo.acked = metadata.GetGcsTelemetryAcked();
+ if ( objInfo.event == EV_UPDATED || objInfo.event == EV_UPDATED_MANUAL )
+ {
+ transInfo.objRequest = false;
+ }
+ else if ( objInfo.event == EV_UPDATE_REQ )
+ {
+ transInfo.objRequest = true;
+ }
+ // Start transaction
+ transPending = true;
+ processObjectTransaction();
+ } else
+ {
+// qDebug() << QString("Process object queue: this is an unpack event for %1").arg(objInfo.obj->getName());
+ }
+
+ // If this is a metaobject then make necessary telemetry updates
+ if (objInfo.obj.isMetadata())
+ {
+ UAVMetaObject metaobj = (UAVMetaObject) objInfo.obj;
+ updateObject( metaobj.getParentObject() );
+ }
+
+ // The fact we received an unpacked event does not mean that
+ // we do not have additional objects still in the queue,
+ // so we have to reschedule queue processing to make sure they are not
+ // stuck:
+ if ( objInfo.event == EV_UNPACKED && !transPending)
+ processObjectQueue();
+
+ }
+
+ /**
+ * Check is any objects are pending for periodic updates
+ * TODO: Clean-up
+ * @throws IOException
+ */
+ private synchronized void processPeriodicUpdates() throws IOException
+ {
+
+ if (DEBUG) Log.d(TAG, "processPeriodicUpdates()");
+ // Stop timer
+ updateTimer.cancel();
+
+ // Iterate through each object and update its timer, if zero then transmit object.
+ // Also calculate smallest delay to next update (will be used for setting timeToNextUpdateMs)
+ int minDelay = MAX_UPDATE_PERIOD_MS;
+ ObjectTimeInfo objinfo;
+ int elapsedMs = 0;
+ long startTime;
+ int offset;
+ ListIterator li = objList.listIterator();
+ while(li.hasNext())
+ {
+ objinfo = li.next();
+ // If object is configured for periodic updates
+ if (objinfo.updatePeriodMs > 0)
+ {
+ objinfo.timeToNextUpdateMs -= timeToNextUpdateMs;
+ // Check if time for the next update
+ if (objinfo.timeToNextUpdateMs <= 0)
+ {
+ // Reset timer
+ offset = (-objinfo.timeToNextUpdateMs) % objinfo.updatePeriodMs;
+ objinfo.timeToNextUpdateMs = objinfo.updatePeriodMs - offset;
+ // Send object
+ startTime = System.currentTimeMillis();
+ enqueueObjectUpdates(objinfo.obj, EV_UPDATED_MANUAL, true, false);
+ elapsedMs = (int) (System.currentTimeMillis() - startTime);
+ // Update timeToNextUpdateMs with the elapsed delay of sending the object;
+ timeToNextUpdateMs += elapsedMs;
+ }
+ // Update minimum delay
+ if (objinfo.timeToNextUpdateMs < minDelay)
+ {
+ minDelay = objinfo.timeToNextUpdateMs;
+ }
+ }
+ }
+
+ // Check if delay for the next update is too short
+ if (minDelay < MIN_UPDATE_PERIOD_MS)
+ {
+ minDelay = MIN_UPDATE_PERIOD_MS;
+ }
+
+ // Done
+ timeToNextUpdateMs = minDelay;
+
+ // Restart timer
+ updateTimerSetPeriod(timeToNextUpdateMs);
+ }
+
+ public TelemetryStats getStats()
+ {
+ // Get UAVTalk stats
+ UAVTalk.ComStats utalkStats = utalk.getStats();
+
+ // Update stats
+ TelemetryStats stats = new TelemetryStats();
+ stats.txBytes = utalkStats.txBytes;
+ stats.rxBytes = utalkStats.rxBytes;
+ stats.txObjectBytes = utalkStats.txObjectBytes;
+ stats.rxObjectBytes = utalkStats.rxObjectBytes;
+ stats.rxObjects = utalkStats.rxObjects;
+ stats.txObjects = utalkStats.txObjects;
+ stats.txErrors = utalkStats.txErrors + txErrors;
+ stats.rxErrors = utalkStats.rxErrors;
+ stats.txRetries = txRetries;
+
+ // Done
+ return stats;
+ }
+
+ public synchronized void resetStats()
+ {
+ utalk.resetStats();
+ txErrors = 0;
+ txRetries = 0;
+ }
+
+
+ private void newObject(UAVObject obj)
+ {
+ registerObject(obj);
+ }
+
+ private synchronized void newInstance(UAVObject obj)
+ {
+ registerObject(obj);
+ }
+
+ /**
+ * Stop all the telemetry timers
+ */
+ public void stopTelemetry()
+ {
+ if (updateTimerTask != null)
+ updateTimerTask.cancel();
+ updateTimerTask = null;
+ if (updateTimer != null)
+ updateTimer.cancel();
+ updateTimer = null;
+ if (transTimerTask != null)
+ transTimerTask.cancel();
+ transTimerTask = null;
+ if (transTimer != null)
+ transTimer.cancel();
+ transTimer = null;
+ }
+
+ /**
+ * Private variables
+ */
+ private final UAVObjectManager objMngr;
+ private final UAVTalk utalk;
+ private UAVObject gcsStatsObj;
+ private final List objList = new ArrayList();
+ private final Queue objQueue = new ConcurrentLinkedQueue();
+ private final Queue objPriorityQueue = new ConcurrentLinkedQueue();
+ private final ObjectTransactionInfo transInfo = new ObjectTransactionInfo();
+ private boolean transPending;
+
+ private Timer updateTimer;
+ private TimerTask updateTimerTask;
+ private Timer transTimer;
+ private TimerTask transTimerTask;
+
+ private int timeToNextUpdateMs;
+ private int txErrors;
+ private int txRetries;
+
+ /**
+ * Private constants
+ */
+ private static final int REQ_TIMEOUT_MS = 250;
+ private static final int MAX_RETRIES = 2;
+ private static final int MAX_UPDATE_PERIOD_MS = 1000;
+ private static final int MIN_UPDATE_PERIOD_MS = 1;
+ private static final int MAX_QUEUE_SIZE = 20;
+
+
+
+}
diff --git a/androidgcs/src/org/openpilot/uavtalk/TelemetryMonitor.java b/androidgcs/src/org/openpilot/uavtalk/TelemetryMonitor.java
new file mode 100644
index 000000000..a1e341197
--- /dev/null
+++ b/androidgcs/src/org/openpilot/uavtalk/TelemetryMonitor.java
@@ -0,0 +1,398 @@
+/**
+ ******************************************************************************
+ * @file TelemetryMonitor.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief High level monitoring of telemetry to handle connection and
+ * disconnection and then signal the rest of the application.
+ * This also makes sure to fetch all objects on initial connection.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.uavtalk;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Observable;
+import java.util.Observer;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import android.util.Log;
+
+public class TelemetryMonitor extends Observable {
+
+ private static final String TAG = "TelemetryMonitor";
+ public static final int LOGLEVEL = 0;
+ public static boolean DEBUG = LOGLEVEL > 2;
+ public static final boolean WARN = LOGLEVEL > 1;
+ public static final boolean ERROR = LOGLEVEL > 0;
+
+ static final int STATS_UPDATE_PERIOD_MS = 4000;
+ static final int STATS_CONNECT_PERIOD_MS = 1000;
+ static final int CONNECTION_TIMEOUT_MS = 8000;
+
+ private final boolean HANDSHAKE_IS_CONNECTED = false;
+
+ private final UAVObjectManager objMngr;
+ private final Telemetry tel;
+ // private UAVObject objPending;
+ private UAVObject gcsStatsObj;
+ private UAVObject flightStatsObj;
+ private Timer periodicTask;
+ private int currentPeriod;
+ private long lastUpdateTime;
+ private final List queue;
+
+ private boolean connected = false;
+ private boolean objects_updated = false;
+
+ public boolean getConnected() {
+ return connected;
+ };
+
+ public boolean getObjectsUpdated() {
+ return objects_updated;
+ };
+
+ public TelemetryMonitor(UAVObjectManager objMngr, Telemetry tel) {
+ this.objMngr = objMngr;
+ this.tel = tel;
+ // this.objPending = null;
+ queue = new ArrayList();
+
+ // Get stats objects
+ gcsStatsObj = objMngr.getObject("GCSTelemetryStats");
+ flightStatsObj = objMngr.getObject("FlightTelemetryStats");
+
+ flightStatsObj.addUpdatedObserver(new Observer() {
+ @Override
+ public void update(Observable observable, Object data) {
+ try {
+ flightStatsUpdated((UAVObject) data);
+ } catch (IOException e) {
+ // The UAVTalk stream was broken, disconnect this signal
+ // TODO: Should this actually be disconnected. Do we create
+ // a new TelemetryMonitor for this
+ // or fix the stream?
+ flightStatsObj.removeUpdatedObserver(this);
+ }
+ }
+ });
+
+ // Start update timer
+ setPeriod(STATS_CONNECT_PERIOD_MS);
+ }
+
+ /**
+ * Initiate object retrieval, initialize queue with objects to be retrieved.
+ *
+ * @throws IOException
+ */
+ public synchronized void startRetrievingObjects() throws IOException {
+ if (DEBUG)
+ Log.d(TAG, "Start retrieving objects");
+
+ // Clear object queue
+ queue.clear();
+ // Get all objects, add metaobjects, settings and data objects with
+ // OnChange update mode to the queue
+ List> objs = objMngr.getObjects();
+
+ ListIterator> objListIterator = objs.listIterator();
+ while (objListIterator.hasNext()) {
+ List instList = objListIterator.next();
+ UAVObject obj = instList.get(0);
+ UAVObject.Metadata mdata = obj.getMetadata();
+ if (obj.isMetadata()) {
+ queue.add(obj);
+ } else /* Data object */
+ {
+ UAVDataObject dobj = (UAVDataObject) obj;
+ if (dobj.isSettings()) {
+ queue.add(obj);
+ } else {
+ if (mdata.GetFlightTelemetryUpdateMode() == UAVObject.UpdateMode.UPDATEMODE_ONCHANGE) {
+ queue.add(obj);
+ }
+ }
+ }
+ }
+ // Start retrieving
+ Log.d(TAG,
+ "Starting to retrieve meta and settings objects from the autopilot ("
+ + queue.size() + " objects)");
+ retrieveNextObject();
+ }
+
+ /**
+ * Cancel the object retrieval
+ */
+ public void stopRetrievingObjects() {
+ Log.d(TAG, "Stop retrieving objects");
+ queue.clear();
+ }
+
+ final Observer transactionObserver = new Observer() {
+ @Override
+ public void update(Observable observable, Object data) {
+ try {
+ UAVObject.TransactionResult result = (UAVObject.TransactionResult) data;
+ transactionCompleted(result.obj, result.success);
+ } catch (IOException e) {
+ // When the telemetry stream is broken disconnect these
+ // updates
+ observable.deleteObserver(this);
+ }
+ }
+ };
+
+ /**
+ * Retrieve the next object in the queue
+ *
+ * @throws IOException
+ */
+ public synchronized void retrieveNextObject() throws IOException {
+ // If queue is empty return
+ if (queue.isEmpty()) {
+ if (DEBUG || true) Log.d(TAG, "All objects retrieved: Connected Successfully");
+ objects_updated = true;
+ if (!HANDSHAKE_IS_CONNECTED) {
+ setChanged();
+ notifyObservers();
+ }
+ return;
+ }
+ // Get next object from the queue
+ UAVObject obj = queue.remove(0);
+
+ if (obj == null) {
+ throw new Error("Got null object forom transaction queue");
+ }
+
+ if (DEBUG)
+ Log.d(TAG, "Retrieving object: " + obj.getName());
+
+ // TODO: Does this need to stay here permanently? This appears to be
+ // used for setup mainly
+ obj.addTransactionCompleted(transactionObserver);
+
+ // Request update
+ obj.updateRequested();
+ }
+
+ /**
+ * Called by the retrieved object when a transaction is completed.
+ *
+ * @throws IOException
+ */
+ public synchronized void transactionCompleted(UAVObject obj, boolean success)
+ throws IOException {
+ if (DEBUG)
+ Log.d(TAG, "transactionCompleted. Status: " + success);
+
+ // Remove the listener for the event that just finished
+ obj.removeTransactionCompleted(transactionObserver);
+
+ if (!success) {
+ // Right now success = false means received a NAK so don't
+ // re-attempt
+ if (ERROR) Log.e(TAG, "Transaction failed.");
+ }
+
+ // Process next object if telemetry is still available
+ if (((String) gcsStatsObj.getField("Status").getValue()).compareTo("Connected") == 0) {
+ retrieveNextObject();
+ } else {
+ stopRetrievingObjects();
+ }
+ }
+
+ /**
+ * Called each time the flight stats object is updated by the autopilot
+ *
+ * @throws IOException
+ */
+ public synchronized void flightStatsUpdated(UAVObject obj)
+ throws IOException {
+ // Force update if not yet connected
+ gcsStatsObj = objMngr.getObject("GCSTelemetryStats");
+ flightStatsObj = objMngr.getObject("FlightTelemetryStats");
+ if (DEBUG)
+ Log.d(TAG, "GCS Status: "
+ + gcsStatsObj.getField("Status").getValue());
+ if (DEBUG)
+ Log.d(TAG, "Flight Status: "
+ + flightStatsObj.getField("Status").getValue());
+ if (((String) gcsStatsObj.getField("Status").getValue())
+ .compareTo("Connected") != 0
+ || ((String) flightStatsObj.getField("Status").getValue())
+ .compareTo("Connected") == 0) {
+ processStatsUpdates();
+ }
+ }
+
+ private long lastStatsTime;
+
+ /**
+ * Called periodically to update the statistics and connection status.
+ *
+ * @throws IOException
+ */
+ public synchronized void processStatsUpdates() throws IOException {
+ // Get telemetry stats
+ if (DEBUG)
+ Log.d(TAG, "processStatsUpdates()");
+ Telemetry.TelemetryStats telStats = tel.getStats();
+
+ if (DEBUG)
+ Log.d(TAG, "processStatsUpdates() - stats reset");
+
+ // Need to compute time because this update is not regular enough
+ float dT = (System.currentTimeMillis() - lastStatsTime) / 1000.0f;
+ lastStatsTime = System.currentTimeMillis();
+
+ // Update stats object
+ gcsStatsObj.getField("RxDataRate").setDouble(telStats.rxBytes / dT);
+ gcsStatsObj.getField("TxDataRate").setDouble(telStats.txBytes / dT);
+ UAVObjectField field = gcsStatsObj.getField("RxFailures");
+ field.setDouble(field.getDouble() + telStats.rxErrors);
+ field = gcsStatsObj.getField("TxFailures");
+ field.setDouble(field.getDouble() + telStats.txErrors);
+ field = gcsStatsObj.getField("TxRetries");
+ field.setDouble(field.getDouble() + telStats.txRetries);
+
+ tel.resetStats();
+
+ if (DEBUG)
+ Log.d(TAG, "processStatsUpdates() - stats updated");
+
+ // Check for a connection timeout
+ boolean connectionTimeout;
+ if (telStats.rxObjects > 0) {
+ lastUpdateTime = System.currentTimeMillis();
+
+ }
+ if ((System.currentTimeMillis() - lastUpdateTime) > CONNECTION_TIMEOUT_MS) {
+ connectionTimeout = true;
+ } else {
+ connectionTimeout = false;
+ }
+
+ // Update connection state
+ gcsStatsObj = objMngr.getObject("GCSTelemetryStats");
+ flightStatsObj = objMngr.getObject("FlightTelemetryStats");
+ if (gcsStatsObj == null) {
+ Log.d(TAG, "No GCS stats yet");
+ return;
+ }
+ UAVObjectField statusField = gcsStatsObj.getField("Status");
+ String oldStatus = new String((String) statusField.getValue());
+
+ if (DEBUG)
+ Log.d(TAG, "GCS: " + statusField.getValue() + " Flight: "
+ + flightStatsObj.getField("Status").getValue());
+
+ if (oldStatus.compareTo("Disconnected") == 0) {
+ // Request connection
+ statusField.setValue("HandshakeReq");
+ } else if (oldStatus.compareTo("HandshakeReq") == 0) {
+ // Check for connection acknowledge
+ if (((String) flightStatsObj.getField("Status").getValue())
+ .compareTo("HandshakeAck") == 0) {
+ statusField.setValue("Connected");
+ if (DEBUG)
+ Log.d(TAG, "Connected" + statusField.toString());
+ }
+ } else if (oldStatus.compareTo("Connected") == 0) {
+ // Check if the connection is still active and the the autopilot is
+ // still connected
+ if (((String) flightStatsObj.getField("Status").getValue())
+ .compareTo("Disconnected") == 0 || connectionTimeout) {
+ statusField.setValue("Disconnected");
+ }
+ }
+
+ // Force telemetry update if not yet connected
+ boolean gcsStatusChanged = !oldStatus.equals(statusField.getValue());
+
+ boolean gcsConnected = statusField.getValue().equals("Connected");
+ boolean gcsDisconnected = statusField.getValue().equals("Disconnected");
+ boolean flightConnected = flightStatsObj.getField("Status").equals(
+ "Connected");
+
+ if (!gcsConnected || !flightConnected) {
+ if (DEBUG)
+ Log.d(TAG, "Sending gcs status");
+ gcsStatsObj.updated();
+ }
+
+ // Act on new connections or disconnections
+ if (gcsConnected && gcsStatusChanged) {
+ if (DEBUG)
+ Log.d(TAG, "Connection with the autopilot established");
+ setPeriod(STATS_UPDATE_PERIOD_MS);
+ connected = true;
+ objects_updated = false;
+ startRetrievingObjects();
+ if (HANDSHAKE_IS_CONNECTED) setChanged(); // Enabling this line makes the opConnected signal occur whenever we get a handshake
+ }
+ if (gcsDisconnected && gcsStatusChanged) {
+ if (DEBUG)
+ Log.d(TAG, "Trying to connect to the autopilot");
+ setPeriod(STATS_CONNECT_PERIOD_MS);
+ connected = false;
+ objects_updated = false;
+ setChanged();
+ }
+
+ if (DEBUG)
+ Log.d(TAG, "processStatsUpdates() - before notify");
+ notifyObservers();
+ if (DEBUG)
+ Log.d(TAG, "processStatsUpdates() - after notify");
+ }
+
+ private void setPeriod(int ms) {
+ if (periodicTask == null)
+ periodicTask = new Timer();
+
+ periodicTask.cancel();
+ currentPeriod = ms;
+ periodicTask = new Timer();
+ periodicTask.scheduleAtFixedRate(new TimerTask() {
+ @Override
+ public void run() {
+ try {
+ processStatsUpdates();
+ } catch (IOException e) {
+ // Once the stream has died stop trying to process these
+ // updates
+ periodicTask.cancel();
+ }
+ }
+ }, currentPeriod, currentPeriod);
+ }
+
+ public void stopMonitor() {
+ periodicTask.cancel();
+ periodicTask = null;
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/uavtalk/UAVDataObject.java b/androidgcs/src/org/openpilot/uavtalk/UAVDataObject.java
new file mode 100644
index 000000000..83b1bba68
--- /dev/null
+++ b/androidgcs/src/org/openpilot/uavtalk/UAVDataObject.java
@@ -0,0 +1,113 @@
+/**
+ ******************************************************************************
+ * @file UAVDataObject.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief The base object for all UAVO data.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.uavtalk;
+
+public abstract class UAVDataObject extends UAVObject {
+
+ /**
+ * @brief Constructor for UAVDataObject
+ * @param objID the object id to be created
+ * @param isSingleInst
+ * @param isSet
+ * @param name
+ */
+ public UAVDataObject(long objID, Boolean isSingleInst, Boolean isSet, String name) {
+ super(objID, isSingleInst, name);
+ mobj = null;
+ this.isSet = isSet;
+ }
+ /**
+ * Initialize instance ID and assign a metaobject
+ */
+ public void initialize(long instID, UAVMetaObject mobj)
+ {
+ //QMutexLocker locker(mutex);
+ this.mobj = mobj;
+ super.initialize(instID);
+ }
+
+ @Override
+ public boolean isMetadata() { return false; };
+ /**
+ * Assign a metaobject
+ */
+ public void initialize(UAVMetaObject mobj)
+ {
+ //QMutexLocker locker(mutex);
+ this.mobj = mobj;
+ }
+
+ /**
+ * Returns true if this is a data object holding module settings
+ */
+ public boolean isSettings()
+ {
+ return isSet;
+ }
+
+ /**
+ * Set the object's metadata
+ */
+ @Override
+ public void setMetadata(Metadata mdata)
+ {
+ if ( mobj != null )
+ {
+ mobj.setData(mdata);
+ }
+ }
+
+ /**
+ * Get the object's metadata
+ */
+ @Override
+ public Metadata getMetadata()
+ {
+ if ( mobj != null)
+ {
+ return mobj.getData();
+ }
+ else
+ {
+ return getDefaultMetadata();
+ }
+ }
+
+ /**
+ * Get the metaobject
+ */
+ public UAVMetaObject getMetaObject()
+ {
+ return mobj;
+ }
+
+ // TODO: Make abstract
+ public UAVDataObject clone(long instID) {
+ return (UAVDataObject) super.clone();
+ }
+
+ private UAVMetaObject mobj;
+ private final boolean isSet;
+
+}
diff --git a/androidgcs/src/org/openpilot/uavtalk/UAVMetaObject.java b/androidgcs/src/org/openpilot/uavtalk/UAVMetaObject.java
new file mode 100644
index 000000000..cef39480d
--- /dev/null
+++ b/androidgcs/src/org/openpilot/uavtalk/UAVMetaObject.java
@@ -0,0 +1,144 @@
+/**
+ ******************************************************************************
+ * @file UAVMetaObject.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief Base object for all UAVO meta data
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.uavtalk;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+public class UAVMetaObject extends UAVObject {
+
+ public UAVMetaObject(long objID, String name, UAVDataObject parent) throws Exception {
+ super(objID, true, name);
+ this.parent = parent;
+
+ ownMetadata = new Metadata();
+
+ ownMetadata.flags = 0; // TODO: Fix flags
+ ownMetadata.gcsTelemetryUpdatePeriod = 0;
+ ownMetadata.loggingUpdatePeriod = 0;
+
+
+ List modesBitField = new ArrayList();
+ modesBitField.add("FlightReadOnly");
+ modesBitField.add("GCSReadOnly");
+ modesBitField.add("FlightTelemetryAcked");
+ modesBitField.add("GCSTelemetryAcked");
+ modesBitField.add("FlightUpdatePeriodic");
+ modesBitField.add("FlightUpdateOnChange");
+ modesBitField.add("GCSUpdatePeriodic");
+ modesBitField.add("GCSUpdateOnChange");
+
+ List fields = new ArrayList();
+ fields.add( new UAVObjectField("Modes", "", UAVObjectField.FieldType.BITFIELD, 1, modesBitField) );
+ fields.add( new UAVObjectField("Flight Telemetry Update Period", "ms", UAVObjectField.FieldType.UINT16, 1, null) );
+ fields.add( new UAVObjectField("GCS Telemetry Update Period", "ms", UAVObjectField.FieldType.UINT16, 1, null) );
+ fields.add( new UAVObjectField("Logging Update Period", "ms", UAVObjectField.FieldType.UINT16, 1, null) );
+
+ int numBytes = 0;
+ ListIterator li = fields.listIterator();
+ while(li.hasNext()) {
+ numBytes += li.next().getNumBytes();
+ }
+
+ // Initialize object
+
+ // Initialize parent
+ super.initialize(0);
+ initializeFields(fields, ByteBuffer.allocate(numBytes), numBytes);
+
+ // Setup metadata of parent
+ parentMetadata = parent.getDefaultMetadata();
+ }
+
+ @Override
+ public boolean isMetadata() {
+ return true;
+ };
+
+ /**
+ * Get the parent object
+ */
+ public UAVObject getParentObject()
+ {
+ return parent;
+ }
+
+ /**
+ * Set the metadata of the metaobject, this function will
+ * do nothing since metaobjects have read-only metadata.
+ */
+ @Override
+ public void setMetadata(Metadata mdata)
+ {
+ return; // can not update metaobject's metadata
+ }
+
+ /**
+ * Get the metadata of the metaobject
+ */
+ @Override
+ public Metadata getMetadata()
+ {
+ return ownMetadata;
+ }
+
+ /**
+ * Get the default metadata
+ */
+ @Override
+ public Metadata getDefaultMetadata()
+ {
+ return ownMetadata;
+ }
+
+ /**
+ * Set the metadata held by the metaobject
+ */
+ public void setData(Metadata mdata)
+ {
+ //QMutexLocker locker(mutex);
+ parentMetadata = mdata;
+ // TODO: Callbacks
+ // emit objectUpdatedAuto(this); // trigger object updated event
+ // emit objectUpdated(this);
+ }
+
+ /**
+ * Get the metadata held by the metaobject
+ */
+ public Metadata getData()
+ {
+// QMutexLocker locker(mutex);
+ return parentMetadata;
+ }
+
+
+ private final UAVObject parent;
+ private final Metadata ownMetadata;
+ private Metadata parentMetadata;
+
+
+}
diff --git a/androidgcs/src/org/openpilot/uavtalk/UAVObject.java b/androidgcs/src/org/openpilot/uavtalk/UAVObject.java
new file mode 100644
index 000000000..a0a8fc8f8
--- /dev/null
+++ b/androidgcs/src/org/openpilot/uavtalk/UAVObject.java
@@ -0,0 +1,855 @@
+/**
+ ******************************************************************************
+ * @file UAVObject.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief Base object for UAVDataObject and UAVMetaObject.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.uavtalk;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Observable;
+import java.util.Observer;
+
+public abstract class UAVObject {
+
+ public class CallbackListener extends Observable {
+ private final UAVObject parent;
+
+ public CallbackListener(UAVObject parent) {
+ this.parent = parent;
+ }
+
+ public void event () {
+ synchronized(this) {
+ setChanged();
+ notifyObservers(parent);
+ }
+ }
+ public void event (Object data) {
+ synchronized(this) {
+ setChanged();
+ notifyObservers(data);
+ }
+ }
+ }
+
+ public class TransactionResult {
+ public UAVObject obj;
+ public boolean success;
+ public TransactionResult(UAVObject obj, boolean success) {
+ this.obj = obj;
+ this.success = success;
+ }
+ }
+
+ private final CallbackListener transactionCompletedListeners = new CallbackListener(this);
+ public void addTransactionCompleted(Observer o) {
+ synchronized(transactionCompletedListeners) {
+ transactionCompletedListeners.addObserver(o);
+ }
+ }
+ public void removeTransactionCompleted(Observer o) {
+ synchronized(transactionCompletedListeners) {
+ transactionCompletedListeners.deleteObserver(o);
+ }
+ }
+ void transactionCompleted(boolean status) {
+ synchronized(transactionCompletedListeners) {
+ transactionCompletedListeners.event(new TransactionResult(this,status));
+ }
+ }
+
+ private final CallbackListener updatedListeners = new CallbackListener(this);
+ public void removeUpdatedObserver(Observer o) {
+ synchronized(updatedListeners) {
+ updatedListeners.deleteObserver(o);
+ }
+ }
+ public void addUpdatedObserver(Observer o) {
+ synchronized(updatedListeners) {
+ updatedListeners.addObserver(o);
+ }
+ }
+ void updated(boolean manually) {
+ synchronized(updatedListeners) {
+ updatedListeners.event();
+ }
+ if(manually)
+ updatedManual();
+ }
+ public void updated() { updated(true); };
+
+ private final CallbackListener unpackedListeners = new CallbackListener(this);
+ public void addUnpackedObserver(Observer o) {
+ synchronized(unpackedListeners) {
+ unpackedListeners.addObserver(o);
+ }
+ }
+ public void removeUnpackedObserver(Observer o) {
+ synchronized(unpackedListeners) {
+ unpackedListeners.deleteObserver(o);
+ }
+ }
+ void unpacked() {
+ synchronized(unpackedListeners) {
+ unpackedListeners.event();
+ }
+ }
+
+ private final CallbackListener updatedAutoListeners = new CallbackListener(this);
+ public void addUpdatedAutoObserver(Observer o) {
+ synchronized(updatedAutoListeners) {
+ updatedAutoListeners.addObserver(o);
+ }
+ }
+ public void removeUpdatedAutoObserver(Observer o) {
+ synchronized(updatedAutoListeners) {
+ updatedAutoListeners.deleteObserver(o);
+ }
+ }
+ void updatedAuto() {
+ synchronized(updatedAutoListeners) {
+ updatedAutoListeners.event();
+ }
+ }
+
+ private final CallbackListener updatedManualListeners = new CallbackListener(this);
+ public void addUpdatedManualObserver(Observer o) {
+ synchronized(updatedManualListeners) {
+ updatedManualListeners.addObserver(o);
+ }
+ }
+ public void removeUpdatedManualObserver(Observer o) {
+ synchronized(updatedManualListeners) {
+ updatedManualListeners.deleteObserver(o);
+ }
+ }
+ void updatedManual() {
+ synchronized(updatedManualListeners) {
+ updatedManualListeners.event();
+ }
+ }
+
+ private final CallbackListener updateRequestedListeners = new CallbackListener(this);
+ public void addUpdateRequestedObserver(Observer o) {
+ synchronized(updateRequestedListeners) {
+ updateRequestedListeners.addObserver(o);
+ }
+ }
+ public void removeUpdateRequestedObserver(Observer o) {
+ synchronized(updateRequestedListeners) {
+ updateRequestedListeners.deleteObserver(o);
+ }
+ }
+ public void updateRequested() {
+ synchronized(updateRequestedListeners) {
+ updateRequestedListeners.event();
+ }
+ }
+
+ public abstract boolean isMetadata();
+
+ /**
+ * Object update mode
+ */
+ public enum UpdateMode {
+ UPDATEMODE_MANUAL, /** Manually update object, by calling the updated() function */
+ UPDATEMODE_PERIODIC, /** Automatically update object at periodic intervals */
+ UPDATEMODE_ONCHANGE, /** Only update object when its data changes */
+ UPDATEMODE_THROTTLED /** Object is updated on change, but not more often than the interval time */
+ };
+
+ /**
+ * Access mode
+ */
+ public enum AccessMode {
+ ACCESS_READWRITE, ACCESS_READONLY
+ };
+
+ public final static int UAVOBJ_ACCESS_SHIFT = 0;
+ public final static int UAVOBJ_GCS_ACCESS_SHIFT = 1;
+ public final static int UAVOBJ_TELEMETRY_ACKED_SHIFT = 2;
+ public final static int UAVOBJ_GCS_TELEMETRY_ACKED_SHIFT = 3;
+ public final static int UAVOBJ_TELEMETRY_UPDATE_MODE_SHIFT = 4;
+ public final static int UAVOBJ_GCS_TELEMETRY_UPDATE_MODE_SHIFT = 6;
+ public final static int UAVOBJ_UPDATE_MODE_MASK = 0x3;
+
+ public final static class Metadata {
+ /**
+ * Object metadata, each object has a meta object that holds its metadata. The metadata define
+ * properties for each object and can be used by multiple modules (e.g. telemetry and logger)
+ *
+ * The object metadata flags are packed into a single 16 bit integer.
+ * The bits in the flag field are defined as:
+ *
+ * Bit(s) Name Meaning
+ * ------ ---- -------
+ * 0 access Defines the access level for the local transactions (readonly=0 and readwrite=1)
+ * 1 gcsAccess Defines the access level for the local GCS transactions (readonly=0 and readwrite=1), not used in the flight s/w
+ * 2 telemetryAcked Defines if an ack is required for the transactions of this object (1:acked, 0:not acked)
+ * 3 gcsTelemetryAcked Defines if an ack is required for the transactions of this object (1:acked, 0:not acked)
+ * 4-5 telemetryUpdateMode Update mode used by the telemetry module (UAVObjUpdateMode)
+ * 6-7 gcsTelemetryUpdateMode Update mode used by the GCS (UAVObjUpdateMode)
+ */
+ public int flags; /** Defines flags for update and logging modes and whether an update should be ACK'd (bits defined above) */
+
+ /** Update period used by the telemetry module (only if telemetry mode is PERIODIC) */
+ public int flightTelemetryUpdatePeriod;
+
+ /** Update period used by the GCS (only if telemetry mode is PERIODIC) */
+ public int gcsTelemetryUpdatePeriod;
+
+ /** Update period used by the GCS (only if telemetry mode is PERIODIC) */
+ public int loggingUpdatePeriod;
+ /**
+ * Update period used by the logging module (only if logging mode is
+ * PERIODIC)
+ */
+
+ /**
+ * @brief Helper method for metadata accessors
+ * @param var The starting value
+ * @param shift The offset of these bits
+ * @param value The new value
+ * @param mask The mask of these bits
+ * @return
+ */
+ private void SET_BITS(int shift, int value, int mask) {
+ this.flags = (this.flags & ~(mask << shift)) | (value << shift);
+ }
+
+ /**
+ * Get the UAVObject metadata access member
+ * \return the access type
+ */
+ public AccessMode GetFlightAccess()
+ {
+ return AccessModeEnum((this.flags >> UAVOBJ_ACCESS_SHIFT) & 1);
+ }
+
+ /**
+ * Set the UAVObject metadata access member
+ * \param[in] mode The access mode
+ */
+ public void SetFlightAccess(Metadata metadata, AccessMode mode)
+ {
+ // Need to convert java enums which have no numeric value to bits
+ SET_BITS(UAVOBJ_ACCESS_SHIFT, AccessModeNum(mode), 1);
+ }
+
+ /**
+ * Get the UAVObject metadata GCS access member
+ * \return the GCS access type
+ */
+ public AccessMode GetGcsAccess()
+ {
+ return AccessModeEnum((this.flags >> UAVOBJ_GCS_ACCESS_SHIFT) & 1);
+ }
+
+ /**
+ * Set the UAVObject metadata GCS access member
+ * \param[in] mode The access mode
+ */
+ public void SetGcsAccess(Metadata metadata, AccessMode mode) {
+ // Need to convert java enums which have no numeric value to bits
+ SET_BITS(UAVOBJ_GCS_ACCESS_SHIFT, AccessModeNum(mode), 1);
+ }
+
+ /**
+ * Get the UAVObject metadata telemetry acked member
+ * \return the telemetry acked boolean
+ */
+ public boolean GetFlightTelemetryAcked() {
+ return (((this.flags >> UAVOBJ_TELEMETRY_ACKED_SHIFT) & 1) == 1);
+ }
+
+ /**
+ * Set the UAVObject metadata telemetry acked member
+ * \param[in] val The telemetry acked boolean
+ */
+ public void SetFlightTelemetryAcked(boolean val) {
+ SET_BITS(UAVOBJ_TELEMETRY_ACKED_SHIFT, val ? 1 : 0, 1);
+ }
+
+ /**
+ * Get the UAVObject metadata GCS telemetry acked member
+ * \return the telemetry acked boolean
+ */
+ public boolean GetGcsTelemetryAcked() {
+ return ((this.flags >> UAVOBJ_GCS_TELEMETRY_ACKED_SHIFT) & 1) == 1;
+ }
+
+ /**
+ * Set the UAVObject metadata GCS telemetry acked member
+ * \param[in] val The GCS telemetry acked boolean
+ */
+ public void SetGcsTelemetryAcked(boolean val) {
+ SET_BITS(UAVOBJ_GCS_TELEMETRY_ACKED_SHIFT, val ? 1 : 0, 1);
+ }
+
+ /**
+ * Maps from the bitfield number to the symbolic java enumeration
+ * @param num The value in the bitfield after shifting
+ * @return The update mode
+ */
+ public static AccessMode AccessModeEnum(int num) {
+ switch(num) {
+ case 0:
+ return AccessMode.ACCESS_READONLY;
+ case 1:
+ return AccessMode.ACCESS_READWRITE;
+ }
+ return AccessMode.ACCESS_READONLY;
+ }
+
+ /**
+ * Maps from the java symbolic enumeration of update mode to the bitfield value
+ * @param e The update mode
+ * @return The numeric value to use on the wire
+ */
+ public static int AccessModeNum(AccessMode e) {
+ switch(e) {
+ case ACCESS_READONLY:
+ return 0;
+ case ACCESS_READWRITE:
+ return 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Maps from the bitfield number to the symbolic java enumeration
+ * @param num The value in the bitfield after shifting
+ * @return The update mode
+ */
+ public static UpdateMode UpdateModeEnum(int num) {
+ switch(num) {
+ case 0:
+ return UpdateMode.UPDATEMODE_MANUAL;
+ case 1:
+ return UpdateMode.UPDATEMODE_PERIODIC;
+ case 2:
+ return UpdateMode.UPDATEMODE_ONCHANGE;
+ default:
+ return UpdateMode.UPDATEMODE_THROTTLED;
+ }
+ }
+
+ /**
+ * Maps from the java symbolic enumeration of update mode to the bitfield value
+ * @param e The update mode
+ * @return The numeric value to use on the wire
+ */
+ public static int UpdateModeNum(UpdateMode e) {
+ switch(e) {
+ case UPDATEMODE_MANUAL:
+ return 0;
+ case UPDATEMODE_PERIODIC:
+ return 1;
+ case UPDATEMODE_ONCHANGE:
+ return 2;
+ case UPDATEMODE_THROTTLED:
+ return 3;
+ }
+ return 0;
+ }
+
+ /**
+ * Get the UAVObject metadata telemetry update mode
+ * \return the telemetry update mode
+ */
+ public UpdateMode GetFlightTelemetryUpdateMode() {
+ return UpdateModeEnum((this.flags >> UAVOBJ_TELEMETRY_UPDATE_MODE_SHIFT) & UAVOBJ_UPDATE_MODE_MASK);
+ }
+
+ /**
+ * Set the UAVObject metadata telemetry update mode member
+ * \param[in] metadata The metadata object
+ * \param[in] val The telemetry update mode
+ */
+ public void SetFlightTelemetryUpdateMode(UpdateMode val) {
+ SET_BITS(UAVOBJ_TELEMETRY_UPDATE_MODE_SHIFT, UpdateModeNum(val), UAVOBJ_UPDATE_MODE_MASK);
+ }
+
+ /**
+ * Get the UAVObject metadata GCS telemetry update mode
+ * \param[in] metadata The metadata object
+ * \return the GCS telemetry update mode
+ */
+ public UpdateMode GetGcsTelemetryUpdateMode() {
+ return UpdateModeEnum((this.flags >> UAVOBJ_GCS_TELEMETRY_UPDATE_MODE_SHIFT) & UAVOBJ_UPDATE_MODE_MASK);
+ }
+
+ /**
+ * Set the UAVObject metadata GCS telemetry update mode member
+ * \param[in] metadata The metadata object
+ * \param[in] val The GCS telemetry update mode
+ */
+ public void SetGcsTelemetryUpdateMode(UpdateMode val) {
+ SET_BITS(UAVOBJ_GCS_TELEMETRY_UPDATE_MODE_SHIFT, UpdateModeNum(val), UAVOBJ_UPDATE_MODE_MASK);
+ }
+
+ };
+
+ public UAVObject(long objID, Boolean isSingleInst, String name) {
+ this.objID = objID;
+ this.instID = 0;
+ this.isSingleInst = isSingleInst;
+ this.name = name;
+ // this.mutex = new QMutex(QMutex::Recursive);
+ };
+
+ public synchronized void initialize(long instID) {
+ this.instID = instID;
+ }
+
+ /**
+ * Initialize objects' data fields
+ *
+ * @param fields
+ * List of fields held by the object
+ * @param data
+ * Pointer to that actual object data, this is needed by the
+ * fields to access the data
+ * @param numBytes
+ * Number of bytes in the object (total, including all fields)
+ * @throws Exception
+ * When unable to unpack a field
+ */
+ public synchronized void initializeFields(List fields, ByteBuffer data,
+ int numBytes) {
+ this.numBytes = numBytes;
+ this.fields = fields;
+ // Initialize fields
+ for (int n = 0; n < fields.size(); ++n) {
+ fields.get(n).initialize(this);
+ }
+ unpack(data);
+ }
+
+ /**
+ * Get the object ID
+ */
+ public long getObjID() {
+ return objID;
+ }
+
+ /**
+ * Get the instance ID
+ */
+ public long getInstID() {
+ return instID;
+ }
+
+ /**
+ * Returns true if this is a single instance object
+ */
+ public boolean isSingleInstance() {
+ return isSingleInst;
+ }
+
+ /**
+ * Get the name of the object
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Get the description of the object
+ *
+ * @return The description of the object
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Set the description of the object
+ *
+ * @param The
+ * description of the object
+ * @return
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ /**
+ * Get the total number of bytes of the object's data
+ */
+ public int getNumBytes() {
+ return numBytes;
+ }
+
+ // /**
+ // * Request that this object is updated with the latest values from the
+ // autopilot
+ // */
+ // /* void UAVObject::requestUpdate()
+ // {
+ // emit updateRequested(this);
+ // } */
+ //
+ // /**
+ // * Signal that the object has been updated
+ // */
+ // /* void UAVObject::updated()
+ // {
+ // emit objectUpdatedManual(this);
+ // emit objectUpdated(this);
+ // } */
+ //
+ // /**
+ // * Lock mutex of this object
+ // */
+ // /* void UAVObject::lock()
+ // {
+ // mutex->lock();
+ // } */
+ //
+ // /**
+ // * Lock mutex of this object
+ // */
+ // /* void UAVObject::lock(int timeoutMs)
+ // {
+ // mutex->tryLock(timeoutMs);
+ // } */
+ //
+ // /**
+ // * Unlock mutex of this object
+ // */
+ // /* void UAVObject::unlock()
+ // {
+ // mutex->unlock();
+ // } */
+ //
+ // /**
+ // * Get object's mutex
+ // */
+ // QMutex* UAVObject::getMutex()
+ // {
+ // return mutex;
+ // }
+
+ /**
+ * Get the number of fields held by this object
+ */
+ public int getNumFields() {
+ return fields.size();
+ }
+
+ /**
+ * Get the object's fields
+ */
+ public synchronized List getFields() {
+ return fields;
+ }
+
+ /**
+ * Get a specific field
+ *
+ * @throws Exception
+ * @returns The field or NULL if not found
+ */
+ public UAVObjectField getField(String name) {
+ // Look for field
+ ListIterator li = fields.listIterator();
+ while (li.hasNext()) {
+ UAVObjectField field = li.next();
+ if (field.getName().equals(name))
+ return field;
+ }
+ //throw new Exception("Field not found");
+ return null;
+ }
+
+ /**
+ * Pack the object data into a byte array
+ *
+ * @param dataOut
+ * ByteBuffer to receive the data.
+ * @throws Exception
+ * @returns The number of bytes copied
+ * @note The array must already have enough space allocated for the object
+ */
+ public synchronized int pack(ByteBuffer dataOut) throws Exception {
+ if (dataOut.remaining() < getNumBytes())
+ throw new Exception("Not enough bytes in ByteBuffer to pack object");
+ int numBytes = 0;
+
+ ListIterator li = fields.listIterator();
+ while (li.hasNext()) {
+ UAVObjectField field = li.next();
+ numBytes += field.pack(dataOut);
+ }
+ return numBytes;
+ }
+
+ /**
+ * Unpack the object data from a byte array
+ *
+ * @param dataIn
+ * The ByteBuffer to pull data from
+ * @throws Exception
+ * @returns The number of bytes copied
+ */
+ public synchronized int unpack(ByteBuffer dataIn) {
+ if( dataIn == null )
+ return 0;
+
+ // QMutexLocker locker(mutex);
+ int numBytes = 0;
+ ListIterator li = fields.listIterator();
+ while (li.hasNext()) {
+ UAVObjectField field = li.next();
+ numBytes += field.unpack(dataIn);
+ }
+
+ // Trigger all the listeners for the unpack event
+ unpacked();
+ updated(false);
+
+ return numBytes;
+ }
+
+ // /**
+ // * Save the object data to the file.
+ // * The file will be created in the current directory
+ // * and its name will be the same as the object with
+ // * the .uavobj extension.
+ // * @returns True on success, false on failure
+ // */
+ // bool UAVObject::save()
+ // {
+ // QMutexLocker locker(mutex);
+ //
+ // // Open file
+ // QFile file(name + ".uavobj");
+ // if (!file.open(QFile::WriteOnly))
+ // {
+ // return false;
+ // }
+ //
+ // // Write object
+ // if ( !save(file) )
+ // {
+ // return false;
+ // }
+ //
+ // // Close file
+ // file.close();
+ // return true;
+ // }
+ //
+ // /**
+ // * Save the object data to the file.
+ // * The file is expected to be already open for writting.
+ // * The data will be appended and the file will not be closed.
+ // * @returns True on success, false on failure
+ // */
+ // bool UAVObject::save(QFile& file)
+ // {
+ // QMutexLocker locker(mutex);
+ // quint8 buffer[numBytes];
+ // quint8 tmpId[4];
+ //
+ // // Write the object ID
+ // qToLittleEndian(objID, tmpId);
+ // if ( file.write((const char*)tmpId, 4) == -1 )
+ // {
+ // return false;
+ // }
+ //
+ // // Write the instance ID
+ // if (!isSingleInst)
+ // {
+ // qToLittleEndian(instID, tmpId);
+ // if ( file.write((const char*)tmpId, 2) == -1 )
+ // {
+ // return false;
+ // }
+ // }
+ //
+ // // Write the data
+ // pack(buffer);
+ // if ( file.write((const char*)buffer, numBytes) == -1 )
+ // {
+ // return false;
+ // }
+ //
+ // // Done
+ // return true;
+ // }
+ //
+ // /**
+ // * Load the object data from a file.
+ // * The file will be openned in the current directory
+ // * and its name will be the same as the object with
+ // * the .uavobj extension.
+ // * @returns True on success, false on failure
+ // */
+ // bool UAVObject::load()
+ // {
+ // QMutexLocker locker(mutex);
+ //
+ // // Open file
+ // QFile file(name + ".uavobj");
+ // if (!file.open(QFile::ReadOnly))
+ // {
+ // return false;
+ // }
+ //
+ // // Load object
+ // if ( !load(file) )
+ // {
+ // return false;
+ // }
+ //
+ // // Close file
+ // file.close();
+ // return true;
+ // }
+ //
+ // /**
+ // * Load the object data from file.
+ // * The file is expected to be already open for reading.
+ // * The data will be read and the file will not be closed.
+ // * @returns True on success, false on failure
+ // */
+ // bool UAVObject::load(QFile& file)
+ // {
+ // QMutexLocker locker(mutex);
+ // quint8 buffer[numBytes];
+ // quint8 tmpId[4];
+ //
+ // // Read the object ID
+ // if ( file.read((char*)tmpId, 4) != 4 )
+ // {
+ // return false;
+ // }
+ //
+ // // Check that the IDs match
+ // if (qFromLittleEndian(tmpId) != objID)
+ // {
+ // return false;
+ // }
+ //
+ // // Read the instance ID
+ // if ( file.read((char*)tmpId, 2) != 2 )
+ // {
+ // return false;
+ // }
+ //
+ // // Check that the IDs match
+ // if (qFromLittleEndian(tmpId) != instID)
+ // {
+ // return false;
+ // }
+ //
+ // // Read and unpack the data
+ // if ( file.read((char*)buffer, numBytes) != numBytes )
+ // {
+ // return false;
+ // }
+ // unpack(buffer);
+ //
+ // // Done
+ // return true;
+ // }
+
+ /**
+ * Return a string with the object information
+ */
+ @Override
+ public String toString() {
+ return toStringBrief(); // + toStringData();
+ }
+
+ /**
+ * Return a string with the object information (only the header)
+ */
+ public String toStringBrief() {
+ return getName(); // + " (" + Integer.toHexString(getObjID()) + " " + Integer.toHexString(getInstID()) + " " + getNumBytes() + ")\n";
+ }
+
+ /**
+ * Return a string with the object information (only the data)
+ */
+ public String toStringData() {
+ String s = new String();
+ ListIterator li = fields.listIterator();
+ while (li.hasNext()) {
+ UAVObjectField field = li.next();
+ s += field.toString();
+ }
+ return s;
+ }
+
+ // /**
+ // * Emit the transactionCompleted event (used by the UAVTalk plugin)
+ // */
+ // void UAVObject::emitTransactionCompleted(bool success)
+ // {
+ // emit transactionCompleted(this, success);
+ // }
+
+ /**
+ * Java specific functions
+ */
+ @Override
+ public synchronized UAVObject clone() {
+ UAVObject newObj = clone();
+ List newFields = new ArrayList();
+ ListIterator li = fields.listIterator();
+ while(li.hasNext()) {
+ UAVObjectField nf = li.next().clone();
+ nf.initialize(newObj);
+ newFields.add(nf);
+ }
+ newObj.initializeFields(newFields, ByteBuffer.allocate(numBytes), numBytes);
+ return newObj;
+ }
+
+ /**
+ * Abstract functions
+ */
+ public abstract void setMetadata(Metadata mdata);
+
+ public abstract Metadata getMetadata();
+
+ public abstract Metadata getDefaultMetadata();
+
+ /**
+ * Private data for the object, common to all
+ */
+ protected long objID;
+ protected long instID;
+ protected boolean isSingleInst;
+ protected String name;
+ protected String description;
+ protected int numBytes;
+ // TODO: QMutex* mutex;
+// protected ByteBuffer data;
+ protected List fields;
+}
diff --git a/androidgcs/src/org/openpilot/uavtalk/UAVObjectField.java b/androidgcs/src/org/openpilot/uavtalk/UAVObjectField.java
new file mode 100644
index 000000000..d07fdf6fd
--- /dev/null
+++ b/androidgcs/src/org/openpilot/uavtalk/UAVObjectField.java
@@ -0,0 +1,733 @@
+/**
+ ******************************************************************************
+ * @file Telemetry.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief Implementation of all the UAVObjectFields.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.uavtalk;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+public class UAVObjectField {
+
+ public enum FieldType { INT8, INT16, INT32, UINT8, UINT16, UINT32, FLOAT32, ENUM, BITFIELD, STRING };
+
+ public UAVObjectField(String name, String units, FieldType type, int numElements, List options) {
+ List elementNames = new ArrayList();
+ // Set element names
+ for (int n = 0; n < numElements; ++n)
+ {
+ elementNames.add(String.valueOf(n));
+ }
+ // Initialize
+ constructorInitialize(name, units, type, elementNames, options);
+ }
+
+ public UAVObjectField(String name, String units, FieldType type, List elementNames, List options) {
+ constructorInitialize(name, units, type, elementNames, options);
+ }
+
+ public void initialize(UAVObject obj){
+ this.obj = obj;
+ //clear();
+ }
+
+ public UAVObject getObject() {
+ return obj;
+ }
+
+ public FieldType getType() {
+ return type;
+ }
+
+ public String getTypeAsString() {
+ switch (type)
+ {
+ case INT8:
+ return "int8";
+ case INT16:
+ return "int16";
+ case INT32:
+ return "int32";
+ case UINT8:
+ return "uint8";
+ case UINT16:
+ return "uint16";
+ case UINT32:
+ return "uint32";
+ case FLOAT32:
+ return "float32";
+ case ENUM:
+ return "enum";
+ case BITFIELD:
+ return "bitfield";
+ case STRING:
+ return "string";
+ default:
+ return "";
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getUnits() {
+ return units;
+ }
+
+ public int getNumElements() {
+ return numElements;
+ }
+
+ public List getElementNames() {
+ return elementNames;
+ }
+
+ public List getOptions() {
+ return options;
+ }
+
+ /**
+ * This function copies this field from the internal storage of the parent object
+ * to a new ByteBuffer for UAVTalk. It also converts from the java standard (big endian)
+ * to the arm/uavtalk standard (little endian)
+ * @param dataOut
+ * @return the number of bytes added
+ **/
+ @SuppressWarnings("unchecked")
+ public synchronized int pack(ByteBuffer dataOut) {
+ // Pack each element in output buffer
+ dataOut.order(ByteOrder.LITTLE_ENDIAN);
+ switch (type)
+ {
+ case INT8:
+ for (int index = 0; index < numElements; ++index) {
+ Integer val = (Integer) getValue(index);
+ dataOut.put(val.byteValue());
+ }
+ break;
+ case INT16:
+ for (int index = 0; index < numElements; ++index) {
+ Integer val = (Integer) getValue(index);
+ dataOut.putShort(val.shortValue());
+ }
+ break;
+ case INT32:
+ for (int index = 0; index < numElements; ++index) {
+ Integer val = (Integer) getValue(index);
+ dataOut.putInt(val);
+ }
+ break;
+ case UINT8:
+ // TODO: Deal properly with unsigned
+ for (int index = 0; index < numElements; ++index) {
+ Integer val = (Integer) getValue(index);
+ dataOut.put(val.byteValue());
+ }
+ break;
+ case UINT16:
+ // TODO: Deal properly with unsigned
+ for (int index = 0; index < numElements; ++index) {
+ Integer val = (Integer) getValue(index);
+ dataOut.putShort(val.shortValue());
+ }
+ break;
+ case UINT32:
+ // TODO: Deal properly with unsigned
+ for (int index = 0; index < numElements; ++index) {
+ Integer val = (int) ( ((Long) getValue(index)).longValue() & 0xffffffffL);
+ dataOut.putInt(val);
+ }
+ break;
+ case FLOAT32:
+ for (int index = 0; index < numElements; ++index)
+ dataOut.putFloat((Float) getValue(index));
+ break;
+ case ENUM:
+ List l = (List) data;
+ for (int index = 0; index < numElements; ++index)
+ dataOut.put(l.get(index));
+ break;
+ case BITFIELD:
+ for (int index = 0; index < numElements; ++index) {
+ Integer val = (Integer) getValue(index);
+ dataOut.put(val.byteValue());
+ }
+ break;
+ case STRING:
+ // TODO: Implement strings
+ throw new Error("Strings not yet implemented. Field name: " + getName());
+ }
+ // Done
+ return getNumBytes();
+ }
+
+ @SuppressWarnings("unchecked")
+ public synchronized int unpack(ByteBuffer dataIn) {
+ // Unpack each element from input buffer
+ dataIn.order(ByteOrder.LITTLE_ENDIAN);
+ switch (type)
+ {
+ case INT8:
+ {
+ List l = (List) this.data;
+ for (int index = 0 ; index < numElements; ++index) {
+ Long val = bound(dataIn.get());
+ l.set(index, val.byteValue());
+ }
+ break;
+ }
+ case INT16:
+ {
+ List l = (List) this.data;
+ for (int index = 0 ; index < numElements; ++index) {
+ Long val = bound(dataIn.getShort());
+ l.set(index, val.shortValue());
+ }
+ break;
+ }
+ case INT32:
+ {
+ List l = (List) this.data;
+ for (int index = 0 ; index < numElements; ++index) {
+ Long val = bound(dataIn.getInt());
+ l.set(index, val.intValue());
+ }
+ break;
+ }
+ case UINT8:
+ {
+ List l = (List) this.data;
+ for (int index = 0 ; index < numElements; ++index) {
+ int signedval = dataIn.get(); // this sign extends it
+ int unsignedval = signedval & 0xff; // drop sign extension
+ l.set(index, (short) unsignedval);
+ }
+ break;
+ }
+ case UINT16:
+ {
+ List l = (List) this.data;
+ for (int index = 0 ; index < numElements; ++index) {
+ int signedval = dataIn.getShort(); // this sign extends it
+ int unsignedval = signedval & 0xffff; // drop sign extension
+ l.set(index, unsignedval);
+ }
+ break;
+ }
+ case UINT32:
+ {
+ List l = (List) this.data;
+ for (int index = 0 ; index < numElements; ++index) {
+ long signedval = dataIn.getInt(); // this sign extends it
+ long unsignedval = signedval & 0xffffffffL; // drop sign extension
+ l.set(index, unsignedval);
+ }
+ break;
+ }
+ case FLOAT32:
+ {
+ List l = (List) this.data;
+ for (int index = 0 ; index < numElements; ++index) {
+ Float val = dataIn.getFloat();
+ l.set(index, val);
+ }
+ break;
+ }
+ case BITFIELD:
+ {
+ List l = (List) this.data;
+ for (int index = 0 ; index < numElements; ++index) {
+ int signedval = dataIn.get(); // this sign extends it
+ int unsignedval = signedval & 0xff; // drop sign extension
+ l.set(index, (short) unsignedval);
+ }
+ break;
+ }
+ case ENUM:
+ {
+ List l = (List) this.data;
+ for (int index = 0 ; index < numElements; ++index) {
+ l.set(index, dataIn.get());
+ }
+ break;
+ }
+ case STRING:
+ // TODO: implement strings
+ //throw new Exception("Strings not handled");
+ }
+ // Done
+ return getNumBytes();
+ }
+
+ public Object getValue() { return getValue(0); };
+ @SuppressWarnings("unchecked")
+ public synchronized Object getValue(int index) {
+ // Check that index is not out of bounds
+ if ( index >= numElements )
+ {
+ return null;
+ }
+
+ switch (type)
+ {
+ case INT8:
+ return ((List) data).get(index).intValue();
+ case INT16:
+ return ((List) data).get(index).intValue();
+ case INT32:
+ return ((List) data).get(index).intValue();
+ case UINT8:
+ return ((List) data).get(index).intValue();
+ case UINT16:
+ return ((List) data).get(index).intValue();
+ case UINT32:
+ return ((List) data).get(index);
+ case FLOAT32:
+ return ((List) data).get(index);
+ case ENUM:
+ {
+ List l = (List) data;
+ Byte val = l.get(index);
+
+ //if(val >= options.size() || val < 0)
+ // throw new Exception("Invalid value for" + name);
+
+ return options.get(val);
+ }
+ case BITFIELD:
+ return ((List) data).get(index).intValue();
+
+ case STRING:
+ {
+ //throw new Exception("Shit I should do this");
+ }
+ }
+ // If this point is reached then we got an invalid type
+ return null;
+ }
+
+ public void setValue(Object data) { setValue(data,0); }
+ @SuppressWarnings("unchecked")
+ public synchronized void setValue(Object data, int index) {
+ // Check that index is not out of bounds
+ //if ( index >= numElements );
+ //throw new Exception("Index out of bounds");
+
+ // Get metadata
+ UAVObject.Metadata mdata = obj.getMetadata();
+ // Update value if the access mode permits
+ if ( mdata.GetGcsAccess() == UAVObject.AccessMode.ACCESS_READWRITE )
+ {
+ switch (type)
+ {
+ case INT8:
+ {
+ List l = (List) this.data;
+ l.set(index, bound(data).byteValue());
+ break;
+ }
+ case INT16:
+ {
+ List l = (List) this.data;
+ l.set(index, bound(data).shortValue());
+ break;
+ }
+ case INT32:
+ {
+ List l = (List) this.data;
+ l.set(index, bound(data).intValue());
+ break;
+ }
+ case UINT8:
+ {
+ List l = (List) this.data;
+ l.set(index, bound(data).shortValue());
+ break;
+ }
+ case UINT16:
+ {
+ List l = (List) this.data;
+ l.set(index, bound(data).intValue());
+ break;
+ }
+ case UINT32:
+ {
+ List l = (List) this.data;
+ l.set(index, bound(data));
+ break;
+ }
+ case FLOAT32:
+ {
+ List l = (List) this.data;
+ l.set(index, ((Number) data).floatValue());
+ break;
+ }
+ case ENUM:
+ {
+ byte val;
+ try {
+ // Test if numeric constant passed in
+ val = ((Number) data).byteValue();
+ } catch (Exception e) {
+ val = (byte) options.indexOf(data);
+ }
+ //if(val < 0) throw new Exception("Enumerated value not found");
+ List l = (List) this.data;
+ l.set(index, val);
+ break;
+ }
+ case BITFIELD:
+ {
+ List l = (List) this.data;
+ l.set(index, bound(data).shortValue());
+ break;
+ }
+ case STRING:
+ {
+ //throw new Exception("Sorry I haven't implemented strings yet");
+ }
+ }
+ //obj.updated();
+ }
+ }
+
+ public double getDouble() { return getDouble(0); };
+ @SuppressWarnings("unchecked")
+ public double getDouble(int index) {
+ switch (type) {
+ case ENUM:
+ return ((List)data).get(index);
+ default:
+ break;
+ }
+ return ((Number) getValue(index)).doubleValue();
+ }
+
+ public void setDouble(double value) { setDouble(value, 0); };
+ public void setDouble(double value, int index) {
+ setValue(value, index);
+ }
+
+ public int getDataOffset() {
+ return offset;
+ }
+
+ public int getNumBytes() {
+ return numBytesPerElement * numElements;
+ }
+
+ public int getNumBytesElement() {
+ return numBytesPerElement;
+ }
+
+ public boolean isNumeric() {
+ switch (type)
+ {
+ case INT8:
+ return true;
+ case INT16:
+ return true;
+ case INT32:
+ return true;
+ case UINT8:
+ return true;
+ case UINT16:
+ return true;
+ case UINT32:
+ return true;
+ case FLOAT32:
+ return true;
+ case ENUM:
+ return false;
+ case BITFIELD:
+ return true;
+ case STRING:
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ public boolean isText() {
+ switch (type)
+ {
+ case INT8:
+ return false;
+ case INT16:
+ return false;
+ case INT32:
+ return false;
+ case UINT8:
+ return false;
+ case UINT16:
+ return false;
+ case UINT32:
+ return false;
+ case FLOAT32:
+ return false;
+ case ENUM:
+ return true;
+ case BITFIELD:
+ return false;
+ case STRING:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ String sout = new String();
+ sout += name + ": ";
+ for (int i = 0; i < numElements; i++) {
+ sout += getValue(i).toString();
+ if (i != numElements-1)
+ sout += ", ";
+ else
+ sout += " ";
+ }
+ if (units.length() > 0)
+ sout += " (" + units + ")\n";
+ else
+ sout += "\n";
+ return sout;
+ }
+
+ void fieldUpdated(UAVObjectField field) {
+
+ }
+
+ @SuppressWarnings("unchecked")
+ public synchronized void clear() {
+ switch (type)
+ {
+ case INT8:
+ ((ArrayList) data).clear();
+ for(int index = 0; index < numElements; ++index) {
+ ((ArrayList) data).add((byte) 0);
+ }
+ break;
+ case INT16:
+ ((ArrayList) data).clear();
+ for(int index = 0; index < numElements; ++index) {
+ ((ArrayList) data).add((short) 0);
+ }
+ break;
+ case INT32:
+ ((ArrayList) data).clear();
+ for(int index = 0; index < numElements; ++index) {
+ ((ArrayList) data).add(0);
+ }
+ break;
+ case UINT8:
+ ((ArrayList) data).clear();
+ for(int index = 0; index < numElements; ++index) {
+ ((ArrayList) data).add((short) 0);
+ }
+ break;
+ case UINT16:
+ ((ArrayList) data).clear();
+ for(int index = 0; index < numElements; ++index) {
+ ((ArrayList) data).add(0);
+ }
+ break;
+ case UINT32:
+ ((ArrayList) data).clear();
+ for(int index = 0; index < numElements; ++index) {
+ ((ArrayList) data).add((long) 0);
+ }
+ break;
+ case FLOAT32:
+ ((ArrayList) data).clear();
+ for(int index = 0; index < numElements; ++index) {
+ ((ArrayList) data).add((float) 0);
+ }
+ break;
+ case BITFIELD:
+ ((ArrayList) data).clear();
+ for(int index = 0; index < numElements; ++index) {
+ ((ArrayList) data).add((short) 0);
+ }
+ break;
+ case ENUM:
+ ((ArrayList) data).clear();
+ for(int index = 0; index < numElements; ++index) {
+ ((ArrayList) data).add((byte) 0);
+ }
+ break;
+ }
+ }
+
+ public synchronized void constructorInitialize(String name, String units, FieldType type, List elementNames, List options) {
+ // Copy params
+ this.name = name;
+ this.units = units;
+ this.type = type;
+ this.options = options;
+ this.numElements = elementNames.size();
+ this.offset = 0;
+ this.data = null;
+ this.obj = null;
+ this.elementNames = elementNames;
+
+ // Set field size
+ switch (type)
+ {
+ case INT8:
+ data = new ArrayList(this.numElements);
+ numBytesPerElement = 1;
+ break;
+ case INT16:
+ data = new ArrayList(this.numElements);
+ numBytesPerElement = 2;
+ break;
+ case INT32:
+ data = new ArrayList(this.numElements);
+ numBytesPerElement = 4;
+ break;
+ case UINT8:
+ data = new ArrayList(this.numElements);
+ numBytesPerElement = 1;
+ break;
+ case UINT16:
+ data = new ArrayList(this.numElements);
+ numBytesPerElement = 2;
+ break;
+ case UINT32:
+ data = new ArrayList(this.numElements);
+ numBytesPerElement = 4;
+ break;
+ case FLOAT32:
+ data = new ArrayList(this.numElements);
+ numBytesPerElement = 4;
+ break;
+ case ENUM:
+ data = new ArrayList(this.numElements);
+ numBytesPerElement = 1;
+ break;
+ case BITFIELD:
+ data = new ArrayList(this.numElements);
+ numBytesPerElement = 1;
+ break;
+ case STRING:
+ data = new ArrayList(this.numElements);
+ numBytesPerElement = 1;
+ break;
+ default:
+ numBytesPerElement = 0;
+ }
+ clear();
+ }
+
+ /**
+ * For numerical types bounds the data appropriately
+ * @param val Can be any object, for numerical tries to cast to Number
+ * @return long value with the right range (for float rounds)
+ * @note This is mostly needed because java has no unsigned integer
+ */
+ protected Long bound (Object val) {
+
+ switch(type) {
+ case ENUM:
+ case STRING:
+ return 0L;
+ case FLOAT32:
+ return ((Number) val).longValue();
+ }
+
+ long num = ((Number) val).longValue();
+
+ switch(type) {
+ case INT8:
+ if(num < Byte.MIN_VALUE)
+ return (long) Byte.MAX_VALUE;
+ if(num > Byte.MAX_VALUE)
+ return (long) Byte.MAX_VALUE;
+ return num;
+ case INT16:
+ if(num < Short.MIN_VALUE)
+ return (long) Short.MIN_VALUE;
+ if(num > Short.MAX_VALUE)
+ return (long) Short.MAX_VALUE;
+ return num;
+ case INT32:
+ if(num < Integer.MIN_VALUE)
+ return (long) Integer.MIN_VALUE;
+ if(num > Integer.MAX_VALUE)
+ return (long) Integer.MAX_VALUE;
+ return num;
+ case UINT8:
+ if(num < 0)
+ return (long) 0;
+ if(num > 255)
+ return (long) 255;
+ return num;
+ case UINT16:
+ if(num < 0)
+ return (long) 0;
+ if(num > 65535)
+ return (long) 65535;
+ return num;
+ case UINT32:
+ if(num < 0)
+ return (long) 0;
+ if(num > 4294967295L)
+ return 4294967295L;
+ return num;
+ case BITFIELD:
+ if(num < 0)
+ return (long) 0;
+ if(num > 255)
+ return (long) 255;
+ return num;
+ }
+
+ return num;
+ }
+
+ @Override
+ public UAVObjectField clone()
+ {
+ UAVObjectField newField = new UAVObjectField(new String(name), new String(units), type,
+ new ArrayList(elementNames),
+ new ArrayList(options));
+ newField.initialize(obj);
+ newField.data = data;
+ return newField;
+ }
+
+ private String name;
+ private String units;
+ private FieldType type;
+ private List elementNames;
+ private List options;
+ private int numElements;
+ private int numBytesPerElement;
+ private int offset;
+ private UAVObject obj;
+ protected Object data;
+
+}
diff --git a/androidgcs/src/org/openpilot/uavtalk/UAVObjectManager.java b/androidgcs/src/org/openpilot/uavtalk/UAVObjectManager.java
new file mode 100644
index 000000000..51cfeafd2
--- /dev/null
+++ b/androidgcs/src/org/openpilot/uavtalk/UAVObjectManager.java
@@ -0,0 +1,384 @@
+/**
+ ******************************************************************************
+ * @file UAVObjectManager.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief Critical class. This is the data store for all UAVOs. Allows
+ * other objects to access and change this data. Takes care of
+ * propagating changes to the UAV.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.uavtalk;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Observable;
+import java.util.Observer;
+
+public class UAVObjectManager {
+
+ public class CallbackListener extends Observable {
+ public void event (UAVObject obj) {
+ setChanged();
+ notifyObservers(obj);
+ }
+ }
+ private final CallbackListener newInstance = new CallbackListener();
+ public void addNewInstanceObserver(Observer o) {
+ synchronized(newInstance) {
+ newInstance.addObserver(o);
+ }
+ }
+ private final CallbackListener newObject = new CallbackListener();
+ public void addNewObjectObserver(Observer o) {
+ synchronized(newObject) {
+ newObject.addObserver(o);
+ }
+ }
+ private final int MAX_INSTANCES = 10;
+
+ // Use array list to store objects since rarely added or deleted
+ private final List> objects = new ArrayList>();
+
+ public UAVObjectManager()
+ {
+ //mutex = new QMutex(QMutex::Recursive);
+ }
+
+ /**
+ * Register an object with the manager. This function must be called for all newly created instances.
+ * A new instance can be created directly by instantiating a new object or by calling clone() of
+ * an existing object. The object will be registered and will be properly initialized so that it can accept
+ * updates.
+ * @throws Exception
+ */
+ public synchronized boolean registerObject(UAVDataObject obj) throws Exception
+ {
+ // QMutexLocker locker(mutex);
+
+ ListIterator> objIt = objects.listIterator(0);
+
+ // Check if this object type is already in the list
+ while(objIt.hasNext()) {
+ List instList = objIt.next();
+
+ // Check if the object ID is in the list
+ if( (instList.size() > 0) && (instList.get(0).getObjID() == obj.getObjID() )) {
+ // Check if this is a single instance object, if yes we can not add a new instance
+ if(obj.isSingleInstance()) {
+ return false;
+ }
+ // The object type has alredy been added, so now we need to initialize the new instance with the appropriate id
+ // There is a single metaobject for all object instances of this type, so no need to create a new one
+ // Get object type metaobject from existing instance
+ UAVDataObject refObj = (UAVDataObject) instList.get(0);
+ if (refObj == null)
+ {
+ return false;
+ }
+ UAVMetaObject mobj = refObj.getMetaObject();
+
+ // Make sure we aren't requesting to create too many instances
+ if(obj.getInstID() >= MAX_INSTANCES || instList.size() >= MAX_INSTANCES || obj.getInstID() < 0) {
+ return false;
+ }
+
+ // If InstID is zero then we find the next open instId and create it
+ if (obj.getInstID() == 0)
+ {
+ // Assign the next available ID and initialize the object instance the nadd
+ obj.initialize(instList.size(), mobj);
+ instList.add(obj);
+ return true;
+ }
+
+ // Check if that inst ID already exists
+ ListIterator instIter = instList.listIterator();
+ while(instIter.hasNext()) {
+ UAVObject testObj = instIter.next();
+ if(testObj.getInstID() == obj.getInstID()) {
+ return false;
+ }
+ }
+
+ // If the instance ID is specified and not at the default value (0) then we need to make sure
+ // that there are no gaps in the instance list. If gaps are found then then additional instances
+ // will be created.
+ for(long instId = instList.size(); instId < obj.getInstID(); instId++) {
+ UAVDataObject newObj = obj.clone(instId);
+ newObj.initialize(mobj);
+ instList.add(newObj);
+ newInstance.event(newObj);
+ }
+ obj.initialize(mobj);
+ instList.add(obj);
+ newInstance.event(obj);
+
+ instIter = instList.listIterator();
+ while(instIter.hasNext()) {
+ UAVObject testObj = instIter.next();
+ if(testObj.getInstID() == obj.getInstID()) {
+ return false;
+ }
+ }
+
+
+ // Check if there are any gaps between the requested instance ID and the ones in the list,
+ // if any then create the missing instances.
+ for (long instId = instList.size(); instId < obj.getInstID(); ++instId)
+ {
+ UAVDataObject cobj = obj.clone(instId);
+ cobj.initialize(mobj);
+ instList.add(cobj);
+ newInstance.event(cobj);
+ }
+ // Finally, initialize the actual object instance
+ obj.initialize(mobj);
+ // Add the actual object instance in the list
+ instList.add(obj);
+ newInstance.event(obj);
+ return true;
+ }
+
+ }
+
+ // If this point is reached then this is the first time this object type (ID) is added in the list
+ // create a new list of the instances, add in the object collection and create the object's metaobject
+ // Create metaobject
+ String mname = obj.getName();
+ mname += "Meta";
+
+ UAVMetaObject mobj = new UAVMetaObject(obj.getObjID()+1, mname, obj);
+ // Initialize object
+ obj.initialize(0, mobj);
+ // Add to list
+ addObject(obj);
+ addObject(mobj);
+ return true;
+ }
+
+ public synchronized void addObject(UAVObject obj)
+ {
+ // Add to list
+ List ls = new ArrayList();
+ ls.add(obj);
+ objects.add(ls);
+ newObject.event(obj);
+ }
+
+ /**
+ * Get all objects. A two dimentional QList is returned. Objects are grouped by
+ * instances of the same object type.
+ */
+ public List> getObjects()
+ {
+ return objects;
+ }
+
+ /**
+ * Same as getObjects() but will only return DataObjects.
+ */
+ public synchronized List< List > getDataObjects()
+ {
+ List< List > dObjects = new ArrayList< List > ();
+
+ // Go through objects and copy to new list when types match
+ ListIterator> objIt = objects.listIterator(0);
+
+ // Check if this object type is already in the list
+ while(objIt.hasNext()) {
+ List instList = objIt.next();
+
+ // If no instances skip
+ if(instList.size() == 0)
+ continue;
+
+ // If meta data skip
+ if(instList.get(0).isMetadata())
+ continue;
+
+ List newInstList = new ArrayList();
+ ListIterator instIt = instList.listIterator();
+ while(instIt.hasNext()) {
+ newInstList.add((UAVDataObject) instIt.next());
+ }
+ dObjects.add(newInstList);
+ }
+ // Done
+ return dObjects;
+ }
+
+ /**
+ * Same as getObjects() but will only return MetaObjects.
+ */
+ public synchronized List > getMetaObjects()
+ {
+ List< List > mObjects = new ArrayList< List > ();
+
+ // Go through objects and copy to new list when types match
+ ListIterator> objIt = objects.listIterator(0);
+
+ // Check if this object type is already in the list
+ while(objIt.hasNext()) {
+ List instList = objIt.next();
+
+ // If no instances skip
+ if(instList.size() == 0)
+ continue;
+
+ // If meta data skip
+ if(!instList.get(0).isMetadata())
+ continue;
+
+ List newInstList = new ArrayList();
+ ListIterator instIt = instList.listIterator();
+ while(instIt.hasNext()) {
+ newInstList.add((UAVMetaObject) instIt.next());
+ }
+ mObjects.add(newInstList);
+ }
+ // Done
+ return mObjects;
+ }
+
+
+ /**
+ * Returns a specific object by name only, returns instance ID zero
+ * @param name The object name
+ * @return The object or null if not found
+ */
+ public UAVObject getObject(String name)
+ {
+ return getObject(name, 0, 0);
+ }
+
+ /**
+ * Get a specific object given its name and instance ID
+ * @returns The object is found or NULL if not
+ */
+ public UAVObject getObject(String name, long instId)
+ {
+ return getObject(name, 0, instId);
+ }
+
+ /**
+ * Get a specific object with given object ID and instance ID zero
+ * @param objId the object id
+ * @returns The object is found or NULL if not
+ */
+ public UAVObject getObject(long objId)
+ {
+ return getObject(null, objId, 0);
+ }
+
+ /**
+ * Get a specific object given its object and instance ID
+ * @returns The object is found or NULL if not
+ */
+ public UAVObject getObject(long objId, long instId)
+ {
+ return getObject(null, objId, instId);
+ }
+
+ /**
+ * Helper function for the public getObject() functions.
+ */
+ public synchronized UAVObject getObject(String name, long objId, long instId)
+ {
+ // Check if this object type is already in the list
+ ListIterator> objIter = objects.listIterator();
+ while(objIter.hasNext()) {
+ List instList = objIter.next();
+ if (instList.size() > 0) {
+ if ( (name != null && instList.get(0).getName().compareTo(name) == 0) || (name == null && instList.get(0).getObjID() == objId) ) {
+ // Look for the requested instance ID
+ ListIterator iter = instList.listIterator();
+ while(iter.hasNext()) {
+ UAVObject obj = iter.next();
+ if(obj.getInstID() == instId) {
+ return obj;
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get all the instances of the object specified by name
+ */
+ public List getObjectInstances(String name)
+ {
+ return getObjectInstances(name, 0);
+ }
+
+ /**
+ * Get all the instances of the object specified by its ID
+ */
+ public List getObjectInstances(long objId)
+ {
+ return getObjectInstances(null, objId);
+ }
+
+ /**
+ * Helper function for the public getObjectInstances()
+ */
+ public synchronized List getObjectInstances(String name, long objId)
+ {
+ // Check if this object type is already in the list
+ ListIterator> objIter = objects.listIterator();
+ while(objIter.hasNext()) {
+ List instList = objIter.next();
+ if (instList.size() > 0) {
+ if ( (name != null && instList.get(0).getName().compareTo(name) == 0) || (name == null && instList.get(0).getObjID() == objId) ) {
+ return instList;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the number of instances for an object given its name
+ */
+ public int getNumInstances(String name)
+ {
+ return getNumInstances(name, 0);
+ }
+
+ /**
+ * Get the number of instances for an object given its ID
+ */
+ public int getNumInstances(long objId)
+ {
+ return getNumInstances(null, objId);
+ }
+
+ /**
+ * Helper function for public getNumInstances
+ */
+ public int getNumInstances(String name, long objId)
+ {
+ return getObjectInstances(name,objId).size();
+ }
+
+}
diff --git a/androidgcs/src/org/openpilot/uavtalk/UAVTalk.java b/androidgcs/src/org/openpilot/uavtalk/UAVTalk.java
new file mode 100644
index 000000000..c996da845
--- /dev/null
+++ b/androidgcs/src/org/openpilot/uavtalk/UAVTalk.java
@@ -0,0 +1,916 @@
+/**
+ ******************************************************************************
+ * @file UAVTalk.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
+ * @brief The protocol layer implementation of UAVTalk. Serializes objects
+ * for transmission (which is done in the object itself which is aware
+ * of byte packing) wraps that in the UAVTalk packet. Parses UAVTalk
+ * packets and updates the UAVObjectManager.
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.uavtalk;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import junit.framework.Assert;
+import android.util.Log;
+
+public class UAVTalk {
+
+ static final String TAG = "UAVTalk";
+ public static int LOGLEVEL = 0;
+ public static boolean VERBOSE = LOGLEVEL > 3;
+ public static boolean WARN = LOGLEVEL > 2;
+ public static boolean DEBUG = LOGLEVEL > 1;
+ public static boolean ERROR = LOGLEVEL > 0;
+
+ private Thread inputProcessingThread = null;
+
+ /**
+ * A reference to the thread for processing the incoming stream. Currently this method is ONLY
+ * used for unit testing
+ */
+ public Thread getInputProcessThread() {
+ if (inputProcessingThread == null)
+
+ inputProcessingThread = new Thread() {
+ @Override
+ public void run() {
+ while(true) {
+ try {
+ if( !processInputStream() )
+ break;
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ };
+ return inputProcessingThread;
+ }
+
+ /**
+ * Constants
+ */
+ private static final int SYNC_VAL = 0x3C;
+
+ private static final short crc_table[] = { 0x00, 0x07, 0x0e, 0x09, 0x1c,
+ 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d,
+ 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f, 0x46,
+ 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb,
+ 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, 0x90,
+ 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, 0xaf, 0xa6, 0xa1,
+ 0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5,
+ 0xd2, 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0,
+ 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93,
+ 0x94, 0x9d, 0x9a, 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32,
+ 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, 0x59,
+ 0x5e, 0x4b, 0x4c, 0x45, 0x42, 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74,
+ 0x7d, 0x7a, 0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c, 0xb1,
+ 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, 0xf9, 0xfe, 0xf7, 0xf0,
+ 0xe5, 0xe2, 0xeb, 0xec, 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3,
+ 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 0x51, 0x56,
+ 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10, 0x05,
+ 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34,
+ 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78,
+ 0x7f, 0x6a, 0x6d, 0x64, 0x63, 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25,
+ 0x2c, 0x2b, 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae,
+ 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f,
+ 0x8a, 0x8d, 0x84, 0x83, 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc,
+ 0xcb, 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3 };
+
+ enum RxStateType {
+ STATE_SYNC, STATE_TYPE, STATE_SIZE, STATE_OBJID, STATE_INSTID, STATE_DATA, STATE_CS
+ };
+
+ static final int TYPE_MASK = 0xF8;
+ static final int TYPE_VER = 0x20;
+ //! Packet contains an object
+ static final int TYPE_OBJ = (TYPE_VER | 0x00);
+ //! Packet is a request for an object
+ static final int TYPE_OBJ_REQ = (TYPE_VER | 0x01);
+ //! Packet is an object with a request for an ack
+ static final int TYPE_OBJ_ACK = (TYPE_VER | 0x02);
+ //! Packet is an ack for an object
+ static final int TYPE_ACK = (TYPE_VER | 0x03);
+ static final int TYPE_NACK = (TYPE_VER | 0x04);
+
+ static final int MIN_HEADER_LENGTH = 8; // sync(1), type (1), size(2),
+ // object ID(4)
+ static final int MAX_HEADER_LENGTH = 10; // sync(1), type (1), size(2),
+ // object ID (4), instance ID(2,
+ // not used in single objects)
+
+ static final int CHECKSUM_LENGTH = 1;
+
+ static final int MAX_PAYLOAD_LENGTH = 256;
+
+ static final int MAX_PACKET_LENGTH = (MAX_HEADER_LENGTH
+ + MAX_PAYLOAD_LENGTH + CHECKSUM_LENGTH);
+
+ static final int ALL_INSTANCES = 0xFFFF;
+ static final int TX_BUFFER_SIZE = 2 * 1024;
+
+ /**
+ * Private data
+ */
+ InputStream inStream;
+ OutputStream outStream;
+ UAVObjectManager objMngr;
+
+ //! Currently only one UAVTalk transaction is permitted at a time. If this is null none are in process
+ //! otherwise points to the pending object
+ UAVObject respObj;
+ //! If the pending transaction is for all the instances
+ boolean respAllInstances;
+ //! The type of response we are expecting
+ int respType;
+
+ // Variables used by the receive state machine
+ ByteBuffer rxTmpBuffer /* 4 */;
+ ByteBuffer rxBuffer;
+ int rxType;
+ long rxObjId;
+ long rxInstId;
+ int rxLength;
+ int rxPacketLength;
+
+ int rxCSPacket, rxCS;
+ int rxCount;
+ int packetSize;
+ RxStateType rxState;
+ ComStats stats = new ComStats();
+
+ /**
+ * Comm stats
+ */
+ public class ComStats {
+ public int txBytes = 0;
+ public int rxBytes = 0;
+ public int txObjectBytes = 0;
+ public int rxObjectBytes = 0;
+ public int rxObjects = 0;
+ public int txObjects = 0;
+ public int txErrors = 0;
+ public int rxErrors = 0;
+ }
+
+ /**
+ * Constructor
+ */
+ public UAVTalk(InputStream inStream, OutputStream outStream,
+ UAVObjectManager objMngr) {
+ this.objMngr = objMngr;
+ this.inStream = inStream;
+ this.outStream = outStream;
+
+ rxState = RxStateType.STATE_SYNC;
+ rxPacketLength = 0;
+
+ // mutex = new QMutex(QMutex::Recursive);
+
+ respObj = null;
+ resetStats();
+ rxTmpBuffer = ByteBuffer.allocate(4);
+ rxTmpBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ rxBuffer = ByteBuffer.allocate(MAX_PAYLOAD_LENGTH);
+ rxBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ // TOOD: Callback connect(io, SIGNAL(readyRead()), this,
+ // SLOT(processInputStream()));
+ }
+
+ /**
+ * Reset the statistics counters
+ */
+ public void resetStats() {
+ // QMutexLocker locker(mutex);
+ stats = new ComStats();
+ }
+
+ /**
+ * Get the statistics counters
+ */
+ public ComStats getStats() {
+ // QMutexLocker locker(mutex);
+ return stats;
+ }
+
+ /**
+ * Process any data in the queue
+ * @throws IOException
+ */
+ public boolean processInputStream() throws IOException {
+ int val;
+
+ //inStream.wait();
+ val = inStream.read();
+
+ if (VERBOSE) Log.v(TAG, "Read: " + val);
+
+ if (val == -1) {
+ return false;
+ }
+
+ processInputByte(val);
+ return true;
+ }
+
+
+ /**
+ * Request an update for the specified object, on success the object data
+ * would have been updated by the GCS. \param[in] obj Object to update
+ * \param[in] allInstances If set true then all instances will be updated
+ * \return Success (true), Failure (false)
+ * @throws IOException
+ */
+ public boolean sendObjectRequest(UAVObject obj, boolean allInstances) throws IOException {
+ return objectTransaction(obj, TYPE_OBJ_REQ, allInstances);
+ }
+
+ /**
+ * Send the specified object through the telemetry link. \param[in] obj
+ * Object to send \param[in] acked Selects if an ack is required \param[in]
+ * allInstances If set true then all instances will be updated \return
+ * Success (true), Failure (false)
+ * @throws IOException
+ */
+ public boolean sendObject(UAVObject obj, boolean acked, boolean allInstances) throws IOException {
+ if (acked) {
+ return objectTransaction(obj, TYPE_OBJ_ACK, allInstances);
+ } else {
+ return objectTransaction(obj, TYPE_OBJ, allInstances);
+ }
+ }
+
+ /**
+ * UAVTalk takes care of it's own transactions but if the caller knows
+ * it wants to give up on one (after a timeout) then it can cancel it
+ * @return True if that object was pending, False otherwise
+ */
+ public synchronized boolean cancelPendingTransaction(UAVObject obj) {
+ if(respObj != null && respObj.getObjID() == obj.getObjID()) {
+ if(transactionListener != null) {
+ Log.d(TAG,"Canceling transaction: " + respObj.getName());
+ transactionListener.TransactionFailed(respObj);
+ }
+ respObj = null;
+ return true;
+ } else
+ return false;
+ }
+
+ /**
+ * Cancel a pending transaction. If there is a pending transaction and
+ * a listener then notify them that the transaction failed.
+ */
+ /*private synchronized void cancelPendingTransaction() {
+ if(respObj != null && transactionListener != null) {
+ Log.d(TAG,"Canceling transaction: " + respObj.getName());
+ transactionListener.TransactionFailed(respObj);
+ }
+ respObj = null;
+ }*/
+
+ /**
+ * This is the code that sets up a new UAVTalk packet that expects a response.
+ */
+ private synchronized void setupTransaction(UAVObject obj, boolean allInstances, int type) {
+
+ // Only cancel if it is for a different object
+ if(respObj != null && respObj.getObjID() != obj.getObjID())
+ cancelPendingTransaction(obj);
+
+ respObj = obj;
+ respAllInstances = allInstances;
+ respType = type;
+ }
+
+ /**
+ * Execute the requested transaction on an object. \param[in] obj Object
+ * \param[in] type Transaction type TYPE_OBJ: send object, TYPE_OBJ_REQ:
+ * request object update TYPE_OBJ_ACK: send object with an ack \param[in]
+ * allInstances If set true then all instances will be updated \return
+ * Success (true), Failure (false)
+ * @throws IOException
+ */
+ private synchronized boolean objectTransaction(UAVObject obj, int type, boolean allInstances) throws IOException {
+ if (type == TYPE_OBJ_ACK || type == TYPE_OBJ_REQ || type == TYPE_OBJ) {
+ return transmitObject(obj, type, allInstances);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Process an byte from the telemetry stream. \param[in] rxbyte Received
+ * byte \return Success (true), Failure (false)
+ * @throws IOException
+ */
+ public boolean processInputByte(int rxbyte) throws IOException {
+ Assert.assertNotNull(objMngr);
+
+ // Only need to synchronize this method on the state machine state
+ synchronized(rxState) {
+ // Update stats
+ stats.rxBytes++;
+
+ rxPacketLength++; // update packet byte count
+
+ // Receive state machine
+ switch (rxState) {
+ case STATE_SYNC:
+
+ if (rxbyte != SYNC_VAL)
+ break;
+
+ // Initialize and update CRC
+ rxCS = updateCRC(0, rxbyte);
+
+ rxPacketLength = 1;
+
+ rxState = RxStateType.STATE_TYPE;
+ break;
+
+ case STATE_TYPE:
+
+ // Update CRC
+ rxCS = updateCRC(rxCS, rxbyte);
+
+ if ((rxbyte & TYPE_MASK) != TYPE_VER) {
+ if (ERROR) Log.e(TAG, "Unknown UAVTalk type:" + rxbyte);
+ rxState = RxStateType.STATE_SYNC;
+ break;
+ }
+
+ rxType = rxbyte;
+ if (VERBOSE) Log.v(TAG, "Received packet type: " + rxType);
+ packetSize = 0;
+
+ rxState = RxStateType.STATE_SIZE;
+ rxCount = 0;
+ break;
+
+ case STATE_SIZE:
+
+ // Update CRC
+ rxCS = updateCRC(rxCS, rxbyte);
+
+ if (rxCount == 0) {
+ packetSize += rxbyte;
+ rxCount++;
+ break;
+ }
+
+ packetSize += (rxbyte << 8) & 0xff00;
+
+ if (packetSize < MIN_HEADER_LENGTH
+ || packetSize > MAX_HEADER_LENGTH + MAX_PAYLOAD_LENGTH) { // incorrect
+ // packet
+ // size
+ rxState = RxStateType.STATE_SYNC;
+ break;
+ }
+
+ rxCount = 0;
+ rxState = RxStateType.STATE_OBJID;
+ rxTmpBuffer.position(0);
+ break;
+
+ case STATE_OBJID:
+
+ // Update CRC
+ rxCS = updateCRC(rxCS, rxbyte);
+
+ rxTmpBuffer.put(rxCount++, (byte) (rxbyte & 0xff));
+ if (rxCount < 4)
+ break;
+
+ // Search for object, if not found reset state machine
+ rxObjId = rxTmpBuffer.getInt(0);
+ // Because java treats ints as only signed we need to do this manually
+ if (rxObjId < 0)
+ rxObjId = 0x100000000l + rxObjId;
+ {
+ UAVObject rxObj = objMngr.getObject(rxObjId);
+ if (rxObj == null) {
+ if (DEBUG) Log.d(TAG, "Unknown ID: " + rxObjId);
+ stats.rxErrors++;
+ rxState = RxStateType.STATE_SYNC;
+ break;
+ }
+
+ // Determine data length
+ if (rxType == TYPE_OBJ_REQ || rxType == TYPE_ACK || rxType == TYPE_NACK)
+ rxLength = 0;
+ else
+ rxLength = rxObj.getNumBytes();
+
+ // Check length and determine next state
+ if (rxLength >= MAX_PAYLOAD_LENGTH) {
+ stats.rxErrors++;
+ rxState = RxStateType.STATE_SYNC;
+ break;
+ }
+
+ // Check the lengths match
+ if ((rxPacketLength + rxLength) != packetSize) { // packet error
+ // -
+ // mismatched
+ // packet
+ // size
+ stats.rxErrors++;
+ rxState = RxStateType.STATE_SYNC;
+ break;
+ }
+
+ // Check if this is a single instance object (i.e. if the
+ // instance ID field is coming next)
+ if (rxObj.isSingleInstance()) {
+ // If there is a payload get it, otherwise receive checksum
+ if (rxLength > 0)
+ rxState = RxStateType.STATE_DATA;
+ else
+ rxState = RxStateType.STATE_CS;
+ rxInstId = 0;
+ rxCount = 0;
+ } else {
+ rxState = RxStateType.STATE_INSTID;
+ rxCount = 0;
+ }
+ }
+
+ break;
+
+ case STATE_INSTID:
+
+ // Update CRC
+ rxCS = updateCRC(rxCS, rxbyte);
+
+ rxTmpBuffer.put(rxCount++, (byte) (rxbyte & 0xff));
+ if (rxCount < 2)
+ break;
+
+ rxInstId = rxTmpBuffer.getShort(0);
+
+ rxCount = 0;
+
+ // If there is a payload get it, otherwise receive checksum
+ if (rxLength > 0)
+ rxState = RxStateType.STATE_DATA;
+ else
+ rxState = RxStateType.STATE_CS;
+
+ break;
+
+ case STATE_DATA:
+
+ // Update CRC
+ rxCS = updateCRC(rxCS, rxbyte);
+
+ rxBuffer.put(rxCount++, (byte) (rxbyte & 0xff));
+ if (rxCount < rxLength)
+ break;
+
+ rxState = RxStateType.STATE_CS;
+ rxCount = 0;
+ break;
+
+ case STATE_CS:
+
+ // The CRC byte
+ rxCSPacket = rxbyte;
+
+ if (rxCS != rxCSPacket) { // packet error - faulty CRC
+ if (DEBUG) Log.d(TAG,"Bad crc");
+ stats.rxErrors++;
+ rxState = RxStateType.STATE_SYNC;
+ break;
+ }
+
+ if (rxPacketLength != (packetSize + 1)) { // packet error -
+ // mismatched packet
+ // size
+ if (DEBUG) Log.d(TAG,"Bad size");
+ stats.rxErrors++;
+ rxState = RxStateType.STATE_SYNC;
+ break;
+ }
+
+ if (DEBUG) Log.d(TAG,"Received");
+
+ rxBuffer.position(0);
+ receiveObject(rxType, rxObjId, rxInstId, rxBuffer);
+ stats.rxObjectBytes += rxLength;
+ stats.rxObjects++;
+
+ rxState = RxStateType.STATE_SYNC;
+ break;
+
+ default:
+ rxState = RxStateType.STATE_SYNC;
+ stats.rxErrors++;
+ }
+ }
+
+ // Done
+ return true;
+ }
+
+ /**
+ * Receive an object. This function process objects received through the
+ * telemetry stream. \param[in] type Type of received message (TYPE_OBJ,
+ * TYPE_OBJ_REQ, TYPE_OBJ_ACK, TYPE_ACK) \param[in] obj Handle of the
+ * received object \param[in] instId The instance ID of UAVOBJ_ALL_INSTANCES
+ * for all instances. \param[in] data Data buffer \param[in] length Buffer
+ * length \return Success (true), Failure (false)
+ * @throws IOException
+ */
+ public boolean receiveObject(int type, long objId, long instId, ByteBuffer data) throws IOException {
+
+ if (DEBUG) Log.d(TAG, "Received object ID: " + objId);
+ assert (objMngr != null);
+
+ UAVObject obj = null;
+ boolean error = false;
+ boolean allInstances = (instId == ALL_INSTANCES ? true : false);
+
+ // Process message type
+ switch (type) {
+ case TYPE_OBJ:
+ // All instances, not allowed for OBJ messages
+ if (!allInstances) {
+ if (DEBUG) Log.d(TAG,"Received object: " + objMngr.getObject(objId).getName());
+
+ // Get object and update its data
+ obj = updateObject(objId, instId, data);
+
+ if (obj != null) {
+ // Check if this is a response to a UAVTalk transaction
+ updateObjReq(obj);
+ } else {
+ error = true;
+ }
+ } else {
+ error = true;
+ }
+ break;
+ case TYPE_OBJ_ACK:
+ // All instances, not allowed for OBJ_ACK messages
+ if (!allInstances) {
+ if (DEBUG) Log.d(TAG,"Received object ack: " + objId + " " + objMngr.getObject(objId).getName());
+ // Get object and update its data
+ obj = updateObject(objId, instId, data);
+ // Transmit ACK
+ if (obj != null) {
+ transmitObject(obj, TYPE_ACK, false);
+ } else {
+ error = true;
+ }
+ } else {
+ error = true;
+ }
+ break;
+ case TYPE_OBJ_REQ:
+ // Get object, if all instances are requested get instance 0 of the
+ // object
+ if (DEBUG) Log.d(TAG,"Received object request: " + objId + " " +
+ objMngr.getObject(objId).getName());
+ if (allInstances) {
+ obj = objMngr.getObject(objId);
+ } else {
+ obj = objMngr.getObject(objId, instId);
+ }
+ // If object was found transmit it
+ if (obj != null) {
+ transmitObject(obj, TYPE_OBJ, allInstances);
+ } else {
+ error = true;
+ }
+ break;
+ case TYPE_NACK:
+ if (DEBUG) Log.d(TAG, "Received NAK: " + objId + " " + objMngr.getObject(objId).getName());
+ // All instances, not allowed for NACK messages
+ if (!allInstances)
+ {
+ // Get object
+ obj = objMngr.getObject(objId, instId);
+ // Check if object exists:
+ if (obj != null)
+ {
+ receivedNack(obj);
+ }
+ else
+ {
+ error = true;
+ }
+ }
+ break;
+ case TYPE_ACK:
+ // All instances, not allowed for ACK messages
+ if (!allInstances) {
+ if (DEBUG) Log.d(TAG,"Received ack: " + objId + " " + objMngr.getObject(objId).getName());
+ // Get object
+ obj = objMngr.getObject(objId, instId);
+ // Check if an ack is pending
+ if (obj != null) {
+ updateAck(obj);
+ } else {
+ error = true;
+ }
+ }
+ break;
+ default:
+ error = true;
+ }
+ // Done
+ return !error;
+ }
+
+ /**
+ * Update the data of an object from a byte array (unpack). If the object
+ * instance could not be found in the list, then a new one is created.
+ */
+ public synchronized UAVObject updateObject(long objId, long instId,
+ ByteBuffer data) {
+ assert (objMngr != null);
+
+ // Get object
+ UAVObject obj = objMngr.getObject(objId, instId);
+
+ // If the instance does not exist create it
+ if (obj == null) {
+ // Get the object type
+ UAVObject tobj = objMngr.getObject(objId);
+ if (tobj == null) {
+ // TODO: Return a NAK since we don't know this object
+ return null;
+ }
+ // Make sure this is a data object
+ UAVDataObject dobj = null;
+ try {
+ dobj = (UAVDataObject) tobj;
+ } catch (Exception e) {
+ // Failed to cast to a data object
+ return null;
+ }
+
+ // Create a new instance, unpack and register
+ UAVDataObject instobj = dobj.clone(instId);
+ try {
+ if (!objMngr.registerObject(instobj)) {
+ return null;
+ }
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ if (DEBUG) Log.d(TAG, "Unpacking new object");
+ instobj.unpack(data);
+ return instobj;
+ } else {
+ // Unpack data into object instance
+ if (DEBUG) Log.d(TAG, "Unpacking existing object: " + obj.getName());
+ obj.unpack(data);
+ return obj;
+ }
+ }
+
+ /**
+ * Called when an object is received to check if this completes
+ * a UAVTalk transaction
+ */
+ private synchronized void updateObjReq(UAVObject obj) {
+ // Check if this is not a possible candidate
+ Assert.assertNotNull(obj);
+
+ if(respObj != null && respType == TYPE_OBJ_REQ && respObj.getObjID() == obj.getObjID() &&
+ ((respObj.getInstID() == obj.getInstID() || !respAllInstances))) {
+
+ // Indicate complete
+ respObj = null;
+
+ // Notify listener
+ if (transactionListener != null)
+ transactionListener.TransactionSucceeded(obj);
+ }
+
+ }
+
+ /**
+ * Check if a transaction is pending and if yes complete it.
+ */
+ private synchronized void receivedNack(UAVObject obj)
+ {
+ Assert.assertNotNull(obj);
+ if(respObj != null && (respType == TYPE_OBJ_REQ || respType == TYPE_OBJ_ACK ) &&
+ respObj.getObjID() == obj.getObjID()) {
+
+ if (DEBUG) Log.d(TAG, "NAK: " + obj.getName());
+
+ // Indicate complete
+ respObj = null;
+
+ // Notify listener
+ if (transactionListener != null)
+ transactionListener.TransactionFailed(obj);
+ }
+ }
+
+ /**
+ * Check if a transaction is pending that this acked object corresponds to
+ * and if yes complete it.
+ */
+ private synchronized void updateAck(UAVObject obj) {
+ if (DEBUG) Log.d(TAG, "Received ack: " + obj.getName());
+ Assert.assertNotNull(obj);
+ if (respObj != null && respObj.getObjID() == obj.getObjID()
+ && (respObj.getInstID() == obj.getInstID() || respAllInstances)) {
+
+ // Indicate complete
+ respObj = null;
+
+ // Notify listener
+ if (transactionListener != null)
+ transactionListener.TransactionSucceeded(obj);
+ }
+ }
+
+ /**
+ * Send an object through the telemetry link.
+ * @param[in] obj Object to send
+ * @param[in] type Transaction type
+ * @param[in] allInstances True is all instances of the object are to be sent
+ * @return Success (true), Failure (false)
+ * @throws IOException
+ */
+ private boolean transmitObject(UAVObject obj, int type, boolean allInstances) throws IOException {
+ // If all instances are requested on a single instance object it is an
+ // error
+ if (allInstances && obj.isSingleInstance()) {
+ allInstances = false;
+ }
+
+ // Process message type
+ if (type == TYPE_OBJ || type == TYPE_OBJ_ACK) {
+ if (allInstances) {
+ // Get number of instances
+ int numInst = objMngr.getNumInstances(obj.getObjID());
+ // Send all instances
+ for (int instId = 0; instId < numInst; ++instId) {
+ // TODO: This code is buggy probably. We should send each request
+ // and wait for an ack in the case of an TYPE_OBJ_ACK
+ Assert.assertNotSame(type, TYPE_OBJ_ACK); // catch any buggy calls
+
+ UAVObject inst = objMngr.getObject(obj.getObjID(), instId);
+ transmitSingleObject(inst, type, false);
+ }
+ return true;
+ } else {
+ return transmitSingleObject(obj, type, false);
+ }
+ } else if (type == TYPE_OBJ_REQ) {
+ return transmitSingleObject(obj, TYPE_OBJ_REQ, allInstances);
+ } else if (type == TYPE_ACK) {
+ if (!allInstances) {
+ return transmitSingleObject(obj, TYPE_ACK, false);
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Send an object through the telemetry link.
+ * @throws IOException
+ * @param[in] obj Object handle to send
+ * @param[in] type Transaction type \return Success (true), Failure (false)
+ */
+ private boolean transmitSingleObject(UAVObject obj, int type, boolean allInstances) throws IOException {
+ int length;
+ int allInstId = ALL_INSTANCES;
+
+ assert (objMngr != null && outStream != null);
+
+ ByteBuffer bbuf = ByteBuffer.allocate(MAX_PACKET_LENGTH);
+ bbuf.order(ByteOrder.LITTLE_ENDIAN);
+
+ // Determine data length
+ if (type == TYPE_OBJ_REQ || type == TYPE_ACK) {
+ length = 0;
+ } else {
+ length = obj.getNumBytes();
+ }
+
+ // Setup type and object id fields
+ bbuf.put((byte) (SYNC_VAL & 0xff));
+ bbuf.put((byte) (type & 0xff));
+ bbuf.putShort((short) (length + 2 /* SYNC, Type */+ 2 /* Size */+ 4 /* ObjID */+ (obj
+ .isSingleInstance() ? 0 : 2)));
+ bbuf.putInt((int)obj.getObjID());
+
+ // Setup instance ID if one is required
+ if (!obj.isSingleInstance()) {
+ // Check if all instances are requested
+ if (allInstances)
+ bbuf.putShort((short) (allInstId & 0xffff));
+ else
+ bbuf.putShort((short) (obj.getInstID() & 0xffff));
+ }
+
+ // Check length
+ if (length >= MAX_PAYLOAD_LENGTH)
+ return false;
+
+ // Copy data (if any)
+ if (length > 0)
+ try {
+ if (obj.pack(bbuf) == 0)
+ return false;
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ return false;
+ }
+
+ // Calculate checksum
+ bbuf.put((byte) (updateCRC(0, bbuf.array(), bbuf.position()) & 0xff));
+
+ int packlen = bbuf.position();
+ bbuf.position(0);
+ byte[] dst = new byte[packlen];
+ bbuf.get(dst, 0, packlen);
+
+ if (type == TYPE_OBJ_ACK || type == TYPE_OBJ_REQ) {
+ // Once we send a UAVTalk packet that requires an ack or object let's set up
+ // the transaction here
+ setupTransaction(obj, allInstances, type);
+ }
+
+ outStream.write(dst);
+
+
+ // Update stats
+ ++stats.txObjects;
+ stats.txBytes += bbuf.position();
+ stats.txObjectBytes += length;
+
+ // Done
+ return true;
+ }
+
+ /**
+ * Update the crc value with new data.
+ *
+ * Generated by pycrc v0.7.5, http://www.tty1.net/pycrc/ using the
+ * configuration: Width = 8 Poly = 0x07 XorIn = 0x00 ReflectIn = False
+ * XorOut = 0x00 ReflectOut = False Algorithm = table-driven
+ *
+ * \param crc The current crc value. \param data Pointer to a buffer of \a
+ * data_len bytes. \param length Number of bytes in the \a data buffer.
+ * \return The updated crc value.
+ */
+ int updateCRC(int crc, int data) {
+ return crc_table[crc ^ (data & 0xff)];
+ }
+
+ int updateCRC(int crc, byte[] data, int length) {
+ for (int i = 0; i < length; i++)
+ crc = updateCRC(crc, data[i]);
+ return crc;
+ }
+
+ private OnTransactionCompletedListener transactionListener = null;
+ abstract class OnTransactionCompletedListener {
+ abstract void TransactionSucceeded(UAVObject data);
+ abstract void TransactionFailed(UAVObject data);
+ };
+ void setOnTransactionCompletedListener(OnTransactionCompletedListener onTransactionListener) {
+ this.transactionListener = onTransactionListener;
+ }
+
+
+}
diff --git a/androidgcs/src/org/openpilot/uavtalk/uavobjects/Accels.java b/androidgcs/src/org/openpilot/uavtalk/uavobjects/Accels.java
new file mode 100644
index 000000000..e3c386e30
--- /dev/null
+++ b/androidgcs/src/org/openpilot/uavtalk/uavobjects/Accels.java
@@ -0,0 +1,150 @@
+/**
+ ******************************************************************************
+ *
+ * @file uavobjecttemplate.cpp
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
+ * @brief Template for an uavobject in java
+ * This is a autogenerated file!! Do not modify and expect a result.
+ * The accel data.
+ *
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.openpilot.uavtalk.uavobjects;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.openpilot.uavtalk.UAVObjectManager;
+import org.openpilot.uavtalk.UAVObject;
+import org.openpilot.uavtalk.UAVDataObject;
+import org.openpilot.uavtalk.UAVObjectField;
+
+/**
+The accel data.
+
+generated from accels.xml
+ **/
+public class Accels extends UAVDataObject {
+
+ public Accels() {
+ super(OBJID, ISSINGLEINST, ISSETTINGS, NAME);
+
+ List fields = new ArrayList();
+
+
+ List xElemNames = new ArrayList();
+ xElemNames.add("0");
+ fields.add( new UAVObjectField("x", "m/s^2", UAVObjectField.FieldType.FLOAT32, xElemNames, null) );
+
+ List yElemNames = new ArrayList();
+ yElemNames.add("0");
+ fields.add( new UAVObjectField("y", "m/s^2", UAVObjectField.FieldType.FLOAT32, yElemNames, null) );
+
+ List zElemNames = new ArrayList();
+ zElemNames.add("0");
+ fields.add( new UAVObjectField("z", "m/s^2", UAVObjectField.FieldType.FLOAT32, zElemNames, null) );
+
+ List temperatureElemNames = new ArrayList();
+ temperatureElemNames.add("0");
+ fields.add( new UAVObjectField("temperature", "deg C", UAVObjectField.FieldType.FLOAT32, temperatureElemNames, null) );
+
+
+ // Compute the number of bytes for this object
+ int numBytes = 0;
+ ListIterator li = fields.listIterator();
+ while(li.hasNext()) {
+ numBytes += li.next().getNumBytes();
+ }
+ NUMBYTES = numBytes;
+
+ // Initialize object
+ initializeFields(fields, ByteBuffer.allocate(NUMBYTES), NUMBYTES);
+ // Set the default field values
+ setDefaultFieldValues();
+ // Set the object description
+ setDescription(DESCRIPTION);
+ }
+
+ /**
+ * Create a Metadata object filled with default values for this object
+ * @return Metadata object with default values
+ */
+ public Metadata getDefaultMetadata() {
+ UAVObject.Metadata metadata = new UAVObject.Metadata();
+ metadata.flags =
+ UAVObject.Metadata.AccessModeNum(UAVObject.AccessMode.ACCESS_READWRITE) << UAVOBJ_ACCESS_SHIFT |
+ UAVObject.Metadata.AccessModeNum(UAVObject.AccessMode.ACCESS_READWRITE) << UAVOBJ_GCS_ACCESS_SHIFT |
+ 0 << UAVOBJ_TELEMETRY_ACKED_SHIFT |
+ 0 << UAVOBJ_GCS_TELEMETRY_ACKED_SHIFT |
+ UAVObject.Metadata.UpdateModeNum(UAVObject.UpdateMode.UPDATEMODE_PERIODIC) << UAVOBJ_TELEMETRY_UPDATE_MODE_SHIFT |
+ UAVObject.Metadata.UpdateModeNum(UAVObject.UpdateMode.UPDATEMODE_MANUAL) << UAVOBJ_GCS_TELEMETRY_UPDATE_MODE_SHIFT;
+ metadata.flightTelemetryUpdatePeriod = 1000;
+ metadata.gcsTelemetryUpdatePeriod = 0;
+ metadata.loggingUpdatePeriod = 0;
+
+ return metadata;
+ }
+
+ /**
+ * Initialize object fields with the default values.
+ * If a default value is not specified the object fields
+ * will be initialized to zero.
+ */
+ public void setDefaultFieldValues()
+ {
+
+ }
+
+ /**
+ * Create a clone of this object, a new instance ID must be specified.
+ * Do not use this function directly to create new instances, the
+ * UAVObjectManager should be used instead.
+ */
+ public UAVDataObject clone(long instID) {
+ // TODO: Need to get specific instance to clone
+ try {
+ Accels obj = new Accels();
+ obj.initialize(instID, this.getMetaObject());
+ return obj;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Static function to retrieve an instance of the object.
+ */
+ public Accels GetInstance(UAVObjectManager objMngr, long instID)
+ {
+ return (Accels)(objMngr.getObject(Accels.OBJID, instID));
+ }
+
+ // Constants
+ protected static final long OBJID = 0xDD9D5FC0l;
+ protected static final String NAME = "Accels";
+ protected static String DESCRIPTION = "The accel data.";
+ protected static final boolean ISSINGLEINST = 1 > 0;
+ protected static final boolean ISSETTINGS = 0 > 0;
+ protected static int NUMBYTES = 0;
+
+
+}
diff --git a/androidgcs/src/org/openpilot/uavtalk/uavobjects/AccessoryDesired.java b/androidgcs/src/org/openpilot/uavtalk/uavobjects/AccessoryDesired.java
new file mode 100644
index 000000000..24da98961
--- /dev/null
+++ b/androidgcs/src/org/openpilot/uavtalk/uavobjects/AccessoryDesired.java
@@ -0,0 +1,138 @@
+/**
+ ******************************************************************************
+ *
+ * @file uavobjecttemplate.cpp
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
+ * @brief Template for an uavobject in java
+ * This is a autogenerated file!! Do not modify and expect a result.
+ * Desired Auxillary actuator settings. Comes from @ref ManualControlModule.
+ *
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.openpilot.uavtalk.uavobjects;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.openpilot.uavtalk.UAVObjectManager;
+import org.openpilot.uavtalk.UAVObject;
+import org.openpilot.uavtalk.UAVDataObject;
+import org.openpilot.uavtalk.UAVObjectField;
+
+/**
+Desired Auxillary actuator settings. Comes from @ref ManualControlModule.
+
+generated from accessorydesired.xml
+ **/
+public class AccessoryDesired extends UAVDataObject {
+
+ public AccessoryDesired() {
+ super(OBJID, ISSINGLEINST, ISSETTINGS, NAME);
+
+ List fields = new ArrayList();
+
+
+ List AccessoryValElemNames = new ArrayList();
+ AccessoryValElemNames.add("0");
+ fields.add( new UAVObjectField("AccessoryVal", "", UAVObjectField.FieldType.FLOAT32, AccessoryValElemNames, null) );
+
+
+ // Compute the number of bytes for this object
+ int numBytes = 0;
+ ListIterator li = fields.listIterator();
+ while(li.hasNext()) {
+ numBytes += li.next().getNumBytes();
+ }
+ NUMBYTES = numBytes;
+
+ // Initialize object
+ initializeFields(fields, ByteBuffer.allocate(NUMBYTES), NUMBYTES);
+ // Set the default field values
+ setDefaultFieldValues();
+ // Set the object description
+ setDescription(DESCRIPTION);
+ }
+
+ /**
+ * Create a Metadata object filled with default values for this object
+ * @return Metadata object with default values
+ */
+ public Metadata getDefaultMetadata() {
+ UAVObject.Metadata metadata = new UAVObject.Metadata();
+ metadata.flags =
+ UAVObject.Metadata.AccessModeNum(UAVObject.AccessMode.ACCESS_READWRITE) << UAVOBJ_ACCESS_SHIFT |
+ UAVObject.Metadata.AccessModeNum(UAVObject.AccessMode.ACCESS_READWRITE) << UAVOBJ_GCS_ACCESS_SHIFT |
+ 0 << UAVOBJ_TELEMETRY_ACKED_SHIFT |
+ 0 << UAVOBJ_GCS_TELEMETRY_ACKED_SHIFT |
+ UAVObject.Metadata.UpdateModeNum(UAVObject.UpdateMode.UPDATEMODE_PERIODIC) << UAVOBJ_TELEMETRY_UPDATE_MODE_SHIFT |
+ UAVObject.Metadata.UpdateModeNum(UAVObject.UpdateMode.UPDATEMODE_MANUAL) << UAVOBJ_GCS_TELEMETRY_UPDATE_MODE_SHIFT;
+ metadata.flightTelemetryUpdatePeriod = 1000;
+ metadata.gcsTelemetryUpdatePeriod = 0;
+ metadata.loggingUpdatePeriod = 0;
+
+ return metadata;
+ }
+
+ /**
+ * Initialize object fields with the default values.
+ * If a default value is not specified the object fields
+ * will be initialized to zero.
+ */
+ public void setDefaultFieldValues()
+ {
+
+ }
+
+ /**
+ * Create a clone of this object, a new instance ID must be specified.
+ * Do not use this function directly to create new instances, the
+ * UAVObjectManager should be used instead.
+ */
+ public UAVDataObject clone(long instID) {
+ // TODO: Need to get specific instance to clone
+ try {
+ AccessoryDesired obj = new AccessoryDesired();
+ obj.initialize(instID, this.getMetaObject());
+ return obj;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Static function to retrieve an instance of the object.
+ */
+ public AccessoryDesired GetInstance(UAVObjectManager objMngr, long instID)
+ {
+ return (AccessoryDesired)(objMngr.getObject(AccessoryDesired.OBJID, instID));
+ }
+
+ // Constants
+ protected static final long OBJID = 0xC409985Al;
+ protected static final String NAME = "AccessoryDesired";
+ protected static String DESCRIPTION = "Desired Auxillary actuator settings. Comes from @ref ManualControlModule.";
+ protected static final boolean ISSINGLEINST = 0 > 0;
+ protected static final boolean ISSETTINGS = 0 > 0;
+ protected static int NUMBYTES = 0;
+
+
+}
diff --git a/androidgcs/src/org/openpilot/uavtalk/uavobjects/ActuatorCommand.java b/androidgcs/src/org/openpilot/uavtalk/uavobjects/ActuatorCommand.java
new file mode 100644
index 000000000..917095171
--- /dev/null
+++ b/androidgcs/src/org/openpilot/uavtalk/uavobjects/ActuatorCommand.java
@@ -0,0 +1,159 @@
+/**
+ ******************************************************************************
+ *
+ * @file uavobjecttemplate.cpp
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
+ * @brief Template for an uavobject in java
+ * This is a autogenerated file!! Do not modify and expect a result.
+ * Contains the pulse duration sent to each of the channels. Set by @ref ActuatorModule
+ *
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.openpilot.uavtalk.uavobjects;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.openpilot.uavtalk.UAVObjectManager;
+import org.openpilot.uavtalk.UAVObject;
+import org.openpilot.uavtalk.UAVDataObject;
+import org.openpilot.uavtalk.UAVObjectField;
+
+/**
+Contains the pulse duration sent to each of the channels. Set by @ref ActuatorModule
+
+generated from actuatorcommand.xml
+ **/
+public class ActuatorCommand extends UAVDataObject {
+
+ public ActuatorCommand() {
+ super(OBJID, ISSINGLEINST, ISSETTINGS, NAME);
+
+ List fields = new ArrayList();
+
+
+ List ChannelElemNames = new ArrayList();
+ ChannelElemNames.add("0");
+ ChannelElemNames.add("1");
+ ChannelElemNames.add("2");
+ ChannelElemNames.add("3");
+ ChannelElemNames.add("4");
+ ChannelElemNames.add("5");
+ ChannelElemNames.add("6");
+ ChannelElemNames.add("7");
+ ChannelElemNames.add("8");
+ ChannelElemNames.add("9");
+ fields.add( new UAVObjectField("Channel", "us", UAVObjectField.FieldType.INT16, ChannelElemNames, null) );
+
+ List MaxUpdateTimeElemNames = new ArrayList();
+ MaxUpdateTimeElemNames.add("0");
+ fields.add( new UAVObjectField("MaxUpdateTime", "ms", UAVObjectField.FieldType.UINT16, MaxUpdateTimeElemNames, null) );
+
+ List UpdateTimeElemNames = new ArrayList();
+ UpdateTimeElemNames.add("0");
+ fields.add( new UAVObjectField("UpdateTime", "ms", UAVObjectField.FieldType.UINT8, UpdateTimeElemNames, null) );
+
+ List NumFailedUpdatesElemNames = new ArrayList();
+ NumFailedUpdatesElemNames.add("0");
+ fields.add( new UAVObjectField("NumFailedUpdates", "", UAVObjectField.FieldType.UINT8, NumFailedUpdatesElemNames, null) );
+
+
+ // Compute the number of bytes for this object
+ int numBytes = 0;
+ ListIterator li = fields.listIterator();
+ while(li.hasNext()) {
+ numBytes += li.next().getNumBytes();
+ }
+ NUMBYTES = numBytes;
+
+ // Initialize object
+ initializeFields(fields, ByteBuffer.allocate(NUMBYTES), NUMBYTES);
+ // Set the default field values
+ setDefaultFieldValues();
+ // Set the object description
+ setDescription(DESCRIPTION);
+ }
+
+ /**
+ * Create a Metadata object filled with default values for this object
+ * @return Metadata object with default values
+ */
+ public Metadata getDefaultMetadata() {
+ UAVObject.Metadata metadata = new UAVObject.Metadata();
+ metadata.flags =
+ UAVObject.Metadata.AccessModeNum(UAVObject.AccessMode.ACCESS_READWRITE) << UAVOBJ_ACCESS_SHIFT |
+ UAVObject.Metadata.AccessModeNum(UAVObject.AccessMode.ACCESS_READWRITE) << UAVOBJ_GCS_ACCESS_SHIFT |
+ 0 << UAVOBJ_TELEMETRY_ACKED_SHIFT |
+ 0 << UAVOBJ_GCS_TELEMETRY_ACKED_SHIFT |
+ UAVObject.Metadata.UpdateModeNum(UAVObject.UpdateMode.UPDATEMODE_PERIODIC) << UAVOBJ_TELEMETRY_UPDATE_MODE_SHIFT |
+ UAVObject.Metadata.UpdateModeNum(UAVObject.UpdateMode.UPDATEMODE_MANUAL) << UAVOBJ_GCS_TELEMETRY_UPDATE_MODE_SHIFT;
+ metadata.flightTelemetryUpdatePeriod = 1000;
+ metadata.gcsTelemetryUpdatePeriod = 0;
+ metadata.loggingUpdatePeriod = 0;
+
+ return metadata;
+ }
+
+ /**
+ * Initialize object fields with the default values.
+ * If a default value is not specified the object fields
+ * will be initialized to zero.
+ */
+ public void setDefaultFieldValues()
+ {
+
+ }
+
+ /**
+ * Create a clone of this object, a new instance ID must be specified.
+ * Do not use this function directly to create new instances, the
+ * UAVObjectManager should be used instead.
+ */
+ public UAVDataObject clone(long instID) {
+ // TODO: Need to get specific instance to clone
+ try {
+ ActuatorCommand obj = new ActuatorCommand();
+ obj.initialize(instID, this.getMetaObject());
+ return obj;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Static function to retrieve an instance of the object.
+ */
+ public ActuatorCommand GetInstance(UAVObjectManager objMngr, long instID)
+ {
+ return (ActuatorCommand)(objMngr.getObject(ActuatorCommand.OBJID, instID));
+ }
+
+ // Constants
+ protected static final long OBJID = 0x5324CB8l;
+ protected static final String NAME = "ActuatorCommand";
+ protected static String DESCRIPTION = "Contains the pulse duration sent to each of the channels. Set by @ref ActuatorModule";
+ protected static final boolean ISSINGLEINST = 1 > 0;
+ protected static final boolean ISSETTINGS = 0 > 0;
+ protected static int NUMBYTES = 0;
+
+
+}
diff --git a/androidgcs/src/org/openpilot/uavtalk/uavobjects/ActuatorDesired.java b/androidgcs/src/org/openpilot/uavtalk/uavobjects/ActuatorDesired.java
new file mode 100644
index 000000000..8f7869ff9
--- /dev/null
+++ b/androidgcs/src/org/openpilot/uavtalk/uavobjects/ActuatorDesired.java
@@ -0,0 +1,158 @@
+/**
+ ******************************************************************************
+ *
+ * @file uavobjecttemplate.cpp
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
+ * @brief Template for an uavobject in java
+ * This is a autogenerated file!! Do not modify and expect a result.
+ * Desired raw, pitch and yaw actuator settings. Comes from either @ref StabilizationModule or @ref ManualControlModule depending on FlightMode.
+ *
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.openpilot.uavtalk.uavobjects;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.openpilot.uavtalk.UAVObjectManager;
+import org.openpilot.uavtalk.UAVObject;
+import org.openpilot.uavtalk.UAVDataObject;
+import org.openpilot.uavtalk.UAVObjectField;
+
+/**
+Desired raw, pitch and yaw actuator settings. Comes from either @ref StabilizationModule or @ref ManualControlModule depending on FlightMode.
+
+generated from actuatordesired.xml
+ **/
+public class ActuatorDesired extends UAVDataObject {
+
+ public ActuatorDesired() {
+ super(OBJID, ISSINGLEINST, ISSETTINGS, NAME);
+
+ List fields = new ArrayList();
+
+
+ List RollElemNames = new ArrayList();
+ RollElemNames.add("0");
+ fields.add( new UAVObjectField("Roll", "%", UAVObjectField.FieldType.FLOAT32, RollElemNames, null) );
+
+ List PitchElemNames = new ArrayList();
+ PitchElemNames.add("0");
+ fields.add( new UAVObjectField("Pitch", "%", UAVObjectField.FieldType.FLOAT32, PitchElemNames, null) );
+
+ List YawElemNames = new ArrayList();
+ YawElemNames.add("0");
+ fields.add( new UAVObjectField("Yaw", "%", UAVObjectField.FieldType.FLOAT32, YawElemNames, null) );
+
+ List