diff --git a/sensorhub-android-app/build.gradle b/sensorhub-android-app/build.gradle
index b60fc16f..bf752963 100644
--- a/sensorhub-android-app/build.gradle
+++ b/sensorhub-android-app/build.gradle
@@ -27,6 +27,7 @@ dependencies {
implementation project(path: ':sensorhub-datastore-h2')
implementation project(path: ':sensorhub-service-sweapi')
implementation project(':sensorhub-android-ste')
+ implementation project(':sensorhub-android-obd2')
}
android {
diff --git a/sensorhub-android-app/res/xml/pref_sensors.xml b/sensorhub-android-app/res/xml/pref_sensors.xml
index a49d5a21..7e0dd51a 100644
--- a/sensorhub-android-app/res/xml/pref_sensors.xml
+++ b/sensorhub-android-app/res/xml/pref_sensors.xml
@@ -232,6 +232,20 @@
android:entryValues="@array/sos_option_values"
android:defaultValue="@array/sos_option_defaults" />
+
+
+
+
diff --git a/sensorhub-android-app/src/org/sensorhub/android/MainActivity.java b/sensorhub-android-app/src/org/sensorhub/android/MainActivity.java
index 8fc29269..598e3fc5 100644
--- a/sensorhub-android-app/src/org/sensorhub/android/MainActivity.java
+++ b/sensorhub-android-app/src/org/sensorhub/android/MainActivity.java
@@ -81,6 +81,7 @@
import org.sensorhub.impl.service.sweapi.SWEApiServiceConfig;
import org.sensorhub.impl.sensor.trupulse.SimulatedDataStream;
import org.sensorhub.impl.sensor.ste.STERadPagerConfig;
+import org.sensorhub.impl.sensor.obd2.Obd2Config;
import java.io.File;
import java.net.MalformedURLException;
@@ -153,6 +154,7 @@ enum Sensors {
ProxySensor,
BLELocation,
STERadPager,
+ Obd2Sensor,
}
@@ -424,6 +426,18 @@ public boolean verify(String arg0, SSLSession arg1) {
sensorhubConfig.add(steRadPagerConfig);
}
+ // OBD2 sensor
+ enabled = prefs.getBoolean("obd2_enabled", false);
+ if(enabled){
+ Obd2Config obd2Config = new Obd2Config();
+ obd2Config.id = "OBD2_SENSOR";
+ obd2Config.name = "OBD2 [" + deviceName + "]";
+ obd2Config.autoStart = true;
+ obd2Config.lastUpdated = ANDROID_SENSORS_LAST_UPDATED;
+
+ sensorhubConfig.add(obd2Config);
+ }
+
// AngelSensor
/*enabled = prefs.getBoolean("angel_enabled", false);
if (enabled)
@@ -971,6 +985,8 @@ private boolean isPushingSensor(Sensors sensor) {
return prefs.getBoolean("ble_enable", false) && prefs.getStringSet("ble_options", Collections.emptySet()).contains("PUSH_REMOTE");
} else if(Sensors.STERadPager.equals(sensor)){
return prefs.getBoolean("ste_radpager_enabled", false) && prefs.getStringSet("radpager_options", Collections.emptySet()).contains("PUSH_REMOTE");
+ } else if(Sensors.Obd2Sensor.equals(sensor)){
+ return prefs.getBoolean("obd2_enabled", false) && prefs.getStringSet("obd2_options", Collections.emptySet()).contains("PUSH_REMOTE");
}
return false;
diff --git a/sensorhub-android-obd2/build.gradle b/sensorhub-android-obd2/build.gradle
new file mode 100644
index 00000000..5b11c387
--- /dev/null
+++ b/sensorhub-android-obd2/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'com.android.library'
+
+description = 'OBD2 Sensor'
+ext.details = 'Driver for OBD2 sensors'
+
+dependencies {
+ api project(':sensorhub-core')
+ implementation "com.android.support:appcompat-v7:28.0.0"
+ implementation 'com.github.pires:obd-java-api:1.0'
+ implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.2'
+ implementation project(':sensorhub-android-service')
+ implementation project(':sensorhub-driver-android')
+}
+
+android {
+ compileSdk = 28
+}
\ No newline at end of file
diff --git a/sensorhub-android-obd2/src/main/AndroidManifest.xml b/sensorhub-android-obd2/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..d25cc1a5
--- /dev/null
+++ b/sensorhub-android-obd2/src/main/AndroidManifest.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Config.java b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Config.java
new file mode 100644
index 00000000..a5e30b99
--- /dev/null
+++ b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Config.java
@@ -0,0 +1,18 @@
+package org.sensorhub.impl.sensor.obd2;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import org.sensorhub.android.SensorHubService;
+import org.sensorhub.api.sensor.SensorConfig;
+
+public class Obd2Config extends SensorConfig {
+ public Obd2Config() {
+ this.moduleClass = Obd2Sensor.class.getCanonicalName();
+ }
+
+ public static String getUid() {
+ Context context = SensorHubService.getContext();
+ return "urn:android:obd2:" + Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
+ }
+}
diff --git a/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Connect.java b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Connect.java
new file mode 100644
index 00000000..864e07eb
--- /dev/null
+++ b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Connect.java
@@ -0,0 +1,79 @@
+package org.sensorhub.impl.sensor.obd2;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+
+import org.sensorhub.api.sensor.SensorException;
+
+import java.util.UUID;
+import java.io.IOException;
+import java.util.Set;
+
+public class Obd2Connect extends Thread {
+ private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
+ private final BluetoothSocket btSocket;
+ private final BluetoothAdapter btAdapter;
+ volatile boolean active = true;
+
+ public Obd2Connect(BluetoothAdapter adapter, BluetoothDevice btDevice) throws SensorException {
+ btAdapter = adapter;
+ BluetoothSocket tmpSocket = null;
+
+ try {
+ tmpSocket = btDevice.createRfcommSocketToServiceRecord(SPP_UUID);
+ } catch (IOException e) {
+ // TODO Is this what I want to happen if creating the socket fails?
+ throw new SensorException("Could not create client socket", e);
+ }
+
+ btSocket = tmpSocket;
+ }
+
+ public BluetoothSocket getBtSocket() {
+ return btSocket;
+ }
+
+ public void run() {
+ btAdapter.cancelDiscovery();
+
+ try {
+ btSocket.connect();
+ } catch (IOException connectException) {
+ try {
+ btSocket.close();
+ } catch (IOException closeException) {
+ // TODO
+ }
+ }
+
+
+// while (active) {
+// btAdapter.cancelDiscovery();
+//
+// // connect to the bluetooth device
+// try {
+// btSocket.connect();
+// } catch (IOException connectException) {
+// System.out.println("*** CONNECTION ERROR: " + connectException);
+// try {
+// btSocket.close();
+// } catch (IOException e) {
+// // TODO What should I do if I can't close the socket?
+// }
+// }
+// }
+ }
+
+ public void cancel() {
+ try {
+ btSocket.close();
+ } catch (IOException e) {
+ // TODO What should I do if I can't close the socket?
+ }
+ }
+
+ public boolean isConnected() {
+ return btSocket.isConnected();
+ }
+}
\ No newline at end of file
diff --git a/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Control.java b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Control.java
new file mode 100644
index 00000000..2dfcbc0a
--- /dev/null
+++ b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Control.java
@@ -0,0 +1,115 @@
+package org.sensorhub.impl.sensor.obd2;
+
+import net.opengis.swe.v20.CategoryOrRange;
+import net.opengis.swe.v20.DataBlock;
+import net.opengis.swe.v20.DataChoice;
+import net.opengis.swe.v20.DataRecord;
+import net.opengis.swe.v20.DataComponent;
+
+import org.checkerframework.checker.units.qual.A;
+import org.sensorhub.impl.sensor.AbstractSensorControl;
+import org.sensorhub.impl.sensor.obd2.commands.Obd2Command;
+import org.sensorhub.api.command.CommandException;
+import org.sensorhub.impl.sensor.obd2.commands.Obd2CommandTask;
+import org.sensorhub.impl.sensor.obd2.commands.Obd2Commands;
+import org.vast.swe.SWEHelper;
+import com.github.pires.obd.commands.control.DistanceMILOnCommand;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+public class Obd2Control extends AbstractSensorControl {
+ DataChoice commandData;
+ DataRecord commandStruct;
+ Obd2Commands commands;
+
+ public Obd2Control(Obd2Sensor parentSensor) {
+ super("control", parentSensor);
+ }
+
+ @Override
+ public DataComponent getCommandDescription() {
+ return commandStruct;
+ }
+
+ @Override
+ protected boolean execCommand(DataBlock commandBlock) throws CommandException {
+ try {
+ DataRecord commandData = commandStruct.copy();
+ commandData.setData(commandBlock);
+// DataComponent component = commandData.getField("Command");
+// String commandName = component.getData().getStringValue();
+// Obd2Command command = commands.get(commandName);
+ // TODO Is this ok?
+ InputStream in = parentSensor.getBtSocket().getInputStream();
+ OutputStream out = parentSensor.getBtSocket().getOutputStream();
+
+ // TODO check boolean value of the trigger control
+
+ try {
+ ArrayList> results = new ArrayList<>();
+ ExecutorService executor = Executors.newFixedThreadPool(commands.getCommands().size());
+
+ for (Obd2Command command: commands.getCommands().values()) {
+ Obd2Command obd2Command = commands.get(command.getCommandName());
+ Callable> task = new Obd2CommandTask(obd2Command, in, out);
+ Future> future = executor.submit(task);
+
+ HashMap result = null;
+ try {
+ result = future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ // TODO
+ }
+
+ results.add(result);
+ }
+ executor.shutdown();
+ parentSensor.getOutput().setData(results);
+ } catch (Exception e) {
+ // TODO Handle errors
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ return true;
+ }
+
+ public void init() {
+ commands = Obd2Commands.getInstance();
+ SWEHelper swe = new SWEHelper();
+
+ commandStruct = swe.createRecord()
+ .name("xxx")
+ .updatable(true)
+ .definition(SWEHelper.getPropertyUri("xxx"))
+ .label("xxx")
+ .description("Sends read commands to the OBD-II device")
+ .addField("Trigger",
+ swe.createBoolean()
+ .name("Trigger reading")
+ .label("Triggers reading")
+ .definition(SWEHelper.getPropertyUri("TriggerControl"))
+ .description("Triggers the OBD-II sensor to read all available data"))
+ .build();
+ }
+
+ public void stop() {
+ // TODO Auto-generated method stub
+ }
+}
\ No newline at end of file
diff --git a/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Descriptor.java b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Descriptor.java
new file mode 100644
index 00000000..6b61b17a
--- /dev/null
+++ b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Descriptor.java
@@ -0,0 +1,38 @@
+package org.sensorhub.impl.sensor.obd2;
+
+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 Obd2Descriptor implements IModuleProvider {
+ @Override
+ public String getModuleName() {
+ return "OBD2 Driver";
+ }
+
+ @Override
+ public String getModuleDescription() {
+ return "Driver for OBD2 sensors conected via BLE";
+ }
+
+ @Override
+ public String getModuleVersion() {
+ return "0.1";
+ }
+
+ @Override
+ public String getProviderName() {
+ return "Botts Innovative Research, Inc.";
+ }
+
+ @Override
+ public Class extends IModule>> getModuleClass() {
+ return Obd2Sensor.class;
+ }
+
+ @Override
+ public Class extends ModuleConfig> getModuleConfigClass() {
+ return Obd2Config.class;
+ }
+}
diff --git a/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Output.java b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Output.java
new file mode 100644
index 00000000..f636d161
--- /dev/null
+++ b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Output.java
@@ -0,0 +1,89 @@
+package org.sensorhub.impl.sensor.obd2;
+
+import net.opengis.swe.v20.DataRecord;
+import net.opengis.swe.v20.DataComponent;
+import net.opengis.swe.v20.DataEncoding;
+import net.opengis.swe.v20.DataBlock;
+import org.sensorhub.api.data.DataEvent;
+import org.sensorhub.impl.sensor.obd2.commands.Obd2Commands;
+import org.sensorhub.impl.sensor.AbstractSensorOutput;
+import org.vast.swe.SWEHelper;
+
+import java.io.FileNotFoundException;
+import java.lang.reflect.Array;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class Obd2Output extends AbstractSensorOutput {
+ private static final String SENSOR_OUTPUT_NAME = "Obd2Output";
+ private static final String SENSOR_OUTPUT_LABEL = "OBD2 Output";
+ private static final String SENSOR_OUTPUT_DESCRIPTION = "Output data from the OBD2 Sensors";
+ DataComponent dataStruct;
+ DataEncoding dataEnc;
+// DataRecord dataRecord;
+
+ public Obd2Output(Obd2Sensor parentSensor) {
+ super(SENSOR_OUTPUT_NAME, parentSensor);
+ }
+
+ public void doInit() {
+ SWEHelper sweHelper = new SWEHelper();
+
+ Obd2Commands commands = Obd2Commands.getInstance();
+ DataRecord distanceMILOn = commands.get("DistanceMILOn").getRecord();
+ DataRecord DistanceSinceCC = commands.get("DistanceSinceCC").getRecord();
+
+ dataStruct = sweHelper.createRecord()
+ .name(SENSOR_OUTPUT_NAME)
+ .label(SENSOR_OUTPUT_LABEL)
+ .description(SENSOR_OUTPUT_DESCRIPTION)
+ .addField("SampleTime", sweHelper.createTime()
+ .asSamplingTimeIsoUTC()
+ .label("Sample Time")
+ .description("Time of data collection"))
+ .addField(distanceMILOn.getName(), sweHelper.createQuantity()
+ .label("xxx")
+ .description(distanceMILOn.getDescription()))
+ .addField(DistanceSinceCC.getName(), sweHelper.createQuantity()
+ .label("xxx")
+ .description(DistanceSinceCC.getDescription()))
+ .build();
+
+ dataEnc = sweHelper.newTextEncoding(",", "\n");
+ }
+
+ // TODO Order of results is not guaranteed so i can't rely on the index
+ public void setData(ArrayList> results) {
+ long timestamp = System.currentTimeMillis();
+
+ DataBlock dataBlock = latestRecord == null ? dataStruct.createDataBlock() : latestRecord.renew();
+ dataBlock.setDoubleValue(0, timestamp / 1000d);
+
+ for (HashMap result: results) {
+ int key = result.keySet().iterator().next();
+ dataBlock.setStringValue(key, result.get(key));
+ }
+
+ latestRecord = dataBlock;
+ eventHandler.publish(new DataEvent(timestamp, Obd2Output.this, dataBlock));
+ }
+
+ @Override
+ public double getAverageSamplingPeriod()
+ {
+ return 1.0;
+ }
+
+ @Override
+ public DataComponent getRecordDescription()
+ {
+ return dataStruct;
+ }
+
+ @Override
+ public DataEncoding getRecommendedEncoding()
+ {
+ return dataEnc;
+ }
+}
diff --git a/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Sensor.java b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Sensor.java
new file mode 100644
index 00000000..be5fe8c6
--- /dev/null
+++ b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/Obd2Sensor.java
@@ -0,0 +1,125 @@
+package org.sensorhub.impl.sensor.obd2;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.os.Build;
+
+import org.sensorhub.android.SensorHubService;
+import org.sensorhub.api.sensor.SensorException;
+import org.sensorhub.impl.sensor.AbstractSensorModule;
+import org.sensorhub.api.common.SensorHubException;
+
+import java.util.Set;
+import java.util.UUID;
+
+public class Obd2Sensor extends AbstractSensorModule {
+ private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
+
+ private String deviceName;
+ private Context context;
+ private Obd2Connect connectionThread;
+ private BluetoothAdapter btAdapter;
+ private Obd2Output output;
+ private Obd2Control control;
+ private boolean btConnected = false;
+
+ public Obd2Sensor() {}
+
+ public BluetoothSocket getBtSocket() {
+ return connectionThread.getBtSocket();
+ }
+
+ @Override
+ public void doInit() throws SensorHubException {
+ // TODO Do I to call super.doInit()?
+ super.doInit();
+
+ // set IDs
+ this.xmlID = "OBD2_" + Build.SERIAL;
+ this.uniqueID = Obd2Config.getUid();
+
+ // get the android's bluetooth adapter
+ context = SensorHubService.getContext();
+ final BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Activity.BLUETOOTH_SERVICE);
+ btAdapter = bluetoothManager.getAdapter();
+
+ if (!btAdapter.isEnabled() || btAdapter == null) {
+ throw new SensorException("Could not get bluetooth adapter, unable to initiate.");
+ }
+
+ BluetoothDevice device = null;
+ Set devices = btAdapter.getBondedDevices();
+
+ // find the bluetooth device
+ for (BluetoothDevice d : devices) {
+ // TODO Get device name from config
+ if (d.getName().equals("VEEPEAK")) {
+ device = d;
+ }
+ }
+
+ if (device == null) {
+ throw new SensorException("Could not find bluetooth device, unable to start.");
+ }
+
+ // create a bluetooth socket via a thread
+ connectionThread = new Obd2Connect(btAdapter, device);
+// connectionThread.start();
+
+ // TODO Do I need to use location data?
+
+ // init osh output(s)
+ output = new Obd2Output(this);
+ output.doInit();
+ addOutput(output, false);
+
+ control = new Obd2Control(this);
+ addControlInput(control);
+ control.init();
+ }
+
+ @Override
+ protected void doStart() throws SensorHubException {
+ // TODO Do I need to call super.doStart()?
+ super.doStart();
+
+ // connect to the bluetooth device via a thread
+ connectionThread.start();
+
+ // TODO and then what? i think we'll need a command class to send commands to read data. where do we interface with the driver? android? computer? api?
+ }
+
+ @Override
+ protected void doStop() {
+ connectionThread.cancel();
+ }
+
+ @Override
+ public void cleanup() throws SensorHubException {}
+
+ @Override
+ protected void updateSensorDescription() {
+ synchronized (sensorDescLock)
+ {
+ super.updateSensorDescription();
+
+ if (!sensorDescription.isSetDescription()) {
+ sensorDescription.setDescription("Driver for OBD2 sensors conected via BLE");
+ }
+ }
+ }
+
+ @Override
+ public boolean isConnected() {
+ return connectionThread.isConnected();
+ }
+
+ // TODO Is this an ok pattern?
+ public Obd2Output getOutput() {
+ return output;
+ }
+}
\ No newline at end of file
diff --git a/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/commands/Obd2Command.java b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/commands/Obd2Command.java
new file mode 100644
index 00000000..bee4d8e5
--- /dev/null
+++ b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/commands/Obd2Command.java
@@ -0,0 +1,58 @@
+package org.sensorhub.impl.sensor.obd2.commands;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import net.opengis.swe.v20.DataRecord;
+
+import org.vast.swe.SWEHelper;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class Obd2Command extends Thread {
+ private DataRecord record;
+ private int index;
+ private String classRef;
+ private String name;
+
+ public Obd2Command() {}
+
+ @JsonCreator
+ public Obd2Command(
+ @JsonProperty("name") String name,
+ @JsonProperty("description") String description,
+ @JsonProperty("index") int index,
+ @JsonProperty("classRef") String classRef
+ ) {
+ SWEHelper swe = new SWEHelper();
+ this.record = swe.createRecord()
+ .name(name)
+ .updatable(true)
+ .label(name)
+ .description(description)
+ .definition(SWEHelper.getPropertyUri(name))
+ .build();
+
+ this.index = index;
+ this.classRef = classRef;
+ this.name = name; // TODO Is this necessary or can i get it from the record?
+ }
+
+ public DataRecord getRecord() {
+ return record;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public String getClassRef() {
+ return classRef;
+ }
+
+ public String getCommandName() { return name; }
+
+ // TODO Does this need a hashcode method since im storing these elsewhere in a Map?
+ // TODO What about toString()?
+}
\ No newline at end of file
diff --git a/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/commands/Obd2CommandTask.java b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/commands/Obd2CommandTask.java
new file mode 100644
index 00000000..09b0e017
--- /dev/null
+++ b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/commands/Obd2CommandTask.java
@@ -0,0 +1,46 @@
+package org.sensorhub.impl.sensor.obd2.commands;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.concurrent.Callable;
+
+public class Obd2CommandTask implements Callable> {
+ private Obd2Command command;
+ private InputStream in;
+ private OutputStream out;
+
+ public Obd2CommandTask(Obd2Command command, InputStream in, OutputStream out) {
+ this.command = command;
+ this.in = in;
+ this.out = out;
+ }
+
+ @Override
+ public HashMap call() {
+ HashMap result = new HashMap<>();
+ String classRef = command.getClassRef();
+ Object[] args = {in, out};
+
+ try {
+ Class> clazz = Class.forName(classRef);
+ Object commandInstance = clazz.getDeclaredConstructor().newInstance();
+ // TODO I'm expecting this and the next call to by synchronous
+ clazz.getMethod("run", InputStream.class, OutputStream.class).invoke(commandInstance, args);
+ result.put(
+ command.getIndex(),
+ (String) clazz.getMethod("getCalculatedResult").invoke(commandInstance)
+ );
+ } catch (ClassNotFoundException e) {
+ // TODO Is this how I want to handle errors?
+ System.err.println("Class not found: " + classRef);
+ } catch (NoSuchMethodException e) {
+ System.err.println("Method 'run' not found in class: " + classRef);
+ } catch (Exception e) {
+ System.out.println("*** ERROR: " + e);
+ e.printStackTrace();
+ }
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/commands/Obd2Commands.java b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/commands/Obd2Commands.java
new file mode 100644
index 00000000..d3a32e76
--- /dev/null
+++ b/sensorhub-android-obd2/src/main/java/org/sensorhub/impl/sensor/obd2/commands/Obd2Commands.java
@@ -0,0 +1,55 @@
+package org.sensorhub.impl.sensor.obd2.commands;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Map;
+import java.io.InputStream;
+import java.io.FileNotFoundException;
+
+public final class Obd2Commands {
+ private static volatile Obd2Commands instance;
+ private Map commands;
+
+ private Obd2Commands() {
+ String fileName = "commands.json";
+
+ InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
+ if (inputStream == null) {
+ throw new RuntimeException("File not found: " + fileName);
+ }
+
+ ObjectMapper objectMapper = new ObjectMapper();
+ try {
+ commands = objectMapper.readValue(inputStream, new TypeReference