diff --git a/build.gradle b/build.gradle
index 8509801e..b67b8475 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
ext.oshCoreVersion = '1.4.0'
-ext.compileSdkVersion = 21
-ext.minSdkVersion = 21
-ext.targetSdkVersion = 21
+ext.compileSdkVersion = 31
+ext.minSdkVersion = 26
+ext.targetSdkVersion = 26
ext.buildToolsVersion = "30.0.2"
version = oshCoreVersion
diff --git a/sensorhub-android-app/AndroidManifest.xml b/sensorhub-android-app/AndroidManifest.xml
index 1ddef0e1..e6c8d7b7 100644
--- a/sensorhub-android-app/AndroidManifest.xml
+++ b/sensorhub-android-app/AndroidManifest.xml
@@ -6,37 +6,57 @@
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+ android:largeHeap="true"
+ android:theme="@style/AppTheme">
+ android:label="@string/app_name"
+ android:screenOrientation="portrait">
+
-
-
-
+ android:label="@string/title_activity_user_settings" />
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/sensorhub-android-app/build.gradle b/sensorhub-android-app/build.gradle
index 9441e669..140bbdbb 100644
--- a/sensorhub-android-app/build.gradle
+++ b/sensorhub-android-app/build.gradle
@@ -3,8 +3,25 @@ apply plugin: 'com.android.application'
description = 'OSH Demo Android App'
ext.details = 'OSH demo app for Android'
+repositories {
+ maven {
+ url "https://repo.eclipse.org/content/repositories/paho-releases/"
+ }
+}
+
dependencies {
api project(':sensorhub-android-lib')
+
+ implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
+ implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.0'
+ implementation 'com.android.support:appcompat-v7:26.1.0'
+ implementation 'com.android.support:design:26.1.0'
+ implementation 'com.android.support.constraint:constraint-layout:2.0.4'
+ implementation 'android.arch.navigation:navigation-fragment:1.0.0'
+ implementation 'android.arch.navigation:navigation-ui:1.0.0'
+ implementation 'com.android.support:support-v4:26.1.0'
+// implementation project(path: ':sensorhub-android-sos-ipc')
+// implementation project(path: ':sensorhub-storage-h2')
}
android {
@@ -41,5 +58,8 @@ android {
lintOptions {
abortOnError false
}
+ buildFeatures {
+ viewBinding true
+ }
}
diff --git a/sensorhub-android-app/ic_launcher-web.png b/sensorhub-android-app/ic_launcher-web.png
index 2a05b7e1..b525d261 100644
Binary files a/sensorhub-android-app/ic_launcher-web.png and b/sensorhub-android-app/ic_launcher-web.png differ
diff --git a/sensorhub-android-app/res/drawable-hdpi/ic_launcher.png b/sensorhub-android-app/res/drawable-hdpi/ic_launcher.png
index befaa77e..b525d261 100644
Binary files a/sensorhub-android-app/res/drawable-hdpi/ic_launcher.png and b/sensorhub-android-app/res/drawable-hdpi/ic_launcher.png differ
diff --git a/sensorhub-android-app/res/drawable-mdpi/ic_launcher.png b/sensorhub-android-app/res/drawable-mdpi/ic_launcher.png
index 4da9daf4..b525d261 100644
Binary files a/sensorhub-android-app/res/drawable-mdpi/ic_launcher.png and b/sensorhub-android-app/res/drawable-mdpi/ic_launcher.png differ
diff --git a/sensorhub-android-app/res/drawable-xhdpi/ic_launcher.png b/sensorhub-android-app/res/drawable-xhdpi/ic_launcher.png
index 1575e9de..b525d261 100644
Binary files a/sensorhub-android-app/res/drawable-xhdpi/ic_launcher.png and b/sensorhub-android-app/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/sensorhub-android-app/res/drawable-xxhdpi/ic_launcher.png b/sensorhub-android-app/res/drawable-xxhdpi/ic_launcher.png
index 104064d5..b525d261 100644
Binary files a/sensorhub-android-app/res/drawable-xxhdpi/ic_launcher.png and b/sensorhub-android-app/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/sensorhub-android-app/res/layout/activity_app_status.xml b/sensorhub-android-app/res/layout/activity_app_status.xml
new file mode 100644
index 00000000..f54bffdd
--- /dev/null
+++ b/sensorhub-android-app/res/layout/activity_app_status.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sensorhub-android-app/res/menu/main.xml b/sensorhub-android-app/res/menu/main.xml
index 673f610b..41eacb89 100644
--- a/sensorhub-android-app/res/menu/main.xml
+++ b/sensorhub-android-app/res/menu/main.xml
@@ -21,9 +21,15 @@
android:title="@string/action_stop"/>
+
+
diff --git a/sensorhub-android-app/res/values/strings.xml b/sensorhub-android-app/res/values/strings.xml
index d70a0e67..eacd6240 100644
--- a/sensorhub-android-app/res/values/strings.xml
+++ b/sensorhub-android-app/res/values/strings.xml
@@ -1,12 +1,20 @@
- SensorHubConnect
- Please configure and start SensorHub using the Options Menu
+ OpenSensorHub SmartHub
+ Please configure and start SmartHub using the Options Menu
Settings
- Start SensorHub
- Stop SensorHub
+ Start SmartHub
+ Stop SmartHub
+ App Status
About
-
+ Start Proxy
+ Stop Proxy
+ SOS Settings (Required)
+ SOS-T Settings (Optional)
+ Android Sensor
+ TruPulse Range Finder Sensor
+ Angel Sensor
+ Flirone Sensor
- JPEG
- H264
@@ -14,4 +22,187 @@
- VP9
- VP8
+
+
+ - Quaternion
+ - Euler
+
+
+ - QUATERNION
+ - EULER
+
+
+ - QUATERNION
+
+
+
+ - GPS
+ - Network
+
+
+ - GPS
+ - NETWORK
+
+
+ - GPS
+
+
+
+ - Fetchable
+ - Store Locally
+ - Push Remote
+
+
+ - FETCH_LOCAL
+ - STORE_LOCAL
+ - PUSH_REMOTE
+
+
+ - PUSH_REMOTE
+
+
+
+ - Streaming Physical Device
+ - Simulate Virtual Device
+
+
+ - STREAM
+ - SIMULATED
+
+ STREAM
+
+
+ - Select Reporting Item
+ - Street Closure
+ - Flooding
+ - Medical
+ - Aid
+
+
+
+ - Nearest Beacon
+ - Trilateration
+
+
+ - NEAREST
+ - TRILATERATION
+
+
+ - NEAREST
+
+
+
+ - GPS
+ - Laser Range Finder
+ - Network
+
+
+ Spot Report
+ NAME:
+ DESCRIPTION:
+ CAPTURE
+ RESET
+ SUBMIT REPORT
+ A name for the report
+
+ Radius:
+ Lat:
+ Lon:
+ Ft.
+
+
+ - Select...
+ - Public
+ - All
+
+
+
+ - Select...
+ - Open
+ - Close
+
+
+ Action:
+ Ref. Id:
+
+
+ Type:
+
+
+ - Select...
+ - Channel Drainage
+ - Land Surface
+
+
+
+ - Select...
+ - Meter
+ - Visual
+ - Model
+
+
+ Feature Type:
+ Depth:
+ Obs. Mode:
+
+ Describe Medical Condition...
+ Enter measurement (BP, Temp, etc.)...
+ Emergency (T/F)
+
+
+ - Select...
+ - Environment
+ - Health
+ - Safety
+ - Services
+
+
+
+ - Select...
+ - 5
+ - 4
+ - 3
+ - 2
+ - 1
+
+
+ Aid Type:
+ # Persons:
+ Urgency:
+ Describe aid needed...
+ Enter your name or id
+
+
+ - Select...
+ - Person
+ - Vehicle
+ - Device
+
+
+
+ - Select...
+ - GPS
+ - BlueTooth Beacon
+ - WiFi
+ - Cellular
+ - UWB
+ - N/A
+
+
+ Tracking Resource:
+ Tracking Method:
+ Enter resource id
+ Enter resource label
+
+
+
+
+ - AAC
+ - AMR-NB
+ - AMR-WB
+ - FLAC
+ - VORBIS
+ - OPUS
+ - PCM
+
diff --git a/sensorhub-android-app/res/values/strings_app_status.xml b/sensorhub-android-app/res/values/strings_app_status.xml
new file mode 100644
index 00000000..3e5941bb
--- /dev/null
+++ b/sensorhub-android-app/res/values/strings_app_status.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ App Status
+
+
+ - Initializing
+ - Initialized
+ - Starting
+ - Started
+ - Stopping
+ - Stopped
+ - Unknown
+
+
+ SOS Service Status
+ HTTP Server Status
+ Android Sensor Status
+ Android Sensor Storage Status
+
\ No newline at end of file
diff --git a/sensorhub-android-app/res/xml/pref_audio.xml b/sensorhub-android-app/res/xml/pref_audio.xml
new file mode 100644
index 00000000..f23302f1
--- /dev/null
+++ b/sensorhub-android-app/res/xml/pref_audio.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
diff --git a/sensorhub-android-app/res/xml/pref_general.xml b/sensorhub-android-app/res/xml/pref_general.xml
index 55f397a2..7ee7b58b 100644
--- a/sensorhub-android-app/res/xml/pref_general.xml
+++ b/sensorhub-android-app/res/xml/pref_general.xml
@@ -1,5 +1,4 @@
-
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sensorhub-android-app/res/xml/pref_headers.xml b/sensorhub-android-app/res/xml/pref_headers.xml
index d84db848..442cac23 100644
--- a/sensorhub-android-app/res/xml/pref_headers.xml
+++ b/sensorhub-android-app/res/xml/pref_headers.xml
@@ -9,5 +9,8 @@
+
diff --git a/sensorhub-android-app/res/xml/pref_sensors.xml b/sensorhub-android-app/res/xml/pref_sensors.xml
index 9b19865e..b7df8635 100644
--- a/sensorhub-android-app/res/xml/pref_sensors.xml
+++ b/sensorhub-android-app/res/xml/pref_sensors.xml
@@ -1,88 +1,224 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sensorhub-android-app/res/xml/pref_video.xml b/sensorhub-android-app/res/xml/pref_video.xml
index e146590c..96080f4b 100644
--- a/sensorhub-android-app/res/xml/pref_video.xml
+++ b/sensorhub-android-app/res/xml/pref_video.xml
@@ -25,5 +25,13 @@
android:negativeButtonText="@null"
android:positiveButtonText="@null"
android:title="Selected Preset" />
+
+
diff --git a/sensorhub-android-app/res/xml/spotreport_paths.xml b/sensorhub-android-app/res/xml/spotreport_paths.xml
new file mode 100644
index 00000000..6a3d2e3c
--- /dev/null
+++ b/sensorhub-android-app/res/xml/spotreport_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/sensorhub-android-app/src/org/sensorhub/android/AppStatusActivity.java b/sensorhub-android-app/src/org/sensorhub/android/AppStatusActivity.java
new file mode 100644
index 00000000..85d29359
--- /dev/null
+++ b/sensorhub-android-app/src/org/sensorhub/android/AppStatusActivity.java
@@ -0,0 +1,35 @@
+package org.sensorhub.android;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+
+public class AppStatusActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_app_status);
+
+ Intent intent = getIntent();
+ Context appContext = getApplicationContext();
+
+ String sosStatus = intent.getStringExtra("sosService");
+ String httpStatus = intent.getStringExtra("httpStatus");
+ String sensorStatus = intent.getStringExtra("androidSensorStatus");
+ String sensorStorageStatus = intent.getStringExtra("sensorStorageStatus");
+
+ TextView sosStatusView = (TextView) findViewById(R.id.sos_service_state);
+ TextView httpStatusView = (TextView) findViewById(R.id.http_service_state);
+ TextView sensorStatusView = (TextView) findViewById(R.id.sensor_service_state);
+ TextView storageStatusView = (TextView) findViewById(R.id.storage_service_state);
+
+ sosStatusView.setText(sosStatus);
+ httpStatusView.setText(httpStatus);
+ sensorStatusView.setText(sensorStatus);
+ storageStatusView.setText(sensorStorageStatus);
+ }
+}
\ No newline at end of file
diff --git a/sensorhub-android-app/src/org/sensorhub/android/MainActivity.java b/sensorhub-android-app/src/org/sensorhub/android/MainActivity.java
index ac47f931..b54be9a1 100644
--- a/sensorhub-android-app/src/org/sensorhub/android/MainActivity.java
+++ b/sensorhub-android-app/src/org/sensorhub/android/MainActivity.java
@@ -14,33 +14,51 @@
package org.sensorhub.android;
+import static android.content.ContentValues.TAG;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.security.cert.X509Certificate;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
-
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
import android.location.LocationManager;
import android.location.LocationProvider;
-import android.preference.DialogPreference;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.preference.PreferenceManager;
+import android.provider.Settings.Secure;
+import android.text.Html;
import android.util.Log;
-import android.view.*;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.TextureView;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.TextView;
+
import org.sensorhub.android.comm.BluetoothCommProvider;
import org.sensorhub.android.comm.BluetoothCommProviderConfig;
import org.sensorhub.android.comm.ble.BleConfig;
import org.sensorhub.android.comm.ble.BleNetwork;
import org.sensorhub.api.common.Event;
import org.sensorhub.api.common.IEventListener;
+import org.sensorhub.api.common.SensorHubException;
import org.sensorhub.api.data.IStreamingDataInterface;
+import org.sensorhub.api.module.IModule;
import org.sensorhub.api.module.IModuleConfigRepository;
import org.sensorhub.api.module.ModuleConfig;
import org.sensorhub.api.module.ModuleEvent;
@@ -48,35 +66,43 @@
import org.sensorhub.impl.client.sost.SOSTClient;
import org.sensorhub.impl.client.sost.SOSTClient.StreamInfo;
import org.sensorhub.impl.client.sost.SOSTClientConfig;
-//import org.sensorhub.impl.driver.dji.DjiConfig;
import org.sensorhub.impl.driver.flir.FlirOneCameraConfig;
import org.sensorhub.impl.module.InMemoryConfigDb;
+import org.sensorhub.impl.module.ModuleRegistry;
+import org.sensorhub.impl.persistence.GenericStreamStorage;
+import org.sensorhub.impl.persistence.MaxAgeAutoPurgeConfig;
+import org.sensorhub.impl.persistence.StreamStorageConfig;
+import org.sensorhub.impl.persistence.h2.MVMultiStorageImpl;
+import org.sensorhub.impl.persistence.h2.MVStorageConfig;
import org.sensorhub.impl.sensor.android.AndroidSensorsConfig;
import org.sensorhub.impl.sensor.android.AndroidSensorsDriver;
+import org.sensorhub.impl.sensor.android.audio.AudioEncoderConfig;
import org.sensorhub.impl.sensor.android.video.VideoEncoderConfig;
import org.sensorhub.impl.sensor.android.video.VideoEncoderConfig.VideoPreset;
import org.sensorhub.impl.sensor.angel.AngelSensorConfig;
import org.sensorhub.impl.sensor.trupulse.TruPulseConfig;
import org.sensorhub.impl.sensor.trupulse.TruPulseWithGeolocConfig;
+import org.sensorhub.impl.service.HttpServerConfig;
+import org.sensorhub.impl.service.sos.SOSService;
+import org.sensorhub.impl.service.sos.SOSServiceConfig;
+import org.sensorhub.impl.service.sos.SensorDataProviderConfig;
import org.sensorhub.test.sensor.trupulse.SimulatedDataStream;
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.preference.PreferenceManager;
-import android.provider.Settings.Secure;
-import android.text.Html;
-import android.widget.EditText;
-import android.widget.TextView;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
@@ -88,6 +114,9 @@
public class MainActivity extends Activity implements TextureView.SurfaceTextureListener, IEventListener
{
+ public static final String ACTION_BROADCAST_RECEIVER = "org.sensorhub.android.BROADCAST_RECEIVER";
+ public static final String ANDROID_SENSORS_MODULE_ID = "ANDROID_SENSORS";
+
TextView mainInfoArea;
TextView videoInfoArea;
SensorHubService boundService;
@@ -101,7 +130,27 @@ public class MainActivity extends Activity implements TextureView.SurfaceTexture
AndroidSensorsDriver androidSensors;
URL sosUrl = null;
boolean showVideo;
-
+
+ String deviceID;
+ String deviceName;
+ String runName;
+
+ // Request codes for permissions
+ final int FINE_LOC_RC = 101;
+ final int CAMERA_RC = 102;
+ final int AUDIO_RC = 103;
+
+ enum Sensors {
+ Android,
+ TruPulse,
+ TruPulseSim,
+ Angel,
+ FlirOne,
+ DJIDrone,
+ ProxySensor,
+ BLELocation
+ }
+
private ServiceConnection sConn = new ServiceConnection()
{
@@ -119,10 +168,11 @@ public void onServiceDisconnected(ComponentName className)
protected void updateConfig(SharedPreferences prefs, String runName)
{
+ deviceID = Secure.getString(getContentResolver(), Secure.ANDROID_ID);
sensorhubConfig = new InMemoryConfigDb();
// get SOS URL from config
- String sosUriConfig = prefs.getString("sos_uri", "");
+ String sosUriConfig = prefs.getString("sos_uri", "http://127.0.0.1:8585");
String sosUser = prefs.getString("sos_username", null);
String sosPwd = prefs.getString("sos_password", null);
if (sosUriConfig != null && sosUriConfig.trim().length() > 0)
@@ -183,6 +233,9 @@ public boolean verify(String arg0, SSLSession arg1) {
sensorsConfig.name = "Android Sensors [" + deviceName + "]";
sensorsConfig.id = "ANDROID_SENSORS";
sensorsConfig.autoStart = true;
+ //TODO: try adding a few options
+// sensorsConfig.
+
sensorsConfig.activateAccelerometer = prefs.getBoolean("accel_enabled", false);
sensorsConfig.activateGyrometer = prefs.getBoolean("gyro_enabled", false);
sensorsConfig.activateMagnetometer = prefs.getBoolean("mag_enabled", false);
@@ -190,8 +243,11 @@ public boolean verify(String arg0, SSLSession arg1) {
sensorsConfig.activateOrientationEuler = prefs.getBoolean("orient_euler_enabled", false);
sensorsConfig.activateGpsLocation = prefs.getBoolean("gps_enabled", false);
sensorsConfig.activateNetworkLocation = prefs.getBoolean("netloc_enabled", false);
- sensorsConfig.activateBackCamera = prefs.getBoolean("cam_enabled", false);
- if (sensorsConfig.activateBackCamera || sensorsConfig.activateFrontCamera)
+ sensorsConfig.enableCamera = prefs.getBoolean("cam_enabled", false);
+ sensorsConfig.selectedCameraId = Integer.parseInt(prefs.getString("camera_select", "0"));
+ /*if (sensorsConfig.activateBackCamera || sensorsConfig.activateFrontCamera)
+ showVideo = true;*/
+ if (sensorsConfig.enableCamera)
showVideo = true;
// video settings
@@ -228,9 +284,67 @@ public boolean verify(String arg0, SSLSession arg1) {
sensorsConfig.videoConfig.presets = presetList.toArray(new VideoPreset[0]);
sensorsConfig.outputVideoRoll = prefs.getBoolean("video_roll_enabled", false);
+
+ // audio
+ sensorsConfig.activateMicAudio = prefs.getBoolean("audio_enabled", false);
+ sensorsConfig.audioConfig.codec = prefs.getString("audio_codec", AudioEncoderConfig.AAC_CODEC);
+ sensorsConfig.audioConfig.sampleRate = Integer.parseInt(prefs.getString("audio_samplerate", "8000"));
+ sensorsConfig.audioConfig.bitRate = Integer.parseInt(prefs.getString("audio_bitrate", "64"));
+
sensorsConfig.runName = runName;
- sensorhubConfig.add(sensorsConfig);
- addSosTConfig(sensorsConfig, sosUser, sosPwd);
+// sensorhubConfig.add(sensorsConfig);
+// addSosTConfig(sensorsConfig, sosUser, sosPwd);
+
+ // START SOS Config ************************************************************************
+// if(prefs.getBoolean("hub_enable", true)) {
+ if(shouldServe(prefs)) {
+ // Setup HTTPServerConfig for enabling more complete node functionality
+ HttpServerConfig serverConfig = new HttpServerConfig();
+ serverConfig.proxyBaseUrl = "";
+ serverConfig.httpPort = 8585;
+ serverConfig.autoStart = true;
+ sensorhubConfig.add(serverConfig);
+ }
+
+ // SOS Config
+// SOSServiceConfig sosConfig = new SOSServiceWithIPCConfig();
+// sosConfig.moduleClass = SOSServiceWithIPC.class.getCanonicalName();
+// ((SOSServiceWithIPCConfig) sosConfig).androidContext = this.getApplicationContext();
+
+ // We don't need android context unless we're doing IPC things
+ SOSServiceConfig sosConfig = new SOSServiceConfig();
+ sosConfig.moduleClass = SOSService.class.getCanonicalName();
+ sosConfig.id = "SOS_SERVICE";
+ sosConfig.name = "SOS Service";
+ sosConfig.autoStart = true;
+ sosConfig.enableTransactional = true;
+
+ // Push Sensors Config
+ AndroidSensorsConfig androidSensorsConfig = sensorsConfig;
+ sensorhubConfig.add(androidSensorsConfig);
+ if (isPushingSensor(Sensors.Android)) {
+ addSosTConfig(androidSensorsConfig, sosUser, sosPwd);
+ }
+// addSosTConfig(sensorsConfig,sosUser,sosPwd);
+
+ //Storage Configuration
+// if(prefs.getBoolean("hub_enable", true) && prefs.getBoolean("hub_enable_local_storage", true)) {
+ if(shouldStore(prefs)) {
+ File dbFile = new File(getApplicationContext().getFilesDir() + "/db/");
+ dbFile.mkdirs();
+ MVStorageConfig basicStorageConfig = new MVStorageConfig();
+ basicStorageConfig.moduleClass = "org.sensorhub.impl.persistence.h2.MVObsStorageImpl";
+ basicStorageConfig.storagePath = dbFile.getAbsolutePath() + "/${STORAGE_ID}.dat";
+ basicStorageConfig.autoStart = true;
+ sosConfig.newStorageConfig = basicStorageConfig;
+
+ StreamStorageConfig androidStreamStorageConfig = createStreamStorageConfig(androidSensorsConfig);
+ addStorageConfig(androidSensorsConfig, androidStreamStorageConfig);
+ }
+
+ SensorDataProviderConfig androidDataProviderConfig = createDataProviderConfig(androidSensorsConfig);
+ addSosServerConfig(sosConfig, androidDataProviderConfig);
+ // END SOS CONFIG **************************************************************************
// TruPulse sensor
boolean enabled = prefs.getBoolean("trupulse_enabled", false);
@@ -326,6 +440,9 @@ public boolean verify(String arg0, SSLSession arg1) {
sensorhubConfig.add(djiConfig);
addSosTConfig(djiConfig, sosUser, sosPwd);
}*/
+
+ // TODO add missing SOS SERVICE config to sensorhub
+ sensorhubConfig.add(sosConfig);
}
@@ -372,6 +489,10 @@ protected void onCreate(Bundle savedInstanceState)
// handler to refresh sensor status in UI
displayHandler = new Handler(Looper.getMainLooper());
+
+ setupBroadcastReceivers();
+
+ checkForPermissions();
}
@@ -418,6 +539,52 @@ else if (id == R.id.action_about)
{
showAboutPopup();
}
+ else if(id == R.id.action_status)
+ {
+ Intent statusIntent = new Intent(this, AppStatusActivity.class);
+ if(boundService.sensorhub != null) {
+ ModuleRegistry moduleRegistry = boundService.sensorhub.getModuleRegistry();
+ Collection modules = moduleRegistry.getAvailableModules();
+
+ for (ModuleConfig moduleConf: modules) {
+ IModule module = null;
+ try {
+ module = moduleRegistry.getModuleById(moduleConf.id);
+ String status = module.getCurrentState().name();
+
+ switch (moduleConf.id){
+ case "HTTP_SERVER_0":
+ statusIntent.putExtra("httpStatus", status);
+ break;
+ case "SOS_SERVICE":
+ statusIntent.putExtra("sosService", status);
+ break;
+ case "ANDROID_SENSORS":
+ statusIntent.putExtra("androidSensorStatus", status);
+ break;
+ case "ANDROID_SENSORS#storage":
+ statusIntent.putExtra("sensorStorageStatus", status);
+ break;
+ }
+
+ } catch (SensorHubException e) {
+ e.printStackTrace();
+ }
+
+ }
+ }else{
+ statusIntent.putExtra("sosService", "N/A");
+ statusIntent.putExtra("httpStatus", "N/A");
+ statusIntent.putExtra("androidSensorStatus", "N/A");
+ statusIntent.putExtra("sensorStorageStatus", "N/A");
+ }
+
+// statusIntent.putExtra("boundService", boundService);
+
+
+ startActivity(statusIntent);
+ return true;
+ }
return super.onOptionsItemSelected(item);
}
@@ -443,22 +610,39 @@ public void onClick(DialogInterface dialog, int whichButton)
{
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
String runName = input.getText().toString();
- newStatusMessage("Starting SensorHub...");
+
updateConfig(PreferenceManager.getDefaultSharedPreferences(MainActivity.this), runName);
AndroidSensorsConfig androidSensorConfig = (AndroidSensorsConfig) sensorhubConfig.get("ANDROID_SENSORS");
VideoEncoderConfig videoConfig = androidSensorConfig.videoConfig;
- if ((androidSensorConfig.activateBackCamera || androidSensorConfig.activateFrontCamera) && videoConfig.selectedPreset < 0 || videoConfig.selectedPreset >= videoConfig.presets.length) {
+ boolean cameraInUse = (androidSensorConfig.activateBackCamera || androidSensorConfig.activateFrontCamera);
+ boolean improperVideoSettings = (videoConfig.selectedPreset < 0 || videoConfig.selectedPreset >= videoConfig.presets.length);
+
+ if (cameraInUse && improperVideoSettings) {
showVideoConfigErrorPopup();
newStatusMessage("Video Config Error: Check Settings");
} else {
+ newStatusMessage("Starting SensorHub...");
sostClients.clear();
boundService.startSensorHub(sensorhubConfig, showVideo, MainActivity.this);
if (boundService.hasVideo())
mainInfoArea.setBackgroundColor(0x80FFFFFF);
+
+ /*SOSServiceCapabilities caps = null;
+ try {
+ GetCapabilitiesRequest getCap = new GetCapabilitiesRequest();
+ getCap.setService(SOSUtils.SOS);
+ getCap.setVersion("V2.0");
+ getCap.setGetServer(PreferenceManager.getDefaultSharedPreferences(MainActivity.this).getString("sos_uri", ""));
+ OWSUtils owsUtils = new OWSUtils();
+ caps = owsUtils.sendRequest(getCap, false);
+ } catch (OWSException e) {
+// throw new SensorHubException("Cannot retrieve SOS capabilities", e);
+ Log.e(TAG, "ERR: Cannot retrieve SOS Capabilities", e);
+ }*/
}
}
@@ -641,6 +825,12 @@ else if (dt > stream.getValue().measPeriodMs)
mainInfoText.setLength(mainInfoText.length()-5); // remove last
mainInfoText.append("
");
+ // Notify we are running when no data is being pushed
+ boolean serveOrStore = shouldServe(PreferenceManager.getDefaultSharedPreferences(MainActivity.this)) || shouldStore(PreferenceManager.getDefaultSharedPreferences(MainActivity.this));
+ if(sostClients.size() == 0 && serveOrStore){
+ mainInfoText.append("No Sensors Set to Push Remotely");
+ }
+
// show video info
if (androidSensors != null && boundService.hasVideo())
{
@@ -716,6 +906,492 @@ protected void hideVideo()
{
}
+ private boolean isPushingSensor(Sensors sensor) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
+
+ if (Sensors.Android.equals(sensor)) {
+ if (prefs.getBoolean("accel_enabled", false)
+ && prefs.getStringSet("accel_options", Collections.emptySet()).contains("PUSH_REMOTE"))
+ return true;
+ if (prefs.getBoolean("gyro_enabled", false)
+ && prefs.getStringSet("gyro_options", Collections.emptySet()).contains("PUSH_REMOTE"))
+ return true;
+ if (prefs.getBoolean("mag_enabled", false)
+ && prefs.getStringSet("mag_options", Collections.emptySet()).contains("PUSH_REMOTE"))
+ return true;
+ if (prefs.getBoolean("orient_quat_enabled", false)
+ && prefs.getStringSet("orient_quat_options", Collections.emptySet()).contains("PUSH_REMOTE"))
+ return true;
+ if (prefs.getBoolean("orient_euler_enabled", false)
+ && prefs.getStringSet("orient_euler_options", Collections.emptySet()).contains("PUSH_REMOTE"))
+ return true;
+ if (prefs.getBoolean("gps_enabled", false)
+ && prefs.getStringSet("gps_options", Collections.emptySet()).contains("PUSH_REMOTE"))
+ return true;
+ if (prefs.getBoolean("netloc_enabled", false)
+ && prefs.getStringSet("netloc_options", Collections.emptySet()).contains("PUSH_REMOTE"))
+ return true;
+ if (prefs.getBoolean("cam_enabled", false)
+ && prefs.getStringSet("cam_options", Collections.emptySet()).contains("PUSH_REMOTE"))
+ return true;
+ if(prefs.getBoolean("audio_enabled", false)
+ && prefs.getStringSet("audio_options", Collections.emptySet()).contains("PUSH_REMOTE"))
+ return true;
+ } else if (Sensors.TruPulse.equals(sensor) || Sensors.TruPulseSim.equals(sensor)) {
+ return prefs.getBoolean("trupulse_enabled", false)
+ && prefs.getStringSet("trupulse_options", Collections.emptySet()).contains("PUSH_REMOTE");
+ } else if(Sensors.BLELocation.equals(sensor)){
+ return prefs.getBoolean("ble_enable", false) && prefs.getStringSet("ble_options", Collections.emptySet()).contains("PUSH_REMOTE");
+ }
+
+ return false;
+ }
+
+ private SensorDataProviderConfig createDataProviderConfig(AndroidSensorsConfig sensorConfig) {
+ Context androidContext = SensorHubService.getContext();
+ SensorDataProviderConfig dataProviderConfig = new SensorDataProviderConfig();
+ dataProviderConfig.sensorID = sensorConfig.id;
+ dataProviderConfig.offeringID = "urn:android:device:" + Secure.getString(androidContext.getContentResolver(), Secure.ANDROID_ID);
+ dataProviderConfig.storageID = sensorConfig.id + "#storage";
+ dataProviderConfig.enabled = true;
+ dataProviderConfig.liveDataTimeout = 600.0;
+ dataProviderConfig.maxFois = 10;
+
+ return dataProviderConfig;
+ }
+
+ private StreamStorageConfig createStreamStorageConfig(AndroidSensorsConfig sensorConfig) {
+ // H2 Storage Config
+ File dbFile = new File(getApplicationContext().getFilesDir() + "/db/", deviceID + "_h2.dat");
+ dbFile.getParentFile().mkdirs();
+ if (!dbFile.exists()) {
+ try {
+ dbFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ MVStorageConfig storageConfig = new MVStorageConfig();
+ storageConfig.moduleClass = MVMultiStorageImpl.class.getCanonicalName();
+ storageConfig.storagePath = dbFile.getPath();
+ storageConfig.autoStart = true;
+ storageConfig.memoryCacheSize = 102400;
+ storageConfig.autoCommitBufferSize = 1024;
+
+ // TODO: Base this on size instead of time. This might error when earliest record is purged and then requested. Test if the capabilities updates...
+ // Auto Purge Config
+ MaxAgeAutoPurgeConfig autoPurgeConfig = new MaxAgeAutoPurgeConfig();
+ autoPurgeConfig.enabled = true;
+ autoPurgeConfig.purgePeriod = 24.0 * 60.0 * 60.0;
+ autoPurgeConfig.maxRecordAge = 24.0 * 60.0 * 60.0;
+
+ // Stream Storage Config
+ StreamStorageConfig streamStorageConfig = new StreamStorageConfig();
+ streamStorageConfig.moduleClass = GenericStreamStorage.class.getCanonicalName();
+ streamStorageConfig.id = sensorConfig.id + "#storage";
+ streamStorageConfig.name = sensorConfig.name + " Storage";
+ streamStorageConfig.dataSourceID = sensorConfig.id;
+ streamStorageConfig.autoStart = true;
+ streamStorageConfig.processEvents = true;
+ streamStorageConfig.minCommitPeriod = 10000;
+ streamStorageConfig.autoPurgeConfig = autoPurgeConfig;
+ streamStorageConfig.storageConfig = storageConfig;
+ return streamStorageConfig;
+ }
+
+ protected void addStorageConfig(SensorConfig sensorConf, StreamStorageConfig storageConf) {
+ if (sensorConf instanceof AndroidSensorsConfig) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
+ SensorManager sensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
+ List deviceSensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
+
+ String sensorName;
+ for (Sensor sensor : deviceSensors) {
+ if (sensor.isWakeUpSensor()) {
+ continue;
+ }
+
+ Log.d(TAG, "addStorageConfig: sensor: " + sensor.getName());
+
+ switch (sensor.getType()) {
+ case Sensor.TYPE_ACCELEROMETER:
+ if (!prefs.getBoolean("accel_enable", false)
+ || !prefs.getStringSet("accel_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+
+ Log.d(TAG, "addStorageConfig: excluding accelerometer");
+
+ sensorName = sensor.getName().replaceAll(" ", "_") + "_data";
+ storageConf.excludedOutputs.add(sensorName);
+ } else {
+ Log.d(TAG, "addStorageConfig: NOT excluding accelerometer");
+ }
+ break;
+ case Sensor.TYPE_GYROSCOPE:
+ if (!prefs.getBoolean("gyro_enable", false)
+ || !prefs.getStringSet("gyro_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+
+ Log.d(TAG, "addStorageConfig: excluding gyroscope");
+
+ sensorName = sensor.getName().replaceAll(" ", "_") + "_data";
+ storageConf.excludedOutputs.add(sensorName);
+ } else {
+ Log.d(TAG, "addStorageConfig: NOT excluding gyroscope");
+ }
+ break;
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ if (!prefs.getBoolean("mag_enable", false)
+ || !prefs.getStringSet("mag_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+
+ Log.d(TAG, "addStorageConfig: excluding magnetometer");
+
+ sensorName = sensor.getName().replaceAll(" ", "_") + "_data";
+ storageConf.excludedOutputs.add(sensorName);
+ } else {
+ Log.d(TAG, "addStorageConfig: NOT excluding magnetometer");
+ }
+ break;
+ case Sensor.TYPE_ROTATION_VECTOR:
+ // TODO: double check, this probably will have an issue when both are checked
+ if (!prefs.getBoolean("orient_quat_enabled", false)
+ || !prefs.getStringSet("orient_quat_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+
+ Log.d(TAG, "addStorageConfig: excluding orientation");
+ sensorName = sensor.getName().replaceAll(" ", "_") + "_data";
+ storageConf.excludedOutputs.add(sensorName);
+ sensorName = "quat_orientation_data";
+ storageConf.excludedOutputs.add(sensorName);
+
+ } else if(!prefs.getBoolean("orient_euler_enabled", false)
+ || !prefs.getStringSet("orient_euler_options", Collections.emptySet()).contains("STORE_LOCAL")){
+
+ Log.d(TAG, "addStorageConfig: excluding orientation");
+ sensorName = sensor.getName().replaceAll(" ", "_") + "_data";
+ storageConf.excludedOutputs.add(sensorName);
+ sensorName = "euler_orientation_data";
+ storageConf.excludedOutputs.add(sensorName);
+
+ } else {
+ Log.d(TAG, "addStorageConfig: NOT excluding orientation");
+ }
+ break;
+ }
+ }
+ if (!prefs.getBoolean("gps_enabled", false)
+ || !prefs.getStringSet("gps_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+
+ Log.d(TAG, "addStorageConfig: excluding gps location");
+
+ sensorName = "gps_data";
+ storageConf.excludedOutputs.add(sensorName);
+
+ } else {
+ Log.d(TAG, "addStorageConfig: NOT excluding gps location");
+ }
+ if (!prefs.getBoolean("netloc_enabled", false)
+ || !prefs.getStringSet("netloc_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+
+ Log.d(TAG, "addStorageConfig: excluding network location");
+
+ sensorName = "network_data";
+ storageConf.excludedOutputs.add(sensorName);
+ } else {
+ Log.d(TAG, "addStorageConfig: NOT excluding network location");
+ }
+ if (!prefs.getBoolean("cam_enabled", false)
+ || !prefs.getStringSet("cam_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+
+ Log.d(TAG, "addStorageConfig: excluding video");
+
+ // TODO: we need a better way of adding these to storage
+ sensorName = "camera0_MJPEG";
+ storageConf.excludedOutputs.add(sensorName);
+ sensorName = "camera0_H264";
+ storageConf.excludedOutputs.add(sensorName);
+ } else {
+ Log.d(TAG, "addStorageConfig: NOT excluding video");
+ }
+ if(!prefs.getBoolean("audio_enabled", false)
+ || !prefs.getStringSet("audio_options", Collections.emptySet()).contains("STORE_LOCAL")){
+ sensorName = "audio_aac";
+ storageConf.excludedOutputs.add(sensorName);
+ }else{
+ Log.d(TAG, "addStorageConfig: NOT excluding audio");
+ }
+ }
+
+ sensorhubConfig.add(storageConf);
+ }
+
+ protected void addSosServerConfig(SOSServiceConfig sosConf, SensorDataProviderConfig dataProviderConf) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
+ SensorManager sensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
+ List deviceSensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
+
+ String sensorName;
+ for (Sensor sensor : deviceSensors) {
+ if (sensor.isWakeUpSensor()) {
+ continue;
+ }
+
+ Log.d(TAG, "addSosServerConfig: sensor: " + sensor.getName());
+
+ switch (sensor.getType()) {
+ case Sensor.TYPE_ACCELEROMETER:
+ if (!prefs.getBoolean("accel_enable", false)
+ || !prefs.getStringSet("accel_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+
+ Log.d(TAG, "addSosServerConfig: excluding accelerometer");
+
+ sensorName = sensor.getName().replaceAll(" ", "_") + "_data";
+ dataProviderConf.excludedOutputs.add(sensorName);
+ } else {
+ Log.d(TAG, "addSosServerConfig: NOT excluding accelerometer");
+ }
+ break;
+ case Sensor.TYPE_GYROSCOPE:
+ if (!prefs.getBoolean("gyro_enable", false)
+ || !prefs.getStringSet("gyro_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+
+ Log.d(TAG, "addSosServerConfig: excluding gyroscope");
+
+ sensorName = sensor.getName().replaceAll(" ", "_") + "_data";
+ dataProviderConf.excludedOutputs.add(sensorName);
+ } else {
+ Log.d(TAG, "addSosServerConfig: NOT excluding gyroscope");
+ }
+ break;
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ if (!prefs.getBoolean("mag_enable", false)
+ || !prefs.getStringSet("mag_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+
+ Log.d(TAG, "addSosServerConfig: excluding magnetometer");
+
+ sensorName = sensor.getName().replaceAll(" ", "_") + "_data";
+ dataProviderConf.excludedOutputs.add(sensorName);
+ } else {
+ Log.d(TAG, "addSosServerConfig: NOT excluding magnetometer");
+ }
+ break;
+ case Sensor.TYPE_ROTATION_VECTOR:
+ if (!prefs.getBoolean("orient_quat_enabled", false)
+ || !prefs.getStringSet("orient_quat_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+
+ Log.d(TAG, "addSosServerConfig: excluding orientation");
+ sensorName = sensor.getName().replaceAll(" ", "_") + "_data";
+ dataProviderConf.excludedOutputs.add(sensorName);
+ sensorName = "quat_orientation_data";
+ dataProviderConf.excludedOutputs.add(sensorName);
+
+ } else if(!prefs.getBoolean("orient_euler_enabled", false)
+ || !prefs.getStringSet("orient_euler_options", Collections.emptySet()).contains("STORE_LOCAL")){
+
+ Log.d(TAG, "addSosServerConfig: excluding orientation");
+ sensorName = sensor.getName().replaceAll(" ", "_") + "_data";
+ dataProviderConf.excludedOutputs.add(sensorName);
+ sensorName = "euler_orientation_data";
+ dataProviderConf.excludedOutputs.add(sensorName);
+
+ } else {
+ Log.d(TAG, "addSosServerConfig: NOT excluding orientation");
+ }
+ break;
+ }
+ }
+ if (!prefs.getBoolean("gps_enabled", false)
+ || !prefs.getStringSet("gps_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+
+ Log.d(TAG, "addSosServerConfig: excluding gps location");
+
+ sensorName = "gps_data";
+ dataProviderConf.excludedOutputs.add(sensorName);
+
+ } else {
+ Log.d(TAG, "addSosServerConfig: NOT excluding gps location");
+ }
+ if (!prefs.getBoolean("netloc_enabled", false)
+ || !prefs.getStringSet("netloc_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+
+ Log.d(TAG, "addSosServerConfig: excluding network location");
+
+ sensorName = "network_data";
+ dataProviderConf.excludedOutputs.add(sensorName);
+ } else {
+ Log.d(TAG, "addSosServerConfig: NOT excluding network location");
+ }
+ if (!prefs.getBoolean("cam_enabled", false)
+ || !prefs.getStringSet("cam_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+
+ Log.d(TAG, "addSosServerConfig: excluding video");
+
+ sensorName = "camera0_MJPEG";
+ dataProviderConf.excludedOutputs.add(sensorName);
+ sensorName = "camera0_H264";
+ dataProviderConf.excludedOutputs.add(sensorName);
+ } else {
+ Log.d(TAG, "addSosServerConfig: NOT excluding video");
+ }
+ if(!prefs.getBoolean("audio_enabled", false)
+ || !prefs.getStringSet("audio_options", Collections.emptySet()).contains("STORE_LOCAL")){
+ sensorName = "audio_aac";
+ dataProviderConf.excludedOutputs.add(sensorName);
+ }else{
+ Log.d(TAG, "addSosServerConfig: NOT excluding audio");
+ }
+ // Add BLE back in
+// if (!prefs.getBoolean("ble_enable", false)
+// || !prefs.getStringSet("ble_location_options", Collections.emptySet()).contains("STORE_LOCAL")) {
+// sensorName = "BLEBeacon";
+// dataProviderConf.excludedOutputs.add(sensorName);
+// sensorName = "BLEBeaconLocation";
+// dataProviderConf.excludedOutputs.add(sensorName);
+// sensorName = "NearestBeacon";
+// dataProviderConf.excludedOutputs.add(sensorName);
+// }
+
+ sosConf.dataProviders.add(dataProviderConf);
+ }
+
+ private SensorConfig createSensorConfig(Sensors sensor) {
+ SensorConfig sensorConfig;
+
+ if (Sensors.Android.equals(sensor)) {
+ sensorConfig = new AndroidSensorsConfig();
+ sensorConfig.id = ANDROID_SENSORS_MODULE_ID;
+ sensorConfig.name = "Android Sensors [" + deviceName + "]";
+ sensorConfig.autoStart = true;
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
+
+ ((AndroidSensorsConfig) sensorConfig).activateAccelerometer = prefs.getBoolean("accelerometer_enable", false);
+ ((AndroidSensorsConfig) sensorConfig).activateGyrometer = prefs.getBoolean("gyroscope_enable", false);
+ ((AndroidSensorsConfig) sensorConfig).activateMagnetometer = prefs.getBoolean("magnetometer_enable", false);
+ if (prefs.getBoolean("orientation_enable", false)) {
+ ((AndroidSensorsConfig) sensorConfig).activateOrientationQuat = prefs.getStringSet("orientation_angles", Collections.emptySet()).contains("QUATERNION");
+ ((AndroidSensorsConfig) sensorConfig).activateOrientationEuler = prefs.getStringSet("orientation_angles", Collections.emptySet()).contains("EULER");
+ }
+ if (prefs.getBoolean("location_enable", false)) {
+ ((AndroidSensorsConfig) sensorConfig).activateGpsLocation = prefs.getStringSet("location_type", Collections.emptySet()).contains("GPS");
+ ((AndroidSensorsConfig) sensorConfig).activateNetworkLocation = prefs.getStringSet("location_type", Collections.emptySet()).contains("NETWORK");
+ }
+// if (prefs.getBoolean("video_enable", false)) {
+// showVideo = true;
+//
+// ((AndroidSensorsConfig) sensorConfig).activateBackCamera = true;
+// ((AndroidSensorsConfig) sensorConfig).videoCodec = prefs.getString("video_codec", AndroidSensorsConfig.JPEG_CODEC);
+// }
+//
+// ((AndroidSensorsConfig) sensorConfig).androidContext = this.getApplicationContext();
+// ((AndroidSensorsConfig) sensorConfig).camPreviewTexture = boundService.getVideoTexture();
+// ((AndroidSensorsConfig) sensorConfig).runName = runName;
+ }/* else if (Sensors.TruPulse.equals(sensor)) {
+ sensorConfig = createTruPulseConfig();
+ sensorConfig.id = "TRUPULSE_SENSOR";
+ sensorConfig.name = "TruPulse Range Finder [" + deviceName + "]";
+ sensorConfig.autoStart = true;
+
+ BluetoothCommProviderConfig btConf = new BluetoothCommProviderConfig();
+ btConf.protocol.deviceName = "TP360RB.*";
+ btConf.moduleClass = BluetoothCommProvider.class.getCanonicalName();
+ ((TruPulseConfig) sensorConfig).commSettings = btConf;
+ ((TruPulseConfig) sensorConfig).serialNumber = deviceID;
+ } else if (Sensors.TruPulseSim.equals(sensor)) {
+ sensorConfig = createTruPulseConfig();
+ sensorConfig.id = "TRUPULSE_SENSOR_SIMULATED";
+ sensorConfig.name = "Simulated TruPulse Range Finder [" + deviceName + "]";
+ sensorConfig.autoStart = true;
+
+ BluetoothCommProviderConfig btConf = new BluetoothCommProviderConfig();
+ btConf.protocol.deviceName = "TP360RB.*";
+ btConf.moduleClass = SimulatedDataStream.class.getCanonicalName();
+ ((TruPulseConfig) sensorConfig).commSettings = btConf;
+ ((TruPulseConfig) sensorConfig).serialNumber = deviceID;
+ } else if (Sensors.Angel.equals(sensor)) {
+ sensorConfig = new AngelSensorConfig();
+ sensorConfig.id = "ANGEL_SENSOR";
+ sensorConfig.name = "Angel Sensor [" + deviceName + "]";
+ sensorConfig.autoStart = true;
+
+ BleConfig bleConf = new BleConfig();
+ bleConf.id = "BLE";
+ bleConf.moduleClass = BleNetwork.class.getCanonicalName();
+ bleConf.androidContext = this.getApplicationContext();
+ bleConf.autoStart = true;
+ sensorhubConfig.add(bleConf);
+
+ ((AngelSensorConfig) sensorConfig).networkID = bleConf.id;
+ } else if (Sensors.FlirOne.equals(sensor)) {
+ sensorConfig = new FlirOneCameraConfig();
+ sensorConfig.id = "FLIRONE_SENSOR";
+ sensorConfig.name = "FLIR One Camera [" + deviceName + "]";
+ sensorConfig.autoStart = true;
+
+ ((FlirOneCameraConfig) sensorConfig).androidContext = this.getApplicationContext();
+ ((FlirOneCameraConfig) sensorConfig).camPreviewTexture = boundService.getVideoTexture();
+ } else if (Sensors.ProxySensor.equals(sensor)) {
+ sensorConfig = new ProxySensorConfig();
+ } else if(Sensors.BLELocation.equals(sensor)){
+ sensorConfig = new BLEBeaconConfig();
+ sensorConfig.id = "BLE_BEACON_SCANNER";
+ sensorConfig.name = "BLE Scanner [" + deviceName + "]";
+ sensorConfig.autoStart = true;
+ }*/else {
+ sensorConfig = new SensorConfig();
+ }
+
+ return sensorConfig;
+ }
+
+
+ private void setupBroadcastReceivers() {
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String origin = intent.getStringExtra("src");
+ if (!context.getPackageName().equalsIgnoreCase(origin)) {
+ String sosEndpointUrl = intent.getStringExtra("sosEndpointUrl");
+ String name = intent.getStringExtra("name");
+ String sensorId = intent.getStringExtra("sensorId");
+ ArrayList properties = intent.getStringArrayListExtra("properties");
+
+ if (sosEndpointUrl == null || name == null || sensorId == null || properties.size() == 0) {
+ return;
+ }
+
+ /* ProxySensorConfig proxySensorConfig = (ProxySensorConfig) createSensorConfig(Sensors.ProxySensor);
+ proxySensorConfig.androidContext = getApplicationContext();
+ proxySensorConfig.sosEndpointUrl = sosEndpointUrl;
+ proxySensorConfig.name = name;
+ proxySensorConfig.id = sensorId;
+ proxySensorConfig.sensorUID = sensorId;
+ proxySensorConfig.observedProperties.addAll(properties);
+ proxySensorConfig.sosUseWebsockets = true;
+ proxySensorConfig.autoStart = true;
+ proxySensorConfigs.add(proxySensorConfig);*/
+
+ // register and "start" new sensor, data stream doesn't begin until someone requests data;
+ try {
+ boundService.stopSensorHub();
+ Thread.sleep(2000);
+ Log.d("OSHApp", "Starting Sensorhub Again");
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ updateConfig(PreferenceManager.getDefaultSharedPreferences(MainActivity.this), runName);
+ sostClients.clear();
+ boundService.startSensorHub(sensorhubConfig, showVideo, MainActivity.this);
+ if (boundService.hasVideo())
+ mainInfoArea.setBackgroundColor(0x80FFFFFF);
+ } catch (InterruptedException e) {
+ Log.e("OSHApp", "Error Loading Proxy Sensor", e);
+ }
+
+ }
+ }
+ };
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_BROADCAST_RECEIVER);
+
+ registerReceiver(receiver, filter);
+ }
+
@Override
protected void onStart()
@@ -794,4 +1470,50 @@ public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture)
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture)
{
}
+
+ private boolean shouldServe(SharedPreferences prefs){
+ Map prefMap = prefs.getAll();
+ for(Map.Entry pref : prefMap.entrySet()){
+ if(pref.getValue() instanceof HashSet) {
+ if(((HashSet) pref.getValue()).contains("FETCH_LOCAL")) {
+ Log.d(TAG, "shouldServe: TRUE");
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean shouldStore(SharedPreferences prefs){
+ Map prefMap = prefs.getAll();
+ for(Map.Entry pref : prefMap.entrySet()){
+ if(pref.getValue() instanceof HashSet) {
+ if(((HashSet) pref.getValue()).contains("STORE_LOCAL")) {
+ Log.d(TAG, "shouldStore: TRUE");
+ return true;}
+ }
+ }
+ return false;
+ }
+
+ private void checkForPermissions(){
+ List permissions = new ArrayList<>();
+
+ //Check for necessary permissions
+ if(checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED){
+ permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
+ }
+ if(checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED){
+ permissions.add(Manifest.permission.CAMERA);
+ }
+ if(checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_DENIED){
+ permissions.add(Manifest.permission.RECORD_AUDIO);
+ }
+ // Does app actually need storage permissions now?
+ String[] permARR = new String[permissions.size()];
+ permARR = permissions.toArray(permARR);
+ if(permARR.length >0) {
+ requestPermissions(permARR, 100);
+ }
+ }
}
diff --git a/sensorhub-android-app/src/org/sensorhub/android/SOSServiceWithIPC.java b/sensorhub-android-app/src/org/sensorhub/android/SOSServiceWithIPC.java
new file mode 100644
index 00000000..a471ed1c
--- /dev/null
+++ b/sensorhub-android-app/src/org/sensorhub/android/SOSServiceWithIPC.java
@@ -0,0 +1,98 @@
+package org.sensorhub.android;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import org.sensorhub.api.common.SensorHubException;
+import org.sensorhub.impl.service.sos.SOSService;
+import org.vast.ows.OWSException;
+import org.vast.ows.OWSRequest;
+import org.vast.ows.OWSUtils;
+import org.vast.xml.DOMHelper;
+import org.vast.xml.DOMHelperException;
+import org.w3c.dom.Element;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class SOSServiceWithIPC extends SOSService
+{
+ public static final String SQAN_TEST = "SA";
+ private static final String SQAN_EXTRA = "channel";
+ public static final String ACTION_SOS = "org.sofwerx.ogc.ACTION_SOS";
+ private static final String EXTRA_PAYLOAD = "SOS";
+ private static final String EXTRA_ORIGIN = "src";
+ private Context androidContext;
+
+ @Override
+ public void start() throws SensorHubException
+ {
+ super.start();
+ androidContext = ((SOSServiceWithIPCConfig) config).androidContext;
+
+ BroadcastReceiver receiver = new BroadcastReceiver()
+ {
+ @Override
+ public void onReceive(Context context, Intent intent)
+ {
+ String origin = intent.getStringExtra(EXTRA_ORIGIN);
+ if (!context.getPackageName().equalsIgnoreCase(origin))
+ {
+ String requestPayload = intent.getStringExtra(EXTRA_PAYLOAD);
+ handleIPCRequest(requestPayload);
+ }
+ }
+ };
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_SOS);
+
+ androidContext.registerReceiver(receiver, filter);
+ }
+
+ private void handleIPCRequest(String body)
+ {
+ OWSUtils owsUtils = new OWSUtils();
+ ByteArrayInputStream is = new ByteArrayInputStream(body.getBytes());
+
+ try {
+ DOMHelper dom = new DOMHelper(is, false);
+ Element requestElt = dom.getBaseElement();
+ OWSRequest request = owsUtils.readXMLQuery(dom, requestElt);
+
+ ByteArrayOutputStream responseStream = new ByteArrayOutputStream();
+ request.setResponseStream(responseStream);
+ servlet.handleRequest(request);
+
+ /**
+ * request are small usually, but responses can be really large. There is a limit to the size of response
+ */
+ String responsePayload = responseStream.toString();
+ Intent responseIntent = new Intent();
+ responseIntent.setAction(ACTION_SOS);
+ responseIntent.putExtra(EXTRA_ORIGIN, androidContext.getPackageName());
+ responseIntent.putExtra(EXTRA_PAYLOAD, responsePayload);
+
+ androidContext.sendBroadcast(responseIntent);
+ }
+ catch (DOMHelperException e)
+ {
+ e.printStackTrace();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ catch (OWSException e)
+ {
+ e.printStackTrace();
+ }
+ // OGCException e
+ /**
+ * TODO: Look how server is handling the this exception
+ */
+ }
+}
+
diff --git a/sensorhub-android-app/src/org/sensorhub/android/SOSServiceWithIPCConfig.java b/sensorhub-android-app/src/org/sensorhub/android/SOSServiceWithIPCConfig.java
new file mode 100644
index 00000000..a9701650
--- /dev/null
+++ b/sensorhub-android-app/src/org/sensorhub/android/SOSServiceWithIPCConfig.java
@@ -0,0 +1,22 @@
+package org.sensorhub.android;
+
+import android.content.Context;
+
+import org.sensorhub.api.module.ModuleConfig;
+import org.sensorhub.impl.service.sos.SOSServiceConfig;
+
+public class SOSServiceWithIPCConfig extends SOSServiceConfig
+{
+ public transient Context androidContext;
+
+ public SOSServiceWithIPCConfig()
+ {
+ super();
+ }
+
+ @Override
+ public ModuleConfig clone()
+ {
+ return this; // disable clone for now as it crashes Android app
+ }
+}
diff --git a/sensorhub-android-app/src/org/sensorhub/android/UserSettingsActivity.java b/sensorhub-android-app/src/org/sensorhub/android/UserSettingsActivity.java
index e54e97e3..df727981 100644
--- a/sensorhub-android-app/src/org/sensorhub/android/UserSettingsActivity.java
+++ b/sensorhub-android-app/src/org/sensorhub/android/UserSettingsActivity.java
@@ -17,7 +17,9 @@
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.DialogInterface;
+import android.content.SharedPreferences;
import android.hardware.Camera;
+import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.EditTextPreference;
@@ -28,14 +30,24 @@
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
+import android.support.v7.view.menu.ListMenuPresenter;
import android.text.InputType;
+import android.text.PrecomputedText;
+import android.util.Log;
import android.widget.BaseAdapter;
import org.sensorhub.impl.sensor.android.video.VideoEncoderConfig;
+import java.math.BigInteger;
+import java.net.InetAddress;
import java.net.URL;
+import java.net.UnknownHostException;
+import java.nio.ByteOrder;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
import java.util.List;
+import java.util.prefs.Preferences;
public class UserSettingsActivity extends PreferenceActivity
@@ -152,6 +164,26 @@ public void onCreate(Bundle savedInstanceState)
bindPreferenceSummaryToValue(findPreference("device_name"));
bindPreferenceSummaryToValue(findPreference("sos_uri"));
bindPreferenceSummaryToValue(findPreference("sos_username"));
+
+ WifiManager wifiManager = (WifiManager) getActivity().getApplicationContext().getSystemService(WIFI_SERVICE);
+ int ipAddress = wifiManager.getConnectionInfo().getIpAddress();
+
+ // Convert little-endian to big-endianif needed
+ if (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) {
+ ipAddress = Integer.reverseBytes(ipAddress);
+ }
+
+ byte[] ipByteArray = BigInteger.valueOf(ipAddress).toByteArray();
+
+ String ipAddressString;
+ try {
+ ipAddressString = InetAddress.getByAddress(ipByteArray).getHostAddress();
+ } catch (UnknownHostException ex) {
+ ipAddressString = "Unable to get IP Address";
+ }
+
+ Preference ipAddressLabel = getPreferenceScreen().findPreference("nop_ipAddress");
+ ipAddressLabel.setSummary(ipAddressString);
}
}
@@ -168,6 +200,115 @@ public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_sensors);
bindPreferenceSummaryToValue(findPreference("angel_address"));
+
+ SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+
+ Preference accelerometerEnable = getPreferenceScreen().findPreference("accel_enabled");
+ Preference accelerometerOptions = getPreferenceScreen().findPreference("accel_options");
+ accelerometerOptions.setEnabled(prefs.getBoolean(accelerometerEnable.getKey(), false));
+ accelerometerEnable.setOnPreferenceChangeListener((preference, newValue) -> {
+ accelerometerOptions.setEnabled((boolean) newValue);
+ return true;
+ });
+
+ Preference gyroEnabled = getPreferenceScreen().findPreference("gyro_enabled");
+ Preference gyroOptions = getPreferenceScreen().findPreference("gyro_options");
+ gyroOptions.setEnabled(prefs.getBoolean(gyroEnabled.getKey(), false));
+ gyroEnabled.setOnPreferenceChangeListener((preference, newValue) -> {
+ gyroOptions.setEnabled((boolean) newValue);
+ return true;
+ });
+
+ Preference magEnabled = getPreferenceScreen().findPreference("mag_enabled");
+ Preference magOptions = getPreferenceScreen().findPreference("mag_options");
+ magOptions.setEnabled(prefs.getBoolean(magEnabled.getKey(), false));
+ magEnabled.setOnPreferenceChangeListener((preference, newValue) -> {
+ magOptions.setEnabled((boolean) newValue);
+ return true;
+ });
+
+ Preference orientQuatEnabled = getPreferenceScreen().findPreference("orient_quat_enabled");
+ Preference orientQuatOptions = getPreferenceScreen().findPreference("orient_quat_options");
+ orientQuatOptions.setEnabled(prefs.getBoolean(orientQuatEnabled.getKey(), false));
+ orientQuatEnabled.setOnPreferenceChangeListener((preference, newValue) -> {
+ orientQuatOptions.setEnabled((boolean) newValue);
+ return true;
+ });
+
+ Preference orientEulerEnabled = getPreferenceScreen().findPreference("orient_euler_enabled");
+ Preference orientEulerOptions = getPreferenceScreen().findPreference("orient_euler_options");
+ orientEulerOptions.setEnabled(prefs.getBoolean(orientEulerEnabled.getKey(), false));
+ orientEulerEnabled.setOnPreferenceChangeListener((preference, newValue) -> {
+ orientEulerOptions.setEnabled((boolean) newValue);
+ return true;
+ });
+
+ Preference gpsEnabled = getPreferenceScreen().findPreference("gps_enabled");
+ Preference gpsOptions = getPreferenceScreen().findPreference("gps_options");
+ gpsOptions.setEnabled(prefs.getBoolean(gpsEnabled.getKey(), false));
+ gpsEnabled.setOnPreferenceChangeListener((preference, newValue) -> {
+ gpsOptions.setEnabled((boolean) newValue);
+ return true;
+ });
+
+ Preference netlocEnabled = getPreferenceScreen().findPreference("netloc_enabled");
+ Preference netlocOptions = getPreferenceScreen().findPreference("netloc_options");
+ netlocOptions.setEnabled(prefs.getBoolean(netlocEnabled.getKey(), false));
+ netlocEnabled.setOnPreferenceChangeListener((preference, newValue) -> {
+ netlocOptions.setEnabled((boolean) newValue);
+ return true;
+ });
+
+ Preference camEnabled = getPreferenceScreen().findPreference("cam_enabled");
+ Preference camOptions = getPreferenceScreen().findPreference("cam_options");
+ camOptions.setEnabled(prefs.getBoolean(camEnabled.getKey(), false));
+ camEnabled.setOnPreferenceChangeListener((preference, newValue) -> {
+ camOptions.setEnabled((boolean) newValue);
+ return true;
+ });
+
+ Preference videoRollEnabled = getPreferenceScreen().findPreference("video_roll_enabled");
+ Preference videoRollOptions = getPreferenceScreen().findPreference("video_roll_options");
+ videoRollOptions.setEnabled(prefs.getBoolean(videoRollEnabled.getKey(), false));
+ videoRollEnabled.setOnPreferenceChangeListener((preference, newValue) -> {
+ videoRollOptions.setEnabled((boolean) newValue);
+ return true;
+ });
+
+ Preference audioEnabled = getPreferenceScreen().findPreference("audio_enabled");
+ Preference audioOptions = getPreferenceScreen().findPreference("audio_options");
+ camOptions.setEnabled(prefs.getBoolean(camEnabled.getKey(), false));
+ audioEnabled.setOnPreferenceChangeListener((preference, newValue) -> {
+ audioOptions.setEnabled((boolean) newValue);
+ return true;
+ });
+
+ Preference trupulseEnabled = getPreferenceScreen().findPreference("trupulse_enabled");
+ Preference trupulseOptions = getPreferenceScreen().findPreference("trupulse_options");
+ Preference trupulseDatasource = getPreferenceScreen().findPreference("trupulse_datasource");
+ trupulseOptions.setEnabled(prefs.getBoolean(trupulseEnabled.getKey(), false));
+ trupulseDatasource.setEnabled(prefs.getBoolean(trupulseEnabled.getKey(), false));
+ trupulseEnabled.setOnPreferenceChangeListener((preference, newValue) -> {
+ trupulseOptions.setEnabled((boolean) newValue);
+ trupulseDatasource.setEnabled((boolean) newValue);
+ return true;
+ });
+
+// Preference bleEnable = getPreferenceScreen().findPreference("ble_enabled");
+// Preference bleLocationMethod = getPreferenceScreen().findPreference("ble_loc_method");
+// Preference bleOptions = getPreferenceScreen().findPreference("ble_options");
+// Preference bleConfigURL = getPreferenceScreen().findPreference("ble_config_url");
+// bleLocationMethod.setEnabled(prefs.getBoolean(bleEnable.getKey(), false));
+// bleOptions.setEnabled((prefs.getBoolean(bleEnable.getKey(), false)));
+// bleConfigURL.setEnabled((prefs.getBoolean(bleEnable.getKey(), false)));
+// bleEnable.setOnPreferenceChangeListener(((preference, newValue) -> {
+// bleLocationMethod.setEnabled((boolean) newValue);
+// bleOptions.setEnabled((boolean) newValue);
+// bleConfigURL.setEnabled((boolean) newValue);
+// return true;
+// }));
+
+ // TODO: introduce FLIR and ANGEL sensors
}
}
@@ -178,6 +319,9 @@ public void onCreate(Bundle savedInstanceState)
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class VideoPreferenceFragment extends PreferenceFragment
{
+ ArrayList frameRateList = new ArrayList<>();
+ ArrayList resList = new ArrayList<>();
+
@Override
public void onCreate(Bundle savedInstanceState)
{
@@ -185,15 +329,34 @@ public void onCreate(Bundle savedInstanceState)
addPreferencesFromResource(R.xml.pref_video);
PreferenceScreen videoOptsScreen = getPreferenceScreen();
+
+ // Create camera selection preference
+ ArrayList cameras = new ArrayList<>();
+ for(int i = 0; i < Camera.getNumberOfCameras(); i++)
+ {
+ Camera.CameraInfo info = new Camera.CameraInfo();
+ Camera.getCameraInfo(i, info);
+ cameras.add(Integer.toString(i));
+ }
+ ListPreference cameraSelectList = (ListPreference)videoOptsScreen.findPreference("camera_select");
+// cameraSelectList.setKey("camera_select");
+// cameraSelectList.setTitle("Selected Camera");
+ cameraSelectList.setEntries(cameras.toArray(new String[0]));
+ cameraSelectList.setEntryValues(cameras.toArray(new String[0]));
+// cameraSelectList.setDefaultValue(0);
+ bindPreferenceSummaryToValue(cameraSelectList);
+ videoOptsScreen.addPreference(cameraSelectList);
+
bindPreferenceSummaryToValue(findPreference("video_codec"));
+ // TODO: verify that this works in cases where a camera might not be available, and also works on the default value
// get possible video capture frame rates and sizes
Camera camera = Camera.open(0);
Camera.Parameters camParams = camera.getParameters();
- ArrayList frameRateList = new ArrayList<>();
+// ArrayList frameRateList = new ArrayList<>();
for (int frameRate : camParams.getSupportedPreviewFrameRates())
frameRateList.add(Integer.toString(frameRate));
- ArrayList resList = new ArrayList<>();
+// ArrayList resList = new ArrayList<>();
for (Camera.Size imgSize : camParams.getSupportedPreviewSizes())
resList.add(imgSize.width + "x" + imgSize.height);
camera.release();
@@ -252,6 +415,58 @@ public void onCreate(Bundle savedInstanceState)
presetIndexes.add("AUTO");
selectedPresetList.setEntries(presetNames.toArray(new String[0]));
selectedPresetList.setEntryValues(presetIndexes.toArray(new String[0]));
+
+ // Setup Camera Listener
+ cameraSelectList.setOnPreferenceChangeListener((preference, newValue) -> {
+ Log.d("CAMERA_SELECT", "New Camera Selected: " + newValue);
+ updateCameraSettings(Integer.parseInt((String) newValue));
+ cameraSelectList.setSummary(newValue.toString());
+ return true;
+ });
+ }
+
+ protected void updateCameraSettings(Integer cameraId){
+ Camera camera = Camera.open(cameraId);
+ Camera.Parameters camParams = camera.getParameters();
+ for (int frameRate : camParams.getSupportedPreviewFrameRates())
+ frameRateList.add(Integer.toString(frameRate));
+ for (Camera.Size imgSize : camParams.getSupportedPreviewSizes())
+ resList.add(imgSize.width + "x" + imgSize.height);
+ camera.release();
+ }
+ }
+
+
+ /*
+ * Fragment for audio settings
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public static class AudioPreferenceFragment extends PreferenceFragment
+ {
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.pref_audio);
+
+ PreferenceScreen audioOptsScreen = getPreferenceScreen();
+ bindPreferenceSummaryToValue(findPreference("audio_codec"));
+
+ // get possible video capture frame rates and sizes
+ List sampleRateList = Arrays.asList("8000", "11025", "22050", "44100", "48000");
+ List bitRateList = Arrays.asList("32", "64", "96", "128", "160", "192");
+
+ // add list of supported sample rates
+ ListPreference sampleRatePrefList = (ListPreference)audioOptsScreen.findPreference("audio_samplerate");
+ sampleRatePrefList.setEntries(sampleRateList.toArray(new String[0]));
+ sampleRatePrefList.setEntryValues(sampleRateList.toArray(new String[0]));
+ bindPreferenceSummaryToValue(findPreference("audio_samplerate"));
+
+ // add list of supported bitrates
+ ListPreference bitRatePrefList = (ListPreference)audioOptsScreen.findPreference("audio_bitrate");
+ bitRatePrefList.setEntries(bitRateList.toArray(new String[0]));
+ bitRatePrefList.setEntryValues(bitRateList.toArray(new String[0]));
+ bindPreferenceSummaryToValue(findPreference("audio_samplerate"));
}
}
diff --git a/sensorhub-android-blebeacon/.gitignore b/sensorhub-android-blebeacon/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/sensorhub-android-blebeacon/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/sensorhub-android-blebeacon/build.gradle b/sensorhub-android-blebeacon/build.gradle
new file mode 100644
index 00000000..26b596ce
--- /dev/null
+++ b/sensorhub-android-blebeacon/build.gradle
@@ -0,0 +1,38 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 28
+
+
+ defaultConfig {
+ minSdkVersion 21
+ targetSdkVersion 26
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles 'consumer-rules.pro'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ implementation project(path: ':sensorhub-core')
+ implementation 'org.altbeacon:android-beacon-library:2.16.2'
+ implementation project(path: ':sensorhub-process-vecmath')
+ implementation project(path: ':sensorhub-process-geoloc')
+ implementation 'com.android.volley:volley:1.1.1'
+}
diff --git a/sensorhub-android-blebeacon/consumer-rules.pro b/sensorhub-android-blebeacon/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/sensorhub-android-blebeacon/docs/BLEDoc.txt b/sensorhub-android-blebeacon/docs/BLEDoc.txt
new file mode 100644
index 00000000..32ebfb29
--- /dev/null
+++ b/sensorhub-android-blebeacon/docs/BLEDoc.txt
@@ -0,0 +1,12 @@
+The BLE Beacon Sensor is a work in progress module designed to use the URL String gathered from
+BLE Beacons using the Eddystone URL protocol with known locations to give an approximate location of
+the phone when indoors.
+
+MODULE MUST:
+1. Have a constellation of registered Beacons with UIDs that have known locations
+2. Be able to triangulate based on the three strongest signals
+3. Have the ability to add a new registered beacon through the App
+4. Have the ability to remove a registered beacon through the app
+
+OPTIONAL:
+1. Notify that a beacon has low battery life
\ No newline at end of file
diff --git a/sensorhub-android-blebeacon/proguard-rules.pro b/sensorhub-android-blebeacon/proguard-rules.pro
new file mode 100644
index 00000000..f1b42451
--- /dev/null
+++ b/sensorhub-android-blebeacon/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/sensorhub-android-blebeacon/src/androidTest/java/org/sensorhub/impl/sensor/blebeacon/ExampleInstrumentedTest.java b/sensorhub-android-blebeacon/src/androidTest/java/org/sensorhub/impl/sensor/blebeacon/ExampleInstrumentedTest.java
new file mode 100644
index 00000000..50c2ab2d
--- /dev/null
+++ b/sensorhub-android-blebeacon/src/androidTest/java/org/sensorhub/impl/sensor/blebeacon/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package org.sensorhub.impl.sensor.blebeacon;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ assertEquals("org.sensorhub.impl.sensor.blebeacon.test", appContext.getPackageName());
+ }
+}
diff --git a/sensorhub-android-blebeacon/src/main/AndroidManifest.xml b/sensorhub-android-blebeacon/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..f6a6a09d
--- /dev/null
+++ b/sensorhub-android-blebeacon/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/AdaptedCellTrilateration.java b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/AdaptedCellTrilateration.java
new file mode 100644
index 00000000..d94b5df7
--- /dev/null
+++ b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/AdaptedCellTrilateration.java
@@ -0,0 +1,29 @@
+package org.sensorhub.impl.sensor.blebeacon;
+
+public class AdaptedCellTrilateration {
+ static double[] trackPhone(double[][] locations, double[] distances){
+ double[] computedLocation = new double[2];
+
+ double A = 2 * locations[1][0] - 2 * locations[0][0];
+ double B = 2 * locations[1][1] - 2 * locations[0][1];
+ double C = Math.pow(distances[0], 2) - Math.pow(distances[1], 2) - Math.pow(locations[0][0], 2) + Math.pow(locations[1][0], 2) - Math.pow(locations[0][1], 2) + Math.pow(locations[1][1], 2);
+
+ double D = 2 * locations[2][0] - 2 * locations[1][0];
+ double E = 2 * locations[2][1] - 2 * locations[1][1];
+ double F = Math.pow(distances[1], 2) - Math.pow(distances[2], 2) - Math.pow(locations[1][0], 2) + Math.pow(locations[2][0], 2) - Math.pow(locations[1][1], 2) + Math.pow(locations[2][1], 2);
+
+ double xCoord = (C*E - F*B) / (E*A - B*D);
+ double yCoord = (C*D - A*F) / (B*D - A*E);
+
+ computedLocation[0] = yCoord;
+ computedLocation[1] = xCoord;
+
+ // returns lon, lat
+ return computedLocation;
+ }
+
+ private void ddmToENU(){
+ // convert to ecef
+ // convert ecef to enu
+ }
+}
diff --git a/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeacon.java b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeacon.java
new file mode 100644
index 00000000..eb9c3317
--- /dev/null
+++ b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeacon.java
@@ -0,0 +1,96 @@
+package org.sensorhub.impl.sensor.blebeacon;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.RemoteException;
+import android.util.Log;
+
+import org.altbeacon.beacon.Beacon;
+import org.altbeacon.beacon.BeaconConsumer;
+import org.altbeacon.beacon.BeaconManager;
+import org.altbeacon.beacon.BeaconParser;
+import org.altbeacon.beacon.Identifier;
+import org.altbeacon.beacon.RangeNotifier;
+import org.altbeacon.beacon.Region;
+import org.altbeacon.beacon.utils.UrlBeaconUrlCompressor;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class BLEBeacon implements BeaconConsumer, RangeNotifier {
+ private Context parentContext;
+ private String TAG = "BLEBeacon-DefaultTAG";
+ private BeaconManager mBeaconManager;
+ private Map beacons;
+
+ public BLEBeacon(Context context){
+ this.parentContext = context;
+ this.beacons = new HashMap();
+ }
+
+ public void BeaconManagerSetup(){
+ mBeaconManager = BeaconManager.getInstanceForApplication(parentContext);
+ // Detect the URL frame:
+ mBeaconManager.getBeaconParsers().add(new BeaconParser().
+ setBeaconLayout(BeaconParser.EDDYSTONE_URL_LAYOUT));
+ mBeaconManager.bind(this);
+ }
+
+ public void unbind(){
+ try {
+ mBeaconManager.stopRangingBeaconsInRegion(new Region("all-beacons-region", null, null, null));
+ } catch (RemoteException e){
+ Log.d(TAG, "unbind: " + e);
+ }
+
+ mBeaconManager.unbind(this);
+ }
+
+ @Override
+ public void onBeaconServiceConnect() {
+ Region region = new Region("all-beacons-region", null, null, null);
+ try {
+ mBeaconManager.startRangingBeaconsInRegion(region);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ mBeaconManager.setRangeNotifier(this);
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return this.parentContext;
+ }
+
+ @Override
+ public void unbindService(ServiceConnection serviceConnection) {
+
+ }
+
+ @Override
+ public boolean bindService(Intent intent, ServiceConnection serviceConnection, int i) {
+ return false;
+ }
+
+ @Override
+ public void didRangeBeaconsInRegion(Collection beacons, Region region) {
+ for (Beacon beacon: beacons) {
+ if (beacon.getServiceUuid() == 0xfeaa && beacon.getBeaconTypeCode() == 0x10) {
+ // This is an Eddystone-URL frame
+ String url = UrlBeaconUrlCompressor.uncompress(beacon.getId1().toByteArray());
+ Log.d(TAG, "Beacon ID: "+ beacon.getId1() + "Beacon URL: " + url +
+ " approximately " + beacon.getDistance() + " meters away.");
+ // TODO: Need to improve this to handle non-EsURL beacons that have info in the other ID slots
+ this.beacons.put(beacon.getId1(), beacon);
+ }
+ }
+ }
+
+ public void printBeaconList(){
+ Log.d(TAG, "printBeaconList: " + this.beacons);
+ }
+
+
+}
diff --git a/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeaconConfig.java b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeaconConfig.java
new file mode 100644
index 00000000..79772cb5
--- /dev/null
+++ b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeaconConfig.java
@@ -0,0 +1,37 @@
+package org.sensorhub.impl.sensor.blebeacon;
+
+import android.content.Context;
+
+import com.google.gson.annotations.SerializedName;
+
+import org.sensorhub.api.config.DisplayInfo;
+import org.sensorhub.api.module.ModuleConfig;
+import org.sensorhub.api.sensor.SensorConfig;
+
+public class BLEBeaconConfig extends SensorConfig {
+ public boolean activateBLEBeacon = false;
+
+ public String deviceName;
+ public String runName;
+ public String runDescription;
+ public String configURL;
+
+ public transient Context androidContext;
+
+ @DisplayInfo(label = "Clamp to Nearest", desc = "Shows location as the the Nearest Beacon")
+ @SerializedName(value = "clampToNearest", alternate = {"enabled"})
+ public boolean clampToNearest;
+
+ public BLEBeaconConfig(){
+ this.moduleClass = BLEBeaconDriver.class.getCanonicalName();
+ this.clampToNearest = true;
+ }
+
+ // clone disabled as it causes crashes for now
+ @Override
+ public ModuleConfig clone(){
+ return this;
+ }
+
+
+}
diff --git a/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeaconDriver.java b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeaconDriver.java
new file mode 100644
index 00000000..f560e34a
--- /dev/null
+++ b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeaconDriver.java
@@ -0,0 +1,356 @@
+package org.sensorhub.impl.sensor.blebeacon;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.hardware.Sensor;
+import android.os.Build;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.JsonObjectRequest;
+import com.android.volley.toolbox.Volley;
+
+import org.altbeacon.beacon.Beacon;
+import org.altbeacon.beacon.BeaconConsumer;
+import org.altbeacon.beacon.BeaconManager;
+import org.altbeacon.beacon.BeaconParser;
+import org.altbeacon.beacon.RangeNotifier;
+import org.altbeacon.beacon.Region;
+import org.altbeacon.beacon.service.RunningAverageRssiFilter;
+import org.altbeacon.beacon.utils.UrlBeaconUrlCompressor;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.sensorhub.algo.geoloc.GeoTransforms;
+import org.sensorhub.algo.vecmath.Mat3d;
+import org.sensorhub.algo.vecmath.Vect3d;
+import org.sensorhub.api.common.SensorHubException;
+import org.sensorhub.api.sensor.ISensorDataInterface;
+import org.sensorhub.impl.sensor.AbstractSensorModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BLEBeaconDriver extends AbstractSensorModule implements BeaconConsumer, RangeNotifier {
+ private static final Logger log = LoggerFactory.getLogger(BLEBeaconDriver.class.getSimpleName());
+ public static final String LOCAL_REF_FRAME = "LOCAL_FRAME";
+ private static final String TAG = "BLEBeaconDriver";
+ private static final double pollTimeMillis = 1000;
+ private static final long runningAveragePollTimeMillis = 5000;
+ private static final double purgeTimeMillis = 5000;
+ String localFrameURI;
+
+ // List smlComponents;
+ private BLEBeaconRawOutput rawOutput;
+ private BLEBeaconLocationOutput locOutput;
+ private NearestBeaconOutput nearestBeaconOutput;
+
+ private BeaconManager mBeaconManager;
+ private List closestBeacons;
+ private Map url2Locations;
+ private Map beaconMap;
+ private Map lastRanged;
+ private Map url2Room;
+ private Triangulation triangulation;
+ Comparator beaconComp;
+ private double lastPoll;
+ private double lastRunningAverageTime;
+
+ public BLEBeaconDriver() {
+ // TODO: implement something better for this down the road (WIP)
+ beaconMap = new HashMap<>();
+ url2Locations = new HashMap<>();
+ lastRanged = new HashMap<>();
+ url2Room = new HashMap<>();
+
+// url2Locations.put("http://rpi4-1", new Vect3d(-86.2012028 * Math.PI / 180, 34.2520736 * Math.PI / 180, 283.0));
+// url2Locations.put("http://opensensorhub.org", new Vect3d(-86.2012018 * Math.PI / 180, 34.2520221 * Math.PI / 180, 283.0));
+// url2Locations.put("http://cardbeacon", new Vect3d(-86.2012561 * Math.PI / 180, 34.2520761 * Math.PI / 180, 283.0));
+
+ beaconComp = new Comparator() {
+ @Override
+ public int compare(Object o1, Object o2) {
+ if (o1 instanceof Beacon && o2 instanceof Beacon) {
+ Beacon b1 = (Beacon) o1;
+ Beacon b2 = (Beacon) o2;
+ /* if((System.currentTimeMillis() - runningAveragePollTimeMillis) >= lastRunningAverageTime ){ // TODO: Make sure this is a reasonable place to check this
+ lastRunningAverageTime = System.currentTimeMillis();
+ }*/
+ return -Double.compare(b1.getRunningAverageRssi(), b2.getRunningAverageRssi());
+ } else {
+ throw new ClassCastException("Arguments must be of type Beacon");
+ }
+ }
+ };
+ }
+
+ @Override
+ public synchronized void init() throws SensorHubException {
+ Context androidContext = config.androidContext;
+
+ String deviceID = Settings.Secure.getString(androidContext.getContentResolver(), Settings.Secure.ANDROID_ID);
+ this.xmlID = "ANDROID_SENSORS_" + Build.SERIAL; // Deprecated in API level 26
+ this.uniqueID = "urn:android:ble_beacon:" + deviceID;
+ this.localFrameURI = this.uniqueID + "#" + LOCAL_REF_FRAME;
+ lastPoll = System.currentTimeMillis() - pollTimeMillis;
+ lastRunningAverageTime = System.currentTimeMillis();
+
+ triangulation = new Triangulation();
+ closestBeacons = new ArrayList<>();
+ rawOutput = new BLEBeaconRawOutput(this);
+ locOutput = new BLEBeaconLocationOutput(this);
+ nearestBeaconOutput = new NearestBeaconOutput(this);
+ addOutput(rawOutput, false);
+ addOutput(locOutput, false);
+ addOutput(nearestBeaconOutput, false);
+
+ // TODO: Switch this to SensorThings API if we want to demonstrate that capability
+ // Get Beacon data
+ RequestQueue requestQueue = Volley.newRequestQueue(this.getConfiguration().androidContext);
+ Log.d(TAG, this.getConfiguration().configURL);
+ // Use a default URL to start, if the preferences at least start with http or https, then use that
+ String url = "https://chainreaction31.github.io/scira-ble-page/beacons.json";
+ String configURL = this.getConfiguration().configURL;
+ if(configURL.startsWith("https://") || configURL.startsWith("http://")){
+ url = configURL;
+ }
+ JsonObjectRequest stringRequest = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener() {
+ @Override
+ public void onResponse(JSONObject response) {
+ JSONArray beaconsJSON = null;
+
+ try {
+ beaconsJSON = response.getJSONArray("beacons");
+
+ for (int i = 0; i < beaconsJSON.length(); i++) {
+ JSONObject beaconInfo = beaconsJSON.getJSONObject(i);
+ String url = beaconInfo.getString("url");
+ double[] beaconLocation = new double[]{beaconInfo.getJSONObject("location").getDouble("lon") * Math.PI / 180,
+ beaconInfo.getJSONObject("location").getDouble("lat") * Math.PI / 180,
+ beaconInfo.getJSONObject("location").getDouble("alt")
+ };
+ url2Locations.put(url, new Vect3d(beaconLocation[0], beaconLocation[1], beaconLocation[2]));
+ url2Room.put(url, beaconInfo.optString("roomDesc", "None Provided"));
+ }
+
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ Log.d(TAG, "onResponse: " + beaconsJSON.toString());
+ }
+ }, new Response.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ Log.d(TAG, "onErrorResponse: GET Beacons failed");
+ }
+ });
+ requestQueue.add(stringRequest);
+ }
+
+ @Override
+ public void start() throws SensorHubException {
+ BeaconManager.setRssiFilterImplClass(RunningAverageRssiFilter.class);
+ RunningAverageRssiFilter.setSampleExpirationMilliseconds(runningAveragePollTimeMillis);
+
+ // start the BLEBeacon Manager scanning
+ // BLE Beacon Initialization
+ mBeaconManager = BeaconManager.getInstanceForApplication(getConfiguration().androidContext);
+ mBeaconManager.getBeaconParsers().add(new BeaconParser().setBeaconLayout(BeaconParser.EDDYSTONE_URL_LAYOUT));
+ mBeaconManager.bind(this);
+ rawOutput.start();
+ locOutput.start();
+ }
+
+ @Override
+ public void stop() throws SensorHubException {
+ try {
+ mBeaconManager.stopRangingBeaconsInRegion(new Region("url-beacons-region", null, null, null));
+ } catch (RemoteException e) {
+ Log.d(TAG, "unbind: " + e);
+ }
+
+ mBeaconManager.unbind(this);
+ }
+
+ protected void useSensor(ISensorDataInterface output, Sensor sensor) {
+ addOutput(output, false);
+ }
+
+ @Override
+ public boolean isConnected() {
+ return true;
+ }
+
+ @Override
+ public Logger getLogger() {
+ return log;
+ }
+
+ // BLE Beacon Consumer/Range Notifier Require Implementations
+ @Override
+ public void onBeaconServiceConnect() {
+ Region region = new Region("url-beacons-region", null, null, null);
+ try {
+ mBeaconManager.startRangingBeaconsInRegion(region);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ mBeaconManager.setRangeNotifier(this);
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return getConfiguration().androidContext;
+ }
+
+ @Override
+ public void unbindService(ServiceConnection serviceConnection) {
+
+ }
+
+ @Override
+ public boolean bindService(Intent intent, ServiceConnection serviceConnection, int i) {
+ return false;
+ }
+
+ @Override
+ public void didRangeBeaconsInRegion(Collection beacons, Region region) {
+ Beacon closestBeacon = null;
+ Log.d(TAG, "didRangeBeaconsInRegion: " + beacons.size());
+ if (System.currentTimeMillis() - lastPoll > pollTimeMillis) {
+ for (Beacon beacon : beacons) {
+ if (beacon.getServiceUuid() == 0xfeaa && beacon.getBeaconTypeCode() == 0x10) {
+ // This is an Eddystone-URL frame
+ String url = UrlBeaconUrlCompressor.uncompress(beacon.getId1().toByteArray());
+ Log.d(TAG, "Beacon ID: " + beacon.getId1() + "\nBeacon URL: " + url +
+// " approximately " + beacon.getDistance() + " meters away.");
+ " approximately " + getBeaconDistance(beacon) + " meters away.");
+ // TODO: Need to improve this to handle non-EsURL beacons that have info in the other ID slots
+ rawOutput.sendBeaconRecord(beacon);
+ if (url2Locations.get(url) != null) {
+ beaconMap.put(url, beacon);
+ lastRanged.put(url, new Date(System.currentTimeMillis()));
+ }
+ }
+ }
+
+ checkForOldBeacons();
+ Beacon[] bArr = beaconMap.values().toArray(new Beacon[0]);
+ double[] estimatedLocation = determineLocation();
+ if (estimatedLocation.length != 0) {
+ locOutput.sendMeasurement(estimatedLocation, bArr); // Put better checks in place after testing}
+ }
+ lastPoll = System.currentTimeMillis();
+ }
+ }
+
+ private double[] determineLocation() {
+ double[] distances = new double[3];
+ Vect3d[] locations = new Vect3d[3];
+ double[][] locationArr = new double[3][3];
+ GeoTransforms geoTransforms = new GeoTransforms();
+ int i = 0;
+ if (config.clampToNearest && !beaconMap.isEmpty()) {
+ ArrayList beaconArrayList = new ArrayList<>();
+ beaconArrayList.addAll(beaconMap.values());
+ beaconArrayList.sort(beaconComp);
+ Beacon nearest = beaconArrayList.get(0);
+ String roomDesc = url2Room.get(UrlBeaconUrlCompressor.uncompress(nearest.getId1().toByteArray()));
+ nearestBeaconOutput.sendMeasurement(nearest, roomDesc);
+ return new double[]{};
+ }
+ if (beaconMap.size() == 3) {
+ // TODO: move into a function
+
+ for (Beacon beacon : beaconMap.values()) {
+ locations[i] = url2Locations.get(UrlBeaconUrlCompressor.uncompress(beacon.getId1().toByteArray()));
+ // todo: we can skip some repeated conversion by only storing the ecef representations of beacon position but that requires us to repeatedly convert when sending a beacon's location
+ Vect3d tempVec = new Vect3d();
+ geoTransforms.LLAtoECEF(locations[i], tempVec);
+
+ locations[i] = tempVec;
+ locationArr[i] = new double[]{locations[i].y, locations[i].x, locations[i].z};
+ distances[i] = getBeaconDistance(beacon);
+ i++;
+ }
+
+ double[] estLocation = AdaptedCellTrilateration.trackPhone(locationArr, distances);
+ Vect3d estECEF = new Vect3d(estLocation[0], estLocation[1], locations[0].z);
+ Vect3d estLLA = new Vect3d();
+ geoTransforms.ECEFtoLLA(estECEF, estLLA);
+ Log.d(TAG, "determineLocation: " + estLocation[0] + "," + estLocation[1]);
+ return new double[]{estLLA.y * 180 / Math.PI, estLLA.x * 180 / Math.PI, estLLA.z};
+ }
+ if (beaconMap.size() > 3) {
+ // find best three
+ ArrayList beaconArrayList = new ArrayList<>();
+ beaconArrayList.addAll(beaconMap.values());
+ beaconArrayList.sort(beaconComp);
+ // do the same as above
+ for (Beacon beacon : beaconArrayList.subList(0, 3)) {
+ locations[i] = url2Locations.get(UrlBeaconUrlCompressor.uncompress(beacon.getId1().toByteArray()));
+ Vect3d tempVec = new Vect3d();
+ geoTransforms.LLAtoECEF(locations[i], tempVec);
+ locations[i] = tempVec;
+ locationArr[i] = new double[]{locations[i].y, locations[i].x, locations[i].z};
+ distances[i] = getBeaconDistance(beacon);
+ i++;
+ }
+ double[] estLocation = AdaptedCellTrilateration.trackPhone(locationArr, distances);
+ Vect3d estECEF = new Vect3d(estLocation[0], estLocation[1], locations[0].z);
+ Vect3d estLLA = new Vect3d();
+ geoTransforms.ECEFtoLLA(estECEF, estLLA);
+ Log.d(TAG, "determineLocation: " + estLocation[0] + "," + estLocation[1]);
+ return new double[]{estLLA.y * 180 / Math.PI, estLLA.x * 180 / Math.PI, estLLA.z};
+
+ }
+ return new double[]{}; // Todo: add better handling of this case
+ }
+
+ private Vect3d MatrixVectorProduct(Mat3d mat, Vect3d vec) {
+ Vect3d resultVector = new Vect3d();
+
+ resultVector.x = mat.m00 * vec.x + mat.m01 * vec.x + mat.m02 * vec.x;
+ resultVector.y = mat.m10 * vec.y + mat.m11 * vec.y + mat.m12 * vec.y;
+ resultVector.z = mat.m20 * vec.z + mat.m21 * vec.z + mat.m22 * vec.z;
+
+ return resultVector;
+ }
+
+ public double[] getBeaconLocation(Beacon beacon) {
+ Vect3d locationVec = url2Locations.get(UrlBeaconUrlCompressor.uncompress(beacon.getId1().toByteArray()));
+ if (locationVec != null)
+ return new double[]{locationVec.y * 180 / Math.PI, locationVec.x * 180 / Math.PI, locationVec.z};
+ else return new double[]{0, 0, 0};
+ }
+
+ private void checkForOldBeacons() {
+ for (Map.Entry entry : lastRanged.entrySet()) {
+ if (entry.getValue().getTime() - lastPoll > purgeTimeMillis) {
+ beaconMap.remove(entry.getKey());
+ }
+ }
+ }
+
+ public double getBeaconDistance(Beacon beacon) {
+ double mPower = -75;
+ // represents the freespace (how obstructed/congested the area is)
+ double n = 3.5;
+ double rssi = beacon.getRssi();
+ return Math.pow(10, (mPower - rssi) / (10 * n));
+ }
+}
diff --git a/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeaconLocationOutput.java b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeaconLocationOutput.java
new file mode 100644
index 00000000..5c3fa8e1
--- /dev/null
+++ b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeaconLocationOutput.java
@@ -0,0 +1,142 @@
+package org.sensorhub.impl.sensor.blebeacon;
+
+import android.content.Intent;
+import android.util.Log;
+
+import net.opengis.swe.v20.DataBlock;
+import net.opengis.swe.v20.DataComponent;
+import net.opengis.swe.v20.DataEncoding;
+import net.opengis.swe.v20.Vector;
+
+import org.altbeacon.beacon.Beacon;
+import org.sensorhub.api.sensor.SensorDataEvent;
+import org.sensorhub.impl.sensor.AbstractSensorOutput;
+import org.vast.swe.helper.GeoPosHelper;
+
+public class BLEBeaconLocationOutput extends AbstractSensorOutput {
+
+ private static final String ACTION_SUBMIT_TRACK_REPORT = "org.sensorhub.android.intent.SPOT_REPORT_TRACK";
+
+ private static final String URL_DEF = "http://sensorml.com/ont/swe/property/";
+
+ String name;
+ DataComponent posDataStruct;
+// DataComponent beaconDataStruct;
+ DataEncoding dataEncoding;
+
+
+ protected BLEBeaconLocationOutput(BLEBeaconDriver parentModule) {
+ super(parentModule);
+ this.name = parentModule.getName() + " Location";
+
+ // output structure (time + location)
+ GeoPosHelper fac = new GeoPosHelper();
+ posDataStruct = fac.newDataRecord();
+ posDataStruct.setName(getName());
+ posDataStruct.setDefinition("http://sensorml.com/ont/swe/property/BLEBeaconLocation");
+
+ // detected beacons structure
+
+
+ // add fields
+ Vector vec = fac.newLocationVectorLLA(null);
+ vec.setLocalFrame(parentSensor.localFrameURI);
+
+ Vector nearBeacon = fac.newVector(URL_DEF + "nearest_beacon",
+ null,
+ new String[]{"lat", "lon", "alt", "dist"},
+ new String[]{"Geodetic Latitude", "Longitude", "Altitude", "Distance"},
+ new String[] {"deg", "deg", "m", "m"},
+ new String[] {"Lat", "Long", "h", "dist"});
+
+ Vector beaconVec = fac.newVector(URL_DEF + "beacons_data",
+ null,
+ new String[]{"lat", "lon", "alt", "dist"},
+ new String[]{"Geodetic Latitude", "Longitude", "Altitude", "Distance"},
+ new String[] {"deg", "deg", "m", "m"},
+ new String[] {"Lat", "Long", "h", "dist"});
+
+// Vector vec1 = fac.newLocationVectorLLA(URL_DEF + "beacon_location");
+// Vector vec2 = fac.newLocationVectorLLA(URL_DEF + "b2_location");
+// Vector vec3 = fac.newLocationVectorLLA(URL_DEF + "b3_location");
+//
+// vec1.setLocalFrame(parentSensor.localFrameURI);
+// vec2.setLocalFrame(parentSensor.localFrameURI);
+// vec3.setLocalFrame(parentSensor.localFrameURI);
+
+ posDataStruct.addComponent("time", fac.newTimeStampIsoUTC());
+ posDataStruct.addComponent("est_location", vec);
+ posDataStruct.addComponent("nearest_beacon", nearBeacon);
+
+ // TODO: maybe there's a better way to present this data... TESTING new approach
+ posDataStruct.addComponent("beacon_1", beaconVec);
+// posDataStruct.addComponent("beacon_1_range", fac.newQuantity(URL_DEF + "b1_distance", null, null, "m"));
+ posDataStruct.addComponent("beacon_2", beaconVec);
+// posDataStruct.addComponent("beacon_2_range", fac.newQuantity(URL_DEF + "b2_distance", null, null, "m"));
+ posDataStruct.addComponent("beacon_3", beaconVec);
+// posDataStruct.addComponent("beacon_3_range", fac.newQuantity(URL_DEF + "b3_distance", null, null, "m"));
+
+ // output encoding
+ dataEncoding = fac.newTextEncoding(",", "\n");
+ }
+
+ protected void start() {
+ }
+
+ @Override
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public DataComponent getRecordDescription() {
+ return posDataStruct;
+ }
+
+ @Override
+ public DataEncoding getRecommendedEncoding() {
+ return dataEncoding;
+ }
+
+ @Override
+ public double getAverageSamplingPeriod() {
+ return 1.0;
+ }
+
+ protected void sendMeasurement(double[] estLocation, Beacon[] beacons) {
+
+ DataBlock dataBlock = posDataStruct.createDataBlock();
+ double sampleTime = System.currentTimeMillis() / 1000;
+ // TODO: Handle null or {0,0,0} better
+ double[] b1Loc = parentSensor.getBeaconLocation(beacons[0]);
+ double[] b2Loc = parentSensor.getBeaconLocation(beacons[1]);
+ double[] b3Loc = parentSensor.getBeaconLocation(beacons[2]);
+
+ dataBlock.setDoubleValue(0, sampleTime);
+ dataBlock.setDoubleValue(1, estLocation[0]);
+ dataBlock.setDoubleValue(2, estLocation[1]);
+ dataBlock.setDoubleValue(3, estLocation[2]);
+
+ dataBlock.setDoubleValue(4, b1Loc[0]);
+ dataBlock.setDoubleValue(5, b1Loc[1]);
+ dataBlock.setDoubleValue(6, b1Loc[2]);
+// dataBlock.setDoubleValue(7, beacons[0].getDistance());
+ dataBlock.setDoubleValue(7, parentSensor.getBeaconDistance(beacons[0]));
+
+ dataBlock.setDoubleValue(8, b2Loc[0]);
+ dataBlock.setDoubleValue(9, b2Loc[1]);
+ dataBlock.setDoubleValue(10, b2Loc[2]);
+// dataBlock.setDoubleValue(11, beacons[1].getDistance());
+ dataBlock.setDoubleValue(11, parentSensor.getBeaconDistance(beacons[1]));
+
+ dataBlock.setDoubleValue(12, b3Loc[0]);
+ dataBlock.setDoubleValue(13, b3Loc[1]);
+ dataBlock.setDoubleValue(14, b3Loc[2]);
+// dataBlock.setDoubleValue(15, beacons[2].getDistance());
+ dataBlock.setDoubleValue(15, parentSensor.getBeaconDistance(beacons[2]));
+
+ // update latest record and send event
+ latestRecordTime = System.currentTimeMillis();
+ eventHandler.publishEvent(new SensorDataEvent(latestRecordTime, BLEBeaconLocationOutput.this, dataBlock));
+ }
+}
diff --git a/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeaconRawOutput.java b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeaconRawOutput.java
new file mode 100644
index 00000000..6afad303
--- /dev/null
+++ b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/BLEBeaconRawOutput.java
@@ -0,0 +1,118 @@
+package org.sensorhub.impl.sensor.blebeacon;
+
+import android.util.Log;
+
+import net.opengis.swe.v20.DataBlock;
+import net.opengis.swe.v20.DataComponent;
+import net.opengis.swe.v20.DataEncoding;
+
+import org.altbeacon.beacon.Beacon;
+import org.altbeacon.beacon.BeaconManager;
+import org.sensorhub.api.sensor.SensorDataEvent;
+import org.sensorhub.impl.sensor.AbstractSensorOutput;
+import org.vast.swe.SWEHelper;
+
+public class BLEBeaconRawOutput extends AbstractSensorOutput{
+ private static final String BLE_BEACON_DEF = "http://sensorml.com/ont/swe/property/BLEBeacon";
+ private static final String URL_DEF = "http://sensorml.com/ont/swe/property/";
+ private static final String TAG = "BLEBeaconOutputRaw";
+
+ String name = "BLE Beacon Raw Data";
+ boolean enabled;
+ DataComponent bleData;
+ DataEncoding dataEncoding;
+ double samplingPeriod;
+ long systemTimeOffset = -1L;
+
+ BeaconManager mBeaconManager;
+
+
+ protected BLEBeaconRawOutput(BLEBeaconDriver parent) {
+ super(parent);
+
+ // create output structure
+ SWEHelper fac = new SWEHelper();
+ bleData = fac.newDataRecord();
+ bleData.setName(getName());
+ bleData.setDefinition(BLE_BEACON_DEF);
+ bleData.setDescription("Bluetooth Low Energy Beacon readings for commonly available data");
+
+ // add fields
+ bleData.addComponent("time", fac.newTimeStampIsoUTC());
+ bleData.addComponent("id", fac.newCategory("http://sensorml.com/ont/swe/property/SensorID", null, null, null));
+ bleData.addComponent("name", fac.newText(URL_DEF + "name", null, null));
+ bleData.addComponent("ID1", fac.newText(URL_DEF + "Identifier_1", null, "First of the three ids used by most BLE beacons, in ES-URL, the only id"));
+ bleData.addComponent("ID2", fac.newText(URL_DEF + "Identifier_2", null, null));
+ bleData.addComponent("ID3", fac.newText(URL_DEF + "Identifier_3", null, null));
+ bleData.addComponent("txPower", fac.newQuantity(URL_DEF + "txPower", null, null, "dBm"));
+ bleData.addComponent("RSSI", fac.newQuantity(URL_DEF + "rssi", null, null, "dBm")); // Is this value measured, and is it affected by the settings in the beacon
+ bleData.addComponent("distance", fac.newQuantity(URL_DEF + "distance", null, null, "m"));
+
+ dataEncoding = fac.newTextEncoding(",", "\n");
+
+ // Other fields that can be added
+ // Extra Data Fields
+ // Manufacturer
+ // Service UUID
+ // MultiFrameBeacon
+ // Parser Identifier // Note: this is part of ABL, not the beacon itself
+ }
+
+ public void start() {
+
+ }
+
+ public void stop() {
+
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public DataComponent getRecordDescription() {
+ return bleData;
+ }
+
+ @Override
+ public DataEncoding getRecommendedEncoding() {
+ return dataEncoding;
+ }
+
+ @Override
+ public double getAverageSamplingPeriod() {
+ return 1.0; // TODO: implement a calculation for this
+ }
+
+ public void sendBeaconRecord(Beacon beacon) {
+ double time = System.currentTimeMillis() / 1000.;
+ DataBlock dataBlock = bleData.createDataBlock();
+
+ dataBlock.setDoubleValue(0, time);
+ dataBlock.setStringValue(1, parentSensor.getUniqueIdentifier());
+ dataBlock.setStringValue(2, beacon.getBluetoothName());
+ dataBlock.setStringValue(3, beacon.getId1().toString());
+ /* if (beacon.getId2() != null) {
+ dataBlock.setStringValue(4, beacon.getId2().toString());
+ } else {
+ dataBlock.setStringValue(4, "NONE");
+ }
+ if (beacon.getId3() != null) {
+ dataBlock.setStringValue(5, beacon.getId3().toString());
+ } else {
+ dataBlock.setStringValue(5, "NONE");
+ }*/
+ dataBlock.setStringValue(4, "NONE");
+ dataBlock.setStringValue(5, "NONE");
+ dataBlock.setDoubleValue(6, beacon.getTxPower());
+ dataBlock.setDoubleValue(7, beacon.getRssi());
+ dataBlock.setDoubleValue(8, beacon.getDistance());
+// dataBlock.setDoubleValue(8, parentSensor.getBeaconDistance(beacon));
+
+ // Push the data
+ latestRecordTime = System.currentTimeMillis();
+ eventHandler.publishEvent(new SensorDataEvent(latestRecordTime, BLEBeaconRawOutput.this, dataBlock));
+ }
+}
diff --git a/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/NearestBeaconOutput.java b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/NearestBeaconOutput.java
new file mode 100644
index 00000000..ef2f92b8
--- /dev/null
+++ b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/NearestBeaconOutput.java
@@ -0,0 +1,103 @@
+package org.sensorhub.impl.sensor.blebeacon;
+
+import android.content.Intent;
+import android.util.Log;
+
+import net.opengis.swe.v20.DataBlock;
+import net.opengis.swe.v20.DataComponent;
+import net.opengis.swe.v20.DataEncoding;
+import net.opengis.swe.v20.Vector;
+
+import org.altbeacon.beacon.Beacon;
+import org.sensorhub.algo.vecmath.Vect3d;
+import org.sensorhub.api.sensor.SensorDataEvent;
+import org.sensorhub.impl.sensor.AbstractSensorOutput;
+import org.vast.swe.SWEHelper;
+import org.vast.swe.helper.GeoPosHelper;
+
+public class NearestBeaconOutput extends AbstractSensorOutput {
+
+ private static final String ACTION_SUBMIT_TRACK_REPORT = "org.sensorhub.android.intent.SPOT_REPORT_TRACK";
+
+ private static final String URL_DEF = "http://sensorml.com/ont/swe/property/";
+
+ String name;
+ DataComponent posDataStruct;
+ // DataComponent beaconDataStruct;
+ DataEncoding dataEncoding;
+
+
+ protected NearestBeaconOutput(BLEBeaconDriver parentModule) {
+ super(parentModule);
+ this.name = parentModule.getName() + " Nearest Beacon";
+
+ // output structure (time + location)
+ GeoPosHelper fac = new GeoPosHelper();
+ posDataStruct = fac.newDataRecord();
+ posDataStruct.setName(getName());
+ posDataStruct.setDefinition("http://sensorml.com/ont/swe/property/NearestBeacon");
+
+ // detected beacons structure
+
+
+ // add fields
+ Vector vec = fac.newLocationVectorLLA(null);
+ vec.setLocalFrame(parentSensor.localFrameURI);
+
+ Vector nearBeacon = fac.newVector(URL_DEF + "nearest_beacon",
+ null,
+ new String[]{"lat", "lon", "alt", "dist"},
+ new String[]{"Geodetic Latitude", "Longitude", "Altitude", "Distance"},
+ new String[] {"deg", "deg", "m", "m"},
+ new String[] {"Lat", "Long", "h", "dist"});
+
+ posDataStruct.addComponent("time", fac.newTimeStampIsoUTC());
+ posDataStruct.addComponent("nearest_beacon", nearBeacon);
+ posDataStruct.addComponent("roomDesc", new SWEHelper().newText(URL_DEF + "roomDesc", null, null));
+
+ // output encoding
+ dataEncoding = fac.newTextEncoding(",", "\n");
+ }
+
+ protected void start() {
+ }
+
+ @Override
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public DataComponent getRecordDescription() {
+ return posDataStruct;
+ }
+
+ @Override
+ public DataEncoding getRecommendedEncoding() {
+ return dataEncoding;
+ }
+
+ @Override
+ public double getAverageSamplingPeriod() {
+ return 1.0;
+ }
+
+ protected void sendMeasurement(Beacon beacon, String roomDesc) {
+
+ DataBlock dataBlock = posDataStruct.createDataBlock();
+ double sampleTime = System.currentTimeMillis() / 1000;
+ double[] locationDecDeg = parentSensor.getBeaconLocation(beacon);
+
+ dataBlock.setDoubleValue(0, sampleTime);
+ dataBlock.setDoubleValue(1, locationDecDeg[0]);
+ dataBlock.setDoubleValue(2, locationDecDeg[1]);
+ dataBlock.setDoubleValue(3, locationDecDeg[2]);
+// dataBlock.setDoubleValue(4, beacon.getDistance());
+ dataBlock.setDoubleValue(4, parentSensor.getBeaconDistance(beacon));
+ dataBlock.setStringValue(5, roomDesc);
+
+ // update latest record and send event
+ latestRecordTime = System.currentTimeMillis();
+ eventHandler.publishEvent(new SensorDataEvent(latestRecordTime, NearestBeaconOutput.this, dataBlock));
+ }
+}
diff --git a/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/Triangulation.java b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/Triangulation.java
new file mode 100644
index 00000000..b1afc3a1
--- /dev/null
+++ b/sensorhub-android-blebeacon/src/main/java/org/sensorhub/impl/sensor/blebeacon/Triangulation.java
@@ -0,0 +1,762 @@
+package org.sensorhub.impl.sensor.blebeacon;
+
+// TODO: see if there is a better way to import this from the Trek1000 driver or if it should be made available through core
+public class Triangulation {
+ /* Largest nonnegative number still considered zero */
+ final static double MAXZERO = 0.001;
+
+ final static int TRIL_3SPHERES = 3;
+ final static int TRIL_4SPHERES = 4;
+
+ final static int ERR_TRIL_CONCENTRIC = -1;
+ final static int ERR_TRIL_COLINEAR_2SOLUTIONS = -2;
+ final static int ERR_TRIL_SQRTNEGNUMB = -3;
+ final static int ERR_TRIL_NOINTERSECTION_SPHERE4 = -4;
+ final static int ERR_TRIL_NEEDMORESPHERE = -5;
+
+ private double mu1;
+ private double mu2;
+ private int nosolution_count;
+ private double best_3derror;
+ private double best_gdoprate;
+
+ protected static class Vec3d
+ {
+ public double x;
+ public double y;
+ public double z;
+
+ public Vec3d(double x, double y, double z)
+ {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+ }
+
+ /* Return the difference of two vectors */
+ private Vec3d vdiff(Vec3d vector1, Vec3d vector2)
+ {
+ return new Vec3d(
+ vector1.x - vector2.x,
+ vector1.y - vector2.y,
+ vector1.z - vector2.z);
+ }
+
+ /* Return the sum of two vectors */
+ private Vec3d vsum(Vec3d vector1, Vec3d vector2)
+ {
+ return new Vec3d(
+ vector1.x + vector2.x,
+ vector1.y + vector2.y,
+ vector1.z + vector2.z);
+ }
+
+ /* Multiply vector by a number */
+ private Vec3d vmul(Vec3d vector1, double n)
+ {
+ return new Vec3d(
+ vector1.x * n,
+ vector1.y * n,
+ vector1.z * n);
+ }
+
+ /* Divide vector by a number */
+ private Vec3d vdiv(Vec3d vector1, double n)
+ {
+ return new Vec3d(
+ vector1.x / n,
+ vector1.y / n,
+ vector1.z / n);
+ }
+
+ /* Return the Euclidean norm. */
+ private double vdist(Vec3d vector1, Vec3d vector2)
+ {
+ double xd = vector1.x - vector2.x;
+ double yd = vector1.y - vector2.y;
+ double zd = vector1.z - vector2.z;
+ return Math.sqrt(xd * xd + yd * yd + zd * zd);
+ }
+
+ /* Return the Euclidean norm. */
+ private double vnorm(Vec3d vector)
+ {
+ return Math.sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
+ }
+
+ /* Return the dot product of two vectors. */
+ private double dot(Vec3d vector1, Vec3d vector2)
+ {
+ return vector1.x * vector2.x + vector1.y * vector2.y + vector1.z * vector2.z;
+ }
+
+ /* Replace vector with its cross product with another vector */
+ private Vec3d cross(Vec3d vector1, Vec3d vector2)
+ {
+ return new Vec3d(
+ vector1.y * vector2.z - vector1.z * vector2.y,
+ vector1.z * vector2.x - vector1.x * vector2.z,
+ vector1.x * vector2.y - vector1.y * vector2.x);
+ }
+
+ /* Return the GDOP (Geometric Dilution of Precision) rate between
+ * 0-1. Lower GDOP rate means better precision of intersection.
+ **/
+ private double gdoprate(Vec3d tag, Vec3d p1, Vec3d p2, Vec3d p3)
+ {
+ Vec3d ex, t1, t2, t3;
+ double h, gdop1, gdop2, gdop3;
+
+ ex = vdiff(p1, tag);
+ h = vnorm(ex);
+ t1 = vdiv(ex, h);
+
+ ex = vdiff(p2, tag);
+ h = vnorm(ex);
+ t2 = vdiv(ex, h);
+
+ ex = vdiff(p3, tag);
+ h = vnorm(ex);
+ t3 = vdiv(ex, h);
+
+ gdop1 = Math.abs(dot(t1, t2));
+ gdop2 = Math.abs(dot(t2, t3));
+ gdop3 = Math.abs(dot(t3, t1));
+
+ return Math.max(gdop1, Math.max(gdop2, gdop3));
+ }
+
+ /* Intersecting a sphere sc with radius of r, with a line p1-p2.
+ * Return zero if successful, negative error otherwise.
+ * mu1 & mu2 are constant to find points of intersection.
+ **/
+ private int sphereline(Vec3d p1, Vec3d p2, Vec3d sc, double r)
+ {
+ double a, b, c, bb4ac;
+ Vec3d dp = new Vec3d(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z);
+
+ a = dp.x * dp.x + dp.y * dp.y + dp.z * dp.z;
+ b = 2 * (dp.x * (p1.x - sc.x) + dp.y * (p1.y - sc.y) + dp.z * (p1.z - sc.z));
+ c = sc.x * sc.x + sc.y * sc.y + sc.z * sc.z;
+ c += p1.x * p1.x + p1.y * p1.y + p1.z * p1.z;
+ c -= 2 * (sc.x * p1.x + sc.y * p1.y + sc.z * p1.z);
+ c -= r * r;
+
+ bb4ac = b * b - 4 * a * c;
+
+ if (Math.abs(a) == 0 || bb4ac < 0)
+ {
+ mu1 = 0.0;
+ mu2 = 0.0;
+ return -1;
+ }
+
+ mu1 = (-b + Math.sqrt(bb4ac)) / (2 * a);
+ mu2 = (-b - Math.sqrt(bb4ac)) / (2 * a);
+
+ return 0;
+ }
+
+ /* Return TRIL_3SPHERES if it is performed using 3 spheres and return
+ * TRIL_4SPHERES if it is performed using 4 spheres
+ * For TRIL_3SPHERES, there are two solutions: result1 and result 2
+ * For TRIL_4SPHERES, there is only one solution: best_ solution
+ *
+ * Return negative number for other errors
+ *
+ * To force the function to work with only 3 spheres, provide a duplicate of
+ * any sphere at any place among p1, p2, p3, or p4.
+ *
+ * The last parameter is the largest nonnegative number considered zero;
+ * it is somewhat analogous to machine epsilon (but inclusive).
+ **/
+ private int trilateration(Vec3d result1, Vec3d result2, Vec3d best_solution,
+ Vec3d p1, double r1,
+ Vec3d p2, double r2,
+ Vec3d p3, double r3,
+ Vec3d p4, double r4,
+ double maxzero)
+ {
+ Vec3d ex, ey, ez, t1, t2, t3;
+ double h, i, j, x, y, z, t,
+ mu = 0.0;
+ int result = 0;
+
+ /*** FINDING TWO POINTS FROM THE FIRST THREE SPHERES ***/
+
+ // if there are at least 2 concentric spheres within the first 3 spheres
+ // then the calculation may not continue, drop it with error -1
+
+ /* h = |p3 - p1|, ex = (p3 - p1) / |p3 - p1| */
+ ex = vdiff(p3, p1); // vector p13
+ h = vnorm(ex); // scalar p13
+ if ( h <= maxzero)
+ {
+ /* p1 and p3 are concentric, not good to obtain a precise intersection point */
+ return ERR_TRIL_CONCENTRIC;
+ }
+
+ /* h = |p3 - p2|, ex = (p3 - p2) / |p3 - p2| */
+ ex = vdiff(p3, p2); // vector p23
+ h = vnorm(ex); // scalar p23
+ if ( h <= maxzero)
+ {
+ /* p2 and p3 are concentric, not good to obtain a precise intersection point */
+ return ERR_TRIL_CONCENTRIC;
+ }
+
+ /* h = |p2 - p1|, ex = (p2 - p1) / |p2 - p1| */
+ ex = vdiff(p2, p1); // vector p12
+ h = vnorm(ex); // scalar p12
+ if ( h <= maxzero)
+ {
+ /* p1 and p2 are concentric, not good to obtain a precise intersection point */
+ return ERR_TRIL_CONCENTRIC;
+ }
+ ex = vdiv(ex, h); // unit vector ex with respect to p1 (new coordinate system)
+
+ /* t1 = p3 - p1, t2 = ex (ex . (p3 - p1)) */
+ t1 = vdiff(p3, p1); // vector p13
+ i = dot(ex, t1); // the scalar of t1 on the ex direction
+ t2 = vmul(ex, i); // colinear vector to p13 with the length of i
+
+ /* ey = (t1 - t2), t = |t1 - t2| */
+ ey = vdiff(t1, t2); // vector t21 perpendicular to t1
+ t = vnorm(ey); // scalar t12
+ if (t > maxzero)
+ {
+ /* ey = (t1 - t2) / |t1 - t2| */
+ ey = vdiv(ey, t); // unit vector ey with respect to p1 (new coordinate system)
+
+ /* j = ey . (p3 - p1) */
+ j = dot(ey, t1); // scalar t1 on the ey direction
+ } else {
+ j = 0.0;
+ }
+
+ /* Note: t <= maxzero implies j = 0.0 */
+ if (Math.abs(j) <= maxzero)
+ {
+ /* Is point p1 + (r1 along the axis) the intersection? */
+ t2 = vsum(p1, vmul(ex, r1));
+ if (Math.abs(vnorm(vdiff(p2, t2)) - r2) <= maxzero &&
+ Math.abs(vnorm(vdiff(p3, t2)) - r3) <= maxzero)
+ {
+ /* Yes, t2 is the only intersection point */
+ if (result1 != null)
+ {
+ result1.x = t2.x;
+ result1.y = t2.y;
+ result1.z = t2.z;
+ }
+ if (result2 != null)
+ {
+ result2.x = t2.x;
+ result2.y = t2.y;
+ result2.z = t2.z;
+ }
+ return TRIL_3SPHERES;
+ }
+
+ /* Is point p1 - (r1 along the axis) the intersection? */
+ t2 = vsum(p1, vmul(ex, -r1));
+ if (Math.abs(vnorm(vdiff(p2, t2)) - r2) <= maxzero &&
+ Math.abs(vnorm(vdiff(p3, t2)) - r3) <= maxzero)
+ {
+ /* Yes, t2 is the only intersection point */
+ if (result1 != null)
+ {
+ result1.x = t2.x;
+ result1.y = t2.y;
+ result1.z = t2.z;
+ }
+ if (result2 != null)
+ {
+ result2.x = t2.x;
+ result2.y = t2.y;
+ result2.z = t2.z;
+ }
+ return TRIL_3SPHERES;
+ }
+
+ /* p1, p2, and p3 are colinear with more than one solution */
+ return ERR_TRIL_COLINEAR_2SOLUTIONS;
+ }
+
+ /* ez = ex * ev */
+ ez = cross(ex, ey); // unit vector ez with respect to p1 (new coordinate system)
+
+ x = (r1*r1 - r2*r2) / (2*h) + h /2;
+ y = (r1*r1 - r3*r3 + i*i) / (2*j) + j / 2 - x * i / j;
+ z = r1*r1 - x*x - y*y;
+ if ( z < -maxzero)
+ {
+ /* The solution is invalid, square root of a negative number */
+ return ERR_TRIL_SQRTNEGNUMB;
+ }
+ else if (z > 0.0)
+ {
+ z = Math.sqrt(z);
+ }
+ else
+ {
+ z = 0.0;
+ }
+
+ /* t2 = p1 + x ex + y ey */
+ t2 = vsum(p1, vmul(ex, x));
+ t2 = vsum(t2, vmul(ey, y));
+
+ /* result1 = p1 + x ex + y ey + z ez */
+ if (result1 != null)
+ {
+ t2 = vsum(t2, vmul(ez, z));
+ result1.x = t2.x;
+ result1.y = t2.y;
+ result1.z = t2.z;
+ }
+
+ /* result2 = p1 + x ex + y ey - z ez */
+ if (result2 != null)
+ {
+ t2 = vsum(t2, vmul(ez, -z));
+ result2.x = t2.x;
+ result2.y = t2.y;
+ result2.z = t2.z;
+ }
+
+ /*********** END OF FINDING TWO POINTS FROM THE FIRST THREE SPHERES **********/
+ /********* RESULT1 AND RESULT2 ARE SOLUTIONS, OTHERWISE RETURN ERROR *********/
+
+
+ /************* FINDING ONE SOLUTION BY INTRODUCING ONE MORE SPHERE ***********/
+
+ // check for concentricness of sphere 4 to sphere 1, 2 and 3
+ // if it is concentric to one of them, then sphere 4 cannot be used
+ // to determine the best solution and return -1
+
+ /* h = |p4 - p1|, ex = (p4 - p1) / |p4 - p1| */
+ ex = vdiff(p4, p1); // vector p14
+ h = vnorm(ex); // scalar p14
+ if (h <= maxzero) {
+ /* p1 and p4 are concentric, not good to obtain a precise intersection point */
+ //printf("concentric14 return 0\n");
+ return TRIL_3SPHERES;
+ }
+ /* h = |p4 - p2|, ex = (p4 - p2) / |p4 - p2| */
+ ex = vdiff(p4, p2); // vector p24
+ h = vnorm(ex); // scalar p24
+ if (h <= maxzero) {
+ /* p2 and p4 are concentric, not good to obtain a precise intersection point */
+ //printf("concentric24 return 0\n");
+ return TRIL_3SPHERES;
+ }
+ /* h = |p4 - p3|, ex = (p4 - p3) / |p4 - p3| */
+ ex = vdiff(p4, p3); // vector p34
+ h = vnorm(ex); // scalar p34
+ if (h <= maxzero) {
+ /* p3 and p4 are concentric, not good to obtain a precise intersection point */
+ //printf("concentric34 return 0\n");
+ return TRIL_3SPHERES;
+ }
+
+ // if sphere 4 is not concentric to any sphere, then best solution can be obtained
+ /* find i as the distance of result1 to p4 */
+ t3 = vdiff(result1, p4);
+ i = vnorm(t3);
+ /* find h as the distance of result2 to p4 */
+ t3 = vdiff(result2, p4);
+ h = vnorm(t3);
+
+ /* pick the result1 as the nearest point to the center of sphere 4 */
+ if (i > h) {
+ best_solution.x = result1.x;
+ best_solution.y = result1.y;
+ best_solution.z = result1.z;
+ result1.x = result2.x;
+ result1.y = result2.y;
+ result1.z = result2.z;
+ result2.x = best_solution.x;
+ result2.y = best_solution.y;
+ result2.z = best_solution.z;
+ }
+
+
+ int count4 = 0;
+ double rr4 = r4;
+ result = 1;
+ /* intersect result1-result2 vector with sphere 4 */
+ while(result != 0 && count4 < 10)
+ {
+ result = sphereline(result1, result2, p4, rr4);
+ rr4+=0.1;
+ count4++;
+ }
+
+ if (result != 0)
+ {
+ /* No intersection between sphere 4 and the line with the gradient of result1-result2! */
+ /* result1 is the closer solution to sphere 4 */
+ best_solution.x = result1.x;
+ best_solution.y = result1.y;
+ best_solution.z = result1.z;
+ //return ERR_TRIL_NOINTERSECTION_SPHERE4;
+ }
+ else
+ {
+ if (mu1 < 0 && mu2 < 0)
+ {
+ /* if both mu1 and mu2 are less than 0 */
+ /* result1-result2 line segment is outside sphere 4 with no intersection */
+ mu = (Math.abs(mu1) <= Math.abs(mu2)) ? mu1 : mu2;
+ /* h = |result2 - result1|, ex = (result2 - result1) / |result2 - result1| */
+ ex = vdiff(result2, result1); // vector result1-result2
+ // TODO HERE
+ h = vnorm(ex); // scalar result1-result2
+ ex = vdiv(ex, h); // unit vector ex with respect to result1 (new coordinate system)
+ /* 50-50 error correction for mu */
+ mu = 0.5*mu;
+ /* t2 points to the intersection */
+ t2 = vmul(ex, mu*h);
+ t2 = vsum(result1, t2);
+ /* the best solution = t2 */
+ best_solution.x = t2.x;
+ best_solution.y = t2.y;
+ best_solution.z = t2.z;
+ }
+ else if ((mu1 < 0 && mu2 > 1) || (mu2 < 0 && mu1 > 1))
+ {
+ /* if mu1 is less than zero and mu2 is greater than 1, or the other way around */
+ /* result1-result2 line segment is inside sphere 4 with no intersection */
+ mu = (mu1 > mu2) ? mu1 : mu2;
+ /* h = |result2 - result1|, ex = (result2 - result1) / |result2 - result1| */
+ ex = vdiff(result2, result1); // vector result1-result2
+ h = vnorm(ex); // scalar result1-result2
+ ex = vdiv(ex, h); // unit vector ex with respect to result1 (new coordinate system)
+ /* t2 points to the intersection */
+ t2 = vmul(ex, mu*h);
+ t2 = vsum(result1, t2);
+ /* vector t2-result2 with 50-50 error correction on the length of t3 */
+ t3 = vmul(vdiff(result2, t2),0.5);
+ /* the best solution = t2 + t3 */
+ best_solution = vsum(t2, t3);
+ }
+ else if (((mu1 > 0 && mu1 < 1) && (mu2 < 0 || mu2 > 1))
+ || ((mu2 > 0 && mu2 < 1) && (mu1 < 0 || mu1 > 1)))
+ {
+ /* if one mu is between 0 to 1 and the other is not */
+ /* result1-result2 line segment intersects sphere 4 at one point */
+ mu = (mu1 >= 0 && mu1 <= 1) ? mu1 : mu;
+ /* add or subtract with 0.5*mu to distribute error equally onto every sphere */
+ if (mu <= 0.5) mu-=0.5*mu; else mu-=0.5*(1-mu);
+ /* h = |result2 - result1|, ex = (result2 - result1) / |result2 - result1| */
+ ex = vdiff(result2, result1); // vector result1-result2
+ h = vnorm(ex); // scalar result1-result2
+ ex = vdiv(ex, h); // unit vector ex with respect to result1 (new coordinate system)
+ /* t2 points to the intersection */
+ t2 = vmul(ex, mu*h);
+ t2 = vsum(result1, t2);
+ /* the best solution = t2 */
+ best_solution = t2;
+ }
+ else if (mu1 == mu2)
+ {
+ /* if both mu1 and mu2 are between 0 and 1, and mu1 = mu2 */
+ /* result1-result2 line segment is tangential to sphere 4 at one point */
+ mu = mu1;
+ /* add or subtract with 0.5*mu to distribute error equally onto every sphere */
+ if (mu <= 0.25) mu-=0.5*mu;
+ else if (mu <=0.5) mu-=0.5*(0.5-mu);
+ else if (mu <=0.75) mu-=0.5*(mu-0.5);
+ else mu-=0.5*(1-mu);
+ /* h = |result2 - result1|, ex = (result2 - result1) / |result2 - result1| */
+ ex = vdiff(result2, result1); // vector result1-result2
+ h = vnorm(ex); // scalar result1-result2
+ ex = vdiv(ex, h); // unit vector ex with respect to result1 (new coordinate system)
+ /* t2 points to the intersection */
+ t2 = vmul(ex, mu*h);
+ t2 = vsum(result1, t2);
+ /* the best solution = t2 */
+ best_solution = t2;
+ }
+ else
+ {
+ /* if both mu1 and mu2 are between 0 and 1 */
+ /* result1-result2 line segment intersects sphere 4 at two points */
+
+ //return ERR_TRIL_NEEDMORESPHERE;
+
+ mu = mu1 + mu2;
+ /* h = |result2 - result1|, ex = (result2 - result1) / |result2 - result1| */
+ ex = vdiff(result2, result1); // vector result1-result2
+ h = vnorm(ex); // scalar result1-result2
+ ex = vdiv(ex, h); // unit vector ex with respect to result1 (new coordinate system)
+ /* 50-50 error correction for mu */
+ mu = 0.5*mu;
+ /* t2 points to the intersection */
+ t2 = vmul(ex, mu*h);
+ t2 = vsum(result1, t2);
+ /* the best solution = t2 */
+ best_solution = t2;
+ }
+ }
+
+ return TRIL_4SPHERES;
+ /******** END OF FINDING ONE SOLUTION BY INTRODUCING ONE MORE SPHERE *********/
+ }
+
+ /* This function calls trilateration to get the best solution.
+ *
+ * If any three spheres does not produce valid solution,
+ * then each distance is increased to ensure intersection to happens.
+ *
+ * Return the selected trilateration mode between TRIL_3SPHERES or TRIL_4SPHERES
+ * For TRIL_3SPHERES, there are two solutions: solution1 and solution2
+ * For TRIL_4SPHERES, there is only one solution: best_solution
+ *
+ * nosolution_count = the number of failed attempt before intersection is found
+ * by increasing the sphere diameter.
+ **/
+ int deca_3dlocate ( Vec3d solution1, Vec3d solution2, Vec3d best_solution,
+ Vec3d p1, double r1,
+ Vec3d p2, double r2,
+ Vec3d p3, double r3,
+ Vec3d p4, double r4,
+ int combination)
+ {
+ Vec3d o1, o2, solution, ptemp;
+ Vec3d solution_compare1, solution_compare2;
+ double /*error_3dcompare1, error_3dcompare2,*/ rtemp;
+ double gdoprate_compare1, gdoprate_compare2;
+ double ovr_r1, ovr_r2, ovr_r3, ovr_r4;
+ int overlook_count, combination_counter;
+ int trilateration_errcounter, trilateration_mode34;
+ int success, concentric, result;
+
+ trilateration_errcounter = 0;
+ trilateration_mode34 = 0;
+
+ combination_counter = 4; /* four spheres combination */
+
+ best_gdoprate = 1; /* put the worst gdoprate init */
+ gdoprate_compare1 = 1; gdoprate_compare2 = 1;
+ solution_compare1 = new Vec3d(0, 0, 0);
+ o1 = new Vec3d(0, 0, 0);
+ o2 = new Vec3d(0, 0, 0);
+ solution = new Vec3d(0, 0, 0);
+ solution_compare1 = new Vec3d(0, 0, 0);
+ solution_compare2 = new Vec3d(0, 0, 0);
+ //error_3dcompare1 = 0;
+
+ do
+ {
+ success = 0;
+ concentric = 0;
+ overlook_count = 0;
+ ovr_r1 = r1; ovr_r2 = r2; ovr_r3 = r3; ovr_r4 = r4;
+
+ do
+ {
+ result = trilateration(o1, o2, solution, p1, ovr_r1, p2, ovr_r2, p3, ovr_r3, p4, ovr_r4, MAXZERO);
+
+ switch (result)
+ {
+ case TRIL_3SPHERES: // 3 spheres are used to get the result
+ trilateration_mode34 = TRIL_3SPHERES;
+ success = 1;
+ break;
+ case TRIL_4SPHERES: // 4 spheres are used to get the result
+ trilateration_mode34 = TRIL_4SPHERES;
+ success = 1;
+ break;
+ case ERR_TRIL_CONCENTRIC:
+ concentric = 1;
+ break;
+ default: // any other return value goes here
+ ovr_r1 += 0.10;
+ ovr_r2 += 0.10;
+ ovr_r3 += 0.10;
+ ovr_r4 += 0.10;
+ overlook_count++;
+ break;
+ }
+ //qDebug() << "while(!success)" << overlook_count << concentric << "result" << result;
+ } while (success != 0 && (overlook_count <= 5) && concentric != 0);
+
+ if (success != 0)
+ {
+ switch (result)
+ {
+ case TRIL_3SPHERES:
+ solution1.x = o1.x;
+ solution1.y = o1.y;
+ solution1.z = o1.z;
+ solution2.x = o2.x;
+ solution2.y = o2.y;
+ solution2.z = o2.z;
+ nosolution_count = overlook_count;
+
+ combination_counter = 0;
+ break;
+ case TRIL_4SPHERES:
+ /* calculate the new gdop */
+ gdoprate_compare1 = gdoprate(solution, p1, p2, p3);
+
+ /* compare and swap with the better result */
+ if (gdoprate_compare1 <= gdoprate_compare2) {
+ solution1.x = o1.x;
+ solution1.y = o1.y;
+ solution1.z = o1.z;
+ solution2.x = o2.x;
+ solution2.y = o2.y;
+ solution2.z = o2.z;
+ best_solution.x = solution.x;
+ best_solution.x = solution.x;
+ best_solution.x = solution.x;
+ nosolution_count = overlook_count;
+ best_3derror = Math.sqrt((vnorm(vdiff(solution, p1))-r1)*(vnorm(vdiff(solution, p1))-r1) +
+ (vnorm(vdiff(solution, p2))-r2)*(vnorm(vdiff(solution, p2))-r2) +
+ (vnorm(vdiff(solution, p3))-r3)*(vnorm(vdiff(solution, p3))-r3) +
+ (vnorm(vdiff(solution, p4))-r4)*(vnorm(vdiff(solution, p4))-r4));
+ best_gdoprate = gdoprate_compare1;
+
+ /* save the previous result */
+ solution_compare2.x = solution_compare1.x;
+ solution_compare2.y = solution_compare1.y;
+ solution_compare2.z = solution_compare1.z;
+ //error_3dcompare2 = error_3dcompare1;
+ gdoprate_compare2 = gdoprate_compare1;
+
+ combination = 5 - combination_counter;
+
+ ptemp = p1; p1 = p2; p2 = p3; p3 = p4; p4 = ptemp;
+ rtemp = r1; r1 = r2; r2 = r3; r3 = r4; r4 = rtemp;
+ combination_counter--;
+
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ {
+ //trilateration_errcounter++;
+ trilateration_errcounter = 4;
+ combination_counter = 0;
+ }
+
+ //ptemp = p1; p1 = p2; p2 = p3; p3 = p4; p4 = ptemp;
+ //rtemp = r1; r1 = r2; r2 = r3; r3 = r4; r4 = rtemp;
+ //combination_counter--;
+ //qDebug() << "while(combination_counter)" << combination_counter;
+
+ } while (combination_counter != 0);
+
+ // if it gives error for all 4 sphere combinations then no valid result is given
+ // otherwise return the trilateration mode used
+ if (trilateration_errcounter >= 4) return -1; else return trilateration_mode34;
+ }
+
+ // TODO: Test that changes made to this do not break the algorithm
+ public int getLocation(Vec3d best_solution, int use4thAnchor, Vec3d[] anchorArray, double[] distanceArray)
+ {
+
+ Vec3d o1 = new Vec3d(0, 0, 0), o2 = new Vec3d(0, 0, 0),
+ p1 = new Vec3d(0, 0, 0), p2 = new Vec3d(0, 0, 0),
+ p3 = new Vec3d(0, 0, 0), p4 = new Vec3d(0, 0, 0);
+ double r1 = 0, r2 = 0, r3 = 0, r4 = 0;
+ int combination = 0;
+ int result;
+
+ Vec3d t3;
+ double dist1, dist2;
+
+ /* Anchors coordinate */
+ p1.x = anchorArray[0].x; p1.y = anchorArray[0].y; p1.z = anchorArray[0].z;
+ p2.x = anchorArray[1].x; p2.y = anchorArray[1].y; p2.z = anchorArray[1].z;
+ p3.x = anchorArray[2].x; p3.y = anchorArray[2].y; p3.z = anchorArray[2].z;
+ p4.x = anchorArray[0].x; p4.y = anchorArray[0].y; p4.z = anchorArray[0].z; //4th same as 1st - only 3 used for trilateration
+
+
+ //Changed to use meters by default
+ r1 = distanceArray[0];
+ r2 = distanceArray[1];
+ r3 = distanceArray[2];
+ r4 = distanceArray[3];
+
+// r1 = (double) distanceArray[0] / 1000.0;
+// r2 = (double) distanceArray[1] / 1000.0;
+// r3 = (double) distanceArray[2] / 1000.0;
+// r4 = (double) distanceArray[3] / 1000.0;
+
+ //qDebug() << "GetLocation" << r1 << r2 << r3 << r4;
+
+ //r4 = r1;
+
+ /* get the best location using 3 or 4 spheres and keep it as know_best_location */
+ result = deca_3dlocate(o1, o2, best_solution,
+ p1, r1, p2, r2, p3, r3, p4, r1, combination);
+
+
+ //qDebug() << "GetLocation" << result << "sol1: " << o1.x << o1.y << o1.z << " sol2: " << o2.x << o2.y << o2.z;
+
+ if(result >= 0)
+ {
+ if (use4thAnchor == 1) //if have 4 ranging results, then use 4th anchor to pick solution closest to it
+ {
+ double diff1, diff2;
+ /* find dist1 as the distance of o1 to known_best_location */
+ t3 = vdiff(o1, anchorArray[3]);
+ dist1 = vnorm(t3);
+
+ t3 = vdiff(o2, anchorArray[3]);
+ dist2 = vnorm(t3);
+
+ /* find the distance closest to received range measurement from 4th anchor */
+ diff1 = Math.abs(r4 - dist1);
+ diff2 = Math.abs(r4 - dist2);
+
+ /* pick the closest match to the 4th anchor range */
+ if (diff1 < diff2)
+ {
+ best_solution.x = o1.x;
+ best_solution.y = o1.y;
+ best_solution.z = o1.z;
+ }
+ else
+ {
+ best_solution.x = o2.x;
+ best_solution.y = o2.y;
+ best_solution.z = o2.z;
+ }
+ }
+ else
+ {
+ //assume tag is below the anchors (1, 2, and 3)
+ if(o1.z < p1.z)
+ {
+ best_solution.x = o1.x;
+ best_solution.y = o1.y;
+ best_solution.z = o1.z;
+ }
+ else
+ {
+ best_solution.x = o2.x;
+ best_solution.y = o2.y;
+ best_solution.z = o2.z;
+ }
+ }
+ }
+
+ if (result >= 0)
+ {
+ return result;
+ }
+
+ //return error
+ return -1;
+ }
+}
diff --git a/sensorhub-android-blebeacon/src/main/res/values/strings.xml b/sensorhub-android-blebeacon/src/main/res/values/strings.xml
new file mode 100644
index 00000000..78b089d9
--- /dev/null
+++ b/sensorhub-android-blebeacon/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ sensorhub-android-blebeacon
+
diff --git a/sensorhub-android-blebeacon/src/test/java/org/sensorhub/impl/sensor/blebeacon/ExampleUnitTest.java b/sensorhub-android-blebeacon/src/test/java/org/sensorhub/impl/sensor/blebeacon/ExampleUnitTest.java
new file mode 100644
index 00000000..5ee2a7eb
--- /dev/null
+++ b/sensorhub-android-blebeacon/src/test/java/org/sensorhub/impl/sensor/blebeacon/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package org.sensorhub.impl.sensor.blebeacon;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/sensorhub-android-lib/build.gradle b/sensorhub-android-lib/build.gradle
index fbb0c756..70e2b5a7 100644
--- a/sensorhub-android-lib/build.gradle
+++ b/sensorhub-android-lib/build.gradle
@@ -10,16 +10,18 @@ dependencies {
api project(':sensorhub-driver-android')
api project(':sensorhub-android-flirone')
//api project(':sensorhub-android-dji')
+ api project(':sensorhub-storage-h2')
implementation 'org.slf4j:slf4j-android:1.6.1-RC1'
+ implementation('javax.xml.stream:stax-api:1.0-2')
}
configurations {
api {
// exclude stuff from APK
- exclude group: "javax.servlet"
+// exclude group: "javax.servlet"
exclude group: "xml-apis"
- exclude group: "org.eclipse.jetty"
+// exclude group: "org.eclipse.jetty"
exclude group: "org.n52.amused"
exclude group: "ch.qos.logback"
exclude group: "org.apache.felix"
diff --git a/sensorhub-android-proxysensor/.gitignore b/sensorhub-android-proxysensor/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/sensorhub-android-proxysensor/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/sensorhub-android-proxysensor/build.gradle b/sensorhub-android-proxysensor/build.gradle
new file mode 100644
index 00000000..050001e4
--- /dev/null
+++ b/sensorhub-android-proxysensor/build.gradle
@@ -0,0 +1,37 @@
+plugins {
+ id 'com.android.library'
+}
+
+android {
+ compileSdk 31
+
+ defaultConfig {
+ minSdk 21
+ targetSdk 31
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation project(path: ':sensorml-core')
+ implementation project(path: ':sensorhub-core')
+ implementation project(path: ':sensorhub-service-swe')
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+}
\ No newline at end of file
diff --git a/sensorhub-android-proxysensor/consumer-rules.pro b/sensorhub-android-proxysensor/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/sensorhub-android-proxysensor/proguard-rules.pro b/sensorhub-android-proxysensor/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/sensorhub-android-proxysensor/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/sensorhub-android-proxysensor/src/androidTest/java/org/sensorhub/impl/swe/proxysensor/ExampleInstrumentedTest.java b/sensorhub-android-proxysensor/src/androidTest/java/org/sensorhub/impl/swe/proxysensor/ExampleInstrumentedTest.java
new file mode 100644
index 00000000..8db3f988
--- /dev/null
+++ b/sensorhub-android-proxysensor/src/androidTest/java/org/sensorhub/impl/swe/proxysensor/ExampleInstrumentedTest.java
@@ -0,0 +1,25 @@
+package org.sensorhub.impl.swe.proxysensor;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("org.sensorhub.impl.swe.proxysensor.test", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/sensorhub-android-proxysensor/src/main/AndroidManifest.xml b/sensorhub-android-proxysensor/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..bad8a116
--- /dev/null
+++ b/sensorhub-android-proxysensor/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/ProxySensor.java b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/ProxySensor.java
new file mode 100644
index 00000000..ba7c24f8
--- /dev/null
+++ b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/ProxySensor.java
@@ -0,0 +1,206 @@
+package org.sensorhub.impl.swe.proxysensor;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import org.sensorhub.android.SOSServiceWithIPCConfig;
+import org.sensorhub.api.sensor.ISensorDataInterface;
+import org.sensorhub.impl.sensor.swe.SWEVirtualSensor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.opengis.gml.v32.AbstractFeature;
+import net.opengis.sensorml.v20.AbstractPhysicalProcess;
+import net.opengis.swe.v20.DataBlock;
+import net.opengis.swe.v20.DataChoice;
+import net.opengis.swe.v20.DataComponent;
+
+import org.sensorhub.api.common.SensorHubException;
+import org.sensorhub.impl.client.sos.SOSClient;
+import org.sensorhub.impl.client.sos.SOSClient.SOSRecordListener;
+import org.sensorhub.impl.client.sps.SPSClient;
+import org.sensorhub.impl.sensor.AbstractSensorModule;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+import org.vast.ows.GetCapabilitiesRequest;
+import org.vast.ows.OWSException;
+import org.vast.ows.OWSUtils;
+import org.vast.ows.sos.GetResultRequest;
+import org.vast.ows.sos.SOSOfferingCapabilities;
+import org.vast.ows.sos.SOSServiceCapabilities;
+import org.vast.ows.sos.SOSUtils;
+import org.vast.util.TimeExtent;
+
+public class ProxySensor extends SWEVirtualSensor {
+ // protected static final Logger log = LoggerFactory.getLogger(ProxySensor.class);
+ private static final String TAG = "OSHProxySensor";
+ private static final String SOS_VERSION = "2.0";
+ private static final String SPS_VERSION = "2.0";
+ private static final double STREAM_END_TIME = 2e9; //
+
+ AbstractFeature currentFoi;
+ List sosClients;
+ SPSClient spsClient;
+
+ public static final String ACTION_PROXY = "org.sofwerx.ogc.ACTION_PROXY";
+ private static final String EXTRA_PAYLOAD = "PROXY";
+ private static final String EXTRA_ORIGIN = "src";
+ private Context androidContext;
+
+ public ProxySensor() {
+ super();
+ }
+
+ @Override
+ public void start() throws SensorHubException {
+ androidContext = ((ProxySensorConfig) config).androidContext;
+
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String origin = intent.getStringExtra(EXTRA_ORIGIN);
+ if (!context.getPackageName().equalsIgnoreCase(origin)) {
+ String requestPayload = intent.getStringExtra(EXTRA_PAYLOAD); // TODO: Can be observable property string
+ try {
+ stopSOSStreams();
+ } catch (SensorHubException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ };
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_PROXY);
+
+ androidContext.registerReceiver(receiver, filter);
+
+ Log.d(TAG, "Starting Proxy Sensor");
+
+ checkConfig();
+ removeAllOutputs();
+ removeAllControlInputs();
+ OWSUtils owsUtils = new OWSUtils();
+
+ // create SOS clients, to be started by a different event
+ if (config.sosEndpointUrl != null) {
+ // find matching offering(s) for sensor UID
+ SOSServiceCapabilities caps = null;
+ try {
+ GetCapabilitiesRequest getCap = new GetCapabilitiesRequest();
+ getCap.setService(SOSUtils.SOS);
+ getCap.setVersion(SOS_VERSION);
+ getCap.setGetServer(config.sosEndpointUrl);
+ caps = owsUtils.sendRequest(getCap, false);
+ } catch (OWSException e) {
+ throw new SensorHubException("Cannot retrieve SOS capabilities", e);
+ }
+
+ // scan all offerings and connect to selected ones
+ int outputNum = 1;
+ sosClients = new ArrayList(config.observedProperties.size());
+ for (SOSOfferingCapabilities offering : caps.getLayers()) {
+ if (offering.getMainProcedure().equals(config.sensorUID)) {
+ String offeringID = offering.getIdentifier();
+
+ for (String obsProp : config.observedProperties) {
+ if (offering.getObservableProperties().contains(obsProp)) {
+ // create data request
+ GetResultRequest req = new GetResultRequest();
+ req.setGetServer(config.sosEndpointUrl);
+ req.setVersion(SOS_VERSION);
+ req.setOffering(offeringID);
+ req.getObservables().add(obsProp);
+ req.setTime(TimeExtent.getPeriodStartingNow(STREAM_END_TIME));
+ req.setXmlWrapper(false);
+
+ // create client and retrieve result template
+ SOSClient sos = new SOSClient(req, config.sosUseWebsockets);
+ sosClients.add(sos);
+ sos.retrieveStreamDescription();
+ DataComponent recordDef = sos.getRecordDescription();
+ if (recordDef.getName() == null)
+ recordDef.setName("output" + outputNum);
+
+ // retrieve sensor description from remote SOS if available (first time only)
+ try {
+ if (outputNum == 1 && config.sensorML == null)
+ this.sensorDescription = (AbstractPhysicalProcess) sos.getSensorDescription(config.sensorUID);
+ } catch (SensorHubException e) {
+ Log.d(TAG, "Cannot get remote sensor description.", e);
+ }
+
+ // create output
+ final ProxySensorOutput output = new ProxySensorOutput(this, recordDef, sos.getRecommendedEncoding());
+ this.addOutput(output, false);
+
+ // HACK TO PREVENT GETRESULT TIME ERROR
+// sos.startStream((data) -> output.publishNewRecord(data));
+// try {
+// Thread.sleep(1000);
+// } catch (InterruptedException e) {
+// e.printStackTrace();
+// }
+// sos.stopStream();
+
+ outputNum++;
+ }
+ }
+ }
+ }
+
+ if (sosClients.isEmpty())
+ throw new SensorHubException("Requested observation data is not available from SOS " + config.sosEndpointUrl +
+ ". Check Sensor UID and observed properties have valid values.");
+ }
+
+ }
+
+ public void startSOSStreams() throws SensorHubException {
+ for (int i = 0; i < sosClients.size(); i++) {
+ String name = sosClients.get(i).getRecordDescription().getName();
+ SOSRecordListener rl = new SOSRecordListener() {
+ @Override
+ public void newRecord(DataBlock data) {
+ ProxySensorOutput output = (ProxySensorOutput) getObservationOutputs().get(name);
+ output.publishNewRecord(data);
+ }
+ };
+ sosClients.get(i).startStream(rl);
+ }
+ }
+
+ public void startSOSStream(String outputName) throws SensorHubException {
+ for (SOSClient client : sosClients) {
+ if (client.getRecordDescription().getName().equals(outputName)) {
+ SOSRecordListener recordListener = new SOSRecordListener() {
+ @Override
+ public void newRecord(DataBlock data) {
+ ProxySensorOutput output = (ProxySensorOutput) getObservationOutputs().get(outputName);
+ output.publishNewRecord(data);
+ }
+ };
+ client.startStream(recordListener);
+ break;
+ }
+ }
+ }
+
+ public void stopSOSStreams() throws SensorHubException {
+ for (int i = 0; i < sosClients.size(); i++) {
+ sosClients.get(i).stopStream();
+ }
+ }
+
+ public void stopSOSStream(String outputName) throws SensorHubException {
+ for (SOSClient client : sosClients) {
+ if (client.getRecordDescription().getName().equals(outputName)) {
+ client.stopStream();
+ break;
+ }
+ }
+ }
+}
diff --git a/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/ProxySensorConfig.java b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/ProxySensorConfig.java
new file mode 100644
index 00000000..36910192
--- /dev/null
+++ b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/ProxySensorConfig.java
@@ -0,0 +1,23 @@
+package org.sensorhub.impl.swe.proxysensor;
+
+import android.content.Context;
+
+import org.sensorhub.api.module.ModuleConfig;
+import org.sensorhub.impl.sensor.swe.SWEVirtualSensorConfig;
+
+public class ProxySensorConfig extends SWEVirtualSensorConfig
+{
+ public transient Context androidContext;
+
+ public ProxySensorConfig()
+ {
+ super();
+ this.moduleClass = ProxySensor.class.getCanonicalName();
+ }
+
+ @Override
+ public ModuleConfig clone()
+ {
+ return this; // disable clone for now as it crashes Android app
+ }
+}
diff --git a/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/ProxySensorDescriptor.java b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/ProxySensorDescriptor.java
new file mode 100644
index 00000000..d643d46b
--- /dev/null
+++ b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/ProxySensorDescriptor.java
@@ -0,0 +1,33 @@
+package org.sensorhub.impl.swe.proxysensor;
+
+import org.sensorhub.api.module.IModule;
+import org.sensorhub.api.module.IModuleProvider;
+import org.sensorhub.api.module.ModuleConfig;
+import org.sensorhub.impl.module.JarModuleProvider;
+
+public class ProxySensorDescriptor extends JarModuleProvider implements IModuleProvider
+{
+ @Override
+ public String getModuleName()
+ {
+ return "Proxy Sensor";
+ }
+
+ @Override
+ public String getModuleDescription()
+ {
+ return "Altered SWEVirtualSensor with changes to event listeners implementation sending data.";
+ }
+
+ @Override
+ public Class extends IModule>> getModuleClass()
+ {
+ return ProxySensor.class;
+ }
+
+ @Override
+ public Class extends ModuleConfig> getModuleConfigClass()
+ {
+ return ProxySensorConfig.class;
+ }
+}
diff --git a/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/ProxySensorOutput.java b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/ProxySensorOutput.java
new file mode 100644
index 00000000..912c6d33
--- /dev/null
+++ b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/ProxySensorOutput.java
@@ -0,0 +1,61 @@
+package org.sensorhub.impl.swe.proxysensor;
+
+import android.util.Log;
+
+import net.opengis.swe.v20.DataBlock;
+import net.opengis.swe.v20.DataComponent;
+import net.opengis.swe.v20.DataEncoding;
+
+import org.sensorhub.api.common.IEventListener;
+import org.sensorhub.api.common.SensorHubException;
+import org.sensorhub.impl.sensor.swe.SWEVirtualSensor;
+import org.sensorhub.impl.sensor.swe.SWEVirtualSensorOutput;
+
+import static android.content.ContentValues.TAG;
+
+public class ProxySensorOutput extends SWEVirtualSensorOutput
+{
+ ProxySensor parentSensor;
+ DataComponent recordStructure;
+ DataEncoding recordEncoding;
+
+ public ProxySensorOutput(ProxySensor sensor, DataComponent recordStructure, DataEncoding recordEncoding) {
+ super(sensor, recordStructure, recordEncoding);
+ this.parentSensor = sensor;
+ this.recordStructure = recordStructure;
+ this.recordEncoding = recordEncoding;
+ }
+
+ @Override
+ public void publishNewRecord(DataBlock dataBlock) {
+ super.publishNewRecord(dataBlock);
+ Log.d(TAG, "publishNewRecord: ");
+ }
+
+ @Override
+ public long getLatestRecordTime() {
+ return System.currentTimeMillis();
+ }
+
+ @Override
+ public void registerListener(IEventListener listener)
+ {
+ Log.d(TAG, "Registering Proxy Sensor Listener for: " + this.name);
+ try {
+ this.parentSensor.startSOSStream(this.name);
+ } catch (SensorHubException e) {
+ Log.d(TAG, "Error Starting Stream while registering Proxy Sensor", e);
+ }
+ eventHandler.registerListener(listener);
+ }
+
+ @Override
+ public void unregisterListener(IEventListener listener) {
+ try {
+ this.parentSensor.stopSOSStream(this.name);
+ Log.d(TAG, "unregisterListener: Stopping stream: " + this.name);
+ } catch (SensorHubException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/mqtt/IMqttSubscriber.java b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/mqtt/IMqttSubscriber.java
new file mode 100644
index 00000000..2265b953
--- /dev/null
+++ b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/mqtt/IMqttSubscriber.java
@@ -0,0 +1,21 @@
+/***************************** BEGIN LICENSE BLOCK ***************************
+
+ The contents of this file are subject to the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one
+ at http://mozilla.org/MPL/2.0/.
+
+ Software distributed under the License is distributed on an "AS IS" basis,
+ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ for the specific language governing rights and limitations under the License.
+
+ Copyright (C) 2020 Botts Innovative Research, Inc. All Rights Reserved.
+
+ ******************************* END LICENSE BLOCK ***************************/
+package org.sensorhub.impl.swe.proxysensor.mqtt;
+
+public interface IMqttSubscriber {
+
+ void subscribeToTopics();
+
+ void onMessage(String message);
+}
diff --git a/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/mqtt/MqttConnectionListener.java b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/mqtt/MqttConnectionListener.java
new file mode 100644
index 00000000..f53edcef
--- /dev/null
+++ b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/mqtt/MqttConnectionListener.java
@@ -0,0 +1,56 @@
+/***************************** BEGIN LICENSE BLOCK ***************************
+
+ The contents of this file are subject to the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one
+ at http://mozilla.org/MPL/2.0/.
+
+ Software distributed under the License is distributed on an "AS IS" basis,
+ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ for the specific language governing rights and limitations under the License.
+
+ Copyright (C) 2020 Botts Innovative Research, Inc. All Rights Reserved.
+
+ ******************************* END LICENSE BLOCK ***************************/
+package org.sensorhub.impl.swe.proxysensor.mqtt;
+
+import android.util.Log;
+
+import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions;
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttToken;
+
+public class MqttConnectionListener implements IMqttActionListener {
+
+ private static final String TAG = "MqttConnectionListener";
+
+ private MqttHelper mqttHelper;
+ private IMqttSubscriber subscriber;
+
+ public MqttConnectionListener(MqttHelper helper, IMqttSubscriber subscriber) {
+
+ mqttHelper = helper;
+
+ this.subscriber = subscriber;
+ }
+
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+
+ Log.d(TAG, "Connected");
+
+ DisconnectedBufferOptions options = new DisconnectedBufferOptions();
+ options.setBufferEnabled(true);
+ options.setBufferSize(100);
+ options.setPersistBuffer(false);
+ options.setDeleteOldestMessages(false);
+ mqttHelper.setDisconnectedBufferOptions(options);
+
+ subscriber.subscribeToTopics();
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
+
+ Log.d(TAG, "Connection failure", exception);
+ }
+}
diff --git a/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/mqtt/MqttHelper.java b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/mqtt/MqttHelper.java
new file mode 100644
index 00000000..90b08c3c
--- /dev/null
+++ b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/mqtt/MqttHelper.java
@@ -0,0 +1,156 @@
+/***************************** BEGIN LICENSE BLOCK ***************************
+
+ The contents of this file are subject to the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one
+ at http://mozilla.org/MPL/2.0/.
+
+ Software distributed under the License is distributed on an "AS IS" basis,
+ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ for the specific language governing rights and limitations under the License.
+
+ Copyright (C) 2020 Botts Innovative Research, Inc. All Rights Reserved.
+
+ ******************************* END LICENSE BLOCK ***************************/
+package org.sensorhub.impl.swe.proxysensor.mqtt;
+
+import android.content.Context;
+import android.util.Log;
+
+import org.eclipse.paho.android.service.MqttAndroidClient;
+import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions;
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttToken;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+/**
+ *
+ * Android MQTT wrapper
+ *
+ *
+ * @author Nick Garay
+ * @since Jan 10, 2020
+ */
+public class MqttHelper {
+
+ private final String TAG = "MqttHelper";
+ private MqttAndroidClient client;
+
+ public MqttHelper() {
+ }
+
+ public IMqttToken connect(Context context, String url) {
+
+ IMqttToken connectionToken = null;
+
+ String clientId = MqttClient.generateClientId();
+
+ client = new MqttAndroidClient(context, url, clientId);
+
+ try {
+
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setConnectionTimeout(3000);
+ options.setKeepAliveInterval(300);
+ options.setAutomaticReconnect(true);
+ options.setCleanSession(false);
+
+ connectionToken = client.connect(options);
+
+ }catch(Exception e) {
+
+ Log.d(TAG, "Connection failure", e);
+ }
+
+ return connectionToken;
+ }
+
+ public IMqttToken connect(Context context, String username, String password, String url) {
+
+ IMqttToken connectionToken = null;
+
+ String clientId = MqttClient.generateClientId();
+
+ client = new MqttAndroidClient(context, url, clientId);
+
+ try {
+
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setConnectionTimeout(3000);
+ options.setKeepAliveInterval(300);
+ options.setAutomaticReconnect(true);
+ options.setCleanSession(false);
+ options.setUserName(username);
+ options.setPassword(password.toCharArray());
+
+ connectionToken = client.connect(options);
+
+ }catch(Exception e) {
+
+ Log.d(TAG, "Connection failure", e);
+ }
+
+ return connectionToken;
+ }
+
+ public void setDisconnectedBufferOptions(DisconnectedBufferOptions options) {
+
+ client.setBufferOpts(options);
+ }
+
+ public IMqttToken subscribe(String topic, MqttMessageHandler listener) {
+
+ IMqttToken subscribeToken = null;
+
+ try {
+
+ subscribeToken = client.subscribe(topic, 0, null, listener, listener);
+
+ } catch (MqttException ex) {
+
+ Log.d(TAG, "Subscribe fail", ex);
+ }
+
+ return subscribeToken;
+ }
+
+ public void unsubscribe(String topic) {
+
+ try {
+
+ client.unsubscribe(topic);
+
+ } catch (MqttException ex) {
+
+ Log.d(TAG, "Unsubscribe fail", ex);
+ }
+ }
+
+ public void disconnect() {
+
+ try {
+
+ client.disconnect().setActionCallback(new IMqttActionListener() {
+
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+
+ Log.d(TAG, "Disconnected");
+ client.unregisterResources();
+ client.close();
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
+
+ Log.d(TAG, "Failed to disconnect", exception);
+ }
+ });
+
+ } catch(Exception e) {
+
+ Log.d(TAG, "Failed to disconnect", e);
+ }
+ }
+}
diff --git a/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/mqtt/MqttMessageHandler.java b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/mqtt/MqttMessageHandler.java
new file mode 100644
index 00000000..547deb90
--- /dev/null
+++ b/sensorhub-android-proxysensor/src/main/java/org/sensorhub/impl/swe/proxysensor/mqtt/MqttMessageHandler.java
@@ -0,0 +1,68 @@
+/***************************** BEGIN LICENSE BLOCK ***************************
+
+ The contents of this file are subject to the Mozilla Public License, v. 2.0.
+ If a copy of the MPL was not distributed with this file, You can obtain one
+ at http://mozilla.org/MPL/2.0/.
+
+ Software distributed under the License is distributed on an "AS IS" basis,
+ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ for the specific language governing rights and limitations under the License.
+
+ Copyright (C) 2020 Botts Innovative Research, Inc. All Rights Reserved.
+
+ ******************************* END LICENSE BLOCK ***************************/
+package org.sensorhub.impl.swe.proxysensor.mqtt;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
+import org.eclipse.paho.client.mqttv3.IMqttToken;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+public class MqttMessageHandler extends Handler implements IMqttActionListener, IMqttMessageListener {
+
+ private static final String TAG = "MqttMessageHandler";
+
+ private IMqttSubscriber subscriber;
+
+ public MqttMessageHandler(IMqttSubscriber subscriber) {
+
+ super();
+
+ this.subscriber = subscriber;
+ }
+
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+
+ Log.d(TAG,"Subscribed - Topic: " + asyncActionToken.getTopics().toString());
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
+
+ Log.d(TAG, "Subscribe fail", exception);
+ }
+
+ @Override
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+
+ Log.d(TAG, "Topic: " + topic);
+ Bundle bundle = new Bundle();
+ bundle.putString("mqtt-message", message.toString());
+ Message msg = new Message();
+ msg.setData(bundle);
+ this.sendMessage(msg);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+
+ String mqttMessage = message.getData().getString("mqtt-message");
+ subscriber.onMessage(mqttMessage);
+ }
+}
diff --git a/sensorhub-android-proxysensor/src/test/java/org/sensorhub/impl/swe/proxysensor/ExampleUnitTest.java b/sensorhub-android-proxysensor/src/test/java/org/sensorhub/impl/swe/proxysensor/ExampleUnitTest.java
new file mode 100644
index 00000000..1bd8a253
--- /dev/null
+++ b/sensorhub-android-proxysensor/src/test/java/org/sensorhub/impl/swe/proxysensor/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package org.sensorhub.impl.swe.proxysensor;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/sensorhub-android-service/src/org/sensorhub/android/SensorHubService.java b/sensorhub-android-service/src/org/sensorhub/android/SensorHubService.java
index c46056d7..d73ae5f2 100644
--- a/sensorhub-android-service/src/org/sensorhub/android/SensorHubService.java
+++ b/sensorhub-android-service/src/org/sensorhub/android/SensorHubService.java
@@ -26,12 +26,14 @@
import android.view.SurfaceView;
import android.view.WindowManager;
import org.sensorhub.api.common.IEventListener;
+import org.sensorhub.api.common.SensorHubException;
import org.sensorhub.api.module.IModuleConfigRepository;
import org.sensorhub.api.module.ModuleConfig;
import org.sensorhub.impl.SensorHub;
import org.sensorhub.impl.SensorHubConfig;
import org.sensorhub.impl.common.EventBus;
import org.sensorhub.impl.module.ModuleRegistry;
+import org.sensorhub.impl.service.HttpServer;
import org.vast.xml.XMLImplFinder;
import android.app.Service;
import android.content.Intent;
@@ -133,6 +135,15 @@ public void run() {
sensorhub.stop();
SensorHub.clearInstance();
sensorhub = null;
+
+ // Make sure the server gets cleaned up
+ try {
+ if (HttpServer.getInstance() != null) {
+ HttpServer.getInstance().cleanup();
+ }
+ } catch (SensorHubException e) {
+ e.printStackTrace();
+ }
}
});
}
diff --git a/sensorhub-driver-android/AndroidManifest.xml b/sensorhub-driver-android/AndroidManifest.xml
index a11d911f..e9a28838 100644
--- a/sensorhub-driver-android/AndroidManifest.xml
+++ b/sensorhub-driver-android/AndroidManifest.xml
@@ -2,4 +2,7 @@
package="org.sensorhub.impl.sensor.android"
android:versionCode="1"
android:versionName="1.0" >
+
+
+
diff --git a/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/AndroidSensorsConfig.java b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/AndroidSensorsConfig.java
index eb505e95..7c2558b2 100644
--- a/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/AndroidSensorsConfig.java
+++ b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/AndroidSensorsConfig.java
@@ -17,6 +17,7 @@
import android.graphics.SurfaceTexture;
import org.sensorhub.api.module.ModuleConfig;
import org.sensorhub.api.sensor.SensorConfig;
+import org.sensorhub.impl.sensor.android.audio.AudioEncoderConfig;
import org.sensorhub.impl.sensor.android.video.VideoEncoderConfig;
import android.content.Context;
@@ -39,16 +40,21 @@ public class AndroidSensorsConfig extends SensorConfig
public boolean activateOrientationEuler = true;
public boolean activateGpsLocation = true;
public boolean activateNetworkLocation = false;
+ public int selectedCameraId = 0;
public boolean activateBackCamera = false;
public boolean activateFrontCamera = false;
+ public boolean enableCamera = false;
public VideoEncoderConfig videoConfig = new VideoEncoderConfig();
public boolean outputVideoRoll = false;
-
+ public boolean activateMicAudio = false;
+ public AudioEncoderConfig audioConfig = new AudioEncoderConfig();
+
+
public String deviceName;
public String runName;
public String runDescription;
-
-
+
+
public AndroidSensorsConfig()
{
this.moduleClass = AndroidSensorsDriver.class.getCanonicalName();
diff --git a/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/AndroidSensorsDriver.java b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/AndroidSensorsDriver.java
index d1e1e4dc..fe4c8215 100644
--- a/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/AndroidSensorsDriver.java
+++ b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/AndroidSensorsDriver.java
@@ -37,6 +37,9 @@
import org.sensorhub.api.sensor.ISensorDataInterface;
import org.sensorhub.api.sensor.SensorException;
import org.sensorhub.impl.sensor.AbstractSensorModule;
+import org.sensorhub.impl.sensor.android.audio.AndroidAudioOutputAAC;
+import org.sensorhub.impl.sensor.android.audio.AndroidAudioOutputOPUS;
+import org.sensorhub.impl.sensor.android.audio.AudioEncoderConfig;
import org.sensorhub.impl.sensor.android.video.AndroidCameraOutputH264;
import org.sensorhub.impl.sensor.android.video.AndroidCameraOutputH265;
import org.sensorhub.impl.sensor.android.video.AndroidCameraOutputMJPEG;
@@ -141,6 +144,9 @@ public synchronized void init() throws SensorHubException
if (androidContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY))
createCameraOutputs(androidContext);
+ // create data interfaces for audio
+ if (androidContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE))
+ createAudioOutputs(androidContext);
}
@@ -160,6 +166,8 @@ public void start() throws SensorException
@SuppressWarnings("deprecation")
protected void createCameraOutputs(Context androidContext) throws SensorException
{
+ int selectedCameraId = config.selectedCameraId;
+
/*if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.LOLLIPOP)
{
CameraManager cameraManager = (CameraManager)androidContext.getSystemService(Context.CAMERA_SERVICE);
@@ -185,34 +193,37 @@ protected void createCameraOutputs(Context androidContext) throws SensorExceptio
}
else*/
{
+ // TODO: check if the rotation issue may actually stem from here
for (int cameraId = 0; cameraId < android.hardware.Camera.getNumberOfCameras(); cameraId++)
{
android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
- android.hardware.Camera.getCameraInfo(cameraId, info);
+// android.hardware.Camera.getCameraInfo(cameraId, info);
+ android.hardware.Camera.getCameraInfo(selectedCameraId, info);
- if ( (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK && config.activateBackCamera) ||
- (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT && config.activateFrontCamera))
+ /*if ( (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK && config.activateBackCamera) ||
+ (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT && config.activateFrontCamera))*/
+ if(config.enableCamera)
{
SurfaceTexture camPreviewTexture = SensorHubService.getVideoTexture();
if (VideoEncoderConfig.JPEG_CODEC.equals(config.videoConfig.codec)) {
- useCamera(new AndroidCameraOutputMJPEG(this, cameraId, camPreviewTexture), cameraId);
+ useCamera(new AndroidCameraOutputMJPEG(this, selectedCameraId, camPreviewTexture), selectedCameraId);
break;
}
else if (VideoEncoderConfig.H264_CODEC.equals(config.videoConfig.codec)) {
- useCamera(new AndroidCameraOutputH264(this, cameraId, camPreviewTexture), cameraId);
+ useCamera(new AndroidCameraOutputH264(this, selectedCameraId, camPreviewTexture), selectedCameraId);
// try a break to test
break;
}
else if (VideoEncoderConfig.H265_CODEC.equals(config.videoConfig.codec)) {
- useCamera(new AndroidCameraOutputH265(this, cameraId, camPreviewTexture), cameraId);
+ useCamera(new AndroidCameraOutputH265(this, selectedCameraId, camPreviewTexture), selectedCameraId);
break;
}
else if (VideoEncoderConfig.VP9_CODEC.equals(config.videoConfig.codec)) {
- useCamera(new AndroidCameraOutputVP9(this, cameraId, camPreviewTexture), cameraId);
+ useCamera(new AndroidCameraOutputVP9(this, selectedCameraId, camPreviewTexture), selectedCameraId);
break;
}
else if (VideoEncoderConfig.VP8_CODEC.equals(config.videoConfig.codec)) {
- useCamera(new AndroidCameraOutputVP8(this, cameraId, camPreviewTexture), cameraId);
+ useCamera(new AndroidCameraOutputVP8(this, selectedCameraId, camPreviewTexture), selectedCameraId);
break;
}
else
@@ -223,6 +234,19 @@ else if (VideoEncoderConfig.VP8_CODEC.equals(config.videoConfig.codec)) {
}
+ protected void createAudioOutputs(Context androidContext) throws SensorException
+ {
+ if(config.activateMicAudio) {
+ if (AudioEncoderConfig.AAC_CODEC.equals(config.audioConfig.codec))
+ useAudio(new AndroidAudioOutputAAC(this), "MIC");
+ else if (AudioEncoderConfig.OPUS_CODEC.equals(config.audioConfig.codec))
+ useAudio(new AndroidAudioOutputOPUS(this), "MIC");
+ else
+ throw new SensorException("Unsupported codec " + config.audioConfig.codec);
+ }
+ }
+
+
protected void useSensor(ISensorDataInterface output, Sensor sensor)
{
addOutput(output, false);
@@ -255,6 +279,14 @@ protected void useCamera2(ISensorDataInterface output, String cameraId)
}
+ protected void useAudio(ISensorDataInterface output, String srcName)
+ {
+ addOutput(output, false);
+ smlComponents.add(smlBuilder.getAudioComponentDescription(srcName));
+ log.info("Getting data from audio source " + srcName);
+ }
+
+
@Override
public void stop() throws SensorException
{
diff --git a/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/SensorMLBuilder.java b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/SensorMLBuilder.java
index 47e2db2e..23f8673c 100644
--- a/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/SensorMLBuilder.java
+++ b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/SensorMLBuilder.java
@@ -1,24 +1,24 @@
-/***************************** BEGIN LICENSE BLOCK ***************************
-
-The contents of this file are subject to the Mozilla Public License, v. 2.0.
-If a copy of the MPL was not distributed with this file, You can obtain one
-at http://mozilla.org/MPL/2.0/.
-
-Software distributed under the License is distributed on an "AS IS" basis,
-WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-for the specific language governing rights and limitations under the License.
-
-Copyright (C) 2012-2015 Sensia Software LLC. All Rights Reserved.
-
-******************************* END LICENSE BLOCK ***************************/
-
+/***************************** BEGIN LICENSE BLOCK ***************************
+
+The contents of this file are subject to the Mozilla Public License, v. 2.0.
+If a copy of the MPL was not distributed with this file, You can obtain one
+at http://mozilla.org/MPL/2.0/.
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+for the specific language governing rights and limitations under the License.
+
+Copyright (C) 2012-2015 Sensia Software LLC. All Rights Reserved.
+
+******************************* END LICENSE BLOCK ***************************/
+
package org.sensorhub.impl.sensor.android;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vast.data.SWEFactory;
import org.vast.sensorML.SMLFactory;
-import net.opengis.sensorml.v20.PhysicalComponent;
+import net.opengis.sensorml.v20.PhysicalComponent;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.location.LocationManager;
@@ -93,18 +93,27 @@ else if (val instanceof Object[])
}*/
return comp;
- }
-
-
- /*
- * Version to support older camera API on devices with SDK < 21
- */
- public PhysicalComponent getComponentDescription(int cameraId)
- {
- PhysicalComponent comp = smlFac.newPhysicalComponent();
- comp.setId("CAM_" + cameraId);
- comp.setName("Android Camera #" + cameraId);
- return comp;
+ }
+
+
+ /*
+ * Version to support older camera API on devices with SDK < 21
+ */
+ public PhysicalComponent getComponentDescription(int cameraId)
+ {
+ PhysicalComponent comp = smlFac.newPhysicalComponent();
+ comp.setId("CAM_" + cameraId);
+ comp.setName("Android Camera #" + cameraId);
+ return comp;
+ }
+
+
+ public PhysicalComponent getAudioComponentDescription(String audioSrcName)
+ {
+ PhysicalComponent comp = smlFac.newPhysicalComponent();
+ comp.setId("AUDIO_" + audioSrcName);
+ comp.setName("Android Audio " + audioSrcName);
+ return comp;
}
diff --git a/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/audio/AndroidAudioOutput.java b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/audio/AndroidAudioOutput.java
new file mode 100644
index 00000000..ea0f6c48
--- /dev/null
+++ b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/audio/AndroidAudioOutput.java
@@ -0,0 +1,400 @@
+/***************************** BEGIN LICENSE BLOCK ***************************
+
+The contents of this file are subject to the Mozilla Public License, v. 2.0.
+If a copy of the MPL was not distributed with this file, You can obtain one
+at http://mozilla.org/MPL/2.0/.
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+for the specific language governing rights and limitations under the License.
+
+Copyright (C) 2012-2015 Sensia Software LLC. All Rights Reserved.
+
+******************************* END LICENSE BLOCK ***************************/
+
+package org.sensorhub.impl.sensor.android.audio;
+
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.hardware.Camera.Parameters;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaRecorder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+
+import net.opengis.swe.v20.BinaryBlock;
+import net.opengis.swe.v20.BinaryComponent;
+import net.opengis.swe.v20.BinaryEncoding;
+import net.opengis.swe.v20.ByteEncoding;
+import net.opengis.swe.v20.ByteOrder;
+import net.opengis.swe.v20.DataBlock;
+import net.opengis.swe.v20.DataComponent;
+import net.opengis.swe.v20.DataEncoding;
+import net.opengis.swe.v20.DataRecord;
+import net.opengis.swe.v20.DataStream;
+import net.opengis.swe.v20.DataType;
+import net.opengis.swe.v20.Quantity;
+
+import org.sensorhub.algo.vecmath.Vect3d;
+import org.sensorhub.api.sensor.SensorDataEvent;
+import org.sensorhub.api.sensor.SensorException;
+import org.sensorhub.impl.sensor.AbstractSensorOutput;
+import org.sensorhub.impl.sensor.android.AndroidSensorsDriver;
+import org.sensorhub.impl.sensor.android.IAndroidOutput;
+import org.sensorhub.impl.sensor.android.video.VideoEncoderConfig;
+import org.sensorhub.impl.sensor.videocam.VideoCamHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.vast.cdm.common.CDMException;
+import org.vast.data.AbstractDataBlock;
+import org.vast.data.DataBlockMixed;
+import org.vast.swe.SWEConstants;
+import org.vast.swe.SWEHelper;
+import org.vast.swe.helper.GeoPosHelper;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+
+
+/**
+ *
+ * Implementation of data interface capturing audio data using Android
+ * AudioRecord API. See https://www.codota.com/code/java/classes/android.media.AudioRecord
+ *
+ *
+ * @author Alex Robin
+ * @since Mar 22, 2021
+ */
+@SuppressWarnings("deprecation")
+public abstract class AndroidAudioOutput extends AbstractSensorOutput implements IAndroidOutput
+{
+ // keep logger name short because in LogCat it's max 23 chars
+ static final Logger log = LoggerFactory.getLogger(AndroidAudioOutput.class.getSimpleName());
+
+ DataComponent dataStruct;
+ DataEncoding dataEncoding;
+ int sampleRateHz = 11025;
+ int pcmEncoding = AudioFormat.ENCODING_PCM_16BIT;
+ int numSamplesPerRecord;
+ int bytesPerSample;
+ int bytesPerRecord;
+ int bitrate = 64 * 1000; // bits/s
+
+ Looper bgLooper;
+ AudioRecord audioRecord;
+ long systemTimeOffset = -1L;
+ long lastRecordTime = 0;
+ byte[] codecInfoData;
+ MediaCodec mCodec;
+ BufferInfo bufferInfo = new BufferInfo();
+ int packetHeaderSize = 0;
+
+ protected abstract String getCodecName();
+
+ protected abstract void initCodec(int inputBufferSize) throws SensorException;
+
+ protected abstract void addPacketHeader(byte[] packet, int packetLen);
+
+
+ protected AndroidAudioOutput(AndroidSensorsDriver parentModule, String name) throws SensorException
+ {
+ super(name, parentModule);
+
+ // init audio recorder and codec
+ initAudio();
+ initCodec(bytesPerRecord);
+ initOutputStructure();
+ }
+
+
+ protected void initOutputStructure()
+ {
+ // create SWE Common data structure and encoding
+ SWEHelper swe = new SWEHelper();
+ String numSamplesId = "NUM_SAMPLES";
+ dataStruct = swe.createRecord()
+ .name(getName())
+ .definition(SWEHelper.getPropertyUri("AudioFrame"))
+ .addField("time", swe.createTime().asPhenomenonTimeIsoUTC().build())
+ .addField("sampleRate", swe.createQuantity()
+ .label("Sample Rate")
+ .description("Number of audio samples per second")
+ .definition(SWEHelper.getQudtUri("DataRate"))
+ .uomCode("Hz"))
+ .addField("numSamples", swe.createCount()
+ .id(numSamplesId)
+ .label("Num Samples")
+ .description("Number of audio samples packaged in this record"))
+ .addField("samples", swe.createArray()
+ .withVariableSize(numSamplesId)
+ .withElement("sample", swe.createCount()
+ .label("Audio Sample")
+ .definition(SWEConstants.DEF_DN)
+ .dataType(DataType.SHORT))
+ .build())
+ .build();
+
+ //////////////////////////
+ // binary encoding spec //
+ //////////////////////////
+ BinaryEncoding dataEnc = swe.newBinaryEncoding();
+ dataEnc.setByteEncoding(ByteEncoding.RAW);
+ dataEnc.setByteOrder(ByteOrder.BIG_ENDIAN);
+ BinaryComponent comp;
+
+ // time stamp
+ comp = swe.newBinaryComponent();
+ comp.setRef("/" + dataStruct.getComponent(0).getName());
+ comp.setCdmDataType(DataType.DOUBLE);
+ dataEnc.addMemberAsComponent(comp);
+
+ // sample rate
+ comp = swe.newBinaryComponent();
+ comp.setRef("/" + dataStruct.getComponent(1).getName());
+ comp.setCdmDataType(DataType.INT);
+ dataEnc.addMemberAsComponent(comp);
+
+ // num samples
+ comp = swe.newBinaryComponent();
+ comp.setRef("/" + dataStruct.getComponent(2).getName());
+ comp.setCdmDataType(DataType.INT);
+ dataEnc.addMemberAsComponent(comp);
+
+ // audio codec
+ BinaryBlock compressedBlock = swe.newBinaryBlock();
+ compressedBlock.setRef("/" + dataStruct.getComponent(3).getName());
+ compressedBlock.setCompression(getCodecName());
+ dataEnc.addMemberAsBlock(compressedBlock);
+
+ try
+ {
+ SWEHelper.assignBinaryEncoding(dataStruct, dataEnc);
+ }
+ catch (CDMException e)
+ {
+ throw new RuntimeException("Invalid binary encoding configuration", e);
+ };
+
+ this.dataEncoding = dataEnc;
+ }
+
+
+ protected void initAudio() throws SensorException
+ {
+ AudioEncoderConfig audioConfig = parentSensor.getConfiguration().audioConfig;
+ sampleRateHz = audioConfig.sampleRate;
+ bitrate = audioConfig.bitRate * 1000;
+
+ if (numSamplesPerRecord <= 0)
+ numSamplesPerRecord = (int)(sampleRateHz/10);
+ bytesPerSample = (pcmEncoding == AudioFormat.ENCODING_PCM_8BIT) ? 1 : 2;
+ bytesPerRecord = numSamplesPerRecord*bytesPerSample;
+
+ // create audio recorder object
+ // we use an internal buffer twice as big as the read buffer
+ // so we have time to compress a chunk while we're recording the next one
+ audioRecord = new AudioRecord(
+ MediaRecorder.AudioSource.MIC,
+ sampleRateHz,
+ AudioFormat.CHANNEL_IN_MONO,
+ pcmEncoding,
+ bytesPerRecord*2);
+ }
+
+
+ @Override
+ public void start(Handler eventHandler) throws SensorException
+ {
+ try
+ {
+ // start codec
+ if (mCodec != null)
+ mCodec.start();
+
+ // reset time
+ lastRecordTime = 0;
+ systemTimeOffset = -1;
+
+ // read audio samples in background thread
+ Thread bgThread = new Thread() {
+ public void run()
+ {
+ try
+ {
+ byte[] buffer = new byte[numSamplesPerRecord*2]; // 16 bits samples
+ audioRecord.startRecording();
+
+ while (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_STOPPED) {
+ readAndEncodeAudioData();
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ };
+ bgThread.start();
+ }
+ catch (Exception e)
+ {
+ throw new SensorException("Cannot start codec " + mCodec.getName(), e);
+ }
+ }
+
+
+ private void readAndEncodeAudioData()
+ {
+ // get next available buffer from codec
+ int inputBufferIndex = mCodec.dequeueInputBuffer(0);
+ if (inputBufferIndex >= 0)
+ {
+ ByteBuffer inputBuffer = mCodec.getInputBuffer(inputBufferIndex);
+ inputBuffer.clear();
+
+ int readBytes = audioRecord.read(inputBuffer, bytesPerRecord);
+ log.debug(lastRecordTime + ": " + readBytes + " audio bytes read");
+ mCodec.queueInputBuffer(inputBufferIndex, 0, readBytes, lastRecordTime, 0);
+ lastRecordTime += (numSamplesPerRecord * 1000000) / sampleRateHz; // in µs
+
+ encode();
+ }
+ }
+
+
+ private void encode()
+ {
+ int outputBufferIndex;
+
+ do {
+ outputBufferIndex = mCodec.dequeueOutputBuffer(bufferInfo, 0);
+ if (outputBufferIndex >= 0) {
+ ByteBuffer outBuffer = mCodec.getOutputBuffer(outputBufferIndex);
+ int outDataSize = outBuffer.remaining();
+ log.debug(bufferInfo.presentationTimeUs + ": " + outDataSize + " compressed audio bytes");
+ int outDataOffset = packetHeaderSize;
+
+ // create buffer to hold packet header + data
+ byte[] outData = new byte[outDataSize + outDataOffset];
+
+ // copy encoded data
+ outBuffer.get(outData, outDataOffset, outDataSize);
+ addPacketHeader(outData, outDataSize);
+
+ // release output buffer
+ mCodec.releaseOutputBuffer(outputBufferIndex, false);
+
+ // send it to output (but skip codec config bytes)
+ if (bufferInfo.flags != MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
+ sendCompressedData(bufferInfo.presentationTimeUs, outData);
+ }
+ }
+ while (outputBufferIndex >= 0);
+ }
+
+
+ protected void sendCompressedData(long timeStamp, byte[] compressedData)
+ {
+ // generate new data record
+ DataBlock newRecord;
+ if (latestRecord == null)
+ newRecord = dataStruct.createDataBlock();
+ else
+ newRecord = latestRecord.renew();
+
+ // set time stamp
+ int idx = 0;
+ double samplingTime = getJulianTimeStamp(timeStamp);
+ newRecord.setDoubleValue(idx++, samplingTime);
+ newRecord.setIntValue(idx++, sampleRateHz);
+ newRecord.setIntValue(idx++, numSamplesPerRecord);
+
+ // set encoded data
+ AbstractDataBlock audioData = ((DataBlockMixed) newRecord).getUnderlyingObject()[idx++];
+ audioData.setUnderlyingObject(compressedData);
+
+ // send event
+ latestRecord = newRecord;
+ latestRecordTime = System.currentTimeMillis();
+ eventHandler.publishEvent(new SensorDataEvent(latestRecordTime, this, latestRecord));
+ }
+
+
+ @Override
+ public void stop()
+ {
+ if (audioRecord != null)
+ {
+ audioRecord.stop();
+ audioRecord = null;
+ }
+
+ if (mCodec != null)
+ {
+ mCodec.stop();
+ mCodec.release();
+ }
+
+ if (bgLooper != null)
+ {
+ bgLooper.quit();
+ bgLooper = null;
+ }
+ }
+
+
+ @Override
+ public double getAverageSamplingPeriod()
+ {
+ return numSamplesPerRecord/(double)sampleRateHz;
+ }
+
+
+ @Override
+ public DataComponent getRecordDescription()
+ {
+ return dataStruct;
+ }
+
+
+ @Override
+ public DataEncoding getRecommendedEncoding()
+ {
+ return dataEncoding;
+ }
+
+
+ @Override
+ public DataBlock getLatestRecord()
+ {
+ return latestRecord;
+ }
+
+
+ @Override
+ public long getLatestRecordTime()
+ {
+ return latestRecordTime;
+ }
+
+
+ protected double getJulianTimeStamp(long sensorTimeStampUs)
+ {
+ long sensorTimeMillis = sensorTimeStampUs / 1000;
+
+ if (systemTimeOffset < 0)
+ systemTimeOffset = System.currentTimeMillis() - sensorTimeMillis;
+
+ return (systemTimeOffset + sensorTimeMillis) / 1000.;
+ }
+}
diff --git a/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/audio/AndroidAudioOutputAAC.java b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/audio/AndroidAudioOutputAAC.java
new file mode 100644
index 00000000..3772f617
--- /dev/null
+++ b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/audio/AndroidAudioOutputAAC.java
@@ -0,0 +1,115 @@
+/***************************** BEGIN LICENSE BLOCK ***************************
+
+The contents of this file are subject to the Mozilla Public License, v. 2.0.
+If a copy of the MPL was not distributed with this file, You can obtain one
+at http://mozilla.org/MPL/2.0/.
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+for the specific language governing rights and limitations under the License.
+
+Copyright (C) 2012-2015 Sensia Software LLC. All Rights Reserved.
+
+******************************* END LICENSE BLOCK ***************************/
+
+package org.sensorhub.impl.sensor.android.audio;
+
+import android.graphics.SurfaceTexture;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+
+import org.sensorhub.api.sensor.SensorException;
+import org.sensorhub.impl.sensor.android.AndroidSensorsDriver;
+
+
+/**
+ *
+ * Implementation of data interface capturing audio data and compressing it
+ * using AAC codec.
+ *
+ *
+ * @author Alex Robin
+ * @since March 22, 2021
+ */
+@SuppressWarnings("deprecation")
+public class AndroidAudioOutputAAC extends AndroidAudioOutput
+{
+ private static final String CODEC_NAME = "AAC";
+ private static int[] ADTS_FREQ_TABLE = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 };
+
+ int adtsFreqIdx;
+
+
+ public AndroidAudioOutputAAC(AndroidSensorsDriver parentModule) throws SensorException
+ {
+ super(parentModule, "audio" + "_" + CODEC_NAME);
+ }
+
+ @Override
+ protected String getCodecName()
+ {
+ return CODEC_NAME;
+ }
+
+ @Override
+ protected void initCodec(int inputBufferSize) throws SensorException
+ {
+ try {
+ final String audioCodec = MediaFormat.MIMETYPE_AUDIO_AAC;
+ mCodec = MediaCodec.createEncoderByType(audioCodec);
+ MediaFormat mediaFormat = MediaFormat.createAudioFormat(audioCodec, sampleRateHz, 1);
+ mediaFormat.setInteger("pcm-encoding", pcmEncoding);
+ mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, inputBufferSize);
+ mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+ mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
+ mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
+ mCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ log.debug("MediaCodec initialized");
+ }
+ catch (Exception e)
+ {
+ throw new SensorException("Cannot initialize codec " + mCodec.getName(), e);
+ }
+ }
+
+
+ @Override
+ protected void initAudio() throws SensorException
+ {
+ // ADTS always includes 1024 samples per packet so align record size to that
+ this.numSamplesPerRecord = 1024;
+ super.initAudio();
+
+ // set ADTS packet header size
+ packetHeaderSize = 7;
+
+ // compute sample rate index to be used in ADTS headers
+ adtsFreqIdx = -1;
+ for (int i = 0; i < ADTS_FREQ_TABLE.length; i++)
+ {
+ if (ADTS_FREQ_TABLE[i] == sampleRateHz)
+ adtsFreqIdx = i;
+ }
+ if (adtsFreqIdx < 0)
+ throw new SensorException("Unsupported sample rate for AAC: " + sampleRateHz);
+ }
+
+
+ protected void addPacketHeader(byte[] packet, int dataLen)
+ {
+ int profile = 2; // AAC LC
+ int freqIdx = adtsFreqIdx;
+ int chanCfg = 1; // mono
+ int frameLen = dataLen + packetHeaderSize;
+
+ // fill in ADTS data
+ packet[0] = (byte)0xFF;
+ packet[1] = (byte)0xF1;
+ packet[2] = (byte)(((profile - 1) << 6) + (freqIdx << 2) +(chanCfg >> 2));
+ packet[3] = (byte)(((chanCfg & 3) << 6) + (frameLen >> 11));
+ packet[4] = (byte)((frameLen & 0x7FF) >> 3);
+ packet[5] = (byte)(((frameLen & 7) << 5) + 0x1F);
+ packet[6] = (byte)0xFC;
+ }
+}
diff --git a/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/audio/AndroidAudioOutputOPUS.java b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/audio/AndroidAudioOutputOPUS.java
new file mode 100644
index 00000000..588e3e8b
--- /dev/null
+++ b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/audio/AndroidAudioOutputOPUS.java
@@ -0,0 +1,87 @@
+/***************************** BEGIN LICENSE BLOCK ***************************
+
+The contents of this file are subject to the Mozilla Public License, v. 2.0.
+If a copy of the MPL was not distributed with this file, You can obtain one
+at http://mozilla.org/MPL/2.0/.
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+for the specific language governing rights and limitations under the License.
+
+Copyright (C) 2012-2015 Sensia Software LLC. All Rights Reserved.
+
+******************************* END LICENSE BLOCK ***************************/
+
+package org.sensorhub.impl.sensor.android.audio;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+
+import org.sensorhub.api.sensor.SensorException;
+import org.sensorhub.impl.sensor.android.AndroidSensorsDriver;
+
+
+/**
+ *
+ * Implementation of data interface capturing audio data and compressing it
+ * using the Opus codec.
+ *
+ *
+ * @author Alex Robin
+ * @since March 29, 2021
+ */
+@SuppressWarnings("deprecation")
+public class AndroidAudioOutputOPUS extends AndroidAudioOutput
+{
+ private static final String CODEC_NAME = "OPUS";
+
+ int adtsFreqIdx;
+
+
+ public AndroidAudioOutputOPUS(AndroidSensorsDriver parentModule) throws SensorException
+ {
+ super(parentModule, "audio" + "_" + CODEC_NAME);
+ }
+
+ @Override
+ protected String getCodecName()
+ {
+ return CODEC_NAME;
+ }
+
+ @Override
+ protected void initCodec(int inputBufferSize) throws SensorException
+ {
+ try {
+ final String audioCodec = MediaFormat.MIMETYPE_AUDIO_OPUS;
+ mCodec = MediaCodec.createEncoderByType(audioCodec);
+ MediaFormat mediaFormat = MediaFormat.createAudioFormat(audioCodec, sampleRateHz, 1);
+ mediaFormat.setInteger("pcm-encoding", pcmEncoding);
+ mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, inputBufferSize);
+ mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+ mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
+ mCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ log.debug("MediaCodec initialized");
+ }
+ catch (Exception e)
+ {
+ throw new SensorException("Cannot initialize codec " + mCodec.getName(), e);
+ }
+ }
+
+
+ @Override
+ protected void initAudio() throws SensorException
+ {
+ super.initAudio();
+
+ // no header for opus
+ packetHeaderSize = 0;
+ }
+
+
+ protected void addPacketHeader(byte[] packet, int dataLen)
+ {
+ }
+}
diff --git a/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/audio/AudioEncoderConfig.java b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/audio/AudioEncoderConfig.java
new file mode 100644
index 00000000..7525b4a5
--- /dev/null
+++ b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/audio/AudioEncoderConfig.java
@@ -0,0 +1,40 @@
+/***************************** BEGIN LICENSE BLOCK ***************************
+
+The contents of this file are subject to the Mozilla Public License, v. 2.0.
+If a copy of the MPL was not distributed with this file, You can obtain one
+at http://mozilla.org/MPL/2.0/.
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+for the specific language governing rights and limitations under the License.
+
+Copyright (C) 2012-2015 Sensia Software LLC. All Rights Reserved.
+
+******************************* END LICENSE BLOCK ***************************/
+
+package org.sensorhub.impl.sensor.android.audio;
+
+
+/**
+ *
+ * Configuration of audio encoding parameters
+ *
+ *
+ * @author Alex Robin
+ * @since Mar 22, 2021
+ */
+public class AudioEncoderConfig
+{
+ public final static String AAC_CODEC = "AAC";
+ public final static String AMRNB_CODEC = "AMR-NB";
+ public final static String AMRWB_CODEC = "AMR-WB";
+ public final static String FLAC_CODEC = "FLAC";
+ public final static String OPUS_CODEC = "OPUS";
+ public final static String VORBIS_CODEC = "VORBIS";
+ public final static String PCM_CODEC = "PCM";
+
+
+ public String codec = AAC_CODEC;
+ public int sampleRate = 8000; // Hz
+ public int bitRate = 64; // kbits/sec
+}
diff --git a/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/video/AndroidCameraOutput.java b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/video/AndroidCameraOutput.java
index c8240a54..a0c8ef13 100644
--- a/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/video/AndroidCameraOutput.java
+++ b/sensorhub-driver-android/src/main/java/org/sensorhub/impl/sensor/android/video/AndroidCameraOutput.java
@@ -88,7 +88,6 @@ public abstract class AndroidCameraOutput extends AbstractSensorOutput
}
}
+include ':sensorhub-android-proxysensor'