Merge branch 'V3.4' into V3.5
This commit is contained in:
@@ -3,7 +3,8 @@
|
|||||||
V3.5.0 - Aug 2020
|
V3.5.0 - Aug 2020
|
||||||
- Added broadcast to request phone call dial alert (handled by separate app OpenSeizureDetector Dialler).
|
- 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.
|
- 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
|
V3.2.1 - Aug2020
|
||||||
- Addition of Spanish Translation, and correction of crash report wording in German.
|
- Addition of Spanish Translation, and correction of crash report wording in German.
|
||||||
V3.2.0 - mar2020
|
V3.2.0 - mar2020
|
||||||
|
|||||||
20
app/release/output-metadata.json
Normal file
20
app/release/output-metadata.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
android:versionCode="72"
|
android:versionCode="72"
|
||||||
android:versionName="3.5.0">
|
android:versionName="3.5.0">
|
||||||
<!-- android:allowBackup="false" -->
|
<!-- android:allowBackup="false" -->
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
@@ -27,8 +29,9 @@
|
|||||||
android:icon="@drawable/star_of_life_48x48"
|
android:icon="@drawable/star_of_life_48x48"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:theme="@style/Theme.AppCompat"
|
android:theme="@style/AppTheme"
|
||||||
>
|
> <!--@android:style/Theme.Holo.Light"-->
|
||||||
|
<activity android:name=".BLEScanActivity"></activity>
|
||||||
<activity android:name=".DBQueryActivity"></activity>
|
<activity android:name=".DBQueryActivity"></activity>
|
||||||
<!-- android:usesCleartextTraffic="true" -->
|
<!-- android:usesCleartextTraffic="true" -->
|
||||||
<activity android:name=".StartupActivity">
|
<activity android:name=".StartupActivity">
|
||||||
|
|||||||
@@ -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<BluetoothDevice> mLeDevices;
|
||||||
|
private LayoutInflater mInflator;
|
||||||
|
|
||||||
|
public LeDeviceListAdapter() {
|
||||||
|
super();
|
||||||
|
mLeDevices = new ArrayList<BluetoothDevice>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,16 +34,19 @@ import android.os.Bundle;
|
|||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.util.List;
|
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 String TAG = "PreferenceActivity";
|
||||||
private OsdUtil mUtil;
|
private OsdUtil mUtil;
|
||||||
private boolean mPrefChanged = false;
|
private boolean mPrefChanged = false;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private Handler mHandler;
|
private Handler mHandler;
|
||||||
|
private Button mSelectBLEButton;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
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;
|
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.
|
* This fragment shows the preferences for the first header.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -23,13 +23,26 @@
|
|||||||
*/
|
*/
|
||||||
package uk.org.openseizuredetector;
|
package uk.org.openseizuredetector;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.text.format.Time;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
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 {
|
interface SdDataReceiver {
|
||||||
public void onSdDataReceived(SdData sdData);
|
public void onSdDataReceived(SdData sdData);
|
||||||
public void onSdDataFault(SdData sdData);
|
public void onSdDataFault(SdData sdData);
|
||||||
@@ -40,14 +53,56 @@ interface SdDataReceiver {
|
|||||||
* network data source.
|
* network data source.
|
||||||
*/
|
*/
|
||||||
public abstract class SdDataSource {
|
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 SdData mSdData;
|
||||||
public String mName = "undefined";
|
public String mName = "undefined";
|
||||||
protected OsdUtil mUtil;
|
protected OsdUtil mUtil;
|
||||||
protected Context mContext;
|
protected Context mContext;
|
||||||
protected Handler mHandler;
|
|
||||||
protected SdDataReceiver mSdDataReceiver;
|
protected SdDataReceiver mSdDataReceiver;
|
||||||
private String TAG = "SdDataSource";
|
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) {
|
public SdDataSource(Context context, Handler handler, SdDataReceiver sdDataReceiver) {
|
||||||
Log.v(TAG, "SdDataSource() Constructor");
|
Log.v(TAG, "SdDataSource() Constructor");
|
||||||
mContext = context;
|
mContext = context;
|
||||||
@@ -70,7 +125,57 @@ public abstract class SdDataSource {
|
|||||||
* make sure any changes to preferences are taken into account.
|
* make sure any changes to preferences are taken into account.
|
||||||
*/
|
*/
|
||||||
public void start() {
|
public void start() {
|
||||||
|
|
||||||
Log.v(TAG, "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() {
|
public void stop() {
|
||||||
Log.v(TAG, "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()"); }
|
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.
|
// 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) {
|
public String updateFromJSON(String jsonStr) {
|
||||||
|
String retVal = "undefined";
|
||||||
|
String watchPartNo;
|
||||||
|
String watchFwVersion;
|
||||||
|
String sdVersion;
|
||||||
|
String sdName;
|
||||||
Log.v(TAG,"updateFromJSON - "+jsonStr);
|
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<nMax;i++) {
|
||||||
|
roiPower = roiPower + getMagnitude(fft,i);
|
||||||
|
}
|
||||||
|
roiPower = roiPower/(nMax - nMin);
|
||||||
|
double roiRatio = 10 * roiPower / specPower;
|
||||||
|
|
||||||
|
// Calculate the simplified spectrum - power in 1Hz bins.
|
||||||
|
double[] simpleSpec = new double[SIMPLE_SPEC_FMAX+1];
|
||||||
|
for (int ifreq=0;ifreq<SIMPLE_SPEC_FMAX;ifreq++) {
|
||||||
|
int binMin = (int)(1 + ifreq/freqRes); // add 1 to loose dc component
|
||||||
|
int binMax = (int)(1 + (ifreq+1)/freqRes);
|
||||||
|
simpleSpec[ifreq]=0;
|
||||||
|
for (int i=binMin;i<binMax;i++) {
|
||||||
|
simpleSpec[ifreq] = simpleSpec[ifreq] + getMagnitude(fft,i);
|
||||||
|
}
|
||||||
|
simpleSpec[ifreq] = simpleSpec[ifreq] / (binMax-binMin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the mSdData structure to communicate with the main SdServer service.
|
||||||
|
mDataStatusTime.setToNow();
|
||||||
|
mSdData.specPower = (long)specPower / ACCEL_SCALE_FACTOR;
|
||||||
|
mSdData.roiPower = (long)roiPower / ACCEL_SCALE_FACTOR;
|
||||||
|
mSdData.dataTime.setToNow();
|
||||||
|
mSdData.maxVal = 0; // not used
|
||||||
|
mSdData.maxFreq = 0; // not used
|
||||||
|
mSdData.haveData = true;
|
||||||
|
mSdData.alarmThresh = mAlarmThresh;
|
||||||
|
mSdData.alarmRatioThresh = mAlarmRatioThresh;
|
||||||
|
mSdData.alarmFreqMin = mAlarmFreqMin;
|
||||||
|
mSdData.alarmFreqMax = mAlarmFreqMax;
|
||||||
|
// note mSdData.batteryPc is set from settings data in updateFromJSON()
|
||||||
|
// FIXME - I haven't worked out why dividing by 1000 seems necessary to get the graph on scale - we don't seem to do that with the Pebble.
|
||||||
|
for(int i=0;i<SIMPLE_SPEC_FMAX;i++) {
|
||||||
|
mSdData.simpleSpec[i] = (int)simpleSpec[i]/ACCEL_SCALE_FACTOR;
|
||||||
|
}
|
||||||
|
Log.v(TAG, "simpleSpec = " + Arrays.toString(mSdData.simpleSpec));
|
||||||
|
|
||||||
|
// Because we have received data, set flag to show watch app running.
|
||||||
|
mWatchAppRunningCheck = true;
|
||||||
|
|
||||||
|
// Check this data to see if it represents an alarm state.
|
||||||
|
alarmCheck();
|
||||||
|
hrCheck();
|
||||||
|
fallCheck();
|
||||||
|
muteCheck();
|
||||||
|
|
||||||
|
mSdDataReceiver.onSdDataReceived(mSdData); // and tell SdServer we have received data.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* checkAlarm() - checks the current accelerometer data and uses
|
||||||
|
* historical data to determine if we are in a fault, warning or ok
|
||||||
|
* state.
|
||||||
|
* Sets mSdData.alarmState and mSdData.hrAlarmStanding
|
||||||
|
*/
|
||||||
|
private void alarmCheck() {
|
||||||
|
boolean inAlarm;
|
||||||
|
Log.v(TAG, "alarmCheck()");
|
||||||
|
// Is the current set of data representing an alarm state?
|
||||||
|
if ((mSdData.roiPower > 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.
|
* Display a Toast message on screen.
|
||||||
* @param msg - message to display.
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
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<BluetoothGattService> serviceList = mBluetoothGatt.getServices();
|
||||||
|
for (int i = 0; i < serviceList.size(); i++) {
|
||||||
|
String uuidStr = serviceList.get(i).getUuid().toString();
|
||||||
|
Log.v(TAG, "Service " + uuidStr);
|
||||||
|
List<BluetoothGattCharacteristic> 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<BluetoothGattService> getSupportedGattServices() {
|
||||||
|
if (mBluetoothGatt == null) return null;
|
||||||
|
|
||||||
|
return mBluetoothGatt.getServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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.
|
* SdWebServer expects POST requests to /data and /settings URLs to send data or watch settings.
|
||||||
*/
|
*/
|
||||||
public class SdDataSourceGarmin extends SdDataSource {
|
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";
|
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,
|
public SdDataSourceGarmin(Context context, Handler handler,
|
||||||
SdDataReceiver sdDataReceiver) {
|
SdDataReceiver sdDataReceiver) {
|
||||||
super(context, handler, sdDataReceiver);
|
super(context, handler, sdDataReceiver);
|
||||||
@@ -116,60 +72,7 @@ public class SdDataSourceGarmin extends SdDataSource {
|
|||||||
public void start() {
|
public void start() {
|
||||||
Log.i(TAG, "start()");
|
Log.i(TAG, "start()");
|
||||||
mUtil.writeToSysLogFile("SdDataSourceGarmin.start()");
|
mUtil.writeToSysLogFile("SdDataSourceGarmin.start()");
|
||||||
updatePrefs();
|
super.start();
|
||||||
// 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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -178,580 +81,8 @@ public class SdDataSourceGarmin extends SdDataSource {
|
|||||||
public void stop() {
|
public void stop() {
|
||||||
Log.i(TAG, "stop()");
|
Log.i(TAG, "stop()");
|
||||||
mUtil.writeToSysLogFile("SdDataSourceGarmin.stop()");
|
mUtil.writeToSysLogFile("SdDataSourceGarmin.stop()");
|
||||||
try {
|
super.stop();
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<nMax;i++) {
|
|
||||||
roiPower = roiPower + getMagnitude(fft,i);
|
|
||||||
}
|
|
||||||
roiPower = roiPower/(nMax - nMin);
|
|
||||||
double roiRatio = 10 * roiPower / specPower;
|
|
||||||
|
|
||||||
// Calculate the simplified spectrum - power in 1Hz bins.
|
|
||||||
double[] simpleSpec = new double[SIMPLE_SPEC_FMAX+1];
|
|
||||||
for (int ifreq=0;ifreq<SIMPLE_SPEC_FMAX;ifreq++) {
|
|
||||||
int binMin = (int)(1 + ifreq/freqRes); // add 1 to loose dc component
|
|
||||||
int binMax = (int)(1 + (ifreq+1)/freqRes);
|
|
||||||
simpleSpec[ifreq]=0;
|
|
||||||
for (int i=binMin;i<binMax;i++) {
|
|
||||||
simpleSpec[ifreq] = simpleSpec[ifreq] + getMagnitude(fft,i);
|
|
||||||
}
|
|
||||||
simpleSpec[ifreq] = simpleSpec[ifreq] / (binMax-binMin);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate the mSdData structure to communicate with the main SdServer service.
|
|
||||||
mDataStatusTime.setToNow();
|
|
||||||
mSdData.specPower = (long)specPower / ACCEL_SCALE_FACTOR;
|
|
||||||
mSdData.roiPower = (long)roiPower / ACCEL_SCALE_FACTOR;
|
|
||||||
mSdData.dataTime.setToNow();
|
|
||||||
mSdData.maxVal = 0; // not used
|
|
||||||
mSdData.maxFreq = 0; // not used
|
|
||||||
mSdData.haveData = true;
|
|
||||||
mSdData.alarmThresh = mAlarmThresh;
|
|
||||||
mSdData.alarmRatioThresh = mAlarmRatioThresh;
|
|
||||||
mSdData.alarmFreqMin = mAlarmFreqMin;
|
|
||||||
mSdData.alarmFreqMax = mAlarmFreqMax;
|
|
||||||
// note mSdData.batteryPc is set from settings data in updateFromJSON()
|
|
||||||
// FIXME - I haven't worked out why dividing by 1000 seems necessary to get the graph on scale - we don't seem to do that with the Pebble.
|
|
||||||
for(int i=0;i<SIMPLE_SPEC_FMAX;i++) {
|
|
||||||
mSdData.simpleSpec[i] = (int)simpleSpec[i]/ACCEL_SCALE_FACTOR;
|
|
||||||
}
|
|
||||||
Log.v(TAG, "simpleSpec = " + Arrays.toString(mSdData.simpleSpec));
|
|
||||||
|
|
||||||
// Because we have received data, set flag to show watch app running.
|
|
||||||
mWatchAppRunningCheck = true;
|
|
||||||
|
|
||||||
// Check this data to see if it represents an alarm state.
|
|
||||||
alarmCheck();
|
|
||||||
hrCheck();
|
|
||||||
fallCheck();
|
|
||||||
muteCheck();
|
|
||||||
|
|
||||||
mSdDataReceiver.onSdDataReceived(mSdData); // and tell SdServer we have received data.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/****************************************************************
|
|
||||||
* checkAlarm() - checks the current accelerometer data and uses
|
|
||||||
* historical data to determine if we are in a fault, warning or ok
|
|
||||||
* state.
|
|
||||||
* Sets mSdData.alarmState and mSdData.hrAlarmStanding
|
|
||||||
*/
|
|
||||||
private void alarmCheck() {
|
|
||||||
boolean inAlarm;
|
|
||||||
Log.v(TAG, "alarmCheck()");
|
|
||||||
// Is the current set of data representing an alarm state?
|
|
||||||
if ((mSdData.roiPower > 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -244,6 +244,11 @@ public class SdServer extends Service implements SdDataReceiver {
|
|||||||
mUtil.writeToSysLogFile("SdServer.onStartCommand() - creating SdDataSourceGarmin");
|
mUtil.writeToSysLogFile("SdServer.onStartCommand() - creating SdDataSourceGarmin");
|
||||||
mSdDataSource = new SdDataSourceGarmin(this.getApplicationContext(), mHandler, this);
|
mSdDataSource = new SdDataSourceGarmin(this.getApplicationContext(), mHandler, this);
|
||||||
break;
|
break;
|
||||||
|
case "BLE":
|
||||||
|
Log.v(TAG, "Selecting BLE DataSource");
|
||||||
|
mUtil.writeToSysLogFile("SdServer.onStartCommand() - creating SdDataSourceBLE");
|
||||||
|
mSdDataSource = new SdDataSourceBLE(this.getApplicationContext(), mHandler, this);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Log.e(TAG, "Datasource " + mSdDataSourceName + " not recognised - Defaulting to Pebble");
|
Log.e(TAG, "Datasource " + mSdDataSourceName + " not recognised - Defaulting to Pebble");
|
||||||
mUtil.writeToSysLogFile("SdServer.onStartCommand() - Datasource " + mSdDataSourceName + " not recognised - exiting");
|
mUtil.writeToSysLogFile("SdServer.onStartCommand() - Datasource " + mSdDataSourceName + " not recognised - exiting");
|
||||||
@@ -679,10 +684,24 @@ public class SdServer extends Service implements SdDataReceiver {
|
|||||||
mSdData.alarmPhrase = "FAULT";
|
mSdData.alarmPhrase = "FAULT";
|
||||||
mSdData.alarmStanding = false;
|
mSdData.alarmStanding = false;
|
||||||
if (webServer != null) webServer.setSdData(mSdData);
|
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();
|
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 */
|
/* 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
|
* beep, provided mAudibleAlarm is set
|
||||||
*/
|
*/
|
||||||
public void faultWarningBeep() {
|
public void faultWarningBeep() {
|
||||||
if (mFaultTimerCompleted) {
|
if (mCancelAudible) {
|
||||||
if (mCancelAudible) {
|
|
||||||
Log.v(TAG, "faultWarningBeep() - CancelAudible Active - silent beep...");
|
Log.v(TAG, "faultWarningBeep() - CancelAudible Active - silent beep...");
|
||||||
} else {
|
} else {
|
||||||
if (mAudibleFaultWarning) {
|
if (mAudibleFaultWarning) {
|
||||||
if (mMp3Alarm) {
|
if (mMp3Alarm) {
|
||||||
Log.v(TAG, "Not making MP3 fault beep - handled by notification");
|
Log.v(TAG, "Not making MP3 fault beep - handled by notification");
|
||||||
} else {
|
|
||||||
beep(10);
|
|
||||||
}
|
|
||||||
Log.v(TAG, "faultWarningBeep()");
|
|
||||||
mUtil.writeToSysLogFile("SdServer.faultWarningBeep() - beeping");
|
|
||||||
} else {
|
} 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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
23
app/src/main/res/layout/actionbar_indeterminate_progress.xml
Normal file
23
app/src/main/res/layout/actionbar_indeterminate_progress.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2013 Google Inc.
|
||||||
|
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="56dp"
|
||||||
|
android:minWidth="56dp">
|
||||||
|
<ProgressBar android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="center"/>
|
||||||
|
</FrameLayout>
|
||||||
28
app/src/main/res/layout/ble_list_item_device.xml
Normal file
28
app/src/main/res/layout/ble_list_item_device.xml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<TextView android:id="@+id/device_name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="24dp"/>
|
||||||
|
<TextView android:id="@+id/device_address"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="12dp"/>
|
||||||
|
</LinearLayout>
|
||||||
10
app/src/main/res/layout/pref_select_ble_device_button.xml
Normal file
10
app/src/main/res/layout/pref_select_ble_device_button.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Button
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/selectBLEDeviceButton"
|
||||||
|
android:text="@string/select_ble_device_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:onClick="onClick"
|
||||||
|
>
|
||||||
|
</Button>
|
||||||
30
app/src/main/res/menu/ble_scan_menu.xml
Normal file
30
app/src/main/res/menu/ble_scan_menu.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:id="@+id/menu_refresh"
|
||||||
|
android:checkable="false"
|
||||||
|
android:orderInCategory="1"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
<item android:id="@+id/menu_scan"
|
||||||
|
android:title="@string/menu_scan"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
app:showAsAction="ifRoom|withText" />
|
||||||
|
<item android:id="@+id/menu_stop"
|
||||||
|
android:title="@string/menu_stop"
|
||||||
|
android:orderInCategory="101"
|
||||||
|
app:showAsAction="ifRoom|withText" />
|
||||||
|
</menu>
|
||||||
@@ -4,11 +4,13 @@
|
|||||||
<item>"Pebble Watch"</item>
|
<item>"Pebble Watch"</item>
|
||||||
<item>"Garmin Watch"</item>
|
<item>"Garmin Watch"</item>
|
||||||
<item>"Network"</item>
|
<item>"Network"</item>
|
||||||
|
<item>"Bluetooth Device"</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="datasource_list_values">
|
<string-array name="datasource_list_values">
|
||||||
<item>"Pebble"</item>
|
<item>"Pebble"</item>
|
||||||
<item>"Garmin"</item>
|
<item>"Garmin"</item>
|
||||||
<item>"Network"</item>
|
<item>"Network"</item>
|
||||||
|
<item>"BLE"</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
<string name="changelog">
|
<string name="changelog">
|
||||||
\n V3.5 - Added support for Phone Call Alerts, using separate OpenSeizureDetector Dialler App
|
\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 - 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
|
||||||
</string>
|
</string>
|
||||||
<string name="UpgradeMsg">
|
<string name="UpgradeMsg">
|
||||||
OpenSeizureDetector does not collect any personal data.
|
OpenSeizureDetector does not collect any personal data.
|
||||||
@@ -135,5 +137,12 @@
|
|||||||
<string name="phone_alarm_disabled">Phone Alarm Disabled</string>
|
<string name="phone_alarm_disabled">Phone Alarm Disabled</string>
|
||||||
<string name="test_phone_alarm_notification">Test Phone Alarm</string>
|
<string name="test_phone_alarm_notification">Test Phone Alarm</string>
|
||||||
<string name="DiallerNotInstalledWarning"><a href="https://github.com/OpenSeizureDetector/Dialler/tree/master/app/release/app-release.apk">OpenSeizureDetector Dialer App</a> Not installed - Required for Phone Call Alerts.</string>
|
<string name="DiallerNotInstalledWarning"><a href="https://github.com/OpenSeizureDetector/Dialler/tree/master/app/release/app-release.apk">OpenSeizureDetector Dialer App</a> Not installed - Required for Phone Call Alerts.</string>
|
||||||
|
<string name="title_devices">BLE Devices</string>
|
||||||
|
<string name="ble_not_supported">BLE Not Supported</string>
|
||||||
|
<string name="error_bluetooth_not_supported">Bluetooth Not Supported</string>
|
||||||
|
<string name="menu_stop">STOP</string>
|
||||||
|
<string name="menu_scan">SCAN</string>
|
||||||
|
<string name="unknown_device">Unknown Device</string>
|
||||||
|
<string name="select_ble_device_title">Select BLE Device</string>
|
||||||
|
<string name="select_ble_device_desc">Select Bluetooth Low Energy (BLE) Device to provide seizure (acceleration and heart rate) data).</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -8,6 +8,12 @@
|
|||||||
android:entryValues="@array/datasource_list_values"
|
android:entryValues="@array/datasource_list_values"
|
||||||
android:defaultValue="Garmin"
|
android:defaultValue="Garmin"
|
||||||
android:dialogTitle="Select Data Source" />
|
android:dialogTitle="Select Data Source" />
|
||||||
|
<Preference
|
||||||
|
android:key="SelectBLEDevice"
|
||||||
|
android:title="@string/select_ble_device_title"
|
||||||
|
android:summary="@string/select_ble_device_desc"
|
||||||
|
android:widgetLayout="@layout/pref_select_ble_device_button"
|
||||||
|
/>
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="LogAlarms"
|
android:key="LogAlarms"
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!-- The ListPreference data is defined in pebble_datasource_values.xml -->
|
||||||
<!-- The ListPreference data is defined in pebble_datasource_values.xml -->
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<PreferenceScreen
|
<PreferenceCategory android:title="BLE Device Settings">
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
<EditTextPreference
|
||||||
|
android:defaultValue=""
|
||||||
|
android:key="BLE_Device_Addr"
|
||||||
|
android:summary="MAC Address of BLE Data Source Device"
|
||||||
|
android:title="Device Address" />
|
||||||
|
<EditTextPreference
|
||||||
|
android:defaultValue=""
|
||||||
|
android:key="BLE_Device_Name"
|
||||||
|
android:summary="Name of BLE Data Source Device"
|
||||||
|
android:title="Device Address" />
|
||||||
|
</PreferenceCategory>
|
||||||
<PreferenceCategory android:title="User Interface Settings">
|
<PreferenceCategory android:title="User Interface Settings">
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
android:defaultValue="5"
|
android:defaultValue="5"
|
||||||
@@ -58,23 +68,22 @@
|
|||||||
android:summary="Period (in seconds) between data analyses"
|
android:summary="Period (in seconds) between data analyses"
|
||||||
android:title="SamplePeriod (sec)" />
|
android:title="SamplePeriod (sec)" />
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:key="PebbleSdMode"
|
android:defaultValue="0"
|
||||||
android:title="Seizure Detector Mode"
|
android:dialogTitle="Select Seizure Detector Mode"
|
||||||
android:summary="Select one of the three available modes of operation."
|
|
||||||
android:entries="@array/pebble_sd_mode_list"
|
android:entries="@array/pebble_sd_mode_list"
|
||||||
android:entryValues="@array/pebble_sd_mode_list_values"
|
android:entryValues="@array/pebble_sd_mode_list_values"
|
||||||
android:defaultValue="0"
|
android:key="PebbleSdMode"
|
||||||
android:dialogTitle="Select Seizure Detector Mode" />
|
android:summary="Select one of the three available modes of operation."
|
||||||
|
android:title="Seizure Detector Mode" />
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:key="SampleFreq"
|
|
||||||
android:title="Select Sample Frequency"
|
|
||||||
android:summary="Higher Frequency is more Accurate, but uses more battery power."
|
|
||||||
android:entries="@array/pebble_sample_freq_list"
|
|
||||||
android:entryValues="@array/pebble_sample_freq_list_values"
|
|
||||||
android:defaultValue="100"
|
android:defaultValue="100"
|
||||||
android:dialogTitle="Select Sample Frequency"
|
android:dialogTitle="Select Sample Frequency"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
/>
|
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" />
|
||||||
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
@@ -102,16 +111,15 @@
|
|||||||
android:title="Fall Detection Window (milli-seconds)" />
|
android:title="Fall Detection Window (milli-seconds)" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory android:title="Watch Communications Settings">
|
||||||
android:title="Watch Communications Settings">
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:key="PebbleDebug"
|
android:defaultValue="0"
|
||||||
android:title="Seizure Detector Debug Mode"
|
android:dialogTitle="Select Debug Mode"
|
||||||
android:summary="Set Debug mode on or off."
|
|
||||||
android:entries="@array/pebble_debug_list"
|
android:entries="@array/pebble_debug_list"
|
||||||
android:entryValues="@array/pebble_debug_values"
|
android:entryValues="@array/pebble_debug_values"
|
||||||
android:defaultValue="0"
|
android:key="PebbleDebug"
|
||||||
android:dialogTitle="Select Debug Mode" />
|
android:summary="Set Debug mode on or off."
|
||||||
|
android:title="Seizure Detector Debug Mode" />
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
android:defaultValue="10"
|
android:defaultValue="10"
|
||||||
android:key="AppRestartTimeout"
|
android:key="AppRestartTimeout"
|
||||||
|
|||||||
Reference in New Issue
Block a user