diff --git a/CHANGELOG.md b/CHANGELOG.md
index 254780f..b22c02d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,8 @@
V3.5.0 - Aug 2020
- Added broadcast to request phone call dial alert (handled by separate app OpenSeizureDetector Dialler).
- Added UUID string to SMS alerts so they can be detected by a custom SMS receiver on the carer's phone.
-
+ V3.4.0 - Aug2020
+ - Added support for BLE data source
V3.2.1 - Aug2020
- Addition of Spanish Translation, and correction of crash report wording in German.
V3.2.0 - mar2020
diff --git a/app/release/app-release.apk b/app/release/app-release-3.4.0a.apk
similarity index 100%
rename from app/release/app-release.apk
rename to app/release/app-release-3.4.0a.apk
diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json
new file mode 100644
index 0000000..1fb9a09
--- /dev/null
+++ b/app/release/output-metadata.json
@@ -0,0 +1,20 @@
+{
+ "version": 1,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "uk.org.openseizuredetector",
+ "variantName": "release",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "properties": [],
+ "versionCode": 72,
+ "versionName": "3.4.0",
+ "enabled": true,
+ "outputFile": "app-release.apk"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8d623cb..8af6043 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,6 +5,8 @@
android:versionCode="72"
android:versionName="3.5.0">
+
+
@@ -27,8 +29,9 @@
android:icon="@drawable/star_of_life_48x48"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
- android:theme="@style/Theme.AppCompat"
- >
+ android:theme="@style/AppTheme"
+ >
+
@@ -69,4 +72,4 @@
android:required="false" />
-
\ No newline at end of file
+
diff --git a/app/src/main/java/uk/org/openseizuredetector/BLEScanActivity.java b/app/src/main/java/uk/org/openseizuredetector/BLEScanActivity.java
new file mode 100644
index 0000000..7d2daf1
--- /dev/null
+++ b/app/src/main/java/uk/org/openseizuredetector/BLEScanActivity.java
@@ -0,0 +1,323 @@
+package uk.org.openseizuredetector;
+
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.ListActivity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.support.v4.app.ActivityCompat;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+
+/**
+ * Activity for scanning and displaying available Bluetooth LE devices.
+ */
+public class BLEScanActivity extends ListActivity {
+ private LeDeviceListAdapter mLeDeviceListAdapter;
+ private BluetoothAdapter mBluetoothAdapter;
+ private boolean mScanning;
+ private Handler mHandler;
+
+ private boolean mPermissionsRequested = false;
+ private final String TAG = "BLEScanActivity";
+
+ private final String[] REQUIRED_PERMISSIONS = {
+ Manifest.permission.ACCESS_COARSE_LOCATION,
+ Manifest.permission.BLUETOOTH,
+ Manifest.permission.BLUETOOTH_ADMIN,
+ };
+
+ private static final int REQUEST_ENABLE_BT = 1;
+ // Stops scanning after 10 seconds.
+ private static final long SCAN_PERIOD = 10000;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ //this.getActionBar().setTitle(R.string.title_devices);
+ this.setTitle(R.string.title_devices);
+ mHandler = new Handler();
+
+ // Use this check to determine whether BLE is supported on the device. Then you can
+ // selectively disable BLE-related features.
+ if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
+ Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
+ finish();
+ }
+
+ // Initializes a Bluetooth adapter. For API level 18 and above, get a reference to
+ // BluetoothAdapter through BluetoothManager.
+ final BluetoothManager bluetoothManager =
+ (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+ mBluetoothAdapter = bluetoothManager.getAdapter();
+
+ // Checks if Bluetooth is supported on the device.
+ if (mBluetoothAdapter == null) {
+ Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.ble_scan_menu, menu);
+ if (!mScanning) {
+ menu.findItem(R.id.menu_stop).setVisible(false);
+ menu.findItem(R.id.menu_scan).setVisible(true);
+ menu.findItem(R.id.menu_refresh).setActionView(null);
+ } else {
+ menu.findItem(R.id.menu_stop).setVisible(true);
+ menu.findItem(R.id.menu_scan).setVisible(false);
+ menu.findItem(R.id.menu_refresh).setActionView(
+ R.layout.actionbar_indeterminate_progress);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_scan:
+ mLeDeviceListAdapter.clear();
+ scanLeDevice(true);
+ break;
+ case R.id.menu_stop:
+ scanLeDevice(false);
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // Ensures Bluetooth is enabled on the device. If Bluetooth is not currently enabled,
+ // fire an intent to display a dialog asking the user to grant permission to enable it.
+ if (!mBluetoothAdapter.isEnabled()) {
+ Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
+ }
+
+ // Initializes list view adapter.
+ mLeDeviceListAdapter = new LeDeviceListAdapter();
+ setListAdapter(mLeDeviceListAdapter);
+
+ scanLeDevice(true);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // User chose not to enable Bluetooth.
+ if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) {
+ finish();
+ return;
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ scanLeDevice(false);
+ mLeDeviceListAdapter.clear();
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ final BluetoothDevice device = mLeDeviceListAdapter.getDevice(position);
+ if (device == null) return;
+ Log.v(TAG, "onListItemClick: Device=" + device.getName() + ", Addr=" + device.getAddress());
+ if (mScanning) {
+ mBluetoothAdapter.stopLeScan(mLeScanCallback);
+ mScanning = false;
+ }
+ Log.v(TAG,"Saving Device Details");
+ SharedPreferences.Editor SPE = PreferenceManager
+ .getDefaultSharedPreferences(this).edit();
+ try {
+ SPE.putString("BLE_Device_Addr", device.getAddress());
+ SPE.putString("BLE_Device_Name", device.getName());
+ SPE.apply();
+ SPE.commit();
+
+ Log.v(TAG, "Saved Device Name="+device.getName()+" and Address="+device.getAddress());
+ } catch (Exception ex) {
+ Log.e(TAG, "Error Saving Devie Name and Address!");
+ Toast toast = Toast.makeText(this, "Problem Saving Device Name and Address", Toast.LENGTH_SHORT);
+ toast.show();
+ }
+ SharedPreferences SP = PreferenceManager.getDefaultSharedPreferences((this));
+ Log.v(TAG,"Check of saved values - Name="+SP.getString("BLE_Device_Name","NOT SET")+", Addr="+SP.getString("BLE_Device_Addr","NOT SET"));
+
+ finish();
+ }
+
+ private void scanLeDevice(final boolean enable) {
+ requestPermissions(this);
+ if (enable) {
+ // Stops scanning after a pre-defined scan period.
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mScanning = false;
+ mBluetoothAdapter.stopLeScan(mLeScanCallback);
+ invalidateOptionsMenu();
+ }
+ }, SCAN_PERIOD);
+
+ mScanning = true;
+ mBluetoothAdapter.startLeScan(mLeScanCallback);
+ } else {
+ mScanning = false;
+ mBluetoothAdapter.stopLeScan(mLeScanCallback);
+ }
+ invalidateOptionsMenu();
+ }
+
+ // Adapter for holding devices found through scanning.
+ private class LeDeviceListAdapter extends BaseAdapter {
+ private ArrayList mLeDevices;
+ private LayoutInflater mInflator;
+
+ public LeDeviceListAdapter() {
+ super();
+ mLeDevices = new ArrayList();
+ mInflator = BLEScanActivity.this.getLayoutInflater();
+ }
+
+ public void addDevice(BluetoothDevice device) {
+ if (!mLeDevices.contains(device)) {
+ Log.v(TAG,"addDevice - "+device.getName());
+ mLeDevices.add(device);
+ }
+ }
+
+ public BluetoothDevice getDevice(int position) {
+ return mLeDevices.get(position);
+ }
+
+ public void clear() {
+ mLeDevices.clear();
+ }
+
+ @Override
+ public int getCount() {
+ return mLeDevices.size();
+ }
+
+ @Override
+ public Object getItem(int i) {
+ return mLeDevices.get(i);
+ }
+
+ @Override
+ public long getItemId(int i) {
+ return i;
+ }
+
+ @Override
+ public View getView(int i, View view, ViewGroup viewGroup) {
+ ViewHolder viewHolder;
+ Log.v(TAG,"scanner getView i="+i);
+ // General ListView optimization code.
+ if (view == null) {
+ view = mInflator.inflate(R.layout.ble_list_item_device, null);
+ viewHolder = new ViewHolder();
+ viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address);
+ viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name);
+ view.setTag(viewHolder);
+ } else {
+ viewHolder = (ViewHolder) view.getTag();
+ }
+
+ BluetoothDevice device = mLeDevices.get(i);
+ final String deviceName = device.getName();
+ if (deviceName != null && deviceName.length() > 0)
+ viewHolder.deviceName.setText(deviceName);
+ else
+ viewHolder.deviceName.setText(R.string.unknown_device);
+ viewHolder.deviceAddress.setText(device.getAddress());
+
+ return view;
+ }
+ }
+
+ // Device scan callback.
+ private BluetoothAdapter.LeScanCallback mLeScanCallback =
+ new BluetoothAdapter.LeScanCallback() {
+
+ @Override
+ public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
+ Log.v(TAG,"LEScanCallback - device="+device.getName());
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mLeDeviceListAdapter.addDevice(device);
+ mLeDeviceListAdapter.notifyDataSetChanged();
+ }
+ });
+ }
+ };
+
+ static class ViewHolder {
+ TextView deviceName;
+ TextView deviceAddress;
+ }
+
+
+ public void requestPermissions(Activity activity) {
+ if (mPermissionsRequested) {
+ Log.i(TAG, "requestPermissions() - request already sent - not doing anything");
+ } else {
+ Log.i(TAG, "requestPermissions() - requesting permissions");
+ for (int i = 0; i < REQUIRED_PERMISSIONS.length; i++) {
+ if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
+ REQUIRED_PERMISSIONS[i])) {
+ Log.i(TAG, "shouldShowRationale for permission" + REQUIRED_PERMISSIONS[i]);
+ }
+ }
+ ActivityCompat.requestPermissions(activity,
+ REQUIRED_PERMISSIONS,
+ 42);
+ mPermissionsRequested = true;
+ }
+ }
+}
diff --git a/app/src/main/java/uk/org/openseizuredetector/GattAttributes.java b/app/src/main/java/uk/org/openseizuredetector/GattAttributes.java
new file mode 100644
index 0000000..9d890f1
--- /dev/null
+++ b/app/src/main/java/uk/org/openseizuredetector/GattAttributes.java
@@ -0,0 +1,24 @@
+package uk.org.openseizuredetector;
+// Defines the servies and characteristics we need to subscribe to.
+import java.util.HashMap;
+
+public class GattAttributes {
+ private static HashMap attributes = new HashMap();
+ public static String HEART_RATE_MEASUREMENT = "00002a37-0000-1000-8000-00805f9b34fb";
+ public static String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb";
+
+
+ static {
+ // Sample Services.
+ attributes.put("0000180d-0000-1000-8000-00805f9b34fb", "Heart Rate Service");
+ attributes.put("0000180a-0000-1000-8000-00805f9b34fb", "Device Information Service");
+ // Sample Characteristics.
+ attributes.put("00002a37-0000-1000-8000-00805f9b34fb", "Heart Rate Measurement");
+ attributes.put("00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String");
+ }
+
+ public static String lookup(String uuid, String defaultName) {
+ String name = attributes.get(uuid);
+ return name == null ? defaultName : name;
+ }
+}
diff --git a/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java b/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java
index 6f402bb..62f31b2 100644
--- a/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java
+++ b/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java
@@ -34,16 +34,19 @@ import android.os.Bundle;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.util.Log;
+import android.view.View;
+import android.widget.Button;
import android.widget.Toast;
import java.util.List;
-public class PrefActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
+public class PrefActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener, View.OnClickListener {
private String TAG = "PreferenceActivity";
private OsdUtil mUtil;
private boolean mPrefChanged = false;
private Context mContext;
private Handler mHandler;
+ private Button mSelectBLEButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -118,6 +121,8 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference
}
}
+ //mSelectBLEButton = findViewById(R.id.selectBLEDeviceButton);
+ //mSelectBLEButton.setOnClickListener(this);
}
@@ -217,6 +222,20 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference
return true;
}
+ @Override
+ public void onClick(View view) {
+ switch (view.getId()) {
+ case R.id.selectBLEDeviceButton:
+ Log.v(TAG,"onClick - SelectBLEDeviceButton");
+ final Intent intent = new Intent(this.mContext, BLEScanActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ break;
+ default:
+ Log.e(TAG,"onClick - unrecognised button");
+ }
+ }
+
/**
* This fragment shows the preferences for the first header.
*/
diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java
index 316a721..e8501e8 100644
--- a/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java
+++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java
@@ -23,13 +23,26 @@
*/
package uk.org.openseizuredetector;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.text.format.Time;
import android.util.Log;
import android.widget.Toast;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.jtransforms.fft.DoubleFFT_1D;
+
+import java.util.Arrays;
+import java.util.Timer;
+import java.util.TimerTask;
+
interface SdDataReceiver {
public void onSdDataReceived(SdData sdData);
public void onSdDataFault(SdData sdData);
@@ -40,14 +53,56 @@ interface SdDataReceiver {
* network data source.
*/
public abstract class SdDataSource {
+ protected Handler mHandler = new Handler();
+ private Timer mStatusTimer;
+ private Timer mSettingsTimer;
+ private Timer mFaultCheckTimer;
+ protected Time mDataStatusTime;
+ protected boolean mWatchAppRunningCheck = false;
+ private int mAppRestartTimeout = 10; // Timeout before re-starting watch app (sec) if we have not received
+ // data after mDataUpdatePeriod
+ private int mFaultTimerPeriod = 30; // Fault Timer Period in sec
+ private int mSettingsPeriod = 60; // period between requesting settings in seconds.
public SdData mSdData;
public String mName = "undefined";
protected OsdUtil mUtil;
protected Context mContext;
- protected Handler mHandler;
protected SdDataReceiver mSdDataReceiver;
private String TAG = "SdDataSource";
+ private short mDebug;
+ private short mFreqCutoff = 12;
+ private short mDisplaySpectrum;
+ private short mDataUpdatePeriod;
+ private short mMutePeriod;
+ private short mManAlarmPeriod;
+ private short mPebbleSdMode;
+ private short mSampleFreq;
+ private short mAlarmFreqMin;
+ private short mAlarmFreqMax;
+ private short mSamplePeriod;
+ private short mWarnTime;
+ private short mAlarmTime;
+ private short mAlarmThresh;
+ private short mAlarmRatioThresh;
+ private boolean mFallActive;
+ private short mFallThreshMin;
+ private short mFallThreshMax;
+ private short mFallWindow;
+ private int mMute; // !=0 means muted by keypress on watch.
+
+ // Values for SD_MODE
+ private int SIMPLE_SPEC_FMAX = 10;
+
+ private int ACCEL_SCALE_FACTOR = 1000; // Amount by which to reduce analysis results to scale to be comparable to analysis on Pebble.
+
+
+
+ private int mAlarmCount;
+ protected String mBleDeviceAddr;
+ protected String mBleDeviceName;
+
+
public SdDataSource(Context context, Handler handler, SdDataReceiver sdDataReceiver) {
Log.v(TAG, "SdDataSource() Constructor");
mContext = context;
@@ -70,7 +125,57 @@ public abstract class SdDataSource {
* make sure any changes to preferences are taken into account.
*/
public void start() {
+
Log.v(TAG, "start()");
+ updatePrefs();
+ // Start timer to check status of watch regularly.
+ mDataStatusTime = new Time(Time.getCurrentTimezone());
+ // use a timer to check the status of the pebble app on the same frequency
+ // as we get app data.
+ if (mStatusTimer == null) {
+ Log.v(TAG, "start(): starting status timer");
+ mUtil.writeToSysLogFile("SdDataSourceBLE.start() - starting status timer");
+ mStatusTimer = new Timer();
+ mStatusTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ getStatus();
+ }
+ }, 0, mDataUpdatePeriod * 1000);
+ } else {
+ Log.v(TAG, "start(): status timer already running.");
+ mUtil.writeToSysLogFile("SdDataSourceBLE.start() - status timer already running??");
+ }
+ if (mFaultCheckTimer == null) {
+ Log.v(TAG, "start(): starting alarm check timer");
+ mUtil.writeToSysLogFile("SdDataSourceBLE.start() - starting alarm check timer");
+ mFaultCheckTimer = new Timer();
+ mFaultCheckTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ faultCheck();
+ }
+ }, 0, 1000);
+ } else {
+ Log.v(TAG, "start(): alarm check timer already running.");
+ mUtil.writeToSysLogFile("SDDataSourceBLE.start() - alarm check timer already running??");
+ }
+
+ if (mSettingsTimer == null) {
+ Log.v(TAG, "start(): starting settings timer");
+ mUtil.writeToSysLogFile("SDDataSourceBLE.start() - starting settings timer");
+ mSettingsTimer = new Timer();
+ mSettingsTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ mSdData.haveSettings = false;
+ }
+ }, 0, 1000 * mSettingsPeriod); // ask for settings less frequently than we get data
+ } else {
+ Log.v(TAG, "start(): settings timer already running.");
+ mUtil.writeToSysLogFile("SDDataSourceBLE.start() - settings timer already running??");
+ }
+
}
/**
@@ -78,6 +183,37 @@ public abstract class SdDataSource {
*/
public void stop() {
Log.v(TAG, "stop()");
+ try {
+ // Stop the status timer
+ if (mStatusTimer != null) {
+ Log.v(TAG, "stop(): cancelling status timer");
+ mUtil.writeToSysLogFile("SDDataSourceBLE.stop() - cancelling status timer");
+ mStatusTimer.cancel();
+ mStatusTimer.purge();
+ mStatusTimer = null;
+ }
+ // Stop the settings timer
+ if (mSettingsTimer != null) {
+ Log.v(TAG, "stop(): cancelling settings timer");
+ mUtil.writeToSysLogFile("SDDataSourceBLE.stop() - cancelling settings timer");
+ mSettingsTimer.cancel();
+ mSettingsTimer.purge();
+ mSettingsTimer = null;
+ }
+ // Stop the alarm check timer
+ if (mFaultCheckTimer != null) {
+ Log.v(TAG, "stop(): cancelling alarm check timer");
+ mUtil.writeToSysLogFile("SDDataSourceBLE.stop() - cancelling alarm check timer");
+ mFaultCheckTimer.cancel();
+ mFaultCheckTimer.purge();
+ mFaultCheckTimer = null;
+ }
+
+ } catch (Exception e) {
+ Log.v(TAG, "Error in stop() - " + e.toString());
+ mUtil.writeToSysLogFile("SDDataSourceBLE.stop() - error - "+e.toString());
+ }
+
}
/**
@@ -101,14 +237,539 @@ public abstract class SdDataSource {
public void acceptAlarm() { Log.v(TAG,"acceptAlarm()"); }
-
// Force the data stored in this datasource to update in line with the JSON string encoded data provided.
- // Used by webServer to update the NetworkPassiveDatasource
+ // Used by webServer to update the GarminDatasource.
+ // Returns a message string that is passed back to the watch.
public String updateFromJSON(String jsonStr) {
+ String retVal = "undefined";
+ String watchPartNo;
+ String watchFwVersion;
+ String sdVersion;
+ String sdName;
Log.v(TAG,"updateFromJSON - "+jsonStr);
- return("OK");
+
+ try {
+ JSONObject mainObject = new JSONObject(jsonStr);
+ //JSONObject dataObject = mainObject.getJSONObject("dataObj");
+ JSONObject dataObject = mainObject;
+ String dataTypeStr = dataObject.getString("dataType");
+ Log.v(TAG,"updateFromJSON - dataType="+dataTypeStr);
+ if (dataTypeStr.equals("raw")) {
+ Log.v(TAG,"updateFromJSON - processing raw data");
+ try {
+ mSdData.mHR = dataObject.getDouble("HR");
+ } catch (JSONException e) {
+ // if we get 'null' HR (For example if the heart rate is not working)
+ mSdData.mHR = -1;
+ }
+ try {
+ mMute = dataObject.getInt("Mute");
+ } catch (JSONException e) {
+ // if we get 'null' HR (For example if the heart rate is not working)
+ mMute = 0;
+ }
+ JSONArray accelVals = dataObject.getJSONArray("data");
+ Log.v(TAG, "Received " + accelVals.length() + " acceleration values");
+ int i;
+ for (i = 0; i < accelVals.length(); i++) {
+ mSdData.rawData[i] = accelVals.getInt(i);
+ }
+ mSdData.mNsamp = accelVals.length();
+ //mNSamp = accelVals.length();
+ mWatchAppRunningCheck = true;
+ doAnalysis();
+ if (mSdData.haveSettings == false) {
+ retVal = "sendSettings";
+ } else {
+ retVal = "OK";
+ }
+ } else if (dataTypeStr.equals("settings")){
+ Log.v(TAG,"updateFromJSON - processing settings");
+ mSamplePeriod = (short)dataObject.getInt("analysisPeriod");
+ mSampleFreq = (short)dataObject.getInt("sampleFreq");
+ mSdData.batteryPc = (short)dataObject.getInt("battery");
+ Log.v(TAG,"updateFromJSON - mSamplePeriod="+mSamplePeriod+" mSampleFreq="+mSampleFreq);
+ mUtil.writeToSysLogFile("SDDataSourceBLE.updateFromJSON - Settings Received");
+ mUtil.writeToSysLogFile(" * mSamplePeriod="+mSamplePeriod+" mSampleFreq="+mSampleFreq);
+ mUtil.writeToSysLogFile(" * batteryPc = "+mSdData.batteryPc);
+
+ try {
+ watchPartNo = dataObject.getString("watchPartNo");
+ watchFwVersion = dataObject.getString("watchFwVersion");
+ sdVersion = dataObject.getString("sdVersion");
+ sdName = dataObject.getString("sdName");
+ mUtil.writeToSysLogFile(" * sdName = "+sdName+" version "+sdVersion);
+ mUtil.writeToSysLogFile(" * watchPartNo = "+watchPartNo+" fwVersion "+watchFwVersion);
+ } catch (Exception e) {
+ Log.e(TAG,"updateFromJSON - Error Parsing V3.2 JSON String - "+e.toString());
+ mUtil.writeToSysLogFile("updateFromJSON - Error Parsing V3.2 JSON String - "+e.toString());
+ mUtil.writeToSysLogFile(" This is probably because of an out of date watch app - please upgrade!");
+ e.printStackTrace();
+ }
+ mSdData.haveSettings = true;
+ mSdData.mSampleFreq = mSampleFreq;
+ mWatchAppRunningCheck = true;
+ retVal = "OK";
+ } else {
+ Log.e(TAG,"updateFromJSON - unrecognised dataType "+dataTypeStr);
+ retVal = "ERROR";
+ }
+ } catch (Exception e) {
+ Log.e(TAG,"updateFromJSON - Error Parsing JSON String - "+e.toString());
+ mUtil.writeToSysLogFile("updateFromJSON - Error Parsing JSON String - "+e.toString());
+ e.printStackTrace();
+ retVal = "ERROR";
+ }
+ return(retVal);
}
+ /**
+ * Calculate the magnitude of entry i in the fft array fft
+ * @param fft
+ * @param i
+ * @return magnitude ( Re*Re + Im*Im )
+ */
+ private double getMagnitude(double[] fft, int i) {
+ double mag;
+ mag = (fft[2*i]*fft[2*i] + fft[2*i + 1] * fft[2*i +1]);
+ return mag;
+ }
+
+ /**
+ * doAnalysis() - analyse the data if the accelerometer data array mAccData
+ * and populate the output data structure mSdData
+ */
+ protected void doAnalysis() {
+ // FIXME - Use specified sampleFreq, not this hard coded one
+ mSampleFreq = 25;
+ double freqRes = 1.0*mSampleFreq/mSdData.mNsamp;
+ Log.v(TAG,"doAnalysis(): mSampleFreq="+mSampleFreq+" mNSamp="+mSdData.mNsamp+": freqRes="+freqRes);
+ // Set the frequency bounds for the analysis in fft output bin numbers.
+ int nMin = (int)(mAlarmFreqMin/freqRes);
+ int nMax = (int)(mAlarmFreqMax /freqRes);
+ Log.v(TAG,"doAnalysis(): mAlarmFreqMin="+mAlarmFreqMin+", nMin="+nMin
+ +", mAlarmFreqMax="+mAlarmFreqMax+", nMax="+nMax);
+ // Calculate the bin number of the cutoff frequency
+ int nFreqCutoff = (int)(mFreqCutoff /freqRes);
+ Log.v(TAG,"mFreqCutoff = "+mFreqCutoff+", nFreqCutoff="+nFreqCutoff);
+
+ DoubleFFT_1D fftDo = new DoubleFFT_1D(mSdData.mNsamp);
+ double[] fft = new double[mSdData.mNsamp * 2];
+ ///System.arraycopy(mAccData, 0, fft, 0, mNsamp);
+ System.arraycopy(mSdData.rawData, 0, fft, 0, mSdData.mNsamp);
+ fftDo.realForward(fft);
+
+ // Calculate the whole spectrum power (well a value equivalent to it that avoids square root calculations
+ // and zero any readings that are above the frequency cutoff.
+ double specPower = 0;
+ for (int i = 1; i < mSdData.mNsamp / 2; i++) {
+ if (i <= nFreqCutoff) {
+ specPower = specPower + getMagnitude(fft,i);
+ } else {
+ fft[2*i] = 0.;
+ fft[2*i+1] = 0.;
+ }
+ }
+ //Log.v(TAG,"specPower = "+specPower);
+ //specPower = specPower/(mSdData.mNsamp/2);
+ specPower = specPower/mSdData.mNsamp/2;
+ //Log.v(TAG,"specPower = "+specPower);
+
+ // Calculate the Region of Interest power and power ratio.
+ double roiPower = 0;
+ for (int i=nMin;i mAlarmThresh) && (10 * (mSdData.roiPower / mSdData.specPower) > mAlarmRatioThresh)) {
+ inAlarm = true;
+ } else {
+ inAlarm = false;
+ }
+
+ // set the alarmState to Alarm, Warning or OK, depending on the current state and previous ones.
+ if (inAlarm) {
+ mAlarmCount += mSamplePeriod;
+ if (mAlarmCount > mAlarmTime) {
+ // full alarm
+ mSdData.alarmState = 2;
+ } else if (mAlarmCount > mWarnTime) {
+ // warning
+ mSdData.alarmState = 1;
+ }
+ } else {
+ // If we are not in an ALARM state, revert back to WARNING, otherwise
+ // revert back to OK.
+ if (mSdData.alarmState == 2) {
+ // revert to warning
+ mSdData.alarmState = 1;
+ mAlarmCount = mWarnTime + 1; // pretend we have only just entered warning state.
+ } else {
+ // revert to OK
+ mSdData.alarmState = 0;
+ mAlarmCount = 0;
+ }
+ }
+
+ Log.v(TAG, "alarmCheck(): inAlarm=" + inAlarm + ", alarmState = " + mSdData.alarmState + " alarmCount=" + mAlarmCount + " mAlarmTime=" + mAlarmTime);
+
+ }
+
+ public void muteCheck() {
+ if (mMute != 0) {
+ Log.v(TAG, "Mute Active - setting alarms to mute");
+ mSdData.alarmState = 6;
+ mSdData.alarmPhrase = "MUTE";
+ mSdData.mHRAlarmStanding = false;
+ }
+
+ }
+
+ /**
+ * hrCheck - check the Heart rate data in mSdData to see if it represents an alarm condition.
+ * Sets mSdData.mHRAlarmStanding
+ */
+ public void hrCheck() {
+ Log.v(TAG, "hrCheck()");
+ /* Check Heart Rate against alarm settings */
+ if (mSdData.mHRAlarmActive) {
+ if (mSdData.mHR < 0) {
+ if (mSdData.mHRNullAsAlarm) {
+ Log.i(TAG, "Heart Rate Null - Alarming");
+ mSdData.mHRFaultStanding = false;
+ mSdData.mHRAlarmStanding = true;
+ } else {
+ Log.i(TAG, "Heart Rate Fault (HR<0)");
+ mSdData.mHRFaultStanding = true;
+ mSdData.mHRAlarmStanding = false;
+ }
+ }
+ else if ((mSdData.mHR > mSdData.mHRThreshMax) || (mSdData.mHR < mSdData.mHRThreshMin)) {
+ Log.i(TAG, "Heart Rate Abnormal - " + mSdData.mHR + " bpm");
+ mSdData.mHRFaultStanding = false;
+ mSdData.mHRAlarmStanding = true;
+ }
+ else {
+ mSdData.mHRFaultStanding = false;
+ mSdData.mHRAlarmStanding = false;
+ }
+ }
+
+ }
+
+ /****************************************************************
+ * Simple threshold analysis to chech for fall.
+ * Called from clock_tick_handler()
+ */
+ public void fallCheck() {
+ int i,j;
+ double minAcc, maxAcc;
+
+ long fallWindowSamp = (mFallWindow*mSdData.mSampleFreq)/1000; // Convert ms to samples.
+ Log.v(TAG, "check_fall() - fallWindowSamp=" +fallWindowSamp);
+ // Move window through sample buffer, checking for fall.
+ // Note - not resetting fallAlarmStanding means that fall alarms will always latch until the 'Accept Alarm' button
+ // is pressed.
+ //mSdData.fallAlarmStanding = false;
+ if (mFallActive) {
+ mSdData.mFallActive = true;
+ for (i = 0; i < mSdData.mNsamp - fallWindowSamp; i++) { // i = window start point
+ // Find max and min acceleration within window.
+ minAcc = mSdData.rawData[i];
+ maxAcc = mSdData.rawData[i];
+ for (j = 0; j < fallWindowSamp; j++) { // j = position within window
+ if (mSdData.rawData[i + j] < minAcc) minAcc = mSdData.rawData[i + j];
+ if (mSdData.rawData[i + j] > maxAcc) maxAcc = mSdData.rawData[i + j];
+ }
+ if ((minAcc < mFallThreshMin) && (maxAcc > mFallThreshMax)) {
+ Log.d(TAG, "check_fall() - minAcc=" + minAcc + ", maxAcc=" + maxAcc);
+ Log.d(TAG, "check_fall() - ****FALL DETECTED****");
+ mSdData.fallAlarmStanding = true;
+ return;
+ }
+ if (mMute != 0) {
+ Log.v(TAG,"Mute Active - setting fall alarm to mute");
+ mSdData.fallAlarmStanding = false;
+ }
+ }
+ } else {
+ mSdData.mFallActive = false;
+ Log.v(TAG,"check_fall - mFallActive is false - doing nothing");
+ }
+ //if (debug) APP_LOG(APP_LOG_LEVEL_DEBUG,"check_fall() - minAcc=%d, maxAcc=%d",
+ // minAcc,maxAcc);
+
+ }
+
+ /**
+ * Checks the status of the connection to the watch,
+ * and sets class variables for use by other functions.
+ */
+ public void getStatus() {
+ Time tnow = new Time(Time.getCurrentTimezone());
+ long tdiff;
+ tnow.setToNow();
+ // get time since the last data was received from the Pebble watch.
+ tdiff = (tnow.toMillis(false) - mDataStatusTime.toMillis(false));
+ Log.v(TAG, "getStatus() - mWatchAppRunningCheck=" + mWatchAppRunningCheck + " tdiff=" + tdiff);
+ Log.v(TAG,"getStatus() - tdiff="+tdiff+", mDataUpatePeriod="+mDataUpdatePeriod+", mAppRestartTimeout="+mAppRestartTimeout);
+
+ mSdData.watchConnected = true; // We can't check connection for passive network connection, so set it to true to avoid errors.
+ // And is the watch app running?
+ // set mWatchAppRunningCheck has been false for more than 10 seconds
+ // the app is not talking to us
+ // mWatchAppRunningCheck is set to true in the receiveData handler.
+ if (!mWatchAppRunningCheck &&
+ (tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) {
+ Log.v(TAG, "getStatus() - tdiff = " + tdiff);
+ mSdData.watchAppRunning = false;
+ // Only make audible warning beep if we have not received data for more than mFaultTimerPeriod seconds.
+ if (tdiff > (mDataUpdatePeriod + mFaultTimerPeriod) * 1000) {
+ Log.v(TAG, "getStatus() - Watch App Not Running");
+ mUtil.writeToSysLogFile("SDDataSourceBLE.getStatus() - Watch App not Running");
+ //mDataStatusTime.setToNow();
+ mSdData.roiPower = -1;
+ mSdData.specPower = -1;
+ mSdDataReceiver.onSdDataFault(mSdData);
+ } else {
+ Log.v(TAG, "getStatus() - Waiting for mFaultTimerPeriod before issuing audible warning...");
+ }
+ } else {
+ mSdData.watchAppRunning = true;
+ }
+
+ // if we have confirmation that the app is running, reset the
+ // status time to now and initiate another check.
+ if (mWatchAppRunningCheck) {
+ mWatchAppRunningCheck = false;
+ mDataStatusTime.setToNow();
+ }
+
+ if (!mSdData.haveSettings) {
+ Log.v(TAG, "getStatus() - no settings received yet");
+ }
+ }
+
+ /**
+ * faultCheck - determines alarm state based on seizure detector data SdData. Called every second.
+ */
+ private void faultCheck() {
+ Time tnow = new Time(Time.getCurrentTimezone());
+ long tdiff;
+ tnow.setToNow();
+
+ // get time since the last data was received from the watch.
+ tdiff = (tnow.toMillis(false) - mDataStatusTime.toMillis(false));
+ Log.v(TAG, "faultCheck() - tdiff=" + tdiff + ", mDataUpatePeriod=" + mDataUpdatePeriod + ", mAppRestartTimeout=" + mAppRestartTimeout
+ + ", combined = " + (mDataUpdatePeriod + mAppRestartTimeout) * 1000);
+ if (!mWatchAppRunningCheck &&
+ (tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) {
+ Log.v(TAG, "faultCheck() - watch app not running so not doing anything");
+ mAlarmCount = 0;
+ }
+ }
+
+ /**
+ * updatePrefs() - update basic settings from the SharedPreferences
+ * - defined in res/xml/SdDataSourceNetworkPassivePrefs.xml
+ */
+ public void updatePrefs() {
+ Log.v(TAG, "updatePrefs()");
+ mUtil.writeToSysLogFile("SDDataSourceBLE.updatePrefs()");
+ SharedPreferences SP = PreferenceManager
+ .getDefaultSharedPreferences(mContext);
+ try {
+ // Parse the AppRestartTimeout period setting.
+ try {
+ String appRestartTimeoutStr = SP.getString("AppRestartTimeout", "10");
+ mAppRestartTimeout = Integer.parseInt(appRestartTimeoutStr);
+ Log.v(TAG, "updatePrefs() - mAppRestartTimeout = " + mAppRestartTimeout);
+ } catch (Exception ex) {
+ Log.v(TAG, "updatePrefs() - Problem with AppRestartTimeout preference!");
+ Toast toast = Toast.makeText(mContext, "Problem Parsing AppRestartTimeout Preference", Toast.LENGTH_SHORT);
+ toast.show();
+ }
+
+ // Parse the FaultTimer period setting.
+ try {
+ String faultTimerPeriodStr = SP.getString("FaultTimerPeriod", "30");
+ mFaultTimerPeriod = Integer.parseInt(faultTimerPeriodStr);
+ Log.v(TAG, "updatePrefs() - mFaultTimerPeriod = " + mFaultTimerPeriod);
+ } catch (Exception ex) {
+ Log.v(TAG, "updatePrefs() - Problem with FaultTimerPeriod preference!");
+ Toast toast = Toast.makeText(mContext, "Problem Parsing FaultTimerPeriod Preference", Toast.LENGTH_SHORT);
+ toast.show();
+ }
+
+
+ // Watch Settings
+ String prefStr;
+ prefStr = SP.getString("BLE_Device_Addr", "SET_FROM_XML");
+ mBleDeviceAddr = prefStr;
+ Log.v(TAG,"mBLEDeviceAddr="+mBleDeviceAddr);
+ prefStr = SP.getString("BLE_Device_Name", "SET_FROM_XML");
+ mBleDeviceName = prefStr;
+ Log.v(TAG,"mBLEDeviceName="+mBleDeviceName);
+
+ prefStr = SP.getString("PebbleDebug", "SET_FROM_XML");
+ if (prefStr != null) {
+ mDebug = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() Debug = " + mDebug);
+
+ prefStr = SP.getString("PebbleDisplaySpectrum", "SET_FROM_XML");
+ mDisplaySpectrum = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() DisplaySpectrum = " + mDisplaySpectrum);
+
+ prefStr = SP.getString("PebbleUpdatePeriod", "SET_FROM_XML");
+ mDataUpdatePeriod = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() DataUpdatePeriod = " + mDataUpdatePeriod);
+
+ prefStr = SP.getString("MutePeriod", "SET_FROM_XML");
+ mMutePeriod = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() MutePeriod = " + mMutePeriod);
+
+ prefStr = SP.getString("ManAlarmPeriod", "SET_FROM_XML");
+ mManAlarmPeriod = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() ManAlarmPeriod = " + mManAlarmPeriod);
+
+ prefStr = SP.getString("PebbleSdMode", "SET_FROM_XML");
+ mPebbleSdMode = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() PebbleSdMode = " + mPebbleSdMode);
+
+ prefStr = SP.getString("SampleFreq", "SET_FROM_XML");
+ mSampleFreq = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() SampleFreq = " + mSampleFreq);
+
+ prefStr = SP.getString("SamplePeriod", "SET_FROM_XML");
+ mSamplePeriod = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() AnalysisPeriod = " + mSamplePeriod);
+
+ prefStr = SP.getString("AlarmFreqMin", "SET_FROM_XML");
+ mAlarmFreqMin = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() AlarmFreqMin = " + mAlarmFreqMin);
+
+ prefStr = SP.getString("AlarmFreqMax", "SET_FROM_XML");
+ mAlarmFreqMax = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() AlarmFreqMax = " + mAlarmFreqMax);
+
+ prefStr = SP.getString("WarnTime", "SET_FROM_XML");
+ mWarnTime = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() WarnTime = " + mWarnTime);
+
+ prefStr = SP.getString("AlarmTime", "SET_FROM_XML");
+ mAlarmTime = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() AlarmTime = " + mAlarmTime);
+
+ prefStr = SP.getString("AlarmThresh", "SET_FROM_XML");
+ mAlarmThresh = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() AlarmThresh = " + mAlarmThresh);
+
+ prefStr = SP.getString("AlarmRatioThresh", "SET_FROM_XML");
+ mAlarmRatioThresh = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() AlarmRatioThresh = " + mAlarmRatioThresh);
+
+ mFallActive = SP.getBoolean("FallActive", false);
+ Log.v(TAG, "updatePrefs() FallActive = " + mFallActive);
+
+ prefStr = SP.getString("FallThreshMin", "SET_FROM_XML");
+ mFallThreshMin = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() FallThreshMin = " + mFallThreshMin);
+
+ prefStr = SP.getString("FallThreshMax", "SET_FROM_XML");
+ mFallThreshMax = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() FallThreshMax = " + mFallThreshMax);
+
+ prefStr = SP.getString("FallWindow", "SET_FROM_XML");
+ mFallWindow = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() FallWindow = " + mFallWindow);
+
+ mSdData.mHRAlarmActive = SP.getBoolean("HRAlarmActive", false);
+ Log.v(TAG, "updatePrefs() HRAlarmActive = " + mSdData.mHRAlarmActive);
+
+ mSdData.mHRNullAsAlarm = SP.getBoolean("HRNullAsAlarm", false);
+ Log.v(TAG, "updatePrefs() HRNullAsAlarm = " + mSdData.mHRNullAsAlarm);
+
+ prefStr = SP.getString("HRThreshMin", "SET_FROM_XML");
+ mSdData.mHRThreshMin = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() HRThreshMin = " + mSdData.mHRThreshMin);
+
+ prefStr = SP.getString("HRThreshMax", "SET_FROM_XML");
+ mSdData.mHRThreshMax = (short) Integer.parseInt(prefStr);
+ Log.v(TAG, "updatePrefs() HRThreshMax = " + mSdData.mHRThreshMax);
+
+ } else {
+ Log.v(TAG, "updatePrefs() - prefStr is null - WHY????");
+ mUtil.writeToSysLogFile("SDDataSourceBLE.updatePrefs() - prefStr is null - WHY??");
+ Toast toast = Toast.makeText(mContext, "Problem Parsing Preferences - Something won't work - Please go back to Settings and correct it!", Toast.LENGTH_SHORT);
+ toast.show();
+ }
+
+ } catch (Exception ex) {
+ Log.v(TAG, "updatePrefs() - Problem parsing preferences!");
+ mUtil.writeToSysLogFile("SDDataSourceBLE.updatePrefs() - ERROR "+ex.toString());
+ Toast toast = Toast.makeText(mContext, "Problem Parsing Preferences - Something won't work - Please go back to Settings and correct it!", Toast.LENGTH_SHORT);
+ toast.show();
+ }
+ }
+
+
+
/**
* Display a Toast message on screen.
* @param msg - message to display.
@@ -119,4 +780,18 @@ public abstract class SdDataSource {
}
+
+ public class SdDataBroadcastReceiver extends BroadcastReceiver {
+ //private String TAG = "SdDataBroadcastReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.v(TAG,"SdDataBroadcastReceiver.onReceive()");
+ String jsonStr = intent.getStringExtra("data");
+ Log.v(TAG,"SdDataBroadcastReceiver.onReceive() - data="+jsonStr);
+ updateFromJSON(jsonStr);
+ }
+ }
+
+
}
diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java
new file mode 100644
index 0000000..9ae0f57
--- /dev/null
+++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java
@@ -0,0 +1,398 @@
+/*
+ Android_Pebble_sd - Android alarm client for openseizuredetector..
+
+ See http://openseizuredetector.org for more information.
+
+ Copyright Graham Jones, 2015, 2016
+
+ This file is part of pebble_sd.
+
+ Android_Pebble_sd is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Android_Pebble_sd is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Android_pebble_sd. If not, see .
+
+*/
+package uk.org.openseizuredetector;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.text.format.Time;
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+
+
+/**
+ * A data source that registers for BLE GATT notifications from a device and
+ * waits to be notified of data being available.
+ */
+public class SdDataSourceBLE extends SdDataSource {
+ private int MAX_RAW_DATA = 125; // 5 seconds at 25 Hz.
+ private String TAG = "SdDataSourceBLE";
+ private BluetoothManager mBluetoothManager;
+ private BluetoothAdapter mBluetoothAdapter;
+ private String mBluetoothDeviceAddress;
+ private BluetoothGatt mBluetoothGatt;
+ private int mConnectionState = STATE_DISCONNECTED;
+
+ private int nRawData = 0;
+ private double[] rawData = new double[MAX_RAW_DATA];
+
+ private static final int STATE_DISCONNECTED = 0;
+ private static final int STATE_CONNECTING = 1;
+ private static final int STATE_CONNECTED = 2;
+
+ public final static String ACTION_GATT_CONNECTED =
+ "com.example.bluetooth.le.ACTION_GATT_CONNECTED";
+ public final static String ACTION_GATT_DISCONNECTED =
+ "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
+ public final static String ACTION_GATT_SERVICES_DISCOVERED =
+ "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
+ public final static String ACTION_DATA_AVAILABLE =
+ "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
+ public final static String EXTRA_DATA =
+ "com.example.bluetooth.le.EXTRA_DATA";
+
+
+ public static String SERV_DEV_INFO = "0000180a-0000-1000-8000-00805f9b34fb";
+ public static String SERV_HEART_RATE = "0000180d-0000-1000-8000-00805f9b34fb";
+ public static String SERV_OSD = "a19585e9-0001-39d0-015f-b3e2b9a0c854";
+ public static String CHAR_HEART_RATE_MEASUREMENT = "00002a37-0000-1000-8000-00805f9b34fb";
+ public static String CHAR_MANUF_NAME = "00002a29-0000-1000-8000-00805f9b34fb";
+ public static String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb";
+ public static String CHAR_OSD_ACC_DATA = "a19585e9-0002-39d0-015f-b3e2b9a0c854";
+ public static String CHAR_OSD_BATT_DATA = "a19585e9-0004-39d0-015f-b3e2b9a0c854";
+
+
+ public final static UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(CHAR_HEART_RATE_MEASUREMENT);
+ private BluetoothGatt mGatt;
+ private BluetoothGattCharacteristic mBattChar;
+
+
+ public SdDataSourceBLE(Context context, Handler handler,
+ SdDataReceiver sdDataReceiver) {
+ super(context, handler, sdDataReceiver);
+ mName = "BLE";
+ // Set default settings from XML files (mContext is set by super().
+ PreferenceManager.setDefaultValues(mContext,
+ R.xml.network_passive_datasource_prefs, true);
+ }
+
+
+ /**
+ * Start the datasource updating - initialises from sharedpreferences first to
+ * make sure any changes to preferences are taken into account.
+ */
+ public void start() {
+ Log.i(TAG, "start()");
+ super.start();
+ mUtil.writeToSysLogFile("SdDataSourceBLE.start() - mBleDeviceAddr=" + mBleDeviceAddr);
+
+ if (mBleDeviceAddr == "" || mBleDeviceAddr == null) {
+ final Intent intent = new Intent(this.mContext, BLEScanActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+ Log.i(TAG, "mBLEDevice is " + mBleDeviceName + ", Addr=" + mBleDeviceAddr);
+
+ bleConnect();
+
+ }
+
+ private void bleConnect() {
+ mSdData.watchConnected = false;
+ mSdData.watchAppRunning = false;
+ mBluetoothGatt = null;
+ mConnectionState = STATE_DISCONNECTED;
+ if (mBluetoothManager == null) {
+ mBluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
+ if (mBluetoothManager == null) {
+ Log.e(TAG, "bleConnect(): Unable to initialize BluetoothManager.");
+ return;
+ }
+ }
+
+ mBluetoothAdapter = mBluetoothManager.getAdapter();
+ if (mBluetoothAdapter == null) {
+ Log.e(TAG, "bleConnect(): Unable to obtain a BluetoothAdapter.");
+ return;
+ }
+
+ if (mBluetoothAdapter == null || mBleDeviceAddr == null) {
+ Log.w(TAG, "bleConnect(): BluetoothAdapter not initialized or unspecified address.");
+ return;
+ }
+
+ final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mBleDeviceAddr);
+ if (device == null) {
+ Log.w(TAG, "bleConnect(): Device not found. Unable to connect.");
+ return;
+ } else {
+ // We want to directly connect to the device, so we are setting the autoConnect
+ // parameter to false.
+ mBluetoothGatt = device.connectGatt(mContext, true, mGattCallback);
+ Log.d(TAG, "bleConnect(): Trying to create a new connection.");
+ mBluetoothDeviceAddress = mBleDeviceAddr;
+ mConnectionState = STATE_CONNECTING;
+ }
+ }
+
+ private void bleDisconnect() {
+ if (mBluetoothAdapter == null || mBluetoothGatt == null) {
+ Log.w(TAG, "BluetoothAdapter not initialized");
+ return;
+ }
+ mBluetoothGatt.disconnect();
+ if (mBluetoothGatt == null) {
+ return;
+ }
+ mBluetoothGatt.close();
+ mBluetoothGatt = null;
+ mSdData.watchAppRunning = false;
+ mSdData.watchConnected = false;
+ mConnectionState = STATE_DISCONNECTED;
+
+ }
+
+ /**
+ * Stop the datasource from updating
+ */
+ public void stop() {
+ Log.i(TAG, "stop()");
+ mUtil.writeToSysLogFile("SDDataSourceBLE.stop()");
+
+ bleDisconnect();
+ super.stop();
+ }
+
+
+ // Implements callback methods for GATT events that the app cares about. For example,
+ // connection change and services discovered.
+ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ mConnectionState = STATE_CONNECTED;
+ mSdData.watchConnected = true;
+ Log.i(TAG, "onConnectionStateChange(): Connected to GATT server.");
+ // Attempts to discover services after successful connection.
+ Log.i(TAG, "onConnectionStateChange(): Attempting to start service discovery:");
+ mBluetoothGatt.discoverServices();
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ mConnectionState = STATE_DISCONNECTED;
+ mSdData.watchConnected = false;
+ Log.i(TAG, "onConnectionStateChange(): Disconnected from GATT server - reconnecting after delay...");
+ //bleDisconnect(); // Tidy up connections
+ // Wait 2 seconds to give the server chance to shutdown, then re-start it
+ mHandler.postDelayed(new Runnable() {
+ public void run() {
+ bleConnect();
+ }
+ }, 2000);
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ boolean foundOsdService = false;
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ Log.v(TAG, "Services discovered");
+ List serviceList = mBluetoothGatt.getServices();
+ for (int i = 0; i < serviceList.size(); i++) {
+ String uuidStr = serviceList.get(i).getUuid().toString();
+ Log.v(TAG, "Service " + uuidStr);
+ List gattCharacteristics =
+ serviceList.get(i).getCharacteristics();
+ if (uuidStr.equals(SERV_DEV_INFO)) {
+ Log.v(TAG, "Device Info Service Discovered");
+ } else if (uuidStr.equals(SERV_HEART_RATE)) {
+ Log.v(TAG, "Heart Rate Service Discovered");
+ for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
+ String charUuidStr = gattCharacteristic.getUuid().toString();
+ if (charUuidStr.equals(CHAR_HEART_RATE_MEASUREMENT)) {
+ Log.v(TAG, "Subscribing to Heart Rate Measurement Change Notifications");
+ setCharacteristicNotification(gattCharacteristic, true);
+ }
+ }
+ } else if (uuidStr.equals(SERV_OSD)) {
+ Log.v(TAG, "OpenSeizureDetector Service Discovered");
+ foundOsdService = true;
+ for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
+ String charUuidStr = gattCharacteristic.getUuid().toString();
+ if (charUuidStr.equals(CHAR_OSD_ACC_DATA)) {
+ Log.v(TAG, "Subscribing to Acceleration Data Change Notifications");
+ setCharacteristicNotification(gattCharacteristic,true);
+ }
+ else if (charUuidStr.equals(CHAR_OSD_BATT_DATA)) {
+ Log.v(TAG,"Saving battery characteristic for later");
+ mBattChar = gattCharacteristic;
+ }
+ }
+ }
+ }
+ if (foundOsdService) {
+ mGatt = gatt;
+ } else {
+ Log.v(TAG, "device is not offering the OSD Gatt Service - re-trying connection");
+ bleDisconnect();
+ // Wait 1 second to give the server chance to shutdown, then re-start it
+ mHandler.postDelayed(new Runnable() {
+ public void run() {
+ bleConnect();
+ }
+ }, 1000);
+ }
+ } else {
+ Log.w(TAG, "onServicesDiscovered received: " + status);
+ }
+ }
+
+ public void onDataReceived(BluetoothGattCharacteristic characteristic) {
+ // FIXME - collect data until we have enough to do analysis, then use onDataReceived to process it.
+ //Log.v(TAG,"onDataReceived: Characteristic="+characteristic.getUuid().toString());
+ if (characteristic.getUuid().toString().equals(CHAR_HEART_RATE_MEASUREMENT)) {
+ int flag = characteristic.getProperties();
+ int format = -1;
+ if ((flag & 0x01) != 0) {
+ format = BluetoothGattCharacteristic.FORMAT_UINT16;
+ //Log.d(TAG, "Heart rate format UINT16.");
+ } else {
+ format = BluetoothGattCharacteristic.FORMAT_UINT8;
+ //Log.d(TAG, "Heart rate format UINT8.");
+ }
+ final int heartRate = characteristic.getIntValue(format, 1);
+ Log.d(TAG, String.format("Received heart rate: %d", heartRate));
+ }
+ else if (characteristic.getUuid().toString().equals(CHAR_OSD_ACC_DATA)) {
+ //Log.v(TAG,"Received OSD ACC DATA"+characteristic.getValue());
+ byte[] rawDataBytes = characteristic.getValue();
+ Log.v(TAG, "CHAR_OSD_ACC_DATA: numSamples = " + rawDataBytes.length+" nRawData="+nRawData);
+ for (int i = 0; i < rawDataBytes.length;i++) {
+ if (nRawData < MAX_RAW_DATA) {
+ rawData[nRawData] = 1000 * rawDataBytes[i] / 64; // Scale to mg
+ nRawData++;
+ } else {
+ Log.i(TAG, "RawData Buffer Full - processing data");
+ // Re-start collecting raw data.
+ mSdData.watchAppRunning = true;
+ for (i = 0; i < rawData.length; i++) {
+ mSdData.rawData[i] = rawData[i];
+ }
+ mSdData.mNsamp = rawData.length;
+ //mNSamp = accelVals.length();
+ mWatchAppRunningCheck = true;
+ mDataStatusTime = new Time(Time.getCurrentTimezone());
+
+ if (mSdData.haveSettings == false) {
+ Log.v(TAG,"Requesting Battery Data");
+ mGatt.readCharacteristic(mBattChar);
+ }
+
+ doAnalysis();
+
+ nRawData = 0;
+ }
+ }
+ }
+ else if (characteristic.getUuid().toString().equals(CHAR_OSD_BATT_DATA)) {
+ mSdData.batteryPc = characteristic.getValue()[0];
+ Log.v(TAG,"Received Battery Data");
+ mSdData.haveSettings = true;
+ }
+ else {
+ Log.v(TAG,"Unrecognised Characteristic Updated "+
+ characteristic.getUuid().toString());
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic,
+ int status) {
+ Log.v(TAG,"onCharacteristicRead");
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ onDataReceived(characteristic);
+ }
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+ Log.v(TAG,"onCharacteristicChanged(): Characteristic "+characteristic.getUuid()+" changed");
+ onDataReceived(characteristic);
+ }
+ };
+
+
+ /**
+ * Enables or disables notification on a give characteristic.
+ *
+ * @param characteristic Characteristic to act on.
+ * @param enabled If true, enable notification. False otherwise.
+ */
+ public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
+ boolean enabled) {
+ if (mBluetoothAdapter == null || mBluetoothGatt == null) {
+ Log.w(TAG, "BluetoothAdapter not initialized");
+ return;
+ }
+ Log.v(TAG,"setCharacteristicNotification - Requesting notifications");
+ mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
+
+ // Tell the device we want notifications? The sample from Google said we only need this for Heart Rate, but the
+ // BangleJS widget did not work without it so do it for everything.
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+ UUID.fromString(GattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ mBluetoothGatt.writeDescriptor(descriptor);
+ }
+
+ /**
+ * Retrieves a list of supported GATT services on the connected device. This should be
+ * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
+ *
+ * @return A {@code List} of supported services.
+ */
+ public List getSupportedGattServices() {
+ if (mBluetoothGatt == null) return null;
+
+ return mBluetoothGatt.getServices();
+ }
+
+
+}
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceGarmin.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceGarmin.java
index 0199691..7dbf47d 100644
--- a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceGarmin.java
+++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceGarmin.java
@@ -53,52 +53,8 @@ import static java.lang.Long.parseLong;
* SdWebServer expects POST requests to /data and /settings URLs to send data or watch settings.
*/
public class SdDataSourceGarmin extends SdDataSource {
- private Handler mHandler = new Handler();
- private Timer mStatusTimer;
- private Timer mSettingsTimer;
- private Timer mFaultCheckTimer;
- private Time mDataStatusTime;
- private boolean mWatchAppRunningCheck = false;
- private int mAppRestartTimeout = 10; // Timeout before re-starting watch app (sec) if we have not received
- // data after mDataUpdatePeriod
- private int mFaultTimerPeriod = 30; // Fault Timer Period in sec
- private int mSettingsPeriod = 60; // period between requesting settings in seconds.
- private SdDataBroadcastReceiver mSdDataBroadcastReceiver;
-
-
private String TAG = "SdDataSourceGarmin";
- // Values for SD_MODE
- private int SD_MODE_FFT = 0; // The original OpenSeizureDetector mode (FFT based)
- private int SD_MODE_RAW = 1; // Send raw, unprocessed data to the phone.
- private int SD_MODE_FILTER = 2; // Use digital filter rather than FFT.
- private int SIMPLE_SPEC_FMAX = 10;
-
- private int ACCEL_SCALE_FACTOR = 1000; // Amount by which to reduce analysis results to scale to be comparable to analysis on Pebble.
-
- private short mDebug;
- private short mFreqCutoff = 12;
- private short mDisplaySpectrum;
- private short mDataUpdatePeriod;
- private short mMutePeriod;
- private short mManAlarmPeriod;
- private short mPebbleSdMode;
- private short mSampleFreq;
- private short mAlarmFreqMin;
- private short mAlarmFreqMax;
- private short mSamplePeriod;
- private short mWarnTime;
- private short mAlarmTime;
- private short mAlarmThresh;
- private short mAlarmRatioThresh;
- private boolean mFallActive;
- private short mFallThreshMin;
- private short mFallThreshMax;
- private short mFallWindow;
- private int mMute; // !=0 means muted by keypress on watch.
-
- private int mAlarmCount;
-
public SdDataSourceGarmin(Context context, Handler handler,
SdDataReceiver sdDataReceiver) {
super(context, handler, sdDataReceiver);
@@ -116,60 +72,7 @@ public class SdDataSourceGarmin extends SdDataSource {
public void start() {
Log.i(TAG, "start()");
mUtil.writeToSysLogFile("SdDataSourceGarmin.start()");
- updatePrefs();
- // Start timer to check status of watch regularly.
- mDataStatusTime = new Time(Time.getCurrentTimezone());
- // use a timer to check the status of the pebble app on the same frequency
- // as we get app data.
- if (mStatusTimer == null) {
- Log.v(TAG, "start(): starting status timer");
- mUtil.writeToSysLogFile("SdDataSourceGarmin.start() - starting status timer");
- mStatusTimer = new Timer();
- mStatusTimer.schedule(new TimerTask() {
- @Override
- public void run() {
- getStatus();
- }
- }, 0, mDataUpdatePeriod * 1000);
- } else {
- Log.v(TAG, "start(): status timer already running.");
- mUtil.writeToSysLogFile("SdDataSourceGarmin.start() - status timer already running??");
- }
- if (mFaultCheckTimer == null) {
- Log.v(TAG, "start(): starting alarm check timer");
- mUtil.writeToSysLogFile("SdDataSourceGarmin.start() - starting alarm check timer");
- mFaultCheckTimer = new Timer();
- mFaultCheckTimer.schedule(new TimerTask() {
- @Override
- public void run() {
- faultCheck();
- }
- }, 0, 1000);
- } else {
- Log.v(TAG, "start(): alarm check timer already running.");
- mUtil.writeToSysLogFile("SdDataSourceGarmin.start() - alarm check timer already running??");
- }
-
- if (mSettingsTimer == null) {
- Log.v(TAG, "start(): starting settings timer");
- mUtil.writeToSysLogFile("SdDataSourceGarmin.start() - starting settings timer");
- mSettingsTimer = new Timer();
- mSettingsTimer.schedule(new TimerTask() {
- @Override
- public void run() {
- mSdData.haveSettings = false;
- }
- }, 0, 1000 * mSettingsPeriod); // ask for settings less frequently than we get data
- } else {
- Log.v(TAG, "start(): settings timer already running.");
- mUtil.writeToSysLogFile("SdDataSourceGarmin.start() - settings timer already running??");
- }
-
- mSdDataBroadcastReceiver = new SdDataBroadcastReceiver();
- //uk.org.openseizuredetector.SdDataReceived
- IntentFilter filter = new IntentFilter("uk.org.openseizuredetector.SdDataReceived");
- mContext.registerReceiver(mSdDataBroadcastReceiver, filter);
-
+ super.start();
}
/**
@@ -178,580 +81,8 @@ public class SdDataSourceGarmin extends SdDataSource {
public void stop() {
Log.i(TAG, "stop()");
mUtil.writeToSysLogFile("SdDataSourceGarmin.stop()");
- try {
- // Stop the status timer
- if (mStatusTimer != null) {
- Log.v(TAG, "stop(): cancelling status timer");
- mUtil.writeToSysLogFile("SdDataSourceGarmin.stop() - cancelling status timer");
- mStatusTimer.cancel();
- mStatusTimer.purge();
- mStatusTimer = null;
- }
- // Stop the settings timer
- if (mSettingsTimer != null) {
- Log.v(TAG, "stop(): cancelling settings timer");
- mUtil.writeToSysLogFile("SdDataSourceGarmin.stop() - cancelling settings timer");
- mSettingsTimer.cancel();
- mSettingsTimer.purge();
- mSettingsTimer = null;
- }
- // Stop the alarm check timer
- if (mFaultCheckTimer != null) {
- Log.v(TAG, "stop(): cancelling alarm check timer");
- mUtil.writeToSysLogFile("SdDataSourceGarmin.stop() - cancelling alarm check timer");
- mFaultCheckTimer.cancel();
- mFaultCheckTimer.purge();
- mFaultCheckTimer = null;
- }
-
- } catch (Exception e) {
- Log.v(TAG, "Error in stop() - " + e.toString());
- mUtil.writeToSysLogFile("SdDataSourceGarmin.stop() - error - "+e.toString());
- }
- mContext.unregisterReceiver(mSdDataBroadcastReceiver);
+ super.stop();
}
-
- /**
- * updatePrefs() - update basic settings from the SharedPreferences
- * - defined in res/xml/SdDataSourceNetworkPassivePrefs.xml
- */
- public void updatePrefs() {
- Log.v(TAG, "updatePrefs()");
- mUtil.writeToSysLogFile("SdDataSourceGarmin.updatePrefs()");
- SharedPreferences SP = PreferenceManager
- .getDefaultSharedPreferences(mContext);
- try {
- // Parse the AppRestartTimeout period setting.
- try {
- String appRestartTimeoutStr = SP.getString("AppRestartTimeout", "10");
- mAppRestartTimeout = Integer.parseInt(appRestartTimeoutStr);
- Log.v(TAG, "updatePrefs() - mAppRestartTimeout = " + mAppRestartTimeout);
- } catch (Exception ex) {
- Log.v(TAG, "updatePrefs() - Problem with AppRestartTimeout preference!");
- Toast toast = Toast.makeText(mContext, "Problem Parsing AppRestartTimeout Preference", Toast.LENGTH_SHORT);
- toast.show();
- }
-
- // Parse the FaultTimer period setting.
- try {
- String faultTimerPeriodStr = SP.getString("FaultTimerPeriod", "30");
- mFaultTimerPeriod = Integer.parseInt(faultTimerPeriodStr);
- Log.v(TAG, "updatePrefs() - mFaultTimerPeriod = " + mFaultTimerPeriod);
- } catch (Exception ex) {
- Log.v(TAG, "updatePrefs() - Problem with FaultTimerPeriod preference!");
- Toast toast = Toast.makeText(mContext, "Problem Parsing FaultTimerPeriod Preference", Toast.LENGTH_SHORT);
- toast.show();
- }
-
-
- // Watch Settings
- String prefStr;
-
- prefStr = SP.getString("PebbleDebug", "SET_FROM_XML");
- if (prefStr != null) {
- mDebug = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() Debug = " + mDebug);
-
- prefStr = SP.getString("PebbleDisplaySpectrum", "SET_FROM_XML");
- mDisplaySpectrum = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() DisplaySpectrum = " + mDisplaySpectrum);
-
- prefStr = SP.getString("PebbleUpdatePeriod", "SET_FROM_XML");
- mDataUpdatePeriod = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() DataUpdatePeriod = " + mDataUpdatePeriod);
-
- prefStr = SP.getString("MutePeriod", "SET_FROM_XML");
- mMutePeriod = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() MutePeriod = " + mMutePeriod);
-
- prefStr = SP.getString("ManAlarmPeriod", "SET_FROM_XML");
- mManAlarmPeriod = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() ManAlarmPeriod = " + mManAlarmPeriod);
-
- prefStr = SP.getString("PebbleSdMode", "SET_FROM_XML");
- mPebbleSdMode = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() PebbleSdMode = " + mPebbleSdMode);
-
- prefStr = SP.getString("SampleFreq", "SET_FROM_XML");
- mSampleFreq = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() SampleFreq = " + mSampleFreq);
-
- prefStr = SP.getString("SamplePeriod", "SET_FROM_XML");
- mSamplePeriod = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() AnalysisPeriod = " + mSamplePeriod);
-
- prefStr = SP.getString("AlarmFreqMin", "SET_FROM_XML");
- mAlarmFreqMin = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() AlarmFreqMin = " + mAlarmFreqMin);
-
- prefStr = SP.getString("AlarmFreqMax", "SET_FROM_XML");
- mAlarmFreqMax = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() AlarmFreqMax = " + mAlarmFreqMax);
-
- prefStr = SP.getString("WarnTime", "SET_FROM_XML");
- mWarnTime = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() WarnTime = " + mWarnTime);
-
- prefStr = SP.getString("AlarmTime", "SET_FROM_XML");
- mAlarmTime = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() AlarmTime = " + mAlarmTime);
-
- prefStr = SP.getString("AlarmThresh", "SET_FROM_XML");
- mAlarmThresh = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() AlarmThresh = " + mAlarmThresh);
-
- prefStr = SP.getString("AlarmRatioThresh", "SET_FROM_XML");
- mAlarmRatioThresh = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() AlarmRatioThresh = " + mAlarmRatioThresh);
-
- mFallActive = SP.getBoolean("FallActive", false);
- Log.v(TAG, "updatePrefs() FallActive = " + mFallActive);
-
- prefStr = SP.getString("FallThreshMin", "SET_FROM_XML");
- mFallThreshMin = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() FallThreshMin = " + mFallThreshMin);
-
- prefStr = SP.getString("FallThreshMax", "SET_FROM_XML");
- mFallThreshMax = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() FallThreshMax = " + mFallThreshMax);
-
- prefStr = SP.getString("FallWindow", "SET_FROM_XML");
- mFallWindow = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() FallWindow = " + mFallWindow);
-
- mSdData.mHRAlarmActive = SP.getBoolean("HRAlarmActive", false);
- Log.v(TAG, "updatePrefs() HRAlarmActive = " + mSdData.mHRAlarmActive);
-
- mSdData.mHRNullAsAlarm = SP.getBoolean("HRNullAsAlarm", false);
- Log.v(TAG, "updatePrefs() HRNullAsAlarm = " + mSdData.mHRNullAsAlarm);
-
- prefStr = SP.getString("HRThreshMin", "SET_FROM_XML");
- mSdData.mHRThreshMin = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() HRThreshMin = " + mSdData.mHRThreshMin);
-
- prefStr = SP.getString("HRThreshMax", "SET_FROM_XML");
- mSdData.mHRThreshMax = (short) Integer.parseInt(prefStr);
- Log.v(TAG, "updatePrefs() HRThreshMax = " + mSdData.mHRThreshMax);
-
- } else {
- Log.v(TAG, "updatePrefs() - prefStr is null - WHY????");
- mUtil.writeToSysLogFile("SdDataSourceGarmin.updatePrefs() - prefStr is null - WHY??");
- Toast toast = Toast.makeText(mContext, "Problem Parsing Preferences - Something won't work - Please go back to Settings and correct it!", Toast.LENGTH_SHORT);
- toast.show();
- }
-
- } catch (Exception ex) {
- Log.v(TAG, "updatePrefs() - Problem parsing preferences!");
- mUtil.writeToSysLogFile("SdDataSourceGarmin.updatePrefs() - ERROR "+ex.toString());
- Toast toast = Toast.makeText(mContext, "Problem Parsing Preferences - Something won't work - Please go back to Settings and correct it!", Toast.LENGTH_SHORT);
- toast.show();
- }
- }
-
-
- // Force the data stored in this datasource to update in line with the JSON string encoded data provided.
- // Used by webServer to update the GarminDatasource.
- // Returns a message string that is passed back to the watch.
- public String updateFromJSON(String jsonStr) {
- String retVal = "undefined";
- String watchPartNo;
- String watchFwVersion;
- String sdVersion;
- String sdName;
- Log.v(TAG,"updateFromJSON - "+jsonStr);
-
- try {
- JSONObject mainObject = new JSONObject(jsonStr);
- //JSONObject dataObject = mainObject.getJSONObject("dataObj");
- JSONObject dataObject = mainObject;
- String dataTypeStr = dataObject.getString("dataType");
- Log.v(TAG,"updateFromJSON - dataType="+dataTypeStr);
- if (dataTypeStr.equals("raw")) {
- Log.v(TAG,"updateFromJSON - processing raw data");
- try {
- mSdData.mHR = dataObject.getDouble("HR");
- } catch (JSONException e) {
- // if we get 'null' HR (For example if the heart rate is not working)
- mSdData.mHR = -1;
- }
- try {
- mMute = dataObject.getInt("Mute");
- } catch (JSONException e) {
- // if we get 'null' HR (For example if the heart rate is not working)
- mMute = 0;
- }
- JSONArray accelVals = dataObject.getJSONArray("data");
- Log.v(TAG, "Received " + accelVals.length() + " acceleration values");
- int i;
- for (i = 0; i < accelVals.length(); i++) {
- mSdData.rawData[i] = accelVals.getInt(i);
- }
- mSdData.mNsamp = accelVals.length();
- //mNSamp = accelVals.length();
- mWatchAppRunningCheck = true;
- doAnalysis();
- if (mSdData.haveSettings == false) {
- retVal = "sendSettings";
- } else {
- retVal = "OK";
- }
- } else if (dataTypeStr.equals("settings")){
- Log.v(TAG,"updateFromJSON - processing settings");
- mSamplePeriod = (short)dataObject.getInt("analysisPeriod");
- mSampleFreq = (short)dataObject.getInt("sampleFreq");
- mSdData.batteryPc = (short)dataObject.getInt("battery");
- Log.v(TAG,"updateFromJSON - mSamplePeriod="+mSamplePeriod+" mSampleFreq="+mSampleFreq);
- mUtil.writeToSysLogFile("SdDataSourceGarmin.updateFromJSON - Settings Received");
- mUtil.writeToSysLogFile(" * mSamplePeriod="+mSamplePeriod+" mSampleFreq="+mSampleFreq);
- mUtil.writeToSysLogFile(" * batteryPc = "+mSdData.batteryPc);
-
- try {
- watchPartNo = dataObject.getString("watchPartNo");
- watchFwVersion = dataObject.getString("watchFwVersion");
- sdVersion = dataObject.getString("sdVersion");
- sdName = dataObject.getString("sdName");
- mUtil.writeToSysLogFile(" * sdName = "+sdName+" version "+sdVersion);
- mUtil.writeToSysLogFile(" * watchPartNo = "+watchPartNo+" fwVersion "+watchFwVersion);
- } catch (Exception e) {
- Log.e(TAG,"updateFromJSON - Error Parsing V3.2 JSON String - "+e.toString());
- mUtil.writeToSysLogFile("updateFromJSON - Error Parsing V3.2 JSON String - "+e.toString());
- mUtil.writeToSysLogFile(" This is probably because of an out of date watch app - please upgrade!");
- e.printStackTrace();
- }
- mSdData.haveSettings = true;
- mSdData.mSampleFreq = mSampleFreq;
- mWatchAppRunningCheck = true;
- retVal = "OK";
- } else {
- Log.e(TAG,"updateFromJSON - unrecognised dataType "+dataTypeStr);
- retVal = "ERROR";
- }
- } catch (Exception e) {
- Log.e(TAG,"updateFromJSON - Error Parsing JSON String - "+e.toString());
- mUtil.writeToSysLogFile("updateFromJSON - Error Parsing JSON String - "+e.toString());
- e.printStackTrace();
- retVal = "ERROR";
- }
- return(retVal);
- }
-
- /**
- * Calculate the magnitude of entry i in the fft array fft
- * @param fft
- * @param i
- * @return magnitude ( Re*Re + Im*Im )
- */
- private double getMagnitude(double[] fft, int i) {
- double mag;
- mag = (fft[2*i]*fft[2*i] + fft[2*i + 1] * fft[2*i +1]);
- return mag;
- }
-
- /**
- * doAnalysis() - analyse the data if the accelerometer data array mAccData
- * and populate the output data structure mSdData
- */
- private void doAnalysis() {
- // FIXME - Use specified sampleFreq, not this hard coded one
- mSampleFreq = 25;
- double freqRes = 1.0*mSampleFreq/mSdData.mNsamp;
- Log.v(TAG,"doAnalysis(): mSampleFreq="+mSampleFreq+" mNSamp="+mSdData.mNsamp+": freqRes="+freqRes);
- // Set the frequency bounds for the analysis in fft output bin numbers.
- int nMin = (int)(mAlarmFreqMin/freqRes);
- int nMax = (int)(mAlarmFreqMax /freqRes);
- Log.v(TAG,"doAnalysis(): mAlarmFreqMin="+mAlarmFreqMin+", nMin="+nMin
- +", mAlarmFreqMax="+mAlarmFreqMax+", nMax="+nMax);
- // Calculate the bin number of the cutoff frequency
- int nFreqCutoff = (int)(mFreqCutoff /freqRes);
- Log.v(TAG,"mFreqCutoff = "+mFreqCutoff+", nFreqCutoff="+nFreqCutoff);
-
- DoubleFFT_1D fftDo = new DoubleFFT_1D(mSdData.mNsamp);
- double[] fft = new double[mSdData.mNsamp * 2];
- ///System.arraycopy(mAccData, 0, fft, 0, mNsamp);
- System.arraycopy(mSdData.rawData, 0, fft, 0, mSdData.mNsamp);
- fftDo.realForward(fft);
-
- // Calculate the whole spectrum power (well a value equivalent to it that avoids square root calculations
- // and zero any readings that are above the frequency cutoff.
- double specPower = 0;
- for (int i = 1; i < mSdData.mNsamp / 2; i++) {
- if (i <= nFreqCutoff) {
- specPower = specPower + getMagnitude(fft,i);
- } else {
- fft[2*i] = 0.;
- fft[2*i+1] = 0.;
- }
- }
- //Log.v(TAG,"specPower = "+specPower);
- //specPower = specPower/(mSdData.mNsamp/2);
- specPower = specPower/mSdData.mNsamp/2;
- //Log.v(TAG,"specPower = "+specPower);
-
- // Calculate the Region of Interest power and power ratio.
- double roiPower = 0;
- for (int i=nMin;i mAlarmThresh) && (10 * (mSdData.roiPower / mSdData.specPower) > mAlarmRatioThresh)) {
- inAlarm = true;
- } else {
- inAlarm = false;
- }
-
- // set the alarmState to Alarm, Warning or OK, depending on the current state and previous ones.
- if (inAlarm) {
- mAlarmCount += mSamplePeriod;
- if (mAlarmCount > mAlarmTime) {
- // full alarm
- mSdData.alarmState = 2;
- } else if (mAlarmCount > mWarnTime) {
- // warning
- mSdData.alarmState = 1;
- }
- } else {
- // If we are not in an ALARM state, revert back to WARNING, otherwise
- // revert back to OK.
- if (mSdData.alarmState == 2) {
- // revert to warning
- mSdData.alarmState = 1;
- mAlarmCount = mWarnTime + 1; // pretend we have only just entered warning state.
- } else {
- // revert to OK
- mSdData.alarmState = 0;
- mAlarmCount = 0;
- }
- }
-
- Log.v(TAG, "alarmCheck(): inAlarm=" + inAlarm + ", alarmState = " + mSdData.alarmState + " alarmCount=" + mAlarmCount + " mAlarmTime=" + mAlarmTime);
-
- }
-
- public void muteCheck() {
- if (mMute != 0) {
- Log.v(TAG, "Mute Active - setting alarms to mute");
- mSdData.alarmState = 6;
- mSdData.alarmPhrase = "MUTE";
- mSdData.mHRAlarmStanding = false;
- }
-
- }
-
- /**
- * hrCheck - check the Heart rate data in mSdData to see if it represents an alarm condition.
- * Sets mSdData.mHRAlarmStanding
- */
- public void hrCheck() {
- Log.v(TAG, "hrCheck()");
- /* Check Heart Rate against alarm settings */
- if (mSdData.mHRAlarmActive) {
- if (mSdData.mHR < 0) {
- if (mSdData.mHRNullAsAlarm) {
- Log.i(TAG, "Heart Rate Null - Alarming");
- mSdData.mHRFaultStanding = false;
- mSdData.mHRAlarmStanding = true;
- } else {
- Log.i(TAG, "Heart Rate Fault (HR<0)");
- mSdData.mHRFaultStanding = true;
- mSdData.mHRAlarmStanding = false;
- }
- }
- else if ((mSdData.mHR > mSdData.mHRThreshMax) || (mSdData.mHR < mSdData.mHRThreshMin)) {
- Log.i(TAG, "Heart Rate Abnormal - " + mSdData.mHR + " bpm");
- mSdData.mHRFaultStanding = false;
- mSdData.mHRAlarmStanding = true;
- }
- else {
- mSdData.mHRFaultStanding = false;
- mSdData.mHRAlarmStanding = false;
- }
- }
-
- }
-
- /****************************************************************
- * Simple threshold analysis to chech for fall.
- * Called from clock_tick_handler()
- */
- public void fallCheck() {
- int i,j;
- double minAcc, maxAcc;
-
- long fallWindowSamp = (mFallWindow*mSdData.mSampleFreq)/1000; // Convert ms to samples.
- Log.v(TAG, "check_fall() - fallWindowSamp=" +fallWindowSamp);
- // Move window through sample buffer, checking for fall.
- // Note - not resetting fallAlarmStanding means that fall alarms will always latch until the 'Accept Alarm' button
- // is pressed.
- //mSdData.fallAlarmStanding = false;
- if (mFallActive) {
- mSdData.mFallActive = true;
- for (i = 0; i < mSdData.mNsamp - fallWindowSamp; i++) { // i = window start point
- // Find max and min acceleration within window.
- minAcc = mSdData.rawData[i];
- maxAcc = mSdData.rawData[i];
- for (j = 0; j < fallWindowSamp; j++) { // j = position within window
- if (mSdData.rawData[i + j] < minAcc) minAcc = mSdData.rawData[i + j];
- if (mSdData.rawData[i + j] > maxAcc) maxAcc = mSdData.rawData[i + j];
- }
- if ((minAcc < mFallThreshMin) && (maxAcc > mFallThreshMax)) {
- Log.d(TAG, "check_fall() - minAcc=" + minAcc + ", maxAcc=" + maxAcc);
- Log.d(TAG, "check_fall() - ****FALL DETECTED****");
- mSdData.fallAlarmStanding = true;
- return;
- }
- if (mMute != 0) {
- Log.v(TAG,"Mute Active - setting fall alarm to mute");
- mSdData.fallAlarmStanding = false;
- }
- }
- } else {
- mSdData.mFallActive = false;
- Log.v(TAG,"check_fall - mFallActive is false - doing nothing");
- }
- //if (debug) APP_LOG(APP_LOG_LEVEL_DEBUG,"check_fall() - minAcc=%d, maxAcc=%d",
- // minAcc,maxAcc);
-
- }
-
-
-
- /**
- * Checks the status of the connection to the watch,
- * and sets class variables for use by other functions.
- */
- public void getStatus() {
- Time tnow = new Time(Time.getCurrentTimezone());
- long tdiff;
- tnow.setToNow();
- // get time since the last data was received from the Pebble watch.
- tdiff = (tnow.toMillis(false) - mDataStatusTime.toMillis(false));
- Log.v(TAG, "getStatus() - mWatchAppRunningCheck=" + mWatchAppRunningCheck + " tdiff=" + tdiff);
- Log.v(TAG,"getStatus() - tdiff="+tdiff+", mDataUpatePeriod="+mDataUpdatePeriod+", mAppRestartTimeout="+mAppRestartTimeout);
-
- mSdData.watchConnected = true; // We can't check connection for passive network connection, so set it to true to avoid errors.
- // And is the watch app running?
- // set mWatchAppRunningCheck has been false for more than 10 seconds
- // the app is not talking to us
- // mWatchAppRunningCheck is set to true in the receiveData handler.
- if (!mWatchAppRunningCheck &&
- (tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) {
- Log.v(TAG, "getStatus() - tdiff = " + tdiff);
- mSdData.watchAppRunning = false;
- // Only make audible warning beep if we have not received data for more than mFaultTimerPeriod seconds.
- if (tdiff > (mDataUpdatePeriod + mFaultTimerPeriod) * 1000) {
- Log.v(TAG, "getStatus() - Watch App Not Running");
- mUtil.writeToSysLogFile("SdDataSourceGarmin.getStatus() - Watch App not Running");
- //mDataStatusTime.setToNow();
- mSdData.roiPower = -1;
- mSdData.specPower = -1;
- mSdDataReceiver.onSdDataFault(mSdData);
- } else {
- Log.v(TAG, "getStatus() - Waiting for mFaultTimerPeriod before issuing audible warning...");
- }
- } else {
- mSdData.watchAppRunning = true;
- }
-
- // if we have confirmation that the app is running, reset the
- // status time to now and initiate another check.
- if (mWatchAppRunningCheck) {
- mWatchAppRunningCheck = false;
- mDataStatusTime.setToNow();
- }
-
- if (!mSdData.haveSettings) {
- Log.v(TAG, "getStatus() - no settings received yet");
- }
- }
-
- /**
- * faultCheck - determines alarm state based on seizure detector data SdData. Called every second.
- */
- private void faultCheck() {
- Time tnow = new Time(Time.getCurrentTimezone());
- long tdiff;
- tnow.setToNow();
-
- // get time since the last data was received from the watch.
- tdiff = (tnow.toMillis(false) - mDataStatusTime.toMillis(false));
- Log.v(TAG, "faultCheck() - tdiff=" + tdiff + ", mDataUpatePeriod=" + mDataUpdatePeriod + ", mAppRestartTimeout=" + mAppRestartTimeout
- + ", combined = " + (mDataUpdatePeriod + mAppRestartTimeout) * 1000);
- if (!mWatchAppRunningCheck &&
- (tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) {
- Log.v(TAG, "faultCheck() - watch app not running so not doing anything");
- mAlarmCount = 0;
- }
- }
-
-
- public class SdDataBroadcastReceiver extends BroadcastReceiver {
- //private String TAG = "SdDataBroadcastReceiver";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.v(TAG,"SdDataBroadcastReceiver.onReceive()");
- String jsonStr = intent.getStringExtra("data");
- Log.v(TAG,"SdDataBroadcastReceiver.onReceive() - data="+jsonStr);
- updateFromJSON(jsonStr);
- }
- }
-
}
diff --git a/app/src/main/java/uk/org/openseizuredetector/SdServer.java b/app/src/main/java/uk/org/openseizuredetector/SdServer.java
index c5ddbde..b93fb72 100644
--- a/app/src/main/java/uk/org/openseizuredetector/SdServer.java
+++ b/app/src/main/java/uk/org/openseizuredetector/SdServer.java
@@ -244,6 +244,11 @@ public class SdServer extends Service implements SdDataReceiver {
mUtil.writeToSysLogFile("SdServer.onStartCommand() - creating SdDataSourceGarmin");
mSdDataSource = new SdDataSourceGarmin(this.getApplicationContext(), mHandler, this);
break;
+ case "BLE":
+ Log.v(TAG, "Selecting BLE DataSource");
+ mUtil.writeToSysLogFile("SdServer.onStartCommand() - creating SdDataSourceBLE");
+ mSdDataSource = new SdDataSourceBLE(this.getApplicationContext(), mHandler, this);
+ break;
default:
Log.e(TAG, "Datasource " + mSdDataSourceName + " not recognised - Defaulting to Pebble");
mUtil.writeToSysLogFile("SdServer.onStartCommand() - Datasource " + mSdDataSourceName + " not recognised - exiting");
@@ -679,10 +684,24 @@ public class SdServer extends Service implements SdDataReceiver {
mSdData.alarmPhrase = "FAULT";
mSdData.alarmStanding = false;
if (webServer != null) webServer.setSdData(mSdData);
- if (mAudibleFaultWarning) {
+ // We only take action to warn the user and re-start the data source to attempt to fix it
+ // ourselves if we have been in a fault condition for a while - signified by the mFaultTimerCompleted
+ // flag.
+ if (mFaultTimerCompleted) {
faultWarningBeep();
+ //mSdDataSource.stop();
+ //mHandler.postDelayed(new Runnable() {
+ // public void run() {
+ // mSdDataSource.start();
+ // }
+ //}, 190);
+ } else {
+ startFaultTimer();
+ Log.v(TAG, "onSdDataFault() - starting Fault Timer");
+ mUtil.writeToSysLogFile("onSdDataFault() - starting Fault Timer");
}
- showNotification(-1);
+
+ showNotification(-1);
}
/* from http://stackoverflow.com/questions/12154940/how-to-make-a-beep-in-android */
@@ -705,26 +724,20 @@ public class SdServer extends Service implements SdDataReceiver {
* beep, provided mAudibleAlarm is set
*/
public void faultWarningBeep() {
- if (mFaultTimerCompleted) {
- if (mCancelAudible) {
+ if (mCancelAudible) {
Log.v(TAG, "faultWarningBeep() - CancelAudible Active - silent beep...");
} else {
- if (mAudibleFaultWarning) {
- if (mMp3Alarm) {
- Log.v(TAG, "Not making MP3 fault beep - handled by notification");
- } else {
- beep(10);
- }
- Log.v(TAG, "faultWarningBeep()");
- mUtil.writeToSysLogFile("SdServer.faultWarningBeep() - beeping");
+ if (mAudibleFaultWarning) {
+ if (mMp3Alarm) {
+ Log.v(TAG, "Not making MP3 fault beep - handled by notification");
} else {
- Log.v(TAG, "faultWarningBeep() - silent...");
+ beep(10);
}
+ Log.v(TAG, "faultWarningBeep()");
+ mUtil.writeToSysLogFile("SdServer.faultWarningBeep() - beeping");
+ } else {
+ Log.v(TAG, "faultWarningBeep() - silent...");
}
- } else {
- startFaultTimer();
- Log.v(TAG, "faultWarningBeep() - starting Fault Timer");
- mUtil.writeToSysLogFile("faultWarningBeep() - starting Fault Timer");
}
}
diff --git a/app/src/main/res/layout/actionbar_indeterminate_progress.xml b/app/src/main/res/layout/actionbar_indeterminate_progress.xml
new file mode 100644
index 0000000..edaac11
--- /dev/null
+++ b/app/src/main/res/layout/actionbar_indeterminate_progress.xml
@@ -0,0 +1,23 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/ble_list_item_device.xml b/app/src/main/res/layout/ble_list_item_device.xml
new file mode 100644
index 0000000..255e412
--- /dev/null
+++ b/app/src/main/res/layout/ble_list_item_device.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/pref_select_ble_device_button.xml b/app/src/main/res/layout/pref_select_ble_device_button.xml
new file mode 100644
index 0000000..d6718b1
--- /dev/null
+++ b/app/src/main/res/layout/pref_select_ble_device_button.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/ble_scan_menu.xml b/app/src/main/res/menu/ble_scan_menu.xml
new file mode 100644
index 0000000..8b934f1
--- /dev/null
+++ b/app/src/main/res/menu/ble_scan_menu.xml
@@ -0,0 +1,30 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/datasource_list.xml b/app/src/main/res/values/datasource_list.xml
index 06374d4..c8a5d21 100644
--- a/app/src/main/res/values/datasource_list.xml
+++ b/app/src/main/res/values/datasource_list.xml
@@ -4,11 +4,13 @@
- "Pebble Watch"
- "Garmin Watch"
- "Network"
+ - "Bluetooth Device"
- "Pebble"
- "Garmin"
- "Network"
+ - "BLE"
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 410e066..8d13d90 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -4,6 +4,8 @@
\n V3.5 - Added support for Phone Call Alerts, using separate OpenSeizureDetector Dialler App
\n - Added Unique Identifier (UUID) to SMS alerts so they can be detected as OpenSeizureDetector SMS messages on client phone.
+ \n V3.4.0 - Aug 2020
+ \n - Added support for BLE Data Source
OpenSeizureDetector does not collect any personal data.
@@ -135,5 +137,12 @@
Phone Alarm Disabled
Test Phone Alarm
OpenSeizureDetector Dialer App Not installed - Required for Phone Call Alerts.
-
+ BLE Devices
+ BLE Not Supported
+ Bluetooth Not Supported
+ STOP
+ SCAN
+ Unknown Device
+ Select BLE Device
+ Select Bluetooth Low Energy (BLE) Device to provide seizure (acceleration and heart rate) data).
diff --git a/app/src/main/res/xml/general_prefs.xml b/app/src/main/res/xml/general_prefs.xml
index 8577261..9e1408a 100644
--- a/app/src/main/res/xml/general_prefs.xml
+++ b/app/src/main/res/xml/general_prefs.xml
@@ -8,6 +8,12 @@
android:entryValues="@array/datasource_list_values"
android:defaultValue="Garmin"
android:dialogTitle="Select Data Source" />
+
-
-
+
+
+
+
+
+
+ android:key="PebbleSdMode"
+ android:summary="Select one of the three available modes of operation."
+ android:title="Seizure Detector Mode" />
+ android:entries="@array/pebble_sample_freq_list"
+ android:entryValues="@array/pebble_sample_freq_list_values"
+ android:key="SampleFreq"
+ android:summary="Higher Frequency is more Accurate, but uses more battery power."
+ android:title="Select Sample Frequency" />
@@ -102,16 +111,15 @@
android:title="Fall Detection Window (milli-seconds)" />
-
+
+ android:key="PebbleDebug"
+ android:summary="Set Debug mode on or off."
+ android:title="Seizure Detector Debug Mode" />