Merge branch 'V4.2.x'

This commit is contained in:
Graham Jones
2024-05-27 21:03:55 +01:00
75 changed files with 5068 additions and 785 deletions

View File

@@ -1,5 +1,23 @@
OpenSeizureDetector Android App - Change Log
============================================
V4.2.8 -
- Fixed crash in export data function when using european style comma based decimal separator.
- Added seizure probability bar graph from CCN algorithm
- Added trap for null context in fragments to avoid crash.
V4.2.7 - BLE2 data source re-start fixed??
V4.2.6 - Fixed problem with notifications in Android 13
- Improved start-up checks for permissions
- Improved system re-start after changing settings (but still not perfect!)
- Disabled the CNN algorithm by default as it is causing some false alarms (Issue #170)
- Added watch signal strength history graph and watch battery hisory graph to main activity
V4.2.5 - Set BLE device time if the characteristic is available.
V4.2.4 - Added checks and a FAULT condition for Bluetooth errors in Bluetooth Data Source
V4.2.3 - Uses 3d accelerometer data to calculate magnitude if vector magnitude is not sent from data source (=support for Version 2 of Garmin watch app)
- fixed latched alarms (Issue #146)
- fixed HR alarms selection issue (#153)
V4.2.2 - Added support for PineTime OSD Status reporting.
V4.2.1 - Added support for PineTime wathes using the Bluetooth Data Source
V4.1.0 - Added experimental support for neural network based seizure detector.
V4.0.7 - Improvements to Data Sharing data log manager screen
- Removed automatic refresh of shared data events list (Issue #62)

View File

@@ -80,6 +80,7 @@ The following libraries are used:
* (jBeep)[http://www.ultraduz.com.br]
* (Chartjs)[http://www.chartjs.org]
* (MPAndroidChart)[https://github.com/PhilJay/MPAndroidChart]
* (CurrentTimeService)[https://github.com/RideBeeline/android-bluetooth-current-time-service]
Logo based on ["Star of life2" by Verdy p - Own work. Licensed under Public Domain via Wikimedia Commons](http://commons.wikimedia.org/wiki/File:Star_of_life2.svg#mediaviewer/File:Star_of_life2.svg).

View File

@@ -1,13 +1,13 @@
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
android {
compileSdkVersion 33
compileSdk 34 // Android 14
useLibrary 'org.apache.http.legacy'
defaultConfig {
applicationId "uk.org.openseizuredetector"
minSdkVersion 23
targetSdkVersion 33
minSdkVersion 23 // Android 6
targetSdkVersion 33 // Android 13 = 33
multiDexEnabled true
}
@@ -29,6 +29,9 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
namespace 'uk.org.openseizuredetector'
buildFeatures {
viewBinding true
}
}
dependencies {
@@ -39,15 +42,15 @@ dependencies {
// how we use ValueFormatter
// FIXME: Update mainactivity so we can use the latest version.
implementation 'com.github.PhilJay:MPAndroidChart:v2.1.3'
implementation 'com.getpebble:pebblekit:3.1.0@aar'
implementation 'com.getpebble:pebblekit:4.0.1@aar'
// Unit testing dependencies
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.google.android.material:material:1.4.0'
implementation 'com.google.firebase:firebase-auth:19.2.0'
implementation 'androidx.test:core:1.4.0'
implementation 'com.google.android.gms:play-services-tflite-java:16.0.0'
implementation 'com.google.android.gms:play-services-tflite-support:16.0.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.android.material:material:1.11.0'
implementation 'com.google.firebase:firebase-auth:22.3.1'
implementation 'androidx.test:core:1.5.0'
implementation 'com.google.android.gms:play-services-tflite-java:16.1.0'
implementation 'com.google.android.gms:play-services-tflite-support:16.1.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'org.apache.commons:commons-math3:3.6.1'
// google play services used for location finding for SMS alerts.
@@ -57,14 +60,16 @@ dependencies {
implementation 'com.google.android.gms:play-services-location:+'
//implementation 'com.github.RohitSurwase.UCE-Handler:uce_handler:1.3'
implementation 'com.android.volley:volley:1.2.1'
implementation platform('com.google.firebase:firebase-bom:29.2.0')
implementation platform('com.google.firebase:firebase-bom:32.7.1')
implementation 'com.google.firebase:firebase-analytics'
implementation 'com.firebaseui:firebase-ui-auth:7.2.0'
implementation 'com.firebaseui:firebase-ui-auth:8.0.2'
implementation 'com.google.firebase:firebase-firestore'
implementation 'androidx.navigation:navigation-fragment:2.7.6'
implementation 'androidx.navigation:navigation-ui:2.7.6'
testImplementation 'junit:junit:4.13.2'
testImplementation "androidx.test:core"
testImplementation 'org.mockito:mockito-core:4.3.1'
testImplementation 'org.mockito:mockito-core:5.9.0'
//testImplementation 'org.hamcrest:hamcrest-library:2.2'
//testImplementation 'org.robolectric:robolectric:4.7.3'
@@ -74,6 +79,9 @@ dependencies {
//androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
//androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
implementation 'com.techyourchance:threadposter:1.0.1'
implementation 'com.google.android.material:material'
implementation "com.github.RideBeeline:android-bluetooth-current-time-service:0.1.2"
implementation 'com.github.weliem:blessed-android:2.5.0'
}
repositories {

View File

@@ -1,11 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="131"
android:versionName="4.1.13">
android:versionCode="142"
android:versionName="4.2.8">
<!-- android:allowBackup="false" -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.INTERNET" />
@@ -21,10 +25,13 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- AD_ID seems to be added automatically as part of google play services. We don't use ads,
so explicitly remove it to keep Google Play store happy. -->
<uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove"/>
<!--
AD_ID seems to be added automatically as part of google play services. We don't use ads,
so explicitly remove it to keep Google Play store happy.
-->
<uses-permission
android:name="com.google.android.gms.permission.AD_ID"
tools:node="remove" />
<uses-feature
android:name="android.hardware.telephony"
@@ -38,8 +45,16 @@
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme">
<activity android:name=".AuthenticateActivity"/>
<!-- @android:style/Theme.Holo.Light" -->
<activity
android:name=".MainActivity2"
android:exported="false" />
<!--<activity
android:name=".MlModelManager"
android:exported="false"
android:label="@string/title_activity_ml_model_manager"
android:theme="@style/AppTheme" />
-->
<activity android:name=".AuthenticateActivity" /> <!-- @android:style/Theme.Holo.Light" -->
<activity android:name=".BLEScanActivity" />
<activity android:name=".ExportDataActivity" /> <!-- android:usesCleartextTraffic="true" -->
<activity
@@ -74,9 +89,8 @@
<receiver
android:name=".BootBroadcastReceiver"
android:label="BootBroadcastReceiver"
android:exported="true"
>
android:label="BootBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>

Binary file not shown.

View File

@@ -66,8 +66,8 @@ public class AuthenticateActivity extends AppCompatActivity {
logoutBtn.setOnClickListener(onLogout);
// Components required only for osdapi backend
if (LogManager.USE_FIREBASE_BACKEND) { }
else {
if (LogManager.USE_FIREBASE_BACKEND) {
} else {
mConnection = new SdServiceConnection(getApplicationContext());
Button registerBtn = (Button) findViewById(R.id.RegisterBtn);
@@ -84,7 +84,7 @@ public class AuthenticateActivity extends AppCompatActivity {
new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.v(TAG,"aboutDataSharingBtn.onClick()");
Log.v(TAG, "aboutDataSharingBtn.onClick()");
String url = OsdUtil.DATA_SHARING_URL;
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
@@ -97,7 +97,7 @@ public class AuthenticateActivity extends AppCompatActivity {
new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.v(TAG,"privacyPolicyBtn.onClick()");
Log.v(TAG, "privacyPolicyBtn.onClick()");
String url = OsdUtil.PRIVACY_POLICY_URL;
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
@@ -150,14 +150,13 @@ public class AuthenticateActivity extends AppCompatActivity {
}
private void initialiseServiceConnection() {
Log.v(TAG,"initialiseServiceConnection()");
Log.v(TAG, "initialiseServiceConnection()");
mLm = mConnection.mSdServer.mLm;
mWac = mConnection.mSdServer.mLm.mWac;
updateUi();
}
// Called after the Firebase Auth UI has completed
private ActivityResultLauncher<Intent> signInLauncher = registerForActivityResult(
new FirebaseAuthUIActivityResultContract(),
@@ -170,13 +169,13 @@ public class AuthenticateActivity extends AppCompatActivity {
private void updateUi() {
Log.v(TAG,"updateUi()");
Log.v(TAG, "updateUi()");
LinearLayout loginLl = (LinearLayout) findViewById(R.id.login_ui);
LinearLayout osdApiLoginLl = (LinearLayout) findViewById(R.id.login_osdapi_ui);
LinearLayout logoutLl = (LinearLayout) findViewById(R.id.logout_ui);
if (mWac == null) {
Log.i(TAG,"mWac is null - not updating UI");
Log.i(TAG, "mWac is null - not updating UI");
return;
}
@@ -204,7 +203,7 @@ public class AuthenticateActivity extends AppCompatActivity {
}
});
} else {
Log.v(TAG,"updateUi() - not logged in..");
Log.v(TAG, "updateUi() - not logged in..");
loginLl.setVisibility(View.VISIBLE);
logoutLl.setVisibility(View.GONE);
if (!LogManager.USE_FIREBASE_BACKEND) {
@@ -252,19 +251,19 @@ public class AuthenticateActivity extends AppCompatActivity {
// FIXME - make this work with Google Authentication like we do for Firebase.
String uname = mUnameEt.getText().toString();
String passwd = mPasswdEt.getText().toString();
Log.v(TAG,"onOK() - uname="+uname+", passwd="+passwd);
Log.v(TAG, "onOK() - uname=" + uname + ", passwd=" + passwd);
mWac.authenticate(uname, passwd, new WebApiConnection.StringCallback() {
@Override
public void accept(String retVal) {
if (retVal != null) {
Log.d(TAG,"Authentication Success - token is "+retVal);
Log.d(TAG, "Authentication Success - token is " + retVal);
mUtil.showToast("Login Successful");
saveAuthToken(retVal);
updateUi();
} else {
Log.e(TAG,"onOk: Authentication failure for "+uname+", "+passwd);
Log.e(TAG, "onOk: Authentication failure for " + uname + ", " + passwd);
mUtil.showToast("ERROR: Authentication Failed - Please Try Again");
mUtil.writeToSysLogFile("AuthActivity - Authorisation failed for "+uname+", "+passwd);
mUtil.writeToSysLogFile("AuthActivity - Authorisation failed for " + uname + ", " + passwd);
}
}
});
@@ -273,29 +272,29 @@ public class AuthenticateActivity extends AppCompatActivity {
};
View.OnClickListener onLogout = new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.v(TAG, "onLogout");
if (LogManager.USE_FIREBASE_BACKEND) {
AuthUI.getInstance()
.signOut(getApplicationContext())
.addOnCompleteListener(new OnCompleteListener<Void>() {
public void onComplete(@NonNull Task<Void> task) {
// user is now signed out
updateUi();
}
});
@Override
public void onClick(View view) {
Log.v(TAG, "onLogout");
if (LogManager.USE_FIREBASE_BACKEND) {
AuthUI.getInstance()
.signOut(getApplicationContext())
.addOnCompleteListener(new OnCompleteListener<Void>() {
public void onComplete(@NonNull Task<Void> task) {
// user is now signed out
updateUi();
}
});
} else {
if (mWac != null) {
mWac.logout();
saveAuthToken(null);
} else {
if (mWac != null) {
mWac.logout();
saveAuthToken(null);
} else {
Log.e(TAG,"logout() - mWac is null - not doing anything");
}
Log.e(TAG, "logout() - mWac is null - not doing anything");
}
updateUi();
}
};
updateUi();
}
};
View.OnClickListener onRegister =
new View.OnClickListener() {

View File

@@ -29,12 +29,16 @@ import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.Html;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -47,6 +51,7 @@ import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
@@ -65,28 +70,33 @@ public class BLEScanActivity extends ListActivity {
private Handler mHandler;
private boolean bleAvailable = false;
private OsdUtil mUtil;
private boolean mPermissionsRequested = false;
private final String TAG = "BLEScanActivity";
private final String[] REQUIRED_PERMISSIONS = {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
//Manifest.permission.BLUETOOTH_PRIVILEGED,
};
private static final int REQUEST_ENABLE_BT = 1;
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
private int okColour = Color.BLUE;
private int warnColour = Color.MAGENTA;
private int alarmColour = Color.RED;
private int okTextColour = Color.WHITE;
private int warnTextColour = Color.WHITE;
private int alarmTextColour = Color.BLACK;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG,"onCreate()");
setContentView(R.layout.ble_scan_activity);
//this.getActionBar().setTitle(R.string.title_devices);
this.setTitle(R.string.title_devices);
mHandler = new Handler();
mUtil = new OsdUtil(this, mHandler);
// Use this check to determine whether BLE is supported on the device. Then you can
// selectively disable BLE-related features.
@@ -111,6 +121,8 @@ public class BLEScanActivity extends ListActivity {
}
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
mPermissionsRequested = false;
}
@Override
@@ -151,6 +163,7 @@ public class BLEScanActivity extends ListActivity {
@Override
protected void onResume() {
super.onResume();
Log.i(TAG,"onResume()");
SharedPreferences SP = PreferenceManager
.getDefaultSharedPreferences(this);
TextView tv = (TextView) findViewById(R.id.current_ble_device_tv);
@@ -158,61 +171,56 @@ public class BLEScanActivity extends ListActivity {
String bleAddr = SP.getString("BLE_Device_Addr", "none");
String bleName = SP.getString("BLE_Device_Name", "none");
tv.setText("Current Device=" + bleName + " (" + bleAddr + ")");
tv.setTextColor(okTextColour);
tv.setBackgroundColor(okColour);
} catch (Exception e) {
tv.setText("Current Device=" + "none" + " (" + "none" + ")");
tv.setTextColor(warnTextColour);
tv.setBackgroundColor(warnColour);
}
tv = (TextView) findViewById(R.id.ble_present_tv);
if (mBluetoothAdapter == null) {
tv.setText("ERROR - Bluetooth Adapter Not Present");
tv.setTextColor(alarmTextColour);
tv.setBackgroundColor(alarmColour);
} else {
tv.setText("Bluetooth Adapter Present - OK");
tv.setTextColor(okTextColour);
tv.setBackgroundColor(okColour);
}
// 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.
tv = (TextView) findViewById(R.id.ble_adapter_tv);
if (!mBluetoothAdapter.isEnabled()) {
tv.setText("ERROR - Bluetoot NOT Enabled");
tv.setText("ERROR - Bluetooth NOT Enabled");
tv.setTextColor(alarmTextColour);
tv.setBackgroundColor(alarmColour);
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
} else {
tv.setText("Bluetooth Adapter Enabled OK");
tv.setTextColor(okTextColour);
tv.setBackgroundColor(okColour);
}
requestBTPermissions(this);
for (int i = 0; i < REQUIRED_PERMISSIONS.length; i++) {
if (ContextCompat.checkSelfPermission(this, REQUIRED_PERMISSIONS[i]) == PERMISSION_GRANTED) {
Log.i(TAG, "Permission " + REQUIRED_PERMISSIONS[i] + " OK");
} else {
Log.e(TAG, "Permission " + REQUIRED_PERMISSIONS[i] + " NOT GRANTED");
Toast.makeText(this, "ERROR - Permission " + REQUIRED_PERMISSIONS[i] + " not Granted - this will not work!!!!!", Toast.LENGTH_SHORT).show();
}
if (!mUtil.areBtPermissionsOk()) {
Log.i(TAG, "onResume - calling requestBTPermissions()");
requestBTPermissions(this);
} else {
Log.i(TAG, "onResume - Bluetooth Permissions OK");
}
tv = (TextView) findViewById(R.id.ble_perm1_tv);
if (ContextCompat.checkSelfPermission(this, REQUIRED_PERMISSIONS[0]) == PERMISSION_GRANTED) {
tv.setText("Permission " + REQUIRED_PERMISSIONS[0] + " OK");
if (mUtil.areBtPermissionsOk()) {
tv.setText("Permissions required for Bluetooth Granted OK");
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
} else {
tv.setText("ERROR: Permission " + REQUIRED_PERMISSIONS[0] + " NOT GRANTED");
}
tv = (TextView) findViewById(R.id.ble_perm2_tv);
if (ContextCompat.checkSelfPermission(this, REQUIRED_PERMISSIONS[1]) == PERMISSION_GRANTED) {
tv.setText("Permission " + REQUIRED_PERMISSIONS[1] + " OK");
} else {
tv.setText("ERROR: Permission " + REQUIRED_PERMISSIONS[1] + " NOT GRANTED");
}
tv = (TextView) findViewById(R.id.ble_perm3_tv);
if (ContextCompat.checkSelfPermission(this, REQUIRED_PERMISSIONS[2]) == PERMISSION_GRANTED) {
tv.setText("Permission " + REQUIRED_PERMISSIONS[2] + " OK");
} else {
tv.setText("ERROR: Permission " + REQUIRED_PERMISSIONS[2] + " NOT GRANTED");
}
tv = (TextView) findViewById(R.id.ble_perm4_tv);
if (ContextCompat.checkSelfPermission(this, REQUIRED_PERMISSIONS[3]) == PERMISSION_GRANTED) {
tv.setText("Permission " + REQUIRED_PERMISSIONS[3] + " OK");
} else {
tv.setText("ERROR: Permission " + REQUIRED_PERMISSIONS[3] + " NOT GRANTED");
tv.setText("ERROR: one or more permissions not granted - this may not work!");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
}
@@ -266,22 +274,56 @@ public class BLEScanActivity extends ListActivity {
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"));
Log.i(TAG, "Restarting start-up activity so change takes effect");
Intent i;
i = new Intent(this, StartupActivity.class);
startActivity(i);
finish();
}
public void requestBTPermissions(Activity activity) {
if (mPermissionsRequested) {
Log.i(TAG, "requestPermissions() - request already sent - not doing anything");
Log.i(TAG, "requestBTPermissions() - request already sent - not doing anything");
} else {
Log.i(TAG, "requestPermissions() - requesting permissions");
for (int i = 0; i < REQUIRED_PERMISSIONS.length; i++) {
Log.i(TAG, "requestBTPermissions() - showing rationale (if necessary)");
boolean showRationale = false;
String btPermissions[] = mUtil.getRequiredBtPermissions();
for (int i = 0; i < btPermissions.length; i++) {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
REQUIRED_PERMISSIONS[i])) {
Log.i(TAG, "shouldShowRationale for permission" + REQUIRED_PERMISSIONS[i]);
btPermissions[i])) {
Log.i(TAG, "shouldShowRationale for permission" + btPermissions[i]);
showRationale = true;
Toast toast = Toast.makeText(this, "Please give us permission! "+ btPermissions[i], Toast.LENGTH_SHORT);
toast.show();
}
}
if (showRationale) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
this);
alertDialogBuilder
.setTitle(getString(R.string.permissions_required))
.setMessage("Additional Permissions are required to scan for Bluetooth Devices - please grant the permissions in the following dialogs")
.setCancelable(false)
.setNegativeButton(getString(R.string.closeBtnTxt), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
finish();
}
})
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
})
.create()
.show();
} else {
Log.i(TAG,"requestBTPermissions() - rationale display not required");
}
Log.i(TAG, "requestBTPermissions() - requesting permissions");
ActivityCompat.requestPermissions(activity,
REQUIRED_PERMISSIONS,
btPermissions,
42);
mPermissionsRequested = true;
}
@@ -292,10 +334,13 @@ public class BLEScanActivity extends ListActivity {
try {
mBluetoothLeScanner.startScan(mLeScanCallback);
} catch (SecurityException e) {
Log.e(TAG,"startScan - SecurityException while starting scan");
Toast toast = Toast.makeText(this, "ERROR Starting Scan - Security Exception", Toast.LENGTH_SHORT);
Log.e(TAG, "startScan - SecurityException while starting scan:" +e.getMessage());
Toast toast = Toast.makeText(this, "ERROR - Security Exception "+e.getMessage(), Toast.LENGTH_SHORT);
toast.show();
} catch (Exception e) {
Log.e(TAG,"startScan - Exception while starting scan:"+e.getMessage());
Toast toast = Toast.makeText(this, "ERROR Starting Scan", Toast.LENGTH_SHORT);
toast.show();
}
}
@@ -304,7 +349,7 @@ public class BLEScanActivity extends ListActivity {
try {
mBluetoothLeScanner.stopScan(mLeScanCallback);
} catch (SecurityException e) {
Log.e(TAG,"stopScan - SecurityException while stopping scan");
Log.e(TAG, "stopScan - SecurityException while stopping scan");
Toast toast = Toast.makeText(this, "ERROR Stopping Scan - Security Exception", Toast.LENGTH_SHORT);
toast.show();
}
@@ -322,6 +367,10 @@ public class BLEScanActivity extends ListActivity {
invalidateOptionsMenu();
TextView tv = (TextView) (findViewById(R.id.ble_scan_status_tv));
tv.setText("Stopped");
tv.setTextColor(okTextColour);
tv.setBackgroundColor(okColour);
Button b = (Button) findViewById(R.id.startScanButton);
b.setEnabled(true);
@@ -331,6 +380,9 @@ public class BLEScanActivity extends ListActivity {
startScan();
tv = (TextView) (findViewById(R.id.ble_scan_status_tv));
tv.setText("Scanning");
tv.setTextColor(warnTextColour);
tv.setBackgroundColor(warnColour);
Button b = (Button) findViewById(R.id.startScanButton);
b.setEnabled(false);
@@ -360,7 +412,7 @@ public class BLEScanActivity extends ListActivity {
try {
Log.v(TAG, "addDevice - " + device.getName());
} catch (SecurityException e) {
Log.e(TAG,"addDevice() - security exception getting device name");
Log.e(TAG, "addDevice() - security exception getting device name");
}
mLeDevices.add(device);
}
@@ -425,7 +477,7 @@ public class BLEScanActivity extends ListActivity {
try {
Log.v(TAG, "ScanCallback - " + result.getDevice().getName());
} catch (SecurityException e) {
Log.e(TAG,"ScanCallback - security exception getting device name");
Log.e(TAG, "ScanCallback - security exception getting device name");
}
mLeDeviceListAdapter.addDevice(result.getDevice());
mLeDeviceListAdapter.notifyDataSetChanged();

View File

@@ -46,8 +46,8 @@ public class BootBroadcastReceiver extends BroadcastReceiver {
Log.v(TAG, "onReceive()");
SharedPreferences SP = PreferenceManager
.getDefaultSharedPreferences(context);
boolean autoStart = SP.getBoolean("AutoStart",false);
Log.v(TAG,"onReceive() - autoStart = "+autoStart);
boolean autoStart = SP.getBoolean("AutoStart", false);
Log.v(TAG, "onReceive() - autoStart = " + autoStart);
if (autoStart && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
Intent startUpIntent = new Intent(context, StartupActivity.class);
startUpIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

View File

@@ -6,9 +6,9 @@ import java.util.ArrayList;
public class CircBuf {
/*
* A circular buffer used to calculate rolling averages
* Based loosely on https://gist.github.com/hardik-vala/dc2d19fa7c5108536fbbff96b4fcf105
*/
* A circular buffer used to calculate rolling averages
* Based loosely on https://gist.github.com/hardik-vala/dc2d19fa7c5108536fbbff96b4fcf105
*/
private final static String TAG = "CircBuf";
private double[] mBuff;
@@ -22,7 +22,7 @@ public class CircBuf {
/**
* Create a circular buffer of doubles, of length n members.
*/
Log.i(TAG, "CircBuf Constructor");
Log.d(TAG, "CircBuf Constructor");
mBuff = new double[n];
mBuffLen = n;
mErrVal = errVal;
@@ -35,7 +35,7 @@ public class CircBuf {
/**
* Add value val to the circular buffer.
*/
Log.d(TAG,"add() - before: mHead="+mHead+", mTail="+mTail);
Log.v(TAG, "add() - before: mHead=" + mHead + ", mTail=" + mTail);
//System.out.println(TAG+" add() - before: mHead="+mHead+", mTail="+mTail);
if (mIsFull)
mHead = (mHead + 1) % mBuffLen;
@@ -44,7 +44,7 @@ public class CircBuf {
mTail = (mTail + 1) % mBuffLen;
if (mTail == mHead)
mIsFull = true;
Log.d(TAG,"add() - after: mHead="+mHead+", mTail="+mTail);
Log.v(TAG, "add() - after: mHead=" + mHead + ", mTail=" + mTail);
//System.out.println(TAG+" add() - before: mHead="+mHead+", mTail="+mTail);
}
@@ -60,7 +60,7 @@ public class CircBuf {
if (mHead > mTail) {
numElements = (mTail + mBuffLen) - mHead;
} else {
numElements = mTail-mHead;
numElements = mTail - mHead;
}
}
return numElements;
@@ -68,11 +68,12 @@ public class CircBuf {
/**
* Returns a double array of buffer items in order of their insertion time
*
* @return double[] of buffer items
*/
public double[] getVals () {
public double[] getVals() {
int numElements = getNumVals();
System.out.println(TAG+" getVals() - numElements=" + numElements);
//System.out.println(TAG + " getVals() - numElements=" + numElements);
double[] retArr = new double[numElements];
for (int i = 0; i < numElements; i++) {
retArr[i] = mBuff[(mHead + i) % mBuffLen];
@@ -89,18 +90,18 @@ public class CircBuf {
int hrCount = 0;
double valArr[] = getVals();
double retVal;
for (int n=0; n<valArr.length; n++) {
for (int n = 0; n < valArr.length; n++) {
if (valArr[n] != mErrVal) {
hrSum += valArr[n];
hrCount++;
}
}
if (hrCount>0) {
if (hrCount > 0) {
retVal = hrSum / hrCount;
} else {
retVal = -1;
}
return(retVal);
return (retVal);
}
}

View File

@@ -3,7 +3,9 @@ package uk.org.openseizuredetector;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
@@ -205,7 +207,7 @@ public class EditEventActivity extends AppCompatActivity {
int alarmStateVal = Integer.parseInt(alarmStateStr);
alarmStateStr = mUtil.alarmStatusToString(alarmStateVal);
} catch (Exception e) {
Log.v(TAG,"updateUi: alarmState does not parse to int so displaying it as string: " +alarmStateStr);
Log.v(TAG, "updateUi: alarmState does not parse to int so displaying it as string: " + alarmStateStr);
}
tv.setText(alarmStateStr);
tv = (TextView) findViewById(R.id.eventNotsTv);
@@ -219,7 +221,7 @@ public class EditEventActivity extends AppCompatActivity {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
tv.setText(dateFormat.format(dataTime));
} catch (Exception e) {
Log.e(TAG,"updateUI: Error Parsing dataDate "+e.getLocalizedMessage());
Log.e(TAG, "updateUI: Error Parsing dataDate " + e.getLocalizedMessage());
tv.setText("---");
}
@@ -234,9 +236,9 @@ public class EditEventActivity extends AppCompatActivity {
}
// Populate the event sub-types radio button list.
Log.v(TAG,"updateUi() - meventsubtypeshashmap="+mEventSubTypesHashMap+", mEventSubtypesListChanged="+mEventSubTypesListChanged);
Log.v(TAG, "updateUi() - meventsubtypeshashmap=" + mEventSubTypesHashMap + ", mEventSubtypesListChanged=" + mEventSubTypesListChanged);
if (mEventSubTypesHashMap != null && mEventSubTypesListChanged) {
Log.v(TAG,"UpdateUi() - populating event sub types list");
Log.v(TAG, "UpdateUi() - populating event sub types list");
if (mEventObj.getString("type") != null) {
// based on https://androidexample.com/create-a-simple-listview
ArrayList<String> subtypesArrayList = mEventSubTypesHashMap.get(mEventObj.getString("type"));
@@ -265,11 +267,10 @@ public class EditEventActivity extends AppCompatActivity {
}
} catch (JSONException e) {
Log.e(TAG,"Error Parsing mEventObj: "+e.getMessage());
Log.e(TAG, "Error Parsing mEventObj: " + e.getMessage());
}
} // updateUi()
View.OnClickListener onCancel =
@@ -287,14 +288,14 @@ public class EditEventActivity extends AppCompatActivity {
@Override
public void onClick(View view) {
//m_status=true;
TextView tv = (TextView)findViewById(R.id.eventNotsTv);
TextView tv = (TextView) findViewById(R.id.eventNotsTv);
try {
mEventObj.put("desc",tv.getText());
mEventObj.put("id",mEventId); // Add event Id to event object manually because firestore does not include it by default.
mEventObj.put("desc", tv.getText());
mEventObj.put("id", mEventId); // Add event Id to event object manually because firestore does not include it by default.
} catch (JSONException e) {
Log.e(TAG,"Error writing mEventObj: "+e.getMessage());
Log.e(TAG, "Error writing mEventObj: " + e.getMessage());
}
Log.v(TAG, "onOK() - eventObj="+mEventObj.toString());
Log.v(TAG, "onOK() - eventObj=" + mEventObj.toString());
try {
mWac.updateEvent(mEventObj, new WebApiConnection.JSONObjectCallback() {
@@ -314,7 +315,7 @@ public class EditEventActivity extends AppCompatActivity {
}
});
} catch (Exception e) {
Log.e(TAG,"onOK() - ERROR: "+e.getMessage()+" : " +e.toString());
Log.e(TAG, "onOK() - ERROR: " + e.getMessage() + " : " + e.toString());
e.printStackTrace();
mUtil.showToast("Error Updating Event");
updateUi();
@@ -327,16 +328,16 @@ public class EditEventActivity extends AppCompatActivity {
new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
Log.v(TAG,"onEventTypeChange() - id="+checkedId);
RadioButton b = (RadioButton)findViewById(group.getCheckedRadioButtonId());
Log.v(TAG, "onEventTypeChange() - id=" + checkedId);
RadioButton b = (RadioButton) findViewById(group.getCheckedRadioButtonId());
String selectedEventType = b.getText().toString();
try {
mEventObj.put("type", selectedEventType);
} catch (JSONException e) {
Log.e(TAG,"Error setting mEventObj.type: "+e.getMessage());
Log.e(TAG, "Error setting mEventObj.type: " + e.getMessage());
}
mEventSubTypesListChanged = true;
Log.v(TAG,"onEventTypeChange() - mEventSubTypesListChanged="+mEventSubTypesListChanged);
Log.v(TAG, "onEventTypeChange() - mEventSubTypesListChanged=" + mEventSubTypesListChanged);
updateUi();
}
};
@@ -344,13 +345,13 @@ public class EditEventActivity extends AppCompatActivity {
new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
Log.v(TAG,"onEventSubTypeChange() - id="+checkedId);
RadioButton b = (RadioButton)findViewById(group.getCheckedRadioButtonId());
Log.v(TAG, "onEventSubTypeChange() - id=" + checkedId);
RadioButton b = (RadioButton) findViewById(group.getCheckedRadioButtonId());
String selectedEventSubType = b.getText().toString();
try {
mEventObj.put("subType", selectedEventSubType);
} catch (JSONException e) {
Log.e(TAG,"Error setting mEventObj.type: "+e.getMessage());
Log.e(TAG, "Error setting mEventObj.type: " + e.getMessage());
}
updateUi();
}

View File

@@ -35,11 +35,13 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public class ExportDataActivity extends AppCompatActivity
implements View.OnClickListener {
@@ -198,8 +200,8 @@ public class ExportDataActivity extends AppCompatActivity
if (view == mExportBtn) {
mDateTxt.setText(String.format("%02d-%02d-%04d", mDay, mMonth + 1, mYear));
mTimeTxt.setText(String.format("%02d:%02d:%02d", mHour, mMinute, 00));
mDuration = Double.parseDouble(mDurationTxt.getText().toString());
mDuration = mUtil.parseToDouble(mDurationTxt.getText().toString());
String dateTimeStr = String.format("%04d-%02d-%02dT%02d:%02d:%02dZ", mYear, mMonth + 1, mDay, mHour, mMinute, 00);
//mUtil.showToast(dateTimeStr);
mEndDate = mUtil.string2date(dateTimeStr);
@@ -226,14 +228,14 @@ public class ExportDataActivity extends AppCompatActivity
public void hideProgressBar() {
runOnUiThread(new Runnable() {
public void run() {
ProgressBar pb = (ProgressBar) findViewById(R.id.exportPb);
pb.setIndeterminate(true);
pb.setVisibility(View.INVISIBLE);
mExportBtn.setEnabled(true);
mExportBtn.setVisibility(View.VISIBLE);
public void run() {
ProgressBar pb = (ProgressBar) findViewById(R.id.exportPb);
pb.setIndeterminate(true);
pb.setVisibility(View.INVISIBLE);
mExportBtn.setEnabled(true);
mExportBtn.setVisibility(View.VISIBLE);
}
}
});
}
@@ -265,8 +267,8 @@ public class ExportDataActivity extends AppCompatActivity
// Perform operations on the document using its URI.
//mUtil.showToast("URI="+uri.toString());
Log.v(TAG, "onActivityResult() - exporting to file " + uri.toString());
mLm.exportToCsvFile(mEndDate, mDuration,uri, (boolean b)-> {
Log.v(TAG,"onActivityResult callback");
mLm.exportToCsvFile(mEndDate, mDuration, uri, (boolean b) -> {
Log.v(TAG, "onActivityResult callback");
hideProgressBar();
});

View File

@@ -0,0 +1,145 @@
package uk.org.openseizuredetector;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.appcompat.widget.SwitchCompat;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.utils.ValueFormatter;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class FragmentBatt extends FragmentOsdBaseClass {
String TAG = "FragmentBatt";
LineChart mLineChart;
LineData lineData;
LineDataSet lineDataSet;
List<Entry> watchHistory = new ArrayList<>();
List<Entry> phoneHistory = new ArrayList<>();
List<String> hrHistoryStrings = new ArrayList<>();
List<String> hrAveragesStrings = new ArrayList<>();
private List<Entry> listToDisplay;
private List<String> listToDisplayStrings;
public FragmentBatt() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lineDataSet = new LineDataSet(new ArrayList<Entry>(), "Battery history");
//lineDataSet.setColors(ColorTemplate.JOYFUL_COLORS);
lineDataSet.setValueTextColor(Color.BLACK);
lineDataSet.setValueTextSize(18f);
lineDataSet.setDrawValues(false);
lineDataSet.setCircleSize(0f);
lineDataSet.setLineWidth(3f);
//lineDataSetAverage = new LineDataSet(new ArrayList<Entry>(),"Heart rate history" );
//lineDataSetAverage.setColors(ColorTemplate.JOYFUL_COLORS);
//lineDataSetAverage.setValueTextColor(Color.BLACK);
//lineDataSetAverage.setValueTextSize(18f);
}
@Override
public void onResume() {
super.onResume();
mLineChart = mRootView.findViewById(R.id.battLineChart);
mLineChart.getLegend().setEnabled(false);
XAxis xAxis = mLineChart.getXAxis();
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxis.setTextSize(10f);
xAxis.setDrawAxisLine(true);
xAxis.setDrawLabels(true);
// Note: the default text colour is BLACK, so does not show up on black background!!!
// This took a lot of finding....
xAxis.setTextColor(Color.WHITE);
YAxis yAxis = mLineChart.getAxisLeft();
yAxis.setAxisMinValue(0f);
yAxis.setAxisMaxValue(100f);
yAxis.setDrawGridLines(true);
yAxis.setDrawLabels(true);
yAxis.setTextColor(Color.WHITE);
// Inhibit the decimal part of the y axis labels.
yAxis.setValueFormatter(new ValueFormatter() {
@Override
public String getFormattedValue(float v) {
DecimalFormat format = new DecimalFormat("###");
return format.format(v);
}
});
YAxis yAxis2 = mLineChart.getAxisRight();
yAxis2.setDrawGridLines(false);
yAxis2.setEnabled(false);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_batt, container, false);
}
@Override
protected void updateUi() {
Log.d(TAG, "updateUi()");
if (mConnection.mBound) {
int nWatchBattArr = mConnection.mSdServer.mSdData.watchBattBuff.getNumVals();
double watchBattArr[] = mConnection.mSdServer.mSdData.watchBattBuff.getVals(); // This gives us a simple vector of hr values to plot.
int nPhoneBattArr = mConnection.mSdServer.mSdData.phoneBattBuff.getNumVals();
double phoneBattArr[] = mConnection.mSdServer.mSdData.phoneBattBuff.getVals();
Log.i(TAG,"updateUi() - nWatchBattArr="+nWatchBattArr+", nPhoneBattArr="+nPhoneBattArr);
if (Objects.nonNull(mConnection.mSdServer.mSdData.watchBattBuff) && nWatchBattArr > 0) {
Log.v(TAG, "hrWatchBattBuff.getNumVals=" + nWatchBattArr);
lineDataSet.clear();
String xVals[] = new String[nWatchBattArr];
for (int i = 0; i < nWatchBattArr; i++) {
//Log.d(TAG,"i="+i+", HR="+hrHistArr[i]);
xVals[i] = String.valueOf(i);
lineDataSet.addEntry(new Entry((float) watchBattArr[i], i));
}
Log.d(TAG, "xVals=" + Arrays.toString(xVals) + ", lneDataSet=" + lineDataSet.toSimpleString());
lineDataSet.setColors(new int[]{0xffff0000});
LineData watchBattHistLineData = new LineData(xVals, lineDataSet);
mLineChart.setData(watchBattHistLineData);
mLineChart.getData().notifyDataChanged();
mLineChart.notifyDataSetChanged();
mLineChart.refreshDrawableState();
float xSpan = (nWatchBattArr * 5.0f) / 60.0f; // time in minutes assuming one point every 5 seconds.
mLineChart.setDescription(getString(R.string.watch_batt_hist)
+ " " + String.format("%.1f", xSpan)
+ " " + getString(R.string.minutes));
mLineChart.setDescriptionTextSize(12f);
mLineChart.invalidate();
//if (mConnection.mBound){
// lineChart.postInvalidate();
//}
}
}
}
}

View File

@@ -0,0 +1,277 @@
package uk.org.openseizuredetector;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.text.format.Time;
public class FragmentCommon extends FragmentOsdBaseClass {
String TAG = "FragmentCommon";
public FragmentCommon() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_common, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Deal with the 'AcceptAlarm Button'
Button button = (Button) mRootView.findViewById(R.id.acceptAlarmButton);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Log.v(TAG, "acceptAlarmButton.onClick()");
if (mConnection.mBound) {
if ((mConnection.mSdServer.mSmsTimer != null)
&& (mConnection.mSdServer.mSmsTimer.mTimeLeft > 0)) {
Log.i(TAG, "acceptAlarmButton.onClick() - Stopping SMS Timer");
mUtil.showToast(getString(R.string.SMSAlarmCancelledMsg));
mConnection.mSdServer.stopSmsTimer();
} else {
Log.v(TAG, "acceptAlarmButton.onClick() - Accepting Alarm");
mConnection.mSdServer.acceptAlarm();
}
}
}
});
// Deal with the 'Cancel Audible Button'
button = (Button) mRootView.findViewById(R.id.cancelAudibleButton);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Log.v(TAG, "cancelAudibleButton.onClick()");
if (mConnection.mBound) {
mConnection.mSdServer.cancelAudible();
}
}
});
// Deal with the 'Raise Alarm'
button = (Button) mRootView.findViewById(R.id.manualAlarmButton);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Log.v(TAG, "manualAlarmButton.onClick()");
if (mConnection.mBound) {
mConnection.mSdServer.raiseManualAlarm();
}
}
});
}
@Override
protected void updateUi() {
//Log.d(TAG,"updateUi()");
TextView tv;
if (mUtil.isServerRunning()) {
tv = (TextView) mRootView.findViewById(R.id.serverStatusTv);
if (mConnection.mBound) {
if (mConnection.mSdServer.mLogNDA)
tv.setText(getString(R.string.ServerRunningOK) + " - NDA Logging");
else
tv.setText(getString(R.string.ServerRunningOK));
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
tv = (TextView) mRootView.findViewById(R.id.data_time_tv);
Time tnow = new Time(Time.getCurrentTimezone());
tnow.setToNow();
double tdiff;
tdiff = (tnow.toMillis(false) - mConnection.mSdServer.mSdData.dataTime.toMillis(false))/1000.;
tv.setText("Time =" + mConnection.mSdServer.mSdData.dataTime.format("%H:%M:%S")
+ " (" + String.format("%.0f s, %.1f s)",mConnection.mSdServer.mSdData.timeDiff, tdiff));
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
tv = (TextView) mRootView.findViewById(R.id.alarmTv);
if ((mConnection.mSdServer.mSdData.alarmState == 0)
&& !mConnection.mSdServer.mSdData.alarmStanding
&& !mConnection.mSdServer.mSdData.fallAlarmStanding) {
tv.setText(getString(R.string.okBtnTxt));
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
}
if ((mConnection.mSdServer.mSdData.alarmState == 1)
&& !mConnection.mSdServer.mSdData.alarmStanding
&& !mConnection.mSdServer.mSdData.fallAlarmStanding) {
tv.setText(R.string.Warning);
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
}
if (mConnection.mSdServer.mSdData.alarmState == 6) {
tv.setText(R.string.Mute);
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
}
if (mConnection.mSdServer.mSdData.alarmStanding) {
tv.setText(R.string.Alarm);
tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour);
}
if (mConnection.mSdServer.mSdData.fallAlarmStanding) {
tv.setText(R.string.Fall);
tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour);
}
if (mConnection.mSdServer.mSdData.alarmState == 4) {
tv.setText(R.string.Fault);
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
}
tv = (TextView) mRootView.findViewById(R.id.algsTv);
tv.setText(R.string.algorithms);
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
tv = (TextView) mRootView.findViewById(R.id.osdAlgTv);
tv.setText("OSD ");
if (mConnection.mSdServer.mSdData.mOsdAlarmActive) {
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
tv.setPaintFlags(tv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
} else {
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
tv.setPaintFlags(tv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
}
tv = (TextView) mRootView.findViewById(R.id.cnnAlgTv);
tv.setText("CNN ");
if (mConnection.mSdServer.mSdData.mCnnAlarmActive) {
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
tv.setPaintFlags(tv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
} else {
tv.setBackgroundColor(okColour);
tv.setTextColor(Color.GRAY);
tv.setPaintFlags(tv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
}
tv = (TextView) mRootView.findViewById(R.id.hrAlgTv);
tv.setText("HR ");
if (mConnection.mSdServer.mSdData.mHRAlarmActive) {
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
tv.setPaintFlags(tv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
} else {
tv.setBackgroundColor(okColour);
tv.setTextColor(Color.GRAY);
tv.setPaintFlags(tv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
}
tv = (TextView) mRootView.findViewById(R.id.o2AlgTv);
tv.setText("O2 ");
if (mConnection.mSdServer.mSdData.mO2SatAlarmActive) {
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
tv.setPaintFlags(tv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
} else {
tv.setBackgroundColor(okColour);
tv.setTextColor(Color.GRAY);
tv.setPaintFlags(tv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
}
tv = (TextView) mRootView.findViewById(R.id.dataSourceInfoTv);
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
if (mConnection.mSdServer.mSdDataSourceName.equals("Phone")) {
tv.setText(getString(R.string.DataSource) + " = " + "Phone (Demo Mode)");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
} else if (mConnection.mSdServer.mSdDataSourceName.equals("BLE")
|| mConnection.mSdServer.mSdDataSourceName.equals("BLE2")) {
tv.setText(getString(R.string.DataSource) + " = " + mConnection.mSdServer.mSdDataSourceName
+ " ("+ mConnection.mSdServer.mSdData.watchSdName + ", "
+ mConnection.mSdServer.mSdData.watchSerNo+")");
} else {
tv.setText(getString(R.string.DataSource) + " = " + mConnection.mSdServer.mSdDataSourceName);
}
}
} else {
tv = (TextView) mRootView.findViewById(R.id.serverStatusTv);
tv.setText(R.string.ServerStopped);
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
/** FIXME - check this is not needed for this fragment
tv = (TextView) mRootView.findViewById(R.id.serverIpTv);
tv.setText("--");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
*/
}
// deal with latch alarms button
Button acceptAlarmButton = (Button) mRootView.findViewById(R.id.acceptAlarmButton);
if (mConnection.mBound) {
if ((mConnection.mSdServer.mSmsTimer != null)
&& (mConnection.mSdServer.mSmsTimer.mTimeLeft > 0)) {
acceptAlarmButton.setText(getString(R.string.SMSWillBeSentIn) + " " +
mConnection.mSdServer.mSmsTimer.mTimeLeft / 1000 +
" s - " + getString(R.string.Cancel));
acceptAlarmButton.setBackgroundColor(alarmColour);
acceptAlarmButton.setEnabled(true);
} else {
acceptAlarmButton.setText(R.string.AcceptAlarm);
acceptAlarmButton.setBackgroundColor(Color.GRAY);
if (mConnection.mBound)
if ((mConnection.mSdServer.isLatchAlarms())
|| mConnection.mSdServer.mSdData.mFallActive) {
acceptAlarmButton.setEnabled(true);
} else {
acceptAlarmButton.setEnabled(false);
}
}
} else {
// acceptAlarmButton.setText(getString(R.string.AcceptAlarm));
// acceptAlarmButton.setBackgroundColor(Color.DKGRAY);
// acceptAlarmButton.setEnabled(false);
}
// Deal with Cancel Audible button
Button cancelAudibleButton =
(Button) mRootView.findViewById(R.id.cancelAudibleButton);
if (mConnection.mBound)
if (mConnection.mSdServer.isAudibleCancelled()) {
cancelAudibleButton.setText(getString(R.string.AudibleAlarmsCancelledFor)
+ " " + mConnection.mSdServer.
cancelAudibleTimeRemaining()
+ " sec");
cancelAudibleButton.setEnabled(true);
} else {
if (mConnection.mSdServer.mAudibleAlarm) {
cancelAudibleButton.setText(R.string.CancelAudibleAlarms);
cancelAudibleButton.setEnabled(true);
} else {
cancelAudibleButton.setText(R.string.AudibleAlarmsOff);
cancelAudibleButton.setEnabled(false);
}
}
}
}

View File

@@ -0,0 +1,44 @@
package uk.org.openseizuredetector;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class FragmentDataSharing extends FragmentOsdBaseClass {
String TAG = "FragmentDataSharing";
public FragmentDataSharing() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_data_sharing, container, false);
}
@Override
protected void updateUi() {
Log.d(TAG, "updateUi()");
TextView tv;
tv = (TextView) mRootView.findViewById(R.id.fragment_data_sharing_tv1);
if (mConnection.mBound) {
tv.setText("Bound to Server");
} else {
tv.setText("****NOT BOUND TO SERVER***");
return;
}
}
}

View File

@@ -0,0 +1,178 @@
package uk.org.openseizuredetector;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.appcompat.widget.SwitchCompat;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.utils.ValueFormatter;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class FragmentHrAlg extends FragmentOsdBaseClass {
String TAG = "FragmentHrAlg";
LineChart mLineChart;
LineData lineData;
LineDataSet lineDataSet;
List<Entry> hrHistory = new ArrayList<>();
List<Entry> hrAverages = new ArrayList<>();
List<String> hrHistoryStrings = new ArrayList<>();
List<String> hrAveragesStrings = new ArrayList<>();
private List<Entry> listToDisplay;
private List<String> listToDisplayStrings;
private TextView tvAvgAHr;
private TextView tvHr;
private TextView tv;
private TextView tvCurrent;
private SwitchCompat switchAverages;
public FragmentHrAlg() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lineDataSet = new LineDataSet(new ArrayList<Entry>(), "Heart rate history");
//lineDataSet.setColors(ColorTemplate.JOYFUL_COLORS);
lineDataSet.setValueTextColor(Color.BLACK);
lineDataSet.setValueTextSize(18f);
lineDataSet.setDrawValues(false);
lineDataSet.setCircleSize(0f);
lineDataSet.setLineWidth(3f);
//lineDataSetAverage = new LineDataSet(new ArrayList<Entry>(),"Heart rate history" );
//lineDataSetAverage.setColors(ColorTemplate.JOYFUL_COLORS);
//lineDataSetAverage.setValueTextColor(Color.BLACK);
//lineDataSetAverage.setValueTextSize(18f);
}
@Override
public void onResume() {
super.onResume();
mLineChart = mRootView.findViewById(R.id.lineChart);
mLineChart.getLegend().setEnabled(false);
XAxis xAxis = mLineChart.getXAxis();
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxis.setTextSize(10f);
xAxis.setDrawAxisLine(true);
xAxis.setDrawLabels(true);
// Note: the default text colour is BLACK, so does not show up on black background!!!
// This took a lot of finding....
xAxis.setTextColor(Color.WHITE);
YAxis yAxis = mLineChart.getAxisLeft();
yAxis.setAxisMinValue(40f);
yAxis.setAxisMaxValue(240f);
yAxis.setDrawGridLines(true);
yAxis.setDrawLabels(true);
yAxis.setTextColor(Color.WHITE);
// Inhibit the decimal part of the y axis labels.
yAxis.setValueFormatter(new ValueFormatter() {
@Override
public String getFormattedValue(float v) {
DecimalFormat format = new DecimalFormat("###");
return format.format(v);
}
});
YAxis yAxis2 = mLineChart.getAxisRight();
yAxis2.setDrawGridLines(false);
yAxis2.setEnabled(false);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_hr_alg, container, false);
}
@Override
protected void updateUi() {
Log.d(TAG, "updateUi()");
tv = (TextView) mRootView.findViewById(R.id.fragment_hr_alg_tv1);
tvHr = (TextView) mRootView.findViewById(R.id.current_hr_tv);
tvAvgAHr = (TextView) mRootView.findViewById(R.id.adaptive_avg_hr_tv);
if (mConnection.mBound) {
tv.setText("Bound to Server");
tvCurrent = mRootView.findViewById(R.id.textView2);
if (Objects.nonNull(tvCurrent)) {
if (Objects.nonNull(tvHr))
tvHr.setText(String.valueOf((short) mConnection.mSdServer.mSdData.mHR));
if (Objects.nonNull(tvAvgAHr))
tvAvgAHr.setText(String.valueOf((short) mConnection.mSdServer.mSdData
.mAdaptiveHrAverage));
tvCurrent.setText(new StringBuilder()
.append("\nResult of checks: Adaptive Hr Alarm Standing: ")
.append(mConnection.mSdServer.mSdData.mAdaptiveHrAlarmStanding)
.append("\nAverage Hr Alarm Standing: ")
.append(mConnection.mSdServer.mSdData.mAdaptiveHrAlarmStanding)
.toString());
//switchAverages = mRootView.findViewById(R.id.hr_average_switch);
if (Objects.nonNull(mConnection.mSdServer.mSdDataSource.mSdAlgHr)) {
//Log.v(TAG,"mSdAlgHr is not null");
CircBuf hrHist = mConnection.mSdServer.mSdDataSource.mSdAlgHr.getHrHistBuff();
int nHistArr = hrHist.getNumVals();
double hrHistArr[] = hrHist.getVals(); // This gives us a simple vector of hr values to plot.
if (Objects.nonNull(hrHist) && nHistArr > 0) {
Log.v(TAG, "hrHist.getNumVals=" + nHistArr);
lineDataSet.clear();
String xVals[] = new String[nHistArr];
for (int i = 0; i < nHistArr; i++) {
//Log.d(TAG,"i="+i+", HR="+hrHistArr[i]);
xVals[i] = String.valueOf(i);
lineDataSet.addEntry(new Entry((float) hrHistArr[i], i));
}
Log.d(TAG, "xVals=" + Arrays.toString(xVals) + ", lneDataSet=" + lineDataSet.toSimpleString());
lineDataSet.setColors(new int[]{0xffff0000});
LineData hrHistLineData = new LineData(xVals, lineDataSet);
mLineChart.setData(hrHistLineData);
mLineChart.getData().notifyDataChanged();
mLineChart.notifyDataSetChanged();
mLineChart.refreshDrawableState();
float xSpan = (nHistArr * 5.0f) / 60.0f; // time in minutes assuming one point every 5 seconds.
mLineChart.setDescription(getString(R.string.heart_rate_history_bpm)
+ String.format("%.1f", xSpan)
+ " " + getString(R.string.minutes));
mLineChart.setDescriptionTextSize(12f);
mLineChart.invalidate();
//if (mConnection.mBound){
// lineChart.postInvalidate();
//}
}
}
} else {
tv.setText("****NOT BOUND TO SERVER***");
return;
}
}
}
}

View File

@@ -0,0 +1,44 @@
package uk.org.openseizuredetector;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class FragmentMlAlg extends FragmentOsdBaseClass {
String TAG = "FragmentMlAlg";
public FragmentMlAlg() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_ml_alg, container, false);
}
@Override
protected void updateUi() {
Log.d(TAG, "updateUi()");
TextView tv;
tv = (TextView) mRootView.findViewById(R.id.fragment_ml_alg_tv1);
if (mConnection.mBound) {
tv.setText("Bound to Server");
} else {
tv.setText("****NOT BOUND TO SERVER***");
return;
}
}
}

View File

@@ -0,0 +1,209 @@
package uk.org.openseizuredetector;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.utils.ValueFormatter;
import java.text.DecimalFormat;
import java.util.ArrayList;
public class FragmentOsdAlg extends FragmentOsdBaseClass {
String TAG = "FragmentOsdAlg";
public FragmentOsdAlg() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_osdalg, container, false);
}
@Override
protected void updateUi() {
//Log.d(TAG,"updateUi()");
TextView tv;
if (mConnection.mBound) {
/////////////////////////////////////////////////////
// Set ProgressBars to show margin to alarm.
long powerPc;
if (mConnection.mSdServer.mSdData.alarmThresh != 0)
powerPc = mConnection.mSdServer.mSdData.roiPower * 100 /
mConnection.mSdServer.mSdData.alarmThresh;
else
powerPc = 0;
long specPc;
if (mConnection.mSdServer.mSdData.specPower != 0 &&
mConnection.mSdServer.mSdData.alarmRatioThresh != 0)
specPc = 100 * (mConnection.mSdServer.mSdData.roiPower * 10 /
mConnection.mSdServer.mSdData.specPower) /
mConnection.mSdServer.mSdData.alarmRatioThresh;
else
specPc = 0;
long specRatio;
if (mConnection.mSdServer.mSdData.specPower != 0) {
specRatio = 10 * mConnection.mSdServer.mSdData.roiPower /
mConnection.mSdServer.mSdData.specPower;
} else
specRatio = 0;
((TextView) mRootView.findViewById(R.id.powerTv)).setText(getString(R.string.PowerEquals) + mConnection.mSdServer.mSdData.roiPower +
" (" + getString(R.string.Threshold) + "=" + mConnection.mSdServer.mSdData.alarmThresh + ")");
ProgressBar pb;
Drawable pbDrawable;
pb = ((ProgressBar) mRootView.findViewById(R.id.powerProgressBar));
pb.setMax(100);
pb.setProgress((int) powerPc);
pbDrawable = mContext.getDrawable(R.drawable.progress_bar_blue);
//pbDrawable = mRootView.getResources().getDrawable(R.drawable.progress_bar_blue);
if (powerPc > 75)
pbDrawable = mContext.getDrawable(R.drawable.progress_bar_yellow);
if (powerPc > 100)
pbDrawable = mContext.getDrawable(R.drawable.progress_bar_red);
pb.setProgressDrawable(pbDrawable);
((TextView) mRootView.findViewById(R.id.spectrumTv)).setText(getString(R.string.SpectrumRatioEquals) + specRatio +
" (" + getString(R.string.Threshold) + "=" + mConnection.mSdServer.mSdData.alarmRatioThresh + ")");
pb = ((ProgressBar) mRootView.findViewById(R.id.spectrumProgressBar));
pb.setMax(100);
pb.setProgress((int) specPc);
//pbDrawable = mRootView.getResources().getDrawable(R.drawable.progress_bar_blue);
pbDrawable = mContext.getDrawable(R.drawable.progress_bar_blue);
if (specPc > 75)
pbDrawable = mContext.getDrawable(R.drawable.progress_bar_yellow);
if (specPc > 100)
pbDrawable = mContext.getDrawable(R.drawable.progress_bar_red);
pb.setProgressDrawable(pbDrawable);
////////////////////////////////////////////////////////////
// set progressbar seizure probability
////////////////////////////////////////////////////////////
long pSeizurePc;
pSeizurePc = (long) (mConnection.mSdServer.mSdData.mPseizure * 100);
pb = ((ProgressBar) mRootView.findViewById(R.id.pSeizureProgressBarM2));
pb.setMax(100);
pb.setProgress((int) pSeizurePc);
pbDrawable = mContext.getDrawable(R.drawable.progress_bar_blue);
if (pSeizurePc > 30)
pbDrawable = mContext.getDrawable(R.drawable.progress_bar_yellow);
if (pSeizurePc > 50)
pbDrawable = mContext.getDrawable(R.drawable.progress_bar_red);
//pb.getProgressDrawable().setColorFilter(colour, PorterDuff.Mode.SRC_IN);
pb.setProgressDrawable(pbDrawable);
////////////////////////////////////////////////////////////
// Produce graph
BarChart mChart = (BarChart) mRootView.findViewById(R.id.chart1);
mChart.setDrawBarShadow(false);
mChart.setNoDataTextDescription("You need to provide data for the chart.");
mChart.setDescription("");
// X and Y Values
ArrayList<String> xVals = new ArrayList<String>();
ArrayList<BarEntry> yBarVals = new ArrayList<BarEntry>();
for (int i = 0; i < 10; i++) {
xVals.add(i + "-" + (i + 1) + " Hz");
if (mConnection.mSdServer != null) {
yBarVals.add(new BarEntry(mConnection.mSdServer.mSdData.simpleSpec[i], i));
} else {
yBarVals.add(new BarEntry(i, i));
}
}
// create a dataset and give it a type
BarDataSet barDataSet = new BarDataSet(yBarVals, "Spectrum");
try {
int[] barColours = new int[10];
for (int i = 0; i < 10; i++) {
if ((i < mConnection.mSdServer.mSdData.alarmFreqMin) ||
(i > mConnection.mSdServer.mSdData.alarmFreqMax)) {
barColours[i] = Color.GRAY;
} else {
barColours[i] = Color.RED;
}
}
barDataSet.setColors(barColours);
} catch (NullPointerException e) {
Log.e(TAG, "Null pointer exception setting bar colours");
}
barDataSet.setBarSpacePercent(20f);
barDataSet.setBarShadowColor(Color.WHITE);
BarData barData = new BarData(xVals, barDataSet);
barData.setValueFormatter(new ValueFormatter() {
@Override
public String getFormattedValue(float v) {
DecimalFormat format = new DecimalFormat("####");
return format.format(v);
}
});
mChart.setData(barData);
// format the axes
XAxis xAxis = mChart.getXAxis();
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxis.setTextSize(10f);
xAxis.setDrawAxisLine(true);
xAxis.setDrawLabels(true);
// Note: the default text colour is BLACK, so does not show up on black background!!!
// This took a lot of finding....
xAxis.setTextColor(Color.WHITE);
xAxis.setDrawGridLines(false);
YAxis yAxis = mChart.getAxisLeft();
yAxis.setAxisMinValue(0f);
yAxis.setAxisMaxValue(3000f);
yAxis.setDrawGridLines(true);
yAxis.setDrawLabels(true);
yAxis.setTextColor(Color.WHITE);
yAxis.setValueFormatter(new ValueFormatter() {
@Override
public String getFormattedValue(float v) {
DecimalFormat format = new DecimalFormat("#####");
return format.format(v);
}
});
YAxis yAxis2 = mChart.getAxisRight();
yAxis2.setDrawGridLines(false);
try {
mChart.getLegend().setEnabled(false);
} catch (NullPointerException e) {
Log.e(TAG, "Null Pointer Exception setting legend");
}
mChart.invalidate();
}
}
}

View File

@@ -0,0 +1,143 @@
package uk.org.openseizuredetector;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.Timer;
import java.util.TimerTask;
public class FragmentOsdBaseClass extends Fragment {
String TAG = "FragmentOsdBaseClass";
Context mContext;
OsdUtil mUtil;
SdServiceConnection mConnection;
final Handler updateUiHandler = new Handler();
Timer mUiTimer;
protected View mRootView;
protected int okColour = Color.BLUE;
protected int warnColour = Color.MAGENTA;
protected int alarmColour = Color.RED;
protected int okTextColour = Color.WHITE;
protected int warnTextColour = Color.WHITE;
protected int alarmTextColour = Color.BLACK;
public FragmentOsdBaseClass() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "onCreate()");
mContext = getContext();
mUtil = new OsdUtil(mContext, updateUiHandler);
mConnection = new SdServiceConnection(mContext);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_sd_data_viewer, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mRootView = view;
}
@Override
public void onStart() {
super.onStart();
Log.i(TAG, "onStart()");
}
@Override
public void onResume() {
super.onResume();
Log.i(TAG, "onResume()");
if (mUtil.isServerRunning()) {
Log.i(TAG, "onResume() - Binding to Server");
mUtil.bindToServer(mContext, mConnection);
} else {
Log.i(TAG, "onResume() - Server Not Running");
}
mUiTimer = new Timer();
mUiTimer.schedule(new TimerTask() {
@Override
public void run() {
updateUiOnUiThread();
}
}, 0, 1000);
}
@Override
public void onPause() {
super.onPause();
Log.i(TAG, "onPause()");
mUiTimer.cancel();
mUtil.unbindFromServer(mContext, mConnection);
}
@Override
public void onStop() {
super.onStop();
Log.i(TAG, "onStop()");
}
/**
* If we don't use this .post() trick, we get errors about the wrong thread trying to
* update the user interface views...
*/
private void updateUiOnUiThread() {
updateUiHandler.post(new Runnable() {
@Override
public void run() {
// Check for context being null is an attempt to stop the crashes reported in Issue No 176
if (mContext != null) {
try {
updateUi();
} catch (Exception e) {
Log.e(TAG,"upateUiOnUiThread() - exception updating UI - "+e.getMessage());
}
} else {
Log.e(TAG,"updateUionUiThread() - mContext is null?? Can't show a Toast message because context is null....");
}
}
});
}
/**
* The subclasses should override this to draw their own UI.
*/
protected void updateUi() {
Log.d(TAG, "updateUi()");
TextView tv;
tv = (TextView) mRootView.findViewById(R.id.fragment_sddata_viewer_tv1);
if (mConnection.mBound) {
tv.setText("Bound to Server");
} else {
tv.setText("****NOT BOUND TO SERVER***");
}
}
}

View File

@@ -0,0 +1,197 @@
package uk.org.openseizuredetector;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.appcompat.widget.LinearLayoutCompat;
public class FragmentSystem extends FragmentOsdBaseClass {
String TAG = "FragmentSystem";
public FragmentSystem() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_system, container, false);
}
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Handle Edit Settings Button
ImageButton button = (ImageButton) mRootView.findViewById(R.id.settingsButton);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Log.i(TAG, "settingsButton.onClick()");
try {
Intent prefsIntent = new Intent(
mContext,
PrefActivity.class);
mContext.startActivity(prefsIntent);
} catch (Exception ex) {
Log.i(TAG, "exception starting settings activity " + ex.toString());
}
}
});
}
@Override
protected void updateUi() {
//Log.d(TAG,"updateUi()");
TextView tv;
tv = (TextView) mRootView.findViewById(R.id.fragment_bound_to_server_tv);
if (mConnection.mBound) {
tv.setText("Bound to Server");
tv.setTextColor(okTextColour);
} else {
tv.setText("****NOT BOUND TO SERVER***");
tv.setTextColor(warnTextColour);
return;
}
LinearLayoutCompat ll = (LinearLayoutCompat) mRootView.findViewById(R.id.fragment_ll);
if (mUtil.isServerRunning()) {
ll.setBackgroundColor(okColour);
tv = (TextView) mRootView.findViewById(R.id.serverStatusTv);
if (mConnection.mBound) {
if (mConnection.mSdServer.mSdDataSourceName.equals("Phone")) {
if (mConnection.mSdServer.mLogNDA)
tv.setText(getString(R.string.ServerRunningOK) + " " + getString(R.string.DataSource) + " = " + "Phone" + " " + "(Demo Mode)" + "\nNDA Logging");
else
tv.setText(getString(R.string.ServerRunningOK) + " " + getString(R.string.DataSource) + " = " + "Phone" + " " + "(Demo Mode)");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
} else {
if (mConnection.mSdServer.mLogNDA)
tv.setText(getString(R.string.ServerRunningOK) + " " + getString(R.string.DataSource) + " = " + mConnection.mSdServer.mSdDataSourceName + ": NDA Logging");
else
tv.setText(getString(R.string.ServerRunningOK) + " " + getString(R.string.DataSource) + " = " + mConnection.mSdServer.mSdDataSourceName);
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
}
}
//Log.v(TAG,"UpdateUi() - displaying server IP address");
tv = (TextView) mRootView.findViewById(R.id.serverIpTv);
tv.setText(getString(R.string.AccessServerAt) + " http://"
+ mUtil.getLocalIpAddress()
+ ":8080");
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
} else {
ll.setBackgroundColor(warnColour);
tv = (TextView) mRootView.findViewById(R.id.serverStatusTv);
tv.setText(R.string.ServerStopped);
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
tv = (TextView) mRootView.findViewById(R.id.serverIpTv);
tv.setText("--");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
}
try {
if (mConnection.mBound) {
tv = (TextView) mRootView.findViewById(R.id.alarmTv);
if ((mConnection.mSdServer.mSdData.alarmState == 0)
&& !mConnection.mSdServer.mSdData.alarmStanding
&& !mConnection.mSdServer.mSdData.fallAlarmStanding) {
tv.setText(getString(R.string.okBtnTxt));
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
}
if ((mConnection.mSdServer.mSdData.alarmState == 1)
&& !mConnection.mSdServer.mSdData.alarmStanding
&& !mConnection.mSdServer.mSdData.fallAlarmStanding) {
tv.setText(R.string.Warning);
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
}
if (mConnection.mSdServer.mSdData.alarmState == 6) {
tv.setText(R.string.Mute);
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
}
if (mConnection.mSdServer.mSdData.alarmStanding) {
tv.setText(R.string.Alarm);
tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour);
}
if (mConnection.mSdServer.mSdData.fallAlarmStanding) {
tv.setText(R.string.Fall);
tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour);
}
tv = (TextView) mRootView.findViewById(R.id.data_time_tv);
tv.setText(mConnection.mSdServer.mSdData.dataTime.format("%H:%M:%S"));
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
tv = (TextView) mRootView.findViewById(R.id.fragment_watch_app_status_tv);
if (mConnection.mSdServer.mSdData.watchAppRunning) {
tv.setText(R.string.WatchAppOK);
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
} else {
tv.setText(R.string.WatchAppNotRunning);
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
}
tv = (TextView) mRootView.findViewById(R.id.battTv);
tv.setText(getString(R.string.WatchBatteryEquals)
+ String.valueOf(mConnection.mSdServer.mSdData.batteryPc) + "% / "
+ String.valueOf(mConnection.mSdServer.mSdData.phoneBatteryPc) + "%");
if (mConnection.mSdServer.mSdData.batteryPc <= 10) {
tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour);
}
if (mConnection.mSdServer.mSdData.batteryPc > 10) {
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
}
if (mConnection.mSdServer.mSdData.batteryPc >= 20) {
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
}
tv = (TextView) mRootView.findViewById(R.id.watch_manuf_tv);
tv.setText(mConnection.mSdServer.mSdData.watchManuf);
tv = (TextView) mRootView.findViewById(R.id.watch_partno_tv);
tv.setText(mConnection.mSdServer.mSdData.watchPartNo);
tv = (TextView) mRootView.findViewById(R.id.watch_fwver_tv);
tv.setText(mConnection.mSdServer.mSdData.watchFwVersion);
tv = (TextView) mRootView.findViewById(R.id.watch_sdname_tv);
tv.setText(mConnection.mSdServer.mSdData.watchSdName);
tv = (TextView) mRootView.findViewById(R.id.watch_sdver_tv);
tv.setText(mConnection.mSdServer.mSdData.watchSdVersion);
tv = (TextView) mRootView.findViewById(R.id.watch_batt_tv);
tv.setText(mConnection.mSdServer.mSdData.batteryPc+" %");
tv = (TextView) mRootView.findViewById(R.id.watch_signal_tv);
tv.setText(String.format("%.0f dB", mConnection.mSdServer.mSdData.watchSignalStrength));
}
} catch (Exception e) {
Log.e(TAG, "UpdateUi: Exception - ");
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,148 @@
package uk.org.openseizuredetector;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.appcompat.widget.SwitchCompat;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.utils.ValueFormatter;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class FragmentWatchSig extends FragmentOsdBaseClass {
String TAG = "FragmentWatchSig";
LineChart mLineChart;
LineData lineData;
LineDataSet lineDataSet;
List<Entry> sigHistory = new ArrayList<>();
List<String> hrHistoryStrings = new ArrayList<>();
private TextView tvCurrSigStren;
public FragmentWatchSig() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lineDataSet = new LineDataSet(new ArrayList<Entry>(), "Watch Signal Strength history");
//lineDataSet.setColors(ColorTemplate.JOYFUL_COLORS);
lineDataSet.setValueTextColor(Color.BLACK);
lineDataSet.setValueTextSize(18f);
lineDataSet.setDrawValues(false);
lineDataSet.setCircleSize(0f);
lineDataSet.setLineWidth(3f);
//lineDataSetAverage = new LineDataSet(new ArrayList<Entry>(),"Heart rate history" );
//lineDataSetAverage.setColors(ColorTemplate.JOYFUL_COLORS);
//lineDataSetAverage.setValueTextColor(Color.BLACK);
//lineDataSetAverage.setValueTextSize(18f);
}
@Override
public void onResume() {
super.onResume();
mLineChart = mRootView.findViewById(R.id.sigStrengthLineChart);
mLineChart.getLegend().setEnabled(false);
XAxis xAxis = mLineChart.getXAxis();
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxis.setTextSize(10f);
xAxis.setDrawAxisLine(true);
xAxis.setDrawLabels(true);
// Note: the default text colour is BLACK, so does not show up on black background!!!
// This took a lot of finding....
xAxis.setTextColor(Color.WHITE);
YAxis yAxis = mLineChart.getAxisLeft();
yAxis.setAxisMaxValue(-50f);
yAxis.setAxisMinValue(-100f);
yAxis.setDrawGridLines(true);
yAxis.setDrawLabels(true);
yAxis.setTextColor(Color.WHITE);
// Inhibit the decimal part of the y axis labels.
yAxis.setValueFormatter(new ValueFormatter() {
@Override
public String getFormattedValue(float v) {
DecimalFormat format = new DecimalFormat("###");
return format.format(v);
}
});
YAxis yAxis2 = mLineChart.getAxisRight();
yAxis2.setDrawGridLines(false);
yAxis2.setEnabled(false);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_watch_sig, container, false);
}
@Override
protected void updateUi() {
Log.d(TAG, "updateUi()");
tvCurrSigStren = (TextView) mRootView.findViewById(R.id.current_sig_strength_tv);
if (mConnection.mBound) {
if (Objects.nonNull(tvCurrSigStren))
tvCurrSigStren.setText(String.valueOf((int) mConnection.mSdServer.mSdData.watchSignalStrength));
double histArr[] = mConnection.mSdServer.mSdData.watchSignalStrengthBuff.getVals();
int nHist = histArr.length;
if (Objects.nonNull(histArr) && nHist > 0) {
Log.v(TAG, "nHist=" + nHist);
lineDataSet.clear();
String xVals[] = new String[nHist];
for (int i = 0; i < nHist; i++) {
//Log.d(TAG,"i="+i+", HR="+hrHistArr[i]);
xVals[i] = String.valueOf(i);
lineDataSet.addEntry(new Entry((float) histArr[i], i));
}
Log.d(TAG, "xVals=" + Arrays.toString(xVals) + ", lneDataSet=" + lineDataSet.toSimpleString());
lineDataSet.setColors(new int[]{0xffff0000});
LineData histLineData = new LineData(xVals, lineDataSet);
mLineChart.setData(histLineData);
mLineChart.getData().notifyDataChanged();
mLineChart.notifyDataSetChanged();
mLineChart.refreshDrawableState();
float xSpan = (nHist * 5.0f) / 60.0f; // time in minutes assuming one point every 5 seconds.
mLineChart.setDescription("Signal Strength History "
+ String.format("%.1f", xSpan)
+ " " + getString(R.string.minutes));
mLineChart.setDescriptionTextSize(12f);
mLineChart.invalidate();
//if (mConnection.mBound){
// lineChart.postInvalidate();
//}
}
} else {
Log.w(TAG,"not Bound to Server");
return;
}
}
}

View File

@@ -0,0 +1,44 @@
package uk.org.openseizuredetector;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class FragmentWebServer extends FragmentOsdBaseClass {
String TAG = "FragmentWebServer";
public FragmentWebServer() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_web_server, container, false);
}
@Override
protected void updateUi() {
Log.d(TAG, "updateUi()");
TextView tv;
tv = (TextView) mRootView.findViewById(R.id.fragment_web_server_tv1);
if (mConnection.mBound) {
tv.setText("Bound to Server");
} else {
tv.setText("****NOT BOUND TO SERVER***");
return;
}
}
}

View File

@@ -1,5 +1,6 @@
package uk.org.openseizuredetector;
// Defines the servies and characteristics we need to subscribe to.
import java.util.HashMap;
public class GattAttributes {

View File

@@ -2,6 +2,7 @@ package uk.org.openseizuredetector;
/**
*
*/
import android.content.Context;
@@ -21,8 +22,7 @@ interface SdLocationReceiver {
}
public class LocationFinder implements LocationListener
{
public class LocationFinder implements LocationListener {
SdLocationReceiver mSdLocationReceiver = null;
Location mLastLocation = null;
OsdUtil mUtil;
@@ -33,7 +33,7 @@ public class LocationFinder implements LocationListener
LocationListener mLocationListener;
int mTimeoutPeriod = 60; // Location search timeout period in seconds.
String TAG="LocationFinder";
String TAG = "LocationFinder";
LocationFinder(Context context) {
mHandler = new Handler();
@@ -56,7 +56,6 @@ public class LocationFinder implements LocationListener
}
public Location getLastLocation() {
return mLastLocation;
}
@@ -82,7 +81,7 @@ public class LocationFinder implements LocationListener
mTimeoutTimer.schedule(new TimerTask() {
@Override
public void run() {
Log.v(TAG,"mTimeOutTimer expired - returning last location");
Log.v(TAG, "mTimeOutTimer expired - returning last location");
//mUtil.showToast("mTimeOutTimer expired - returning last location");
mLocationManager.removeUpdates(mLocationListener);
mSdLocationReceiver.onSdLocationReceived(mLastLocation);
@@ -93,7 +92,7 @@ public class LocationFinder implements LocationListener
@Override
public void onLocationChanged(Location location) {
Log.v(TAG,"onLocationChanged - "+location.toString());
Log.v(TAG, "onLocationChanged - " + location.toString());
// if we do not have a last location, this is the best we have!
if (mLastLocation == null) {

View File

@@ -498,7 +498,7 @@ public class LogManager {
* @param endDate end date of period to export (Date type)
* @param duration duration in hours of period to export (double)
* @param uri uri of file to save.
* @param callback function to be called on completion of the task (returns true on success, false on error)
* @param callback function to be called on completion of the task (returns true on success, false on error)
*/
public void exportToCsvFile(Date endDate, double duration, Uri uri, BooleanCallback callback) {
Log.v(TAG, "exportToCsvFile(): uri=" + uri.toString());
@@ -510,8 +510,6 @@ public class LogManager {
}
/**
* Return an array list of objects representing the events in the database by calling the specified callback function.
*
@@ -807,7 +805,7 @@ public class LogManager {
ExportDataTask(Date endDate, double duration, Uri uri, BooleanCallback callback) {
Log.i(TAG,"ExportDataTask constructor()");
Log.i(TAG, "ExportDataTask constructor()");
this.mCallback = callback;
mEndDate = endDate;
mDuration = duration;
@@ -882,7 +880,7 @@ public class LogManager {
@Override
protected void onPostExecute(final Boolean result) {
Log.i(TAG,"ExportDataTask.onPostExecute() - notifying callback function of result: "+result);
Log.i(TAG, "ExportDataTask.onPostExecute() - notifying callback function of result: " + result);
mCallback.accept(result);
}
@@ -923,12 +921,12 @@ public class LogManager {
} catch (IOException e) {
Log.e(TAG, "exportToFile() - ERROR Writing File: " + e.toString());
mUtil.showToast("ERROR WRITING FILE");
return(-1);
return (-1);
}
}
Log.d(TAG, "writeDatapointsToFile() - data written to file ok");
mUtil.showToast(mContext.getString(R.string.data_exported_ok)+ " "+nRec);
mUtil.showToast(mContext.getString(R.string.data_exported_ok) + " " + nRec);
return nRec;
} catch (JSONException | NullPointerException e) {
@@ -936,7 +934,7 @@ public class LogManager {
dataObj = null;
mUtil.showToast(mContext.getString(R.string.error_exporting_data));
Log.e(TAG, "exportToFile() - JSONException: " + e.toString());
return(-1);
return (-1);
}
}

View File

@@ -86,6 +86,7 @@ public class MainActivity extends AppCompatActivity {
private Intent sdServerIntent;
final Handler serverStatusHandler = new Handler();
final Handler mHandler = new Handler();
Messenger messenger = new Messenger(new ResponseHandler());
Timer mUiTimer;
private Context mContext;
@@ -275,20 +276,27 @@ public class MainActivity extends AppCompatActivity {
mConnection.mSdServer.acceptAlarm();
}
return true;
case R.id.action_start_stop:
case R.id.action_exit:
// Respond to the start/stop server menu item.
Log.i(TAG, "action_sart_stop");
if (mConnection.mBound) {
Log.i(TAG, "Stopping Server");
mUtil.unbindFromServer(getApplicationContext(), mConnection);
stopServer();
} else {
Log.i(TAG, "Starting Server");
startServer();
// and bind to it so we can see its data
Log.i(TAG, "Binding to Server");
mUtil.bindToServer(getApplicationContext(), mConnection);
}
Log.i(TAG, "action_exit: Stopping Server");
mUtil.unbindFromServer(getApplicationContext(), mConnection);
stopServer();
// We exit this activity as a crude way of forcing the fragments to disconnect from the server
// so that the server exits properly - otherwise we end up with multiple threads running.
// FIXME - tell the threads to unbind from the serer before calling stopServer as an alternative.
finish();
return true;
case R.id.action_start_stop:
Log.i(TAG, "action_start_stop: restarting server");
mUtil.showToast("Stopping Background Service....");
mUtil.stopServer();
// Wait 1 second to give the server chance to shutdown, then re-start it
mHandler.postDelayed(new Runnable() {
public void run() {
mUtil.showToast("Re-Starting Background Service...");
mUtil.startServer();
}
}, 1000);
return true;
/* fault beep test does not work with fault timer, so disable test option.
case R.id.action_test_fault_beep:
@@ -517,16 +525,16 @@ public class MainActivity extends AppCompatActivity {
if (mConnection.mBound) {
if (mConnection.mSdServer.mSdDataSourceName.equals("Phone")) {
if (mConnection.mSdServer.mLogNDA)
tv.setText(getString(R.string.ServerRunningOK) + getString(R.string.DataSource) + " = " + "Phone" + "\n" + "(Demo Mode)" + "\nNDA Logging");
tv.setText(getString(R.string.ServerRunningOK) + "\n" + getString(R.string.DataSource) + " = " + "Phone" + "\n" + "(Demo Mode)" + "\nNDA Logging");
else
tv.setText(getString(R.string.ServerRunningOK) + getString(R.string.DataSource) + " = " + "Phone" + "\n" + "(Demo Mode)");
tv.setText(getString(R.string.ServerRunningOK) + "\n" + getString(R.string.DataSource) + " = " + "Phone" + "\n" + "(Demo Mode)");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
} else {
if (mConnection.mSdServer.mLogNDA)
tv.setText(getString(R.string.ServerRunningOK) + getString(R.string.DataSource) + " = " + mConnection.mSdServer.mSdDataSourceName + "\nNDA Logging");
tv.setText(getString(R.string.ServerRunningOK) + "\n" + getString(R.string.DataSource) + " = " + mConnection.mSdServer.mSdDataSourceName + "\nNDA Logging");
else
tv.setText(getString(R.string.ServerRunningOK) + getString(R.string.DataSource) + " = " + mConnection.mSdServer.mSdDataSourceName);
tv.setText(getString(R.string.ServerRunningOK) + "\n" + getString(R.string.DataSource) + " = " + mConnection.mSdServer.mSdDataSourceName);
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
}
@@ -632,7 +640,7 @@ public class MainActivity extends AppCompatActivity {
tv.setTextColor(warnTextColour);
}
if (mConnection.mSdServer.mSdData.alarmStanding) {
tv.setText(R.string.Alarm);
tv.setText(getString(R.string.Alarm) + "\n" + mConnection.mSdServer.mSdData.alarmCause);
tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour);
}
@@ -642,7 +650,7 @@ public class MainActivity extends AppCompatActivity {
tv.setTextColor(alarmTextColour);
}
tv = (TextView) findViewById(R.id.pebTimeTv);
tv = (TextView) findViewById(R.id.data_time_tv);
tv.setText(mConnection.mSdServer.mSdData.dataTime.format("%H:%M:%S"));
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
@@ -652,16 +660,16 @@ public class MainActivity extends AppCompatActivity {
//if (mConnection.mSdServer.mSdData.mHRAlarmActive) {
if (mConnection.mSdServer.mSdData.mO2Sat > 0) {
tv.setText(getString(R.string.HR_Equals) + " " + Math.round(mConnection.mSdServer.mSdData.mHR) + " bpm\n"
+"(av = "
+Math.round(mConnection.mSdServer.mSdData.mAdaptiveHrAverage)+", "
+Math.round(mConnection.mSdServer.mSdData.mAverageHrAverage)+" bpm)\n"
+ getString(R.string.SpO2)+" = " + Math.round(mConnection.mSdServer.mSdData.mO2Sat) + "%");
+ "(av = "
+ Math.round(mConnection.mSdServer.mSdData.mAdaptiveHrAverage) + ", "
+ Math.round(mConnection.mSdServer.mSdData.mAverageHrAverage) + " bpm)\n"
+ getString(R.string.SpO2) + " = " + Math.round(mConnection.mSdServer.mSdData.mO2Sat) + "%");
} else {
tv.setText(getString(R.string.HR_Equals) + " " + Math.round(mConnection.mSdServer.mSdData.mHR) + " bpm\n"
+"(av = "
+Math.round(mConnection.mSdServer.mSdData.mAdaptiveHrAverage)+", "
+Math.round(mConnection.mSdServer.mSdData.mAverageHrAverage)+" bpm)\n"
+ getString(R.string.SpO2)+" = ---%");
+ "(av = "
+ Math.round(mConnection.mSdServer.mSdData.mAdaptiveHrAverage) + ", "
+ Math.round(mConnection.mSdServer.mSdData.mAverageHrAverage) + " bpm)\n"
+ getString(R.string.SpO2) + " = ---%");
}
if (mConnection.mSdServer.mSdData.mHRAlarmStanding
|| mConnection.mSdServer.mSdData.mAdaptiveHrAlarmStanding
@@ -690,7 +698,7 @@ public class MainActivity extends AppCompatActivity {
}
*/
tv = (TextView) findViewById(R.id.appTv);
tv = (TextView) findViewById(R.id.fragment_watch_app_status_tv);
if (mConnection.mSdServer.mSdData.watchAppRunning) {
tv.setText(R.string.WatchAppOK);
tv.setBackgroundColor(okColour);
@@ -701,20 +709,31 @@ public class MainActivity extends AppCompatActivity {
tv.setTextColor(warnTextColour);
}
tv = (TextView) findViewById(R.id.battTv);
tv.setText(getString(R.string.WatchBatteryEquals) + String.valueOf(mConnection.mSdServer.mSdData.batteryPc) + "%");
if (mConnection.mSdServer.mSdData.batteryPc <= 10) {
tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour);
}
if (mConnection.mSdServer.mSdData.batteryPc > 10) {
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
}
if (mConnection.mSdServer.mSdData.batteryPc >= 20) {
if (mConnection.mSdServer.mSdData.dataSourceName.equals("Phone")) {
tv.setText(getString(R.string.WatchBatteryEquals)
+ "---% / "
+ String.valueOf(mConnection.mSdServer.mSdData.phoneBatteryPc) + "%");
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
}
} else {
tv.setText(getString(R.string.WatchBatteryEquals)
+ String.valueOf(mConnection.mSdServer.mSdData.batteryPc) + "% / "
+ String.valueOf(mConnection.mSdServer.mSdData.phoneBatteryPc) + "%");
if (mConnection.mSdServer.mSdData.batteryPc <= 10) {
tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour);
}
if (mConnection.mSdServer.mSdData.batteryPc > 10) {
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
}
if (mConnection.mSdServer.mSdData.batteryPc >= 20) {
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
}
}
////////////////////////////////////////////////////////////
// Populate the Data Sharing Status Box
// We start off with it set to OK, then check for several different abnormal conditions
@@ -810,7 +829,7 @@ public class MainActivity extends AppCompatActivity {
" (" + getString(R.string.Threshold) + "=" + mConnection.mSdServer.mSdData.alarmThresh + ")");
((TextView) findViewById(R.id.spectrumTv)).setText(getString(R.string.SpectrumRatioEquals) + specRatio +
" (" + getString(R.string.Threshold) + "=" + mConnection.mSdServer.mSdData.alarmRatioThresh + ")");
((TextView) findViewById(R.id.pSeizureTv)).setText(getString(R.string.seizure_probability)+" = " + pSeizurePc + "%");
((TextView) findViewById(R.id.pSeizureTv)).setText(getString(R.string.seizure_probability) + " = " + pSeizurePc + "%");
ProgressBar pb;
Drawable pbDrawable;
@@ -865,12 +884,12 @@ public class MainActivity extends AppCompatActivity {
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
}
tv = (TextView) findViewById(R.id.pebTimeTv);
tv = (TextView) findViewById(R.id.data_time_tv);
tv.setText(mConnection.mSdServer.mSdData.dataTime.format("%H:%M:%S"));
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
tv = (TextView) findViewById(R.id.pebTimeTv);
tv = (TextView) findViewById(R.id.data_time_tv);
tv.setText("--:--:--");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
@@ -880,7 +899,7 @@ public class MainActivity extends AppCompatActivity {
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
tv = (TextView) findViewById(R.id.appTv);
tv = (TextView) findViewById(R.id.fragment_watch_app_status_tv);
tv.setText(getString(R.string.WatchApp) + " ----");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
@@ -895,12 +914,12 @@ public class MainActivity extends AppCompatActivity {
tv.setText(R.string.Dashes);
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
tv = (TextView) findViewById(R.id.pebTimeTv);
tv = (TextView) findViewById(R.id.data_time_tv);
tv.setText(mConnection.mSdServer.mSdData.dataTime.format("%H:%M:%S"));
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
tv = (TextView) findViewById(R.id.pebTimeTv);
tv = (TextView) findViewById(R.id.data_time_tv);
tv.setText("--:--:--");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
@@ -910,7 +929,7 @@ public class MainActivity extends AppCompatActivity {
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
tv = (TextView) findViewById(R.id.appTv);
tv = (TextView) findViewById(R.id.fragment_watch_app_status_tv);
tv.setText(getString(R.string.WatchApp) + " -----");
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
@@ -1076,6 +1095,25 @@ public class MainActivity extends AppCompatActivity {
super.onResume();
Log.i(TAG, "onResume()");
mUtil.writeToSysLogFile("MainActivity.onResume()");
// Check to see if the user has asked for the new user interface to be used instead of this one
// and start that if necessary.
try {
SharedPreferences SP = PreferenceManager
.getDefaultSharedPreferences(getBaseContext());
boolean useNewUi = SP.getBoolean("UseNewUi", false);
if (useNewUi) {
Log.i(TAG,"onResume() - launching new User Interface");
Intent intent = new Intent(
getApplicationContext(),
MainActivity2.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
finish();
}
} catch (Exception ex) {
Log.e(TAG, "exception starting main activity " + ex.toString());
}
}

View File

@@ -0,0 +1,418 @@
package uk.org.openseizuredetector;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.MenuCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import com.rohitss.uceh.UCEHandler;
import java.util.Objects;
import java.util.Timer;
import java.util.TimerTask;
public class MainActivity2 extends AppCompatActivity {
private String TAG = "MainActivity2";
private int okColour = Color.BLUE;
private int warnColour = Color.MAGENTA;
private int alarmColour = Color.RED;
private int okTextColour = Color.WHITE;
private int warnTextColour = Color.WHITE;
private int alarmTextColour = Color.BLACK;
private ViewPager2 mFragmentPager;
private FragmentStateAdapter mFragmentStateAdapter;
private Context mContext;
private OsdUtil mUtil;
private SdServiceConnection mConnection;
final Handler serverStatusHandler = new Handler();
private Handler mHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Log.i(TAG, "onCreate()");
// Set our custom uncaught exception handler to report issues.
//Thread.setDefaultUncaughtExceptionHandler(new OsdUncaughtExceptionHandler(MainActivity.this));
new UCEHandler.Builder(this)
.addCommaSeparatedEmailAddresses("crashreports@openseizuredetector.org.uk,")
.build();
//int i = 5/0; // Force exception to test handler.
mUtil = new OsdUtil(getApplicationContext(), serverStatusHandler);
mConnection = new SdServiceConnection(getApplicationContext());
mUtil.writeToSysLogFile("MainActivity2.onCreate()");
mContext = this;
mHandler = new Handler();
}
/**
* Create Action Bar
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
Log.i(TAG, "onCreateOptionsMenu()");
getMenuInflater().inflate(R.menu.main_activity_actions, menu);
MenuCompat.setGroupDividerEnabled(menu, true);
return true;
}
@Override
protected void onStart() {
super.onStart();
Log.i(TAG, "onStart()");
mUtil.writeToSysLogFile("MainActivity.onStart()");
SharedPreferences SP = PreferenceManager
.getDefaultSharedPreferences(getBaseContext());
boolean audibleAlarm = SP.getBoolean("AudibleAlarm", true);
Log.v(TAG, "onStart - audibleAlarm = " + audibleAlarm);
TextView tv;
tv = (TextView) findViewById(R.id.versionTv);
String versionName = mUtil.getAppVersionName();
tv.setText(getString(R.string.AppTitleText) + " " + versionName);
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
if (mUtil.isServerRunning()) {
Log.i(TAG, "MainActivity2.onStart() - Binding to Server");
mUtil.writeToSysLogFile("MainActivity2.onStart - Binding to Server");
mUtil.bindToServer(getApplicationContext(), mConnection);
} else {
Log.i(TAG, "MainActivity2.onStart() - Server Not Running");
mUtil.writeToSysLogFile("MainActivity2.onStart - Server Not Running");
}
}
@Override
protected void onStop() {
super.onStop();
Log.i(TAG, "onStop() - unbinding from server");
mUtil.writeToSysLogFile("MainActivity.onStop()");
mUtil.unbindFromServer(getApplicationContext(), mConnection);
}
@Override
protected void onPause() {
super.onPause();
Log.i(TAG, "onPause()");
}
@Override
protected void onResume() {
super.onResume();
Log.i(TAG, "onResume()");
// Instantiate a ViewPager2 and a PagerAdapter.
mFragmentPager = findViewById(R.id.fragment_pager);
mFragmentStateAdapter = new ScreenSlideFragmentPagerAdapter(this);
mFragmentPager.setAdapter(mFragmentStateAdapter);
getSupportFragmentManager().beginTransaction()
.setReorderingAllowed(true)
.add(R.id.fragment_common_container_view, FragmentCommon.class, null)
.commit();
// Check to see if the user has asked for the original user interface to be used instead of this one
// and start that if necessary.
try {
SharedPreferences SP = PreferenceManager
.getDefaultSharedPreferences(getBaseContext());
boolean useNewUi = SP.getBoolean("UseNewUi", false);
if (!useNewUi) {
Log.i(TAG,"onResume() - launching original User Interface");
Intent intent = new Intent(
getApplicationContext(),
MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
finish();
}
} catch (Exception ex) {
Log.e(TAG, "exception starting main activity " + ex.toString());
}
// Force the screen to stay on when the app is running
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@Override
public void onBackPressed() {
if (Objects.isNull(mFragmentPager) || mFragmentPager.getCurrentItem() == 0) {
// If the user is currently looking at the first step, allow the system to handle the
// Back button. This calls finish() on this activity and pops the back stack.
super.onBackPressed();
} else {
// Otherwise, select the previous step.
mFragmentPager.setCurrentItem(mFragmentPager.getCurrentItem() - 1);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Log.i(TAG, "onOptionsItemSelected() : " + item.getItemId() + " selected");
switch (item.getItemId()) {
/*case R.id.action_launch_pebble_app:
Log.i(TAG, "action_launch_pebble_app");
mConnection.mSdServer.mSdDataSource.startPebbleApp();
return true;
*/
case R.id.action_install_watch_app:
Log.i(TAG, "action_install_watch_app");
mConnection.mSdServer.mSdDataSource.installWatchApp();
return true;
case R.id.action_accept_alarm:
Log.i(TAG, "action_accept_alarm");
if (mConnection.mBound) {
mConnection.mSdServer.acceptAlarm();
}
return true;
case R.id.action_exit:
// Respond to the start/stop server menu item.
Log.i(TAG, "action_exit: Stopping Server");
mUtil.unbindFromServer(getApplicationContext(), mConnection);
stopServer();
// We exit this activity as a crude way of forcing the fragments to disconnect from the server
// so that the server exits properly - otherwise we end up with multiple threads running.
// FIXME - tell the threads to unbind from the serer before calling stopServer as an alternative.
finish();
return true;
case R.id.action_start_stop:
// FIXME: We need to unbind the fragments from the service, or else unbindFromServer does not work!
// Disabled this menu option until I work out how to fix it!
Log.i(TAG, "action_start_stop: restarting server");
mUtil.unbindFromServer(this, mConnection );
mUtil.showToast("Stopping Background Service....");
mUtil.stopServer();
// Wait 1 second to give the server chance to shutdown, then re-start it
mHandler.postDelayed(new Runnable() {
public void run() {
mUtil.showToast("NOT Re-Starting Background Service...");
//mUtil.startServer();
}
}, 1000);
return true;
case R.id.action_test_alarm_beep:
Log.i(TAG, "action_test_alarm_beep");
if (mConnection.mBound) {
mConnection.mSdServer.alarmBeep();
}
return true;
case R.id.action_test_warning_beep:
Log.i(TAG, "action_test_warning_beep");
if (mConnection.mBound) {
mConnection.mSdServer.warningBeep();
}
return true;
case R.id.action_test_sms_alarm:
Log.i(TAG, "action_test_sms_alarm");
if (mConnection.mBound) {
mConnection.mSdServer.sendSMSAlarm();
}
return true;
case R.id.action_authenticate_api:
Log.i(TAG, "action_autheticate_api");
try {
Intent i = new Intent(
MainActivity2.this,
AuthenticateActivity.class);
this.startActivity(i);
} catch (Exception ex) {
Log.i(TAG, "exception starting export activity " + ex.toString());
}
return true;
case R.id.action_about_datasharing:
Log.i(TAG, "action_about_datasharing");
showDataSharingDialog();
return true;
case R.id.action_logmanager:
Log.i(TAG, "action_logmanager");
try {
Intent intent = new Intent(
MainActivity2.this,
LogManagerControlActivity.class);
this.startActivity(intent);
} catch (Exception ex) {
Log.i(TAG, "exception starting log manager activity " + ex.toString());
}
return true;
case R.id.action_report_seizure:
Log.i(TAG, "action_report_seizure");
try {
Intent intent = new Intent(
MainActivity2.this,
ReportSeizureActivity.class);
this.startActivity(intent);
} catch (Exception ex) {
Log.i(TAG, "exception starting Report Seizure activity " + ex.toString());
}
return true;
case R.id.action_settings:
Log.i(TAG, "action_settings");
try {
Intent prefsIntent = new Intent(
MainActivity2.this,
PrefActivity.class);
this.startActivity(prefsIntent);
} catch (Exception ex) {
Log.i(TAG, "exception starting settings activity " + ex.toString());
}
return true;
case R.id.action_about:
Log.i(TAG, "action_about");
showAbout();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in
* sequence.
*/
private class ScreenSlideFragmentPagerAdapter extends FragmentStateAdapter {
private String TAG = "ScreenSlideFragmentPagerAdapter";
public ScreenSlideFragmentPagerAdapter(FragmentActivity fa) {
super(fa);
}
@Override
public Fragment createFragment(int position) {
// Note - the number of positions must match the value returned by getItemCount() below.
switch (position) {
case 0:
return new FragmentOsdAlg();
case 1:
return new FragmentHrAlg();
case 2:
return new FragmentSystem();
case 3:
return new FragmentWatchSig();
case 4:
return new FragmentBatt();
//case 4:
// return new FragmentDataSharing();
default:
Log.e(TAG, "createFragment() - invalid Position " + position);
return null;
}
}
@Override
public int getItemCount() {
return 5;
}
}
private void startServer() {
mUtil.writeToSysLogFile("MainActivity.startServer()");
Log.i(TAG, "startServer(): starting Server...");
mUtil.startServer();
}
private void stopServer() {
mUtil.writeToSysLogFile("MainActivity.stopServer()");
Log.i(TAG, "stopServer(): stopping Server...");
mUtil.stopServer();
}
private void showAbout() {
mUtil.writeToSysLogFile("MainActivity.showAbout()");
View aboutView = getLayoutInflater().inflate(R.layout.about_layout, null, false);
String versionName = mUtil.getAppVersionName();
Log.i(TAG, "showAbout() - version name = " + versionName);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIcon(R.drawable.icon_24x24);
builder.setTitle("OpenSeizureDetector V" + versionName);
builder.setNeutralButton(getString(R.string.closeBtnTxt), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
builder.setPositiveButton(R.string.privacy_policy_button_title, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
String url = OsdUtil.PRIVACY_POLICY_URL;
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);
dialog.cancel();
}
});
builder.setNegativeButton(R.string.data_sharing_button_title, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
String url = OsdUtil.DATA_SHARING_URL;
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);
dialog.cancel();
}
});
builder.setView(aboutView);
builder.create();
builder.show();
}
private void showDataSharingDialog() {
mUtil.writeToSysLogFile("MainActivity.showDataSharingDialog()");
View aboutView = getLayoutInflater().inflate(R.layout.data_sharing_dialog_layout, null, false);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIcon(R.drawable.datasharing_fault_24x24);
builder.setTitle(R.string.data_sharing_dialog_title);
builder.setNegativeButton(getString(R.string.cancel), null);
builder.setPositiveButton(getString(R.string.login), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.i(TAG, "dataSharingDialog.positiveButton.onClick()");
try {
Intent i = new Intent(
MainActivity2.this,
AuthenticateActivity.class);
mContext.startActivity(i);
} catch (Exception ex) {
Log.i(TAG, "exception starting activity " + ex.toString());
}
}
});
builder.setView(aboutView);
builder.create();
builder.show();
}
}

View File

@@ -0,0 +1,111 @@
package uk.org.openseizuredetector;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
// This class manages machine learning models by downloading them from a remote server when necessary.
public class MlModelManager {
protected Context mContext;
protected OsdUtil mUtil;
private String TAG = "MlModelManager";
public boolean mServerConnectionOk = false;
public boolean mModelReady = false;
private final String mUrlBase = "https://openseizuredetector.org.uk/static/MLmodels/";
private final String mModelIndexFname = "MLmodels.json";
RequestQueue mQueue;
public interface JSONObjectCallback {
public void accept(JSONObject retValObj);
}
public MlModelManager(Context context) {
Log.i(TAG, "MlModelManager Constructor");
mContext = context;
mUtil = new OsdUtil(mContext, new Handler());
mQueue = Volley.newRequestQueue(mContext);
}
public void close() {
Log.i(TAG, "close()");
mQueue.stop();
}
/**
* Retrieve the file containing the list of available ML models from the server.
* Calls the specified callback function, passing a JSONObject as a parameter when the data has been received and parsed.
*
* @return true if request sent successfully or else false.
*/
public boolean getMlModelIndex(JSONObjectCallback callback) {
Log.v(TAG, "getMlModelIndex()");
String urlStr = mUrlBase + mModelIndexFname;
Log.v(TAG, "urlStr=" + urlStr);
StringRequest req = new StringRequest(Request.Method.GET, urlStr,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.v(TAG, "getMlModelIndex.onResponse(): Response is: " + response);
mServerConnectionOk = true;
try {
JSONObject retObj = new JSONObject(response);
callback.accept(retObj);
} catch (JSONException e) {
Log.e(TAG, "getMlModelIndex.onRespons(): Error: " + e.getMessage() + "," + e.toString());
callback.accept(null);
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
mServerConnectionOk = false;
if (error != null) {
Log.e(TAG, "getMlModelIndex.onErrorResponse(): " + error.toString() + ", message:" + error.getMessage());
} else {
Log.e(TAG, "getMlModelIndex.onErrorResponse() - returned null response");
}
callback.accept(null);
}
}) {
// Note, this is overriding part of StringRequest, not one of the sub-classes above!
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> params = new HashMap<String, String>();
params.put("Content-Type", "application/json; charset=UTF-8");
//params.put("Authorization", "Token " + getStoredToken());
return params;
}
};
mQueue.add(req);
return (true);
}
}

View File

@@ -160,8 +160,8 @@ public class OsdUncaughtExceptionHandler implements Thread.UncaughtExceptionHand
});
builder.setMessage("Please report the " +
"problem by email using the button below so we can fix it.\n" +
"You can review the information being sent in the next screen:"+
"\n"+errorContent.toString());
"You can review the information being sent in the next screen:" +
"\n" + errorContent.toString());
Dialog dialog = builder.create();
//dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
dialog.show();

View File

@@ -59,6 +59,7 @@ import java.io.File;
import java.io.FileWriter;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -66,6 +67,7 @@ import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.function.Consumer;
/**
@@ -99,6 +101,21 @@ public class OsdUtil {
private static int mNbound = 0;
public final String[] BT_PERMISSIONS_API30 = {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.BLUETOOTH_SCAN,
//Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.BLUETOOTH_CONNECT,
};
public final String[] BT_PERMISSIONS_OLD = {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
};
public String[] BT_PERMISSIONS;
public OsdUtil(Context context, Handler handler) {
mContext = context;
mHandler = handler;
@@ -325,13 +342,13 @@ public class OsdUtil {
*
* @param msgStr
*/
public void writeToSysLogFile(String msgStr,String logType) {
writeLogEntryToLocalDb(msgStr,logType);
}
public void writeToSysLogFile(String msgStr) {
writeLogEntryToLocalDb(msgStr,"v");
public void writeToSysLogFile(String msgStr, String logType) {
writeLogEntryToLocalDb(msgStr, logType);
}
public void writeToSysLogFile(String msgStr) {
writeLogEntryToLocalDb(msgStr, "v");
}
/**
@@ -403,12 +420,11 @@ public class OsdUtil {
public File[] getDataFilesList() {
File[] files = getDataStorageDir().listFiles();
Log.d("Files", "Size: "+ files.length);
for (int i = 0; i < files.length; i++)
{
Log.d("Files", "Size: " + files.length);
for (int i = 0; i < files.length; i++) {
Log.d("Files", "FileName:" + files[i].getName());
}
return(files);
return (files);
}
/* Checks if external storage is available for read and write */
@@ -459,6 +475,7 @@ public class OsdUtil {
* It first attempts to parse it as a long integer, in which case it is assumed to
* be a unix timestamp.
* If that fails it attempts to parse it as yyyy-MM-dd'T'HH:mm:ss'Z' format.
*
* @param dateStr String reprenting a date
* @return Date object or null if parsing fails.
*/
@@ -468,7 +485,7 @@ public class OsdUtil {
Long tstamp = Long.parseLong(dateStr);
dataTime = new Date(tstamp);
} catch (NumberFormatException e) {
Log.v(TAG, "remoteEventsAdapter.getView: Error Parsing dataDate as Long: " + e.getLocalizedMessage()+" trying as string");
Log.v(TAG, "remoteEventsAdapter.getView: Error Parsing dataDate as Long: " + e.getLocalizedMessage() + " trying as string");
try {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
dataTime = dateFormat.parse(dateStr);
@@ -477,7 +494,7 @@ public class OsdUtil {
dataTime = null;
}
}
return(dataTime);
return (dataTime);
}
@@ -503,20 +520,20 @@ public class OsdUtil {
break;
}
return(retVal);
return (retVal);
}
private static boolean openDb() {
Log.d(TAG, "openDb");
try {
if (mSysLogDb == null) {
Log.i(TAG,"openDb: mSysLogDb is null - initialising");
Log.i(TAG, "openDb: mSysLogDb is null - initialising");
mSysLogDb = new OsdSysLogHelper(mContext).getWritableDatabase();
} else {
Log.i(TAG,"openDb: mSysLogDb has been initialised already so not doing anything");
Log.i(TAG, "openDb: mSysLogDb has been initialised already so not doing anything");
}
if (!checkTableExists(mSysLogDb, mSysLogTableName)) {
Log.e(TAG, "ERROR - Table "+mSysLogTableName+" does not exist");
Log.e(TAG, "ERROR - Table " + mSysLogTableName + " does not exist");
return false;
} else {
Log.d(TAG, "table " + mSysLogTableName + " exists ok");
@@ -565,7 +582,7 @@ public class OsdUtil {
+ 0
+ ")";
mSysLogDb.execSQL(SQLStr);
Log.v(TAG, "syslog entry written to database: "+logText);
Log.v(TAG, "syslog entry written to database: " + logText);
pruneSysLogDb();
} catch (SQLException e) {
@@ -612,7 +629,6 @@ public class OsdUtil {
/**
* Executes the sqlite query (=SELECT statement)
* Use as new SelectQueryTask(xxx,xxx,xx,xxxx).execute()
*
*/
static private class SelectQueryTask extends AsyncTask<Void, Void, Cursor> {
// Based on https://stackoverflow.com/a/21120199/2104584
@@ -669,7 +685,6 @@ public class OsdUtil {
}
/**
* pruneSysLogDb() removes data that is older than 7 days
*/
@@ -728,15 +743,64 @@ public class OsdUtil {
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// This database is only a cache for online data, so its upgrade policy is
// to simply to discard the data and start over
Log.i(TAG,"onUpgrade()");
Log.i(TAG, "onUpgrade()");
db.execSQL("Drop table if exists " + mSysLogTableName + ";");
onCreate(db);
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i(TAG,"onDowngrade()");
Log.i(TAG, "onDowngrade()");
onUpgrade(db, oldVersion, newVersion);
}
}
public String[] getRequiredBtPermissions() {
// API 31 is Android 12 - see https://developer.android.com/develop/connectivity/bluetooth/bt-permissions
if (Build.VERSION.SDK_INT >= 31) {
Log.d(TAG, "getRequiredBtPermissions() - using new Bluetooth Permissions");
BT_PERMISSIONS = BT_PERMISSIONS_API30;
} else {
Log.d(TAG, "getRequiredBtPermissions() - using old Bluetooth Permissions");
BT_PERMISSIONS = BT_PERMISSIONS_OLD;
}
return (BT_PERMISSIONS);
}
public boolean areBtPermissionsOk() {
String[] btPermissions = getRequiredBtPermissions();
boolean allOk = true;
Log.d(TAG, "areBTPermissions OK()");
for (int i = 0; i < btPermissions.length; i++) {
if (ContextCompat.checkSelfPermission(mContext, btPermissions[i])
!= PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, btPermissions[i] + " Permission Not Granted");
allOk = false;
}
}
return allOk;
}
public double parseToDouble(String userInput) {
/**
* Parse a string to a double value, taking localisation into account.
* Using NumberFormat as recommended by https://docs.oracle.com/javase%2F7%2Fdocs%2Fapi%2F%2F/java/lang/Double.html#valueOf(java.lang.String)
*/
double retVal;
try {
Locale currentLocale;
if (android.os.Build.VERSION.SDK_INT < 24) {
currentLocale = mContext.getResources().getConfiguration().locale;
} else {
currentLocale = mContext.getResources().getConfiguration().getLocales().get(0);
}
NumberFormat nf = NumberFormat.getInstance(currentLocale);
retVal = nf.parse(userInput).doubleValue();
} catch (ParseException e) {
// Handle invalid input (e.g., non-numeric characters)
showToast("Invalid input. Please enter a valid numeric value.");
retVal = 0.0;
}
return(retVal);
}
}

View File

@@ -146,36 +146,58 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference
// if we have enabled the SMS alarm, we may need extra permissions approving. This is handled in
// StartUpActivity, so we exit this activity and start start-up activity.
if (s.equals("SMSAlarm")) {
if (s.equals("SMSAlarm")) {
if (sharedPreferences.getBoolean("SMSAlarm", false) == true) {
mUtil.showToast("Restarting OpenSeizureDetector");
Log.i(TAG, "onSharedPreferenceChanged(): SMS Alarm Enabled - Restarting start-up activity to check permissions");
Intent i;
i = new Intent(this, StartupActivity.class);
startActivity(i);
Log.i(TAG,"onSharedPreferenceChanged() - finishing PrefActivity");
Log.i(TAG, "onSharedPreferenceChanged() - finishing PrefActivity");
finish();
return;
} else {
Log.i(TAG, "OnSharedPreferenceChanged(): SMS Alarm disabled so do not need permissions");
}
}
// For all other preference changes we just restart SdServer so it is not as alarming for the user!
//mUtil.showToast("Setting " + s + " Changed - restarting server");
mPrefChanged = true;
mUtil.stopServer();
// Wait 0.1 second to give the server chance to shutdown, then re-start it
mHandler.postDelayed(new Runnable() {
public void run() {
mUtil.startServer();
// If we have changed the data source, re-start the whole system
if (s.equals("DataSource")) {
Log.i(TAG, "onSharedPreferenceChanged(): Data Source Changed - Restarting start-up activity to check permissions");
mUtil.showToast("Restarting OpenSeizureDetector");
mUtil.stopServer();
// Wait 1 second to give the server chance to shutdown, then re-start it
mHandler.postDelayed(new Runnable() {
public void run() {
Intent i;
Log.i(TAG, "onSharedPreferenceChanged(): Data Source Changed - Restarting start-up activity to check permissions");
i = new Intent(getApplicationContext(), StartupActivity.class);
startActivity(i);
Log.i(TAG, "onSharedPreferenceChanged() - finishing PrefActivity");
finish();
return;
}
}, 1000);
return;
} else {
// For all other preference changes we just restart SdServer so it is not as alarming for the user!
//mUtil.showToast("Setting " + s + " Changed - restarting server");
mUtil.showToast("Stopping Background Service...");
mPrefChanged = true;
mUtil.stopServer();
// Wait 1 second to give the server chance to shutdown, then re-start it
mHandler.postDelayed(new Runnable() {
public void run() {
mUtil.showToast("Re-Starting Background Service...");
mUtil.startServer();
}
}, 1000);
if (s.equals("advancedMode")) {
Log.i(TAG, "Re-starting PrefActivity to refresh list");
startActivity(getIntent());
finish();
}
}, 100);
if (s.equals("DataSource") || s.equals("advancedMode")) {
Log.i(TAG, "Re-starting PrefActivity to refresh list");
finish();
startActivity(getIntent());
}
}
@Override
@@ -218,7 +240,7 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference
protected void onStop() {
super.onStop();
mUtil.writeToSysLogFile("PrefActvity.onStop()");
Log.i(TAG,"onStop()");
Log.i(TAG, "onStop()");
}
/**

View File

@@ -9,7 +9,9 @@ import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.preference.PreferenceManager;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.webkit.WebSettings;
@@ -114,7 +116,7 @@ public class RemoteDbActivity extends AppCompatActivity {
private HashMap<String, String> getAuthHeaders() {
HashMap<String, String> headersMap = new HashMap<>();
String authToken = getAuthToken();
headersMap.put("Authorization", "Token "+authToken);
headersMap.put("Authorization", "Token " + authToken);
return (headersMap);
}
@@ -125,7 +127,7 @@ public class RemoteDbActivity extends AppCompatActivity {
}
private void updateUi() {
Log.v(TAG,"updateUi()");
Log.v(TAG, "updateUi()");
TextView tv;
Button btn;
// Local Database Information
@@ -137,10 +139,9 @@ public class RemoteDbActivity extends AppCompatActivity {
//tv.setText(String.format("%d",datapointsCount));
// Remote Database Information
tv = (TextView)findViewById(R.id.authStatusTv);
btn = (Button)findViewById(R.id.auth_button);
tv = (TextView) findViewById(R.id.authStatusTv);
btn = (Button) findViewById(R.id.auth_button);
if (mLm != null) {
if (mLm.mWac.isLoggedIn()) {
tv.setText("Authenticated");
@@ -158,7 +159,7 @@ public class RemoteDbActivity extends AppCompatActivity {
public void onClick(View view) {
Log.v(TAG, "onAuth");
Intent i;
i =new Intent(mContext, AuthenticateActivity.class);
i = new Intent(mContext, AuthenticateActivity.class);
startActivity(i);
}
};

View File

@@ -209,24 +209,23 @@ public class ReportSeizureActivity extends AppCompatActivity {
}
private void updateUi() {
//Log.v(TAG,"updateUi()");
TextView tv;
Button btn;
RadioButton b;
tv = (TextView)findViewById(R.id.date_day_tv);
tv.setText(String.format("%02d",mDay));
tv = (TextView)findViewById(R.id.date_mon_tv);
tv.setText(String.format("%02d",mMonth+1)); // Month counted from zero
tv = (TextView)findViewById(R.id.date_year_tv);
tv.setText(String.format("%04d",mYear));
tv = (TextView)findViewById(R.id.time_hh_tv);
tv.setText(String.format("%02d",mHour));
tv = (TextView)findViewById(R.id.time_mm_tv);
tv.setText(String.format("%02d",mMinute));
tv = (TextView)findViewById(R.id.msg_tv);
tv = (TextView) findViewById(R.id.date_day_tv);
tv.setText(String.format("%02d", mDay));
tv = (TextView) findViewById(R.id.date_mon_tv);
tv.setText(String.format("%02d", mMonth + 1)); // Month counted from zero
tv = (TextView) findViewById(R.id.date_year_tv);
tv.setText(String.format("%04d", mYear));
tv = (TextView) findViewById(R.id.time_hh_tv);
tv.setText(String.format("%02d", mHour));
tv = (TextView) findViewById(R.id.time_mm_tv);
tv.setText(String.format("%02d", mMinute));
tv = (TextView) findViewById(R.id.msg_tv);
tv.setText(mMsg);
// Populate event type button group if necessary
@@ -250,12 +249,12 @@ public class ReportSeizureActivity extends AppCompatActivity {
if (b != null) {
seizureTypeStr = b.getText().toString();
}
Log.i(TAG,"updateUi - SeizureType="+seizureTypeStr);
Log.i(TAG, "updateUi - SeizureType=" + seizureTypeStr);
// Populate the event sub-types radio button list.
Log.v(TAG,"updateUi() - meventsubtypeshashmap="+mEventSubTypesHashMap+", mEventSubtypesListChanged="+mEventSubTypesListChanged);
Log.v(TAG, "updateUi() - meventsubtypeshashmap=" + mEventSubTypesHashMap + ", mEventSubtypesListChanged=" + mEventSubTypesListChanged);
if (mEventSubTypesHashMap != null && mRedrawEventSubTypesList) {
Log.v(TAG,"UpdateUi() - populating event sub types list");
Log.v(TAG, "UpdateUi() - populating event sub types list");
if (seizureTypeStr != null) {
// based on https://androidexample.com/create-a-simple-listview
ArrayList<String> subtypesArrayList = mEventSubTypesHashMap.get(seizureTypeStr);
@@ -281,8 +280,8 @@ public class ReportSeizureActivity extends AppCompatActivity {
String notesStr = null;
Log.v(TAG, "onOk");
//SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr=String.format("%4d-%02d-%02d %02d:%02d:30",mYear,mMonth+1,mDay, mHour, mMinute);
Log.v(TAG, "onOk() - dateSTr="+dateStr);
String dateStr = String.format("%4d-%02d-%02d %02d:%02d:30", mYear, mMonth + 1, mDay, mHour, mMinute);
Log.v(TAG, "onOk() - dateSTr=" + dateStr);
// Read seizure type from radio buttons
int checkedRadioButtonId = mEventTypeRg.getCheckedRadioButtonId();
@@ -290,19 +289,19 @@ public class ReportSeizureActivity extends AppCompatActivity {
if (b != null) {
seizureTypeStr = b.getText().toString();
}
Log.i(TAG,"onOk() - SeizureType="+seizureTypeStr);
Log.i(TAG, "onOk() - SeizureType=" + seizureTypeStr);
checkedRadioButtonId = mEventSubTypeRg.getCheckedRadioButtonId();
b = (RadioButton) findViewById(checkedRadioButtonId);
if (b != null) {
seizureSubTypeStr = b.getText().toString();
}
Log.i(TAG,"onOk() - SeizureSubType="+seizureSubTypeStr);
Log.i(TAG, "onOk() - SeizureSubType=" + seizureSubTypeStr);
TextView tv = (TextView)findViewById(R.id.eventNotesTv);
TextView tv = (TextView) findViewById(R.id.eventNotesTv);
notesStr = tv.getText().toString();
mLm.createLocalEvent(dateStr,5,seizureTypeStr, seizureSubTypeStr, notesStr,
mLm.createLocalEvent(dateStr, 5, seizureTypeStr, seizureSubTypeStr, notesStr,
mConnection.mSdServer.mSdData.toSettingsJSON());
mUtil.showToast("Seizure Event Created");
finish();

View File

@@ -27,6 +27,7 @@ public class SdAlgHr {
private CircBuf mAdaptiveHrBuff;
private CircBuf mAverageHrBuff;
private CircBuf mHrHist;
public SdAlgHr(Context context) {
Log.i(TAG, "SdAlgHr Constructor");
@@ -34,6 +35,8 @@ public class SdAlgHr {
updatePrefs();
mAdaptiveHrBuff = new CircBuf(mAdaptiveHrAlarmWindowDp, -1.0);
mAverageHrBuff = new CircBuf(mAverageHrAlarmWindowDp, -1.0);
// FIXME - this is a hard coded 3 hour period (at 5 second intervals)
mHrHist = new CircBuf((int) (3 * 3600 / 5), -1);
}
public void close() {
@@ -69,34 +72,33 @@ public class SdAlgHr {
mSimpleHrAlarmActive = SP.getBoolean("HRAlarmActive", false);
mSimpleHrAlarmThreshMin = readDoublePref(SP, "HRThreshMin", "20");
mSimpleHrAlarmThreshMax = readDoublePref(SP, "HRThreshMax", "150");
Log.d(TAG,"updatePrefs(): mSimpleHrAlarmActive="+mSimpleHrAlarmActive);
Log.d(TAG,"updatePrefs(): mSimpleHrAlarmThreshMin="+mSimpleHrAlarmThreshMin);
Log.d(TAG,"updatePrefs(): mSimpleHrAlarmThreshMax="+mSimpleHrAlarmThreshMax);
Log.d(TAG, "updatePrefs(): mSimpleHrAlarmActive=" + mSimpleHrAlarmActive);
Log.d(TAG, "updatePrefs(): mSimpleHrAlarmThreshMin=" + mSimpleHrAlarmThreshMin);
Log.d(TAG, "updatePrefs(): mSimpleHrAlarmThreshMax=" + mSimpleHrAlarmThreshMax);
mAdaptiveHrAlarmActive = SP.getBoolean("HRAdaptiveAlarmActive", false);
mAdaptiveHrAlarmWindowSecs = readDoublePref(SP, "HRAdaptiveAlarmWindowSecs", "30");
mAdaptiveHrAlarmWindowDp = (int)Math.round(mAdaptiveHrAlarmWindowSecs/5.0);
mAdaptiveHrAlarmWindowDp = (int) Math.round(mAdaptiveHrAlarmWindowSecs / 5.0);
mAdaptiveHrAlarmThresh = readDoublePref(SP, "HRAdaptiveAlarmThresh", "20");
Log.d(TAG,"updatePrefs(): mAdaptiveHrAlarmActive="+mAdaptiveHrAlarmActive);
Log.d(TAG,"updatePrefs(): mAdaptiveHrWindowSecs="+mAdaptiveHrAlarmWindowSecs);
Log.d(TAG,"updatePrefs(): mAdaptiveHrWindowDp="+mAdaptiveHrAlarmWindowDp);
Log.d(TAG,"updatePrefs(): mAdaptiveHrAlarmThresh="+mAdaptiveHrAlarmThresh);
Log.d(TAG, "updatePrefs(): mAdaptiveHrAlarmActive=" + mAdaptiveHrAlarmActive);
Log.d(TAG, "updatePrefs(): mAdaptiveHrWindowSecs=" + mAdaptiveHrAlarmWindowSecs);
Log.d(TAG, "updatePrefs(): mAdaptiveHrWindowDp=" + mAdaptiveHrAlarmWindowDp);
Log.d(TAG, "updatePrefs(): mAdaptiveHrAlarmThresh=" + mAdaptiveHrAlarmThresh);
mAverageHrAlarmActive = SP.getBoolean("HRAverageAlarmActive", false);
mAverageHrAlarmWindowSecs = readDoublePref(SP, "HRAverageAlarmWindowSecs", "120");
mAverageHrAlarmWindowDp = (int)Math.round(mAverageHrAlarmWindowSecs/5.0);
mAverageHrAlarmWindowDp = (int) Math.round(mAverageHrAlarmWindowSecs / 5.0);
mAverageHrAlarmThreshMin = readDoublePref(SP, "HRAverageAlarmThreshMin", "40");
mAverageHrAlarmThreshMax = readDoublePref(SP, "HRAverageAlarmThreshMax", "120");
Log.d(TAG,"updatePrefs(): mAverageHrAlarmActive="+mAverageHrAlarmActive);
Log.d(TAG,"updatePrefs(): mAverageHrAlarmWindowSecs="+mAverageHrAlarmWindowSecs);
Log.d(TAG,"updatePrefs(): mAverageHrAlarmWindowDp="+mAverageHrAlarmWindowDp);
Log.d(TAG,"updatePrefs(): mAverageHrAlarmThreshMin="+mAverageHrAlarmThreshMin);
Log.d(TAG,"updatePrefs(): mAverageHrAlarmThreshMax="+mAverageHrAlarmThreshMax);
Log.d(TAG, "updatePrefs(): mAverageHrAlarmActive=" + mAverageHrAlarmActive);
Log.d(TAG, "updatePrefs(): mAverageHrAlarmWindowSecs=" + mAverageHrAlarmWindowSecs);
Log.d(TAG, "updatePrefs(): mAverageHrAlarmWindowDp=" + mAverageHrAlarmWindowDp);
Log.d(TAG, "updatePrefs(): mAverageHrAlarmThreshMin=" + mAverageHrAlarmThreshMin);
Log.d(TAG, "updatePrefs(): mAverageHrAlarmThreshMax=" + mAverageHrAlarmThreshMax);
}
private boolean checkSimpleHr(double hrVal) {
/**
* Check heart rate value against simple thresholds
@@ -104,15 +106,16 @@ public class SdAlgHr {
boolean retVal = false;
if (mSimpleHrAlarmActive) {
if ((hrVal > mSimpleHrAlarmThreshMax)
|| (hrVal <mSimpleHrAlarmThreshMin)) {
|| (hrVal < mSimpleHrAlarmThreshMin)) {
retVal = true;
}
}
return(retVal);
return (retVal);
}
/**
* Returns the average heart rate being used by the Adaptive heart rate algorithm
*
* @return Average Heart reate in bpm.
*/
public double getAdaptiveHrAverage() {
@@ -127,8 +130,13 @@ public class SdAlgHr {
return mAdaptiveHrBuff;
}
public CircBuf getHrHistBuff() {
return mHrHist;
}
/**
* Returns the average heart rate being used by the Average heart rate algorithm
*
* @return Average Heart rate in bpm.
*/
public double getAverageHrAverage() {
@@ -138,41 +146,44 @@ public class SdAlgHr {
private boolean checkAdaptiveHr(double hrVal) {
boolean retVal;
double hrThreshMin;
double hrThreshMax;
double avHr = getAdaptiveHrAverage();
hrThreshMin = avHr - mAdaptiveHrAlarmThresh;
hrThreshMax = avHr + mAdaptiveHrAlarmThresh;
retVal = false;
if (hrVal < hrThreshMin) {
retVal = true;
if (mAdaptiveHrAlarmActive) {
double hrThreshMin;
double hrThreshMax;
double avHr = getAdaptiveHrAverage();
hrThreshMin = avHr - mAdaptiveHrAlarmThresh;
hrThreshMax = avHr + mAdaptiveHrAlarmThresh;
if (hrVal < hrThreshMin) {
retVal = true;
}
if (hrVal > hrThreshMax) {
retVal = true;
}
Log.d(TAG, "checkAdaptiveHr() - hrVal=" + hrVal + ", avHr=" + avHr + ", thresholds=(" + hrThreshMin + ", " + hrThreshMax + "): Alarm=" + retVal);
}
if (hrVal > hrThreshMax) {
retVal = true;
}
Log.d(TAG, "checkAdaptiveHr() - hrVal="+hrVal+", avHr="+avHr+", thresholds=("+hrThreshMin+", "+hrThreshMax+"): Alarm="+retVal);
return(retVal);
return (retVal);
}
private boolean checkAverageHr(double hrVal) {
boolean retVal;
double avHr = getAverageHrAverage();
retVal = false;
if (avHr < mAverageHrAlarmThreshMin) {
retVal = true;
if (mAverageHrAlarmActive) {
double avHr = getAverageHrAverage();
if (avHr < mAverageHrAlarmThreshMin) {
retVal = true;
}
if (avHr > mAverageHrAlarmThreshMax) {
retVal = true;
}
Log.d(TAG, "checkAverageHr() - hrVal=" + hrVal + ", avHr=" + avHr + ", thresholds=(" + mAverageHrAlarmThreshMin + ", " + mAverageHrAlarmThreshMin + "): Alarm=" + retVal);
}
if (avHr > mAverageHrAlarmThreshMax) {
retVal = true;
}
Log.d(TAG, "checkAverageHr() - hrVal="+hrVal+", avHr="+avHr+", thresholds=("+mAverageHrAlarmThreshMin+", "+mAverageHrAlarmThreshMin+"): Alarm="+retVal);
return(retVal);
return (retVal);
}
public ArrayList<Boolean> checkHr(double hrVal) {
/**
* Checks the current Heart Rate reading hrVal against the
@@ -180,14 +191,16 @@ public class SdAlgHr {
* and returns an ArrayList of the alarm status of each algorithm in the above order.
* true=ALARM, false=OK.
*/
Log.v(TAG, "checkHr("+hrVal+")");
Log.v(TAG, "checkHr(" + hrVal + ")");
mAdaptiveHrBuff.add(hrVal);
mAverageHrBuff.add(hrVal);
mHrHist.add(hrVal);
ArrayList<Boolean> retVal = new ArrayList<Boolean>();
retVal.add(checkSimpleHr(hrVal));
retVal.add(checkAdaptiveHr(hrVal));
retVal.add(checkAverageHr(hrVal));
return(retVal);
return (retVal);
}
}

View File

@@ -1,7 +1,10 @@
package uk.org.openseizuredetector;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
import com.android.volley.AuthFailureError;
import com.android.volley.Request;
@@ -29,30 +32,54 @@ public class SdAlgNn {
private String mUrlBase = "https://osdApi.ddns.net";
private InterpreterApi interpreter;
private Context mContext;
private MlModelManager mMm;
RequestQueue mQueue;
private double mSdThresh; // Acceleration Standard Deviation Threshold required to activate analysis (%)
private int mModelId; // ID of ML Model to be used (refers to information in MlModels.json for details).
private int mInputFormat; // ID of input format required for model (populated from MlModels.json).
public SdAlgNn(Context context) {
Log.d(TAG, "SdAlgNn Constructor");
mContext = context;
mMm = new MlModelManager(mContext);
SharedPreferences SP = PreferenceManager
.getDefaultSharedPreferences(mContext);
try {
String threshStr = SP.getString("CnnAlarmThreshold", "5");
mSdThresh = Double.parseDouble(threshStr);
Log.v(TAG, "SdAlgNn Constructor mSdThresh = " + mSdThresh);
threshStr = SP.getString("CnnModelId", "1");
mModelId = Integer.parseInt(threshStr);
Log.v(TAG, "SdAlgNn Constructor mModelId = " + mModelId);
} catch (Exception ex) {
Log.v(TAG, "SdAlgNn Constructor - problem parsing preferences. " + ex.toString());
Toast toast = Toast.makeText(mContext, "Problem Parsing ML Algorithm Preferences", Toast.LENGTH_SHORT);
toast.show();
}
mInputFormat = 1; // FIXME - this needs to be determined from the model ID specified by retrieving a configuration file.
Task<Void> initializeTask = TfLite.initialize(mContext);
initializeTask.addOnSuccessListener(a -> {
MappedByteBuffer modelBuffer;
try {
Log.d(TAG, "onSuccessListener - loading model");
modelBuffer = FileUtil.loadMappedFile(context, MODEL_PATH);
Log.d(TAG, "onSuccessListener - model loaded");
} catch (IOException e) {
Log.e(TAG, "Error Loading Model File");
return;
}
Log.d(TAG, "onSuccessListener - creating interpreter");
interpreter = InterpreterApi.create(modelBuffer,
new InterpreterApi.Options().setRuntime(
InterpreterApi.Options.TfLiteRuntime.FROM_SYSTEM_ONLY));
Log.d(TAG, "onSuccessListener - interpreter created ok");
})
MappedByteBuffer modelBuffer;
try {
Log.d(TAG, "onSuccessListener - loading model");
modelBuffer = FileUtil.loadMappedFile(context, MODEL_PATH);
Log.d(TAG, "onSuccessListener - model loaded");
} catch (IOException e) {
Log.e(TAG, "Error Loading Model File");
return;
}
Log.d(TAG, "onSuccessListener - creating interpreter");
interpreter = InterpreterApi.create(modelBuffer,
new InterpreterApi.Options().setRuntime(
InterpreterApi.Options.TfLiteRuntime.FROM_SYSTEM_ONLY));
Log.d(TAG, "onSuccessListener - interpreter created ok");
})
.addOnFailureListener(e -> {
Log.e(TAG, String.format("Cannot initialize interpreter: %s",
e.getMessage()));
@@ -62,25 +89,82 @@ public class SdAlgNn {
}
public void close() {
Log.d(TAG,"close()");
Log.d(TAG, "close()");
if (interpreter != null) {
interpreter.close();
}
}
public float getPseizure(SdData sdData) {
/**
* getPseizureFmt1 - calculate probability of sdData representing seizure-like movement
* using a model with input format #1, which is a simple vector of 125 accelerometer vector
* magnitude readings.
*
* @param sdData - seizure detector data as input to the model
* @return probability of data representing seizure-like movement.
*/
private float getPseizureFmt1(SdData sdData) {
int i;
float[][][] modelInput = new float[1][125][1];
float[][] modelOutput = new float[1][2];
for (int j = 0; j < 125; j++) {
modelInput[0][j][0] = (float)sdData.rawData[j];
modelInput[0][j][0] = (float) sdData.rawData[j];
}
if (interpreter == null) {
Log.d(TAG,"getPSeizure() - interpreter is null - returning zero seizure probability");
Log.d(TAG, "getPSeizure() - interpreter is null - returning zero seizure probability");
return (0.0f);
}
interpreter.run(modelInput, modelOutput);
Log.d(TAG,"run - pSeizure="+modelOutput[0][1]);
return(modelOutput[0][1]);
Log.d(TAG, "run - pSeizure=" + modelOutput[0][1]);
return (modelOutput[0][1]);
}
public float getPseizure(SdData sdData) {
int i;
// First check that we have enough movement to analyse by comparing the acceleration standard deviation to a threshold.
double stdDev;
stdDev = calcRawDataStd(sdData);
if (stdDev < mSdThresh) {
Log.d(TAG, "getPseizure - acceleration stdev below movement threshold: std=" + stdDev + ", thresh=" + mSdThresh);
return (0);
}
float pSeizure;
switch (mModelId) {
case 1:
pSeizure = getPseizureFmt1(sdData);
break;
default:
Log.e(TAG, "getPSeizure - invalid model ID " + mModelId);
pSeizure = 0;
}
return (pSeizure);
}
private double calcRawDataStd(SdData sdData) {
/**
* Calculate the standard deviation in % of the rawData array in the SdData instance provided.
* It assumes that rawdata will contain 125 samples.
* Returns the standard deviation in %.
*/
// FIXME - assumes length of rawdata array is 125 data points
int j;
double sum = 0.0;
for (j = 0; j < 125; j++) { // FIXME - assumed length!
sum += sdData.rawData[j];
}
double mean = sum / 125;
double standardDeviation = 0.0;
for (j = 0; j < 125; j++) { // FIXME - assumed length!
standardDeviation += Math.pow(sdData.rawData[j] - mean, 2);
}
standardDeviation = Math.sqrt(standardDeviation / 125); // FIXME - assumed length!
// Convert standard deviation from milli-g to %
standardDeviation = 100. * standardDeviation / mean;
return (standardDeviation);
}
}

View File

@@ -36,7 +36,7 @@ import org.json.JSONArray;
public class SdData implements Parcelable {
private final static String TAG = "SdData";
private final static int N_RAW_DATA = 500; // 5 seconds at 100 Hz.
private final static int N_RAW_DATA = 125; // 5 seconds at 25 Hz.
// Seizure Detection Algorithm Selection
public boolean mOsdAlarmActive;
@@ -64,7 +64,12 @@ public class SdData implements Parcelable {
public long alarmTime;
public long alarmThresh;
public long alarmRatioThresh;
public long batteryPc;
public long batteryPc; // watch battery
public int phoneBatteryPc;
public CircBuf watchBattBuff = new CircBuf(24*3600/5, -1); // 24 hour buffer
public CircBuf phoneBattBuff = new CircBuf(24*3600/5, -1); // 24 hour buffer
public CircBuf watchSignalStrengthBuff = new CircBuf(4*3600/5, -1); // 4 hour buffer
/* Heart Rate Alarm Settings */
public boolean mHRAlarmActive = false;
@@ -79,6 +84,8 @@ public class SdData implements Parcelable {
/* Watch App Settings */
public String dataSourceName = "";
public String watchManuf = "";
public String watchSerNo = "";
public String watchPartNo = "";
public String watchFwVersion = "";
public String watchSdVersion = "";
@@ -104,7 +111,9 @@ public class SdData implements Parcelable {
/* Analysis results */
public Time dataTime = null;
public float timeDiff = 0f;
public long alarmState;
public String alarmCause = "";
public boolean alarmStanding = false;
public boolean fallAlarmStanding = false;
public long maxVal;
@@ -128,12 +137,15 @@ public class SdData implements Parcelable {
public double mO2Sat = 0;
public double mPseizure = 0.;
public float watchSignalStrength;
public SdData() {
simpleSpec = new int[10];
rawData = new double[N_RAW_DATA];
rawData3D = new double[N_RAW_DATA * 3];
dataTime = new Time(Time.getCurrentTimezone());
dataTime.setToNow();
timeDiff = 0f;
}
/*
@@ -151,6 +163,14 @@ public class SdData implements Parcelable {
//cal.setTime(sdf.parse(jo.optString("dataTimeStr")));
//dataTime = cal.getTime();
// FIXME - this doesn't work!!!
Time tnow = new Time();
tnow.setToNow();
if (dataTime != null) {
timeDiff = (tnow.toMillis(false)
- dataTime.toMillis(false)) / 1000f;
} else {
timeDiff = 0f;
}
dataTime.setToNow();
Log.v(TAG, "fromJSON(): dataTime = " + dataTime.toString());
maxVal = jo.optInt("maxVal");
@@ -158,6 +178,7 @@ public class SdData implements Parcelable {
specPower = jo.optInt("specPower");
roiPower = jo.optInt("roiPower");
batteryPc = jo.optInt("batteryPc");
watchBattBuff.add(batteryPc);
watchConnected = jo.optBoolean("watchConnected");
watchAppRunning = jo.optBoolean("watchAppRunning");
alarmState = jo.optInt("alarmState");
@@ -180,7 +201,7 @@ public class SdData implements Parcelable {
try {
mO2Sat = jo.optDouble("o2Sat");
} catch (Exception e) {
Log.w(TAG,"Error parsing o2Sat value");
Log.w(TAG, "Error parsing o2Sat value");
mO2Sat = -1;
}
haveData = true;
@@ -222,6 +243,7 @@ public class SdData implements Parcelable {
jsonObj.put("roiRatio", 10 * roiPower / specPower);
jsonObj.put("alarmState", alarmState);
jsonObj.put("alarmPhrase", alarmPhrase);
jsonObj.put("alarmCause", alarmCause);
jsonObj.put("hr", mHR);
jsonObj.put("adaptiveHrAv", mAdaptiveHrAverage);
jsonObj.put("averageHrAv", mAverageHrAverage);
@@ -246,7 +268,7 @@ public class SdData implements Parcelable {
jsonObj.put("rawData3D", raw3DArr);
retval = jsonObj.toString();
Log.v(TAG,"retval rawData="+retval);
Log.v(TAG, "retval rawData=" + retval);
} catch (Exception ex) {
Log.v(TAG, "Error Creating Data Object - " + ex.toString());
retval = "Error Creating Data Object - " + ex.toString();
@@ -269,8 +291,10 @@ public class SdData implements Parcelable {
jsonObj.put("dataTime", "00-00-00 00:00:00");
}
jsonObj.put("batteryPc", batteryPc);
jsonObj.put("phoneBatteryPc", phoneBatteryPc);
jsonObj.put("alarmState", alarmState);
jsonObj.put("alarmPhrase", alarmPhrase);
jsonObj.put("alarmCause", alarmCause);
jsonObj.put("sdMode", mSdMode);
jsonObj.put("sampleFreq", mSampleFreq);
jsonObj.put("analysisPeriod", analysisPeriod);
@@ -297,12 +321,15 @@ public class SdData implements Parcelable {
jsonObj.put("o2SatAlarmStanding", mO2SatAlarmStanding);
jsonObj.put("o2SatThreshMin", mO2SatThreshMin);
jsonObj.put("dataSourceName", dataSourceName);
Log.v(TAG,"phoneAppVersion="+phoneAppVersion);
Log.v(TAG, "phoneAppVersion=" + phoneAppVersion);
jsonObj.put("phoneAppVersion", phoneAppVersion);
jsonObj.put("watchManuf", watchManuf);
jsonObj.put("watchPartNo", watchPartNo);
jsonObj.put("watchSerNo", watchSerNo);
jsonObj.put("watchSdName", watchSdName);
jsonObj.put("watchFwVersion", watchFwVersion);
jsonObj.put("watchSdVersion", watchSdVersion);
jsonObj.put("watchSignalStrength", watchSignalStrength);
retval = jsonObj.toString();
} catch (Exception ex) {
@@ -330,11 +357,13 @@ public class SdData implements Parcelable {
jsonObj.put("specPower", specPower);
jsonObj.put("roiPower", roiPower);
jsonObj.put("batteryPc", batteryPc);
jsonObj.put("phoneBatteryPc", phoneBatteryPc);
jsonObj.put("watchConnected", watchConnected);
jsonObj.put("watchAppRunning", watchAppRunning);
jsonObj.put("haveSettings", haveSettings);
jsonObj.put("alarmState", alarmState);
jsonObj.put("alarmPhrase", alarmPhrase);
jsonObj.put("alarmCause", alarmCause);
jsonObj.put("sdMode", mSdMode);
jsonObj.put("sampleFreq", mSampleFreq);
jsonObj.put("analysisPeriod", analysisPeriod);

View File

@@ -26,8 +26,10 @@ package uk.org.openseizuredetector;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.format.Time;
@@ -93,7 +95,7 @@ public abstract class SdDataSource {
private short mFallWindow;
private int mMute; // !=0 means muted by keypress on watch.
private SdAlgNn mSdAlgNn;
private SdAlgHr mSdAlgHr;
protected SdAlgHr mSdAlgHr;
// Values for SD_MODE
private int SIMPLE_SPEC_FMAX = 10;
@@ -152,7 +154,6 @@ public abstract class SdDataSource {
}
// 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
@@ -286,6 +287,7 @@ public abstract class SdDataSource {
String watchFwVersion;
String sdVersion;
String sdName;
boolean have3dData = false;
JSONArray accelVals = null;
JSONArray accelVals3D = null;
Log.v(TAG, "updateFromJSON - " + jsonStr);
@@ -316,17 +318,6 @@ public abstract class SdDataSource {
// if we get 'null' HR (For example if the heart rate is not working)
mMute = 0;
}
accelVals = dataObject.getJSONArray("data");
Log.v(TAG, "Received " + accelVals.length() + " acceleration values, rawData Length is " + mSdData.rawData.length);
if (accelVals.length() > mSdData.rawData.length) {
mUtil.writeToSysLogFile("ERROR: Received " + accelVals.length() + " acceleration values, but rawData storage length is "
+ mSdData.rawData.length);
}
int i;
for (i = 0; i < accelVals.length(); i++) {
mSdData.rawData[i] = accelVals.getDouble(i);
}
mSdData.mNsamp = accelVals.length();
//Log.d(TAG,"accelVals[0]="+accelVals.getDouble(0)+", mSdData.rawData[0]="+mSdData.rawData[0]);
try {
accelVals3D = dataObject.getJSONArray("data3D");
@@ -335,16 +326,56 @@ public abstract class SdDataSource {
mUtil.writeToSysLogFile("ERROR: Received " + accelVals3D.length() + " 3D acceleration values, but rawData3D storage length is "
+ mSdData.rawData3D.length);
}
for (i = 0; i < accelVals3D.length(); i++) {
for (int i = 0; i < accelVals3D.length(); i++) {
mSdData.rawData3D[i] = accelVals3D.getDouble(i);
}
have3dData = true;
} catch (JSONException e) {
// If we get an error, just set rawData3D to zero
Log.i(TAG,"updateFromJSON - error parsing 3D data - setting it to zero");
for (i = 0; i < mSdData.rawData3D.length; i++) {
Log.i(TAG, "updateFromJSON - error parsing 3D data - setting it to zero");
for (int i = 0; i < mSdData.rawData3D.length; i++) {
mSdData.rawData3D[i] = 0.;
}
have3dData = false;
}
// Try to read the vector magnitude data from the JSON string.
try {
accelVals = dataObject.getJSONArray("data");
Log.v(TAG, "Received " + accelVals.length() + " acceleration values, rawData Length is " + mSdData.rawData.length);
if (accelVals.length() > mSdData.rawData.length) {
mUtil.writeToSysLogFile("ERROR: Received " + accelVals.length() + " acceleration values, but rawData storage length is "
+ mSdData.rawData.length);
}
int i;
for (i = 0; i < accelVals.length(); i++) {
mSdData.rawData[i] = accelVals.getDouble(i);
}
mSdData.mNsamp = accelVals.length();
} catch (JSONException e) {
// If we do not have vector magnitude data, calculate it from the 3d data.
if (have3dData) {
Log.i(TAG,"Deriving Vector Magnitudes from 3d accelerometer data");
int i;
for (i = 0; i < 125; i++) {
double x, y, z;
x = mSdData.rawData3D[i*3 + 0];
y = mSdData.rawData3D[i*3 + 1];
z = mSdData.rawData3D[i*3 + 2];
mSdData.rawData[i] = Math.sqrt(x*x + y*y + z*z);
}
mSdData.mNsamp = 125;
} else {
// If we do not have vector magnitude or 3d data, set the vector magnitude array to zero.
Log.e(TAG, "ERROR - no accelerometer data received - setting it to zero");
int i;
// FIXME - assumed fixed length of array!!
for (i = 0; i < 125; i++) {
mSdData.rawData[i] = 0.0;
}
mSdData.mNsamp = 125;
}
}
mWatchAppRunningCheck = true;
doAnalysis();
@@ -359,6 +390,7 @@ public abstract class SdDataSource {
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("SDDataSource.updateFromJSON - Settings Received");
mUtil.writeToSysLogFile(" * mSamplePeriod=" + mSamplePeriod + " mSampleFreq=" + mSampleFreq);
@@ -404,6 +436,19 @@ public abstract class SdDataSource {
return (retVal);
}
private int getPhoneBatteryLevel() {
/* Returns the current phone battery level in percent */
// Check phone battery level
int batPc;
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = mContext.registerReceiver(null, ifilter);
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
batPc = (int) (level * 100 / (float) scale);
Log.v(TAG, "SdDataSource.getPhoneBatteryLevel - Phone Bat = " + level + ", scale=" + scale + ", phoneBatteryPc=" + batPc);
return batPc;
}
/**
* Calculate the magnitude of entry i in the fft array fft
*
@@ -426,12 +471,16 @@ public abstract class SdDataSource {
int nMax = 0;
int nFreqCutoff = 0;
double[] fft = null;
// Update phone battery level - it is done here so it is called for all data sources.
mSdData.phoneBatteryPc = getPhoneBatteryLevel();
mSdData.phoneBattBuff.add(mSdData.phoneBatteryPc);
mSdData.watchBattBuff.add(mSdData.batteryPc);
try {
// 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);
Log.v(TAG,"doAnalysis(): rawData=" + Arrays.toString(mSdData.rawData));
Log.v(TAG, "doAnalysis(): rawData=" + Arrays.toString(mSdData.rawData));
// Set the frequency bounds for the analysis in fft output bin numbers.
nMin = (int) (mAlarmFreqMin / freqRes);
nMax = (int) (mAlarmFreqMax / freqRes);
@@ -487,6 +536,16 @@ public abstract class SdDataSource {
mDataStatusTime.setToNow();
mSdData.specPower = (long) specPower / ACCEL_SCALE_FACTOR;
mSdData.roiPower = (long) roiPower / ACCEL_SCALE_FACTOR;
Time tnow = new Time();
tnow.setToNow();
if (mSdData.dataTime != null) {
mSdData.timeDiff = (tnow.toMillis(false)
- mSdData.dataTime.toMillis(false)) / 1000f;
} else {
mSdData.timeDiff = 0f;
}
mSdData.dataTime.setToNow();
mSdData.dataTime.setToNow();
mSdData.maxVal = 0; // not used
mSdData.maxFreq = 0; // not used
@@ -508,11 +567,11 @@ public abstract class SdDataSource {
Log.e(TAG, "doAnalysis - Exception during Analysis");
mUtil.writeToSysLogFile("doAnalysis - Exception during analysis - " + e.toString());
mUtil.writeToSysLogFile("doAnalysis: Exception at Line Number: " + e.getCause().getStackTrace()[0].getLineNumber() + ", " + e.getCause().getStackTrace()[0].toString());
mUtil.writeToSysLogFile("doAnalysis: mSdData.mNsamp="+mSdData.mNsamp);
mUtil.writeToSysLogFile("doAnalysis: alarmFreqMin="+mAlarmFreqMin+" nMin="+nMin);
mUtil.writeToSysLogFile("doAnalysis: alarmFreqMax="+mAlarmFreqMax+" nMax="+nMax);
mUtil.writeToSysLogFile("doAnalysis: nFreqCutoff.="+nFreqCutoff);
mUtil.writeToSysLogFile("doAnalysis: fft.length="+fft.length);
mUtil.writeToSysLogFile("doAnalysis: mSdData.mNsamp=" + mSdData.mNsamp);
mUtil.writeToSysLogFile("doAnalysis: alarmFreqMin=" + mAlarmFreqMin + " nMin=" + nMin);
mUtil.writeToSysLogFile("doAnalysis: alarmFreqMax=" + mAlarmFreqMax + " nMax=" + nMax);
mUtil.writeToSysLogFile("doAnalysis: nFreqCutoff.=" + nFreqCutoff);
mUtil.writeToSysLogFile("doAnalysis: fft.length=" + fft.length);
mWatchAppRunningCheck = false;
}
@@ -523,12 +582,13 @@ public abstract class SdDataSource {
}
// Check this data to see if it represents an alarm state.
mSdData.alarmCause = "";
alarmCheck();
hrCheck();
o2SatCheck();
fallCheck();
muteCheck();
Log.v(TAG,"after fallCheck, mSdData.fallAlarmStanding="+mSdData.fallAlarmStanding);
Log.v(TAG, "after fallCheck, mSdData.fallAlarmStanding=" + mSdData.fallAlarmStanding);
mSdDataReceiver.onSdDataReceived(mSdData); // and tell SdServer we have received data.
}
@@ -545,18 +605,20 @@ public abstract class SdDataSource {
// Avoid potential divide by zero issue
if (mSdData.specPower == 0)
mSdData.specPower = 1;
Log.v(TAG, "alarmCheck() - roiPower="+mSdData.roiPower+" specPower="+ mSdData.specPower+" ratio="+10*mSdData.roiPower/ mSdData.specPower);
Log.v(TAG, "alarmCheck() - roiPower=" + mSdData.roiPower + " specPower=" + mSdData.specPower + " ratio=" + 10 * mSdData.roiPower / mSdData.specPower);
if (mSdData.mOsdAlarmActive) {
// Is the current set of data representing an alarm state?
if ((mSdData.roiPower > mAlarmThresh) && ((10 * mSdData.roiPower / mSdData.specPower) > mAlarmRatioThresh)) {
inAlarm = true;
mSdData.alarmCause = mSdData.alarmCause + "OsdAlg ";
}
}
if (mSdData.mCnnAlarmActive) {
if (mSdData.mPseizure > 0.5) {
inAlarm = true;
mSdData.alarmCause = mSdData.alarmCause + "CnnAlg ";
}
}
@@ -584,7 +646,10 @@ public abstract class SdDataSource {
}
}
Log.v(TAG, "alarmCheck(): inAlarm=" + inAlarm + ", alarmState = " + mSdData.alarmState + " alarmCount=" + mAlarmCount + " mWarnTime=" + mWarnTime+ " mAlarmTime=" + mAlarmTime);
Log.v(TAG, "alarmCheck(): inAlarm=" + inAlarm + ", alarmCause="
+ mSdData.alarmCause + ", alarmState = " + mSdData.alarmState
+ " alarmCount=" + mAlarmCount + " mWarnTime=" + mWarnTime
+ " mAlarmTime=" + mAlarmTime);
}
@@ -622,6 +687,8 @@ public abstract class SdDataSource {
mSdData.mHRAlarmStanding = true;
mSdData.mAdaptiveHrAlarmStanding = false;
mSdData.mAverageHrAlarmStanding = false;
mSdData.alarmCause = mSdData.alarmCause + "HrNull ";
} else {
Log.i(TAG, "Heart Rate Fault (HR<0)");
mSdData.mHRFaultStanding = true;
@@ -632,8 +699,14 @@ public abstract class SdDataSource {
} else {
mSdData.mHRFaultStanding = false;
mSdData.mHRAlarmStanding = checkResults.get(0);
if (mSdData.mHRAlarmStanding)
mSdData.alarmCause = mSdData.alarmCause + "HR ";
mSdData.mAdaptiveHrAlarmStanding = checkResults.get(1);
if (mSdData.mAdaptiveHrAlarmStanding)
mSdData.alarmCause = mSdData.alarmCause + "HR_ADAPT ";
mSdData.mAverageHrAlarmStanding = checkResults.get(2);
if (mSdData.mAverageHrAlarmStanding)
mSdData.alarmCause = mSdData.alarmCause + "HR_AVG ";
// Show an ALARM state if any of the HR alarms is standing.
if (mSdData.mHRAlarmStanding | mSdData.mAdaptiveHrAlarmStanding | mSdData.mAverageHrAlarmStanding) {
mSdData.alarmState = 2;
@@ -661,15 +734,17 @@ public abstract class SdDataSource {
Log.i(TAG, "Oxygen Saturation Null - Alarming");
mSdData.mO2SatFaultStanding = false;
mSdData.mO2SatAlarmStanding = true;
mSdData.alarmCause = mSdData.alarmCause + "O2_NULL ";
} else {
Log.i(TAG, "Oxygen Saturation Fault (O2Sat<0)");
mSdData.mO2SatFaultStanding = true;
mSdData.mO2SatAlarmStanding = false;
}
} else if (mSdData.mO2Sat < mSdData.mO2SatThreshMin) {
} else if (mSdData.mO2Sat < mSdData.mO2SatThreshMin) {
Log.i(TAG, "Oxygen Saturation Abnormal - " + mSdData.mO2Sat + " %");
mSdData.mO2SatFaultStanding = false;
mSdData.mO2SatAlarmStanding = true;
mSdData.alarmCause = mSdData.alarmCause + "O2SAT ";
} else {
mSdData.mO2SatFaultStanding = false;
mSdData.mO2SatAlarmStanding = false;
@@ -706,11 +781,12 @@ public abstract class SdDataSource {
if (mSdData.rawData[i + j] < minAcc) minAcc = mSdData.rawData[i + j];
if (mSdData.rawData[i + j] > maxAcc) maxAcc = mSdData.rawData[i + j];
}
Log.d(TAG, "check_fall() - minAcc=" + minAcc +" (mFallThreshMin="+mFallThreshMin+ "), maxAcc=" + maxAcc+" (mFallThreshMax="+mFallThreshMax+")") ;
Log.d(TAG, "check_fall() - minAcc=" + minAcc + " (mFallThreshMin=" + mFallThreshMin + "), maxAcc=" + maxAcc + " (mFallThreshMax=" + mFallThreshMax + ")");
if ((minAcc < mFallThreshMin) && (maxAcc > mFallThreshMax)) {
Log.d(TAG, "check_fall() ****FALL DETECTED***** minAcc=" + minAcc + ", maxAcc=" + maxAcc);
Log.d(TAG, "check_fall() - ****FALL DETECTED****");
mSdData.fallAlarmStanding = true;
mSdData.alarmCause = mSdData.alarmCause + "FALL ";
return;
}
if (mMute != 0) {
@@ -819,14 +895,13 @@ public abstract class SdDataSource {
Log.v(TAG, "getStatus() - no settings received yet");
}
} catch(Exception e) {
Log.e(TAG,"getStatus - Exception: "+e.toString());
Log.e(TAG,e.getMessage());
mSdData.watchAppRunning = false;
mSdData.roiPower = -1;
mSdData.specPower = -1;
mSdDataReceiver.onSdDataFault(mSdData);
Log.e(TAG,"getStatus - Exception: "+e.toString());
Log.e(TAG,e.getMessage());
mSdData.watchAppRunning = false;
mSdData.roiPower = -1;
mSdData.specPower = -1;
mSdDataReceiver.onSdDataFault(mSdData);
}
}
/**
@@ -863,22 +938,28 @@ public abstract class SdDataSource {
}
}
} catch(Exception e) {
Log.e(TAG,"faultCheck - Exception: "+e.toString());
Log.e(TAG,e.getMessage());
mSdData.watchAppRunning = false;
mSdData.roiPower = -1;
mSdData.specPower = -1;
mSdDataReceiver.onSdDataFault(mSdData);
}
Log.e(TAG,"faultCheck - Exception: "+e.toString());
Log.e(TAG,e.getMessage());
mSdData.watchAppRunning = false;
mSdData.roiPower = -1;
mSdData.specPower = -1;
mSdDataReceiver.onSdDataFault(mSdData);
}
}
void nnAnalysis() {
//Check the current set of data using the neural network model to look for alarms.
Log.d(TAG,"nnAnalysis");
Log.d(TAG, "nnAnalysis");
if (mSdData.mCnnAlarmActive) {
float pSeizure = mSdAlgNn.getPseizure(mSdData);
Log.d(TAG, "nnAnalysis - nnResult=" + pSeizure);
mSdData.mPseizure = pSeizure;
try {
float pSeizure = mSdAlgNn.getPseizure(mSdData);
Log.d(TAG, "nnAnalysis - nnResult=" + pSeizure);
mSdData.mPseizure = pSeizure;
} catch(Exception e) {
Log.e(TAG,"nnAnalysis - Error running Analysis - "+e.getMessage());
}
} else {
Log.d(TAG, "nnAnalysis - mCnAlarmActive is false - not analysing");
mSdData.mPseizure = 0;
@@ -889,9 +970,9 @@ public abstract class SdDataSource {
* Read a preference value, and return it as a double.
* FIXME - this should be in osdUtil so other classes can use it.
*
* @param SP - Shared Preferences object
* @param SP - Shared Preferences object
* @param prefName - name of preference to read.
* @param defVal - default value if it is not stored.
* @param defVal - default value if it is not stored.
* @return double value of the stored specified preference, or the default value.
*/
private double readDoublePref(SharedPreferences SP, String prefName, String defVal) {
@@ -922,10 +1003,10 @@ public abstract class SdDataSource {
String appRestartTimeoutStr = SP.getString("AppRestartTimeout", "10");
mAppRestartTimeout = Integer.parseInt(appRestartTimeoutStr);
Log.v(TAG, "updatePrefs() - mAppRestartTimeout = " + mAppRestartTimeout);
mUtil.writeToSysLogFile( "updatePrefs() - mAppRestartTimeout = " + mAppRestartTimeout);
mUtil.writeToSysLogFile("updatePrefs() - mAppRestartTimeout = " + mAppRestartTimeout);
} catch (Exception ex) {
Log.v(TAG, "updatePrefs() - Problem with AppRestartTimeout preference!");
mUtil.writeToSysLogFile( "updatePrefs() - Problem with AppRestartTimeout preference!");
mUtil.writeToSysLogFile("updatePrefs() - Problem with AppRestartTimeout preference!");
Toast toast = Toast.makeText(mContext, "Problem Parsing AppRestartTimeout Preference", Toast.LENGTH_SHORT);
toast.show();
}
@@ -935,10 +1016,10 @@ public abstract class SdDataSource {
String faultTimerPeriodStr = SP.getString("FaultTimerPeriod", "30");
mFaultTimerPeriod = Integer.parseInt(faultTimerPeriodStr);
Log.v(TAG, "updatePrefs() - mFaultTimerPeriod = " + mFaultTimerPeriod);
mUtil.writeToSysLogFile( "updatePrefs() - mFaultTimerPeriod = " + mFaultTimerPeriod);
mUtil.writeToSysLogFile("updatePrefs() - mFaultTimerPeriod = " + mFaultTimerPeriod);
} catch (Exception ex) {
Log.v(TAG, "updatePrefs() - Problem with FaultTimerPeriod preference!");
mUtil.writeToSysLogFile( "updatePrefs() - Problem with FaultTimerPeriod preference!");
mUtil.writeToSysLogFile("updatePrefs() - Problem with FaultTimerPeriod preference!");
Toast toast = Toast.makeText(mContext, "Problem Parsing FaultTimerPeriod Preference", Toast.LENGTH_SHORT);
toast.show();
}
@@ -949,7 +1030,7 @@ public abstract class SdDataSource {
mFidgetPeriod = readDoublePref(SP, "FidgetDetectorPeriod", "20"); // minutes
Log.v(TAG, "updatePrefs() - mFidgetPeriod = " + mFidgetPeriod);
mFidgetThreshold = readDoublePref(SP, "FidgetDetectorThreshold", "0.6 ");
Log.d(TAG,"updatePrefs(): mFidgetThreshold="+mFidgetThreshold);
Log.d(TAG, "updatePrefs(): mFidgetThreshold=" + mFidgetThreshold);
} catch (Exception ex) {
Log.v(TAG, "updatePrefs() - Problem with FidgetDetector preferences!");
@@ -963,57 +1044,57 @@ public abstract class SdDataSource {
prefStr = SP.getString("BLE_Device_Addr", "SET_FROM_XML");
mBleDeviceAddr = prefStr;
Log.v(TAG, "mBLEDeviceAddr=" + mBleDeviceAddr);
mUtil.writeToSysLogFile( "mBLEDeviceAddr=" + mBleDeviceAddr);
mUtil.writeToSysLogFile("mBLEDeviceAddr=" + mBleDeviceAddr);
prefStr = SP.getString("BLE_Device_Name", "SET_FROM_XML");
mBleDeviceName = prefStr;
Log.v(TAG, "mBLEDeviceName=" + mBleDeviceName);
mUtil.writeToSysLogFile( "mBLEDeviceName=" + mBleDeviceName);
mUtil.writeToSysLogFile("mBLEDeviceName=" + mBleDeviceName);
prefStr = SP.getString("PebbleDebug", "SET_FROM_XML");
if (prefStr != null) {
mDebug = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() Debug = " + mDebug);
mUtil.writeToSysLogFile( "updatePrefs() Debug = " + mDebug);
mUtil.writeToSysLogFile("updatePrefs() Debug = " + mDebug);
prefStr = SP.getString("PebbleDisplaySpectrum", "SET_FROM_XML");
mDisplaySpectrum = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() DisplaySpectrum = " + mDisplaySpectrum);
mUtil.writeToSysLogFile( "updatePrefs() DisplaySpectrum = " + mDisplaySpectrum);
mUtil.writeToSysLogFile("updatePrefs() DisplaySpectrum = " + mDisplaySpectrum);
prefStr = SP.getString("PebbleUpdatePeriod", "SET_FROM_XML");
mDataUpdatePeriod = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() DataUpdatePeriod = " + mDataUpdatePeriod);
mUtil.writeToSysLogFile( "updatePrefs() DataUpdatePeriod = " + mDataUpdatePeriod);
mUtil.writeToSysLogFile("updatePrefs() DataUpdatePeriod = " + mDataUpdatePeriod);
prefStr = SP.getString("MutePeriod", "SET_FROM_XML");
mMutePeriod = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() MutePeriod = " + mMutePeriod);
mUtil.writeToSysLogFile( "updatePrefs() MutePeriod = " + mMutePeriod);
mUtil.writeToSysLogFile("updatePrefs() MutePeriod = " + mMutePeriod);
prefStr = SP.getString("ManAlarmPeriod", "SET_FROM_XML");
mManAlarmPeriod = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() ManAlarmPeriod = " + mManAlarmPeriod);
mUtil.writeToSysLogFile( "updatePrefs() ManAlarmPeriod = " + mManAlarmPeriod);
mUtil.writeToSysLogFile("updatePrefs() ManAlarmPeriod = " + mManAlarmPeriod);
prefStr = SP.getString("PebbleSdMode", "SET_FROM_XML");
mPebbleSdMode = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() PebbleSdMode = " + mPebbleSdMode);
mUtil.writeToSysLogFile( "updatePrefs() PebbleSdMode = " + mPebbleSdMode);
mUtil.writeToSysLogFile("updatePrefs() PebbleSdMode = " + mPebbleSdMode);
prefStr = SP.getString("SampleFreq", "SET_FROM_XML");
mSampleFreq = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() SampleFreq = " + mSampleFreq);
mUtil.writeToSysLogFile( "updatePrefs() SampleFreq = " + mSampleFreq);
mUtil.writeToSysLogFile("updatePrefs() SampleFreq = " + mSampleFreq);
prefStr = SP.getString("SamplePeriod", "SET_FROM_XML");
mSamplePeriod = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() AnalysisPeriod = " + mSamplePeriod);
mUtil.writeToSysLogFile( "updatePrefs() AnalysisPeriod = " + mSamplePeriod);
mUtil.writeToSysLogFile("updatePrefs() AnalysisPeriod = " + mSamplePeriod);
prefStr = SP.getString("AlarmFreqMin", "SET_FROM_XML");
mAlarmFreqMin = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() AlarmFreqMin = " + mAlarmFreqMin);
mUtil.writeToSysLogFile( "updatePrefs() AlarmFreqMin = " + mAlarmFreqMin);
mUtil.writeToSysLogFile("updatePrefs() AlarmFreqMin = " + mAlarmFreqMin);
prefStr = SP.getString("AlarmFreqMax", "SET_FROM_XML");
mAlarmFreqMax = (short) Integer.parseInt(prefStr);
@@ -1023,58 +1104,58 @@ public abstract class SdDataSource {
prefStr = SP.getString("WarnTime", "SET_FROM_XML");
mWarnTime = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() WarnTime = " + mWarnTime);
mUtil.writeToSysLogFile( "updatePrefs() WarnTime = " + mWarnTime);
mUtil.writeToSysLogFile("updatePrefs() WarnTime = " + mWarnTime);
prefStr = SP.getString("AlarmTime", "SET_FROM_XML");
mAlarmTime = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() AlarmTime = " + mAlarmTime);
mUtil.writeToSysLogFile( "updatePrefs() AlarmTime = " + mAlarmTime);
mUtil.writeToSysLogFile("updatePrefs() AlarmTime = " + mAlarmTime);
prefStr = SP.getString("AlarmThresh", "SET_FROM_XML");
mAlarmThresh = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() AlarmThresh = " + mAlarmThresh);
mUtil.writeToSysLogFile( "updatePrefs() AlarmThresh = " + mAlarmThresh);
mUtil.writeToSysLogFile("updatePrefs() AlarmThresh = " + mAlarmThresh);
prefStr = SP.getString("AlarmRatioThresh", "SET_FROM_XML");
mAlarmRatioThresh = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() AlarmRatioThresh = " + mAlarmRatioThresh);
mUtil.writeToSysLogFile( "updatePrefs() AlarmRatioThresh = " + mAlarmRatioThresh);
mUtil.writeToSysLogFile("updatePrefs() AlarmRatioThresh = " + mAlarmRatioThresh);
mFallActive = SP.getBoolean("FallActive", false);
Log.v(TAG, "updatePrefs() FallActive = " + mFallActive);
mUtil.writeToSysLogFile( "updatePrefs() FallActive = " + mFallActive);
mUtil.writeToSysLogFile("updatePrefs() FallActive = " + mFallActive);
prefStr = SP.getString("FallThreshMin", "SET_FROM_XML");
mFallThreshMin = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() FallThreshMin = " + mFallThreshMin);
mUtil.writeToSysLogFile( "updatePrefs() FallThreshMin = " + mFallThreshMin);
mUtil.writeToSysLogFile("updatePrefs() FallThreshMin = " + mFallThreshMin);
prefStr = SP.getString("FallThreshMax", "SET_FROM_XML");
mFallThreshMax = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() FallThreshMax = " + mFallThreshMax);
mUtil.writeToSysLogFile( "updatePrefs() FallThreshMax = " + mFallThreshMax);
mUtil.writeToSysLogFile("updatePrefs() FallThreshMax = " + mFallThreshMax);
prefStr = SP.getString("FallWindow", "SET_FROM_XML");
mFallWindow = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() FallWindow = " + mFallWindow);
mUtil.writeToSysLogFile( "updatePrefs() FallWindow = " + mFallWindow);
mUtil.writeToSysLogFile("updatePrefs() FallWindow = " + mFallWindow);
mSdData.mOsdAlarmActive = SP.getBoolean("OsdAlarmActive", false);
Log.v(TAG, "updatePrefs() OsdAlarmActive = " + mSdData.mOsdAlarmActive);
mUtil.writeToSysLogFile( "updatePrefs() OsdAlarmActive = " + mSdData.mOsdAlarmActive);
mUtil.writeToSysLogFile("updatePrefs() OsdAlarmActive = " + mSdData.mOsdAlarmActive);
mSdData.mCnnAlarmActive = SP.getBoolean("CnnAlarmActive", false);
Log.v(TAG, "updatePrefs() CnnAlarmActive = " + mSdData.mCnnAlarmActive);
mUtil.writeToSysLogFile( "updatePrefs() CnnAlarmActive = " + mSdData.mCnnAlarmActive);
mUtil.writeToSysLogFile("updatePrefs() CnnAlarmActive = " + mSdData.mCnnAlarmActive);
mSdData.mHRAlarmActive = SP.getBoolean("HRAlarmActive", false);
Log.v(TAG, "updatePrefs() HRAlarmActive = " + mSdData.mHRAlarmActive);
mUtil.writeToSysLogFile( "updatePrefs() HRAlarmActive = " + mSdData.mHRAlarmActive);
mUtil.writeToSysLogFile("updatePrefs() HRAlarmActive = " + mSdData.mHRAlarmActive);
mSdData.mHRNullAsAlarm = SP.getBoolean("HRNullAsAlarm", false);
Log.v(TAG, "updatePrefs() HRNullAsAlarm = " + mSdData.mHRNullAsAlarm);
mUtil.writeToSysLogFile( "updatePrefs() HRNullAsAlarm = " + mSdData.mHRNullAsAlarm);
mUtil.writeToSysLogFile("updatePrefs() HRNullAsAlarm = " + mSdData.mHRNullAsAlarm);
mHrFrozenAlarm = SP.getBoolean("HrFrozenAlarm", true);
Log.v(TAG, "updatePrefs() - mHrFrozenAlarm = " + mHrFrozenAlarm);
@@ -1083,42 +1164,42 @@ public abstract class SdDataSource {
prefStr = SP.getString("HRThreshMin", "SET_FROM_XML");
mSdData.mHRThreshMin = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() HRThreshMin = " + mSdData.mHRThreshMin);
mUtil.writeToSysLogFile( "updatePrefs() HRThreshMin = " + mSdData.mHRThreshMin);
mUtil.writeToSysLogFile("updatePrefs() HRThreshMin = " + mSdData.mHRThreshMin);
prefStr = SP.getString("HRThreshMax", "SET_FROM_XML");
mSdData.mHRThreshMax = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() HRThreshMax = " + mSdData.mHRThreshMax);
mUtil.writeToSysLogFile( "updatePrefs() HRThreshMax = " + mSdData.mHRThreshMax);
mUtil.writeToSysLogFile("updatePrefs() HRThreshMax = " + mSdData.mHRThreshMax);
mSdData.mAdaptiveHrAlarmActive = SP.getBoolean("HRAdaptiveAlarmActive", false);
mSdData.mAdaptiveHrAlarmWindowSecs = readDoublePref(SP, "HRAdaptiveAlarmWindowSecs", "30");
mSdData.mAdaptiveHrAlarmThresh = readDoublePref(SP, "HRAdaptiveAlarmThresh", "20");
Log.d(TAG,"updatePrefs(): mAdaptiveHrAlarmActive="+mSdData.mAdaptiveHrAlarmActive);
Log.d(TAG,"updatePrefs(): mAdaptiveHrWindowSecs="+mSdData.mAdaptiveHrAlarmWindowSecs);
Log.d(TAG,"updatePrefs(): mAdaptiveHrAlarmThresh="+mSdData.mAdaptiveHrAlarmThresh);
Log.d(TAG, "updatePrefs(): mAdaptiveHrAlarmActive=" + mSdData.mAdaptiveHrAlarmActive);
Log.d(TAG, "updatePrefs(): mAdaptiveHrWindowSecs=" + mSdData.mAdaptiveHrAlarmWindowSecs);
Log.d(TAG, "updatePrefs(): mAdaptiveHrAlarmThresh=" + mSdData.mAdaptiveHrAlarmThresh);
mSdData.mAverageHrAlarmActive = SP.getBoolean("HRAverageAlarmActive", false);
mSdData.mAverageHrAlarmWindowSecs = readDoublePref(SP, "HRAverageAlarmWindowSecs", "120");
mSdData.mAverageHrAlarmThreshMin = readDoublePref(SP, "HRAverageAlarmThreshMin", "40");
mSdData.mAverageHrAlarmThreshMax = readDoublePref(SP, "HRAverageAlarmThreshMax", "120");
Log.d(TAG,"updatePrefs(): mAverageHrAlarmActive="+mSdData.mAverageHrAlarmActive);
Log.d(TAG,"updatePrefs(): mAverageHrAlarmWindowSecs="+mSdData.mAverageHrAlarmWindowSecs);
Log.d(TAG,"updatePrefs(): mAverageHrAlarmThreshMin="+mSdData.mAverageHrAlarmThreshMin);
Log.d(TAG,"updatePrefs(): mAverageHrAlarmThreshMax="+mSdData.mAverageHrAlarmThreshMax);
Log.d(TAG, "updatePrefs(): mAverageHrAlarmActive=" + mSdData.mAverageHrAlarmActive);
Log.d(TAG, "updatePrefs(): mAverageHrAlarmWindowSecs=" + mSdData.mAverageHrAlarmWindowSecs);
Log.d(TAG, "updatePrefs(): mAverageHrAlarmThreshMin=" + mSdData.mAverageHrAlarmThreshMin);
Log.d(TAG, "updatePrefs(): mAverageHrAlarmThreshMax=" + mSdData.mAverageHrAlarmThreshMax);
mSdData.mO2SatAlarmActive = SP.getBoolean("O2SatAlarmActive", false);
Log.v(TAG, "updatePrefs() O2SatAlarmActive = " + mSdData.mO2SatAlarmActive);
mUtil.writeToSysLogFile( "updatePrefs() O2SatAlarmActive = " + mSdData.mO2SatAlarmActive);
mUtil.writeToSysLogFile("updatePrefs() O2SatAlarmActive = " + mSdData.mO2SatAlarmActive);
mSdData.mO2SatNullAsAlarm = SP.getBoolean("O2SatNullAsAlarm", false);
Log.v(TAG, "updatePrefs() O2SatNullAsAlarm = " + mSdData.mO2SatNullAsAlarm);
mUtil.writeToSysLogFile( "updatePrefs() O2SatNullAsAlarm = " + mSdData.mO2SatNullAsAlarm);
mUtil.writeToSysLogFile("updatePrefs() O2SatNullAsAlarm = " + mSdData.mO2SatNullAsAlarm);
prefStr = SP.getString("O2SatThreshMin", "SET_FROM_XML");
mSdData.mO2SatThreshMin = (short) Integer.parseInt(prefStr);
Log.v(TAG, "updatePrefs() O2SatThreshMin = " + mSdData.mO2SatThreshMin);
mUtil.writeToSysLogFile( "updatePrefs() O2SatThreshMin = " + mSdData.mO2SatThreshMin);
mUtil.writeToSysLogFile("updatePrefs() O2SatThreshMin = " + mSdData.mO2SatThreshMin);
} else {
Log.v(TAG, "updatePrefs() - prefStr is null - WHY????");

File diff suppressed because one or more lines are too long

View File

@@ -23,6 +23,7 @@
*/
package uk.org.openseizuredetector;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
@@ -34,19 +35,29 @@ import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.format.Time;
import android.util.Log;
import androidx.core.app.ActivityCompat;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import co.beeline.android.bluetooth.currenttimeservice.CurrentTimeService;
/**
* A data source that registers for BLE GATT notifications from a device and
@@ -63,12 +74,15 @@ public class SdDataSourceBLE extends SdDataSource {
private int nRawData = 0;
private double[] rawData = new double[MAX_RAW_DATA];
private double[] rawData3d = new double[MAX_RAW_DATA * 3];
private int mAccFmt = 0;
private boolean waitForDescriptorWrite = false;
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 =
@@ -79,30 +93,42 @@ public class SdDataSourceBLE extends SdDataSource {
"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 = "000085e9-0000-1000-8000-00805f9b34fb";
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 = "000085ea-0000-1000-8000-00805f9b34fb";
public static String CHAR_OSD_BATT_DATA = "000085eb-0000-1000-8000-00805f9b34fb";
public final static UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(CHAR_HEART_RATE_MEASUREMENT);
public static String SERV_OSD = "000085e9-0000-1000-8000-00805f9b34fb";
public static String CHAR_OSD_ACC_DATA = "000085e9-0001-1000-8000-00805f9b34fb";
public static String CHAR_OSD_BATT_DATA = "000085e9-0002-1000-8000-00805f9b34fb";
public static String CHAR_OSD_WATCH_ID = "000085e9-0003-1000-8000-00805f9b34fb";
public static String CHAR_OSD_WATCH_FW = "000085e9-0004-1000-8000-00805f9b34fb";
public static String CHAR_OSD_ACC_FMT = "000085e9-0005-1000-8000-00805f9b34fb";
// Valid values are 0: 8 bit vector magnitude scaled so 1g=44
public final static int ACC_FMT_8BIT = 0;
public final static int ACC_FMT_16BIT = 1;
public final static int ACC_FMT_3D = 3;
public static String CHAR_OSD_STATUS = "000085e9-0006-1000-8000-00805f9b34fb";
public static String SERV_INFINITIME_MOTION = "00030000-78fc-48fe-8e23-433b3a1942d0";
public static String CHAR_INFINITIME_ACC_DATA = "00030002-78fc-48fe-8e23-433b3a1942d0";
public static String CHAR_INFINITIME_OSD_STATUS = "00030078-78fc-48fe-8e23-433b3a1942d0";
public static String CHAR_BATT_DATA = "00002a19-0000-1000-8000-00805f9b34fb";
public static String SERV_BATT = "0000180f-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";
private BluetoothGatt mGatt;
private BluetoothGattCharacteristic mBattChar;
private BluetoothGattCharacteristic mOsdChar;
private BluetoothGattCharacteristic mStatusChar;
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);
}
@@ -111,8 +137,8 @@ public class SdDataSourceBLE extends SdDataSource {
* make sure any changes to preferences are taken into account.
*/
public void start() {
Log.i(TAG, "start()");
super.start();
Log.i(TAG, "start() - mBleDeviceAddr="+mBleDeviceAddr);
mUtil.writeToSysLogFile("SdDataSourceBLE.start() - mBleDeviceAddr=" + mBleDeviceAddr);
if (mBleDeviceAddr == "" || mBleDeviceAddr == null) {
@@ -120,7 +146,16 @@ public class SdDataSourceBLE extends SdDataSource {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
// Note, these values are set in BleScanActivity and written to shared preferences, which
// ae read in SdDataSource.java
// FIXME: Read the shared preferences in this class so SdDataSource does not need to know
// FIXME: about BLE details.
Log.i(TAG, "mBLEDevice is " + mBleDeviceName + ", Addr=" + mBleDeviceAddr);
mSdData.watchSdName = mBleDeviceName;
mSdData.watchPartNo = mBleDeviceAddr;
boolean success = CurrentTimeService.startServer(mContext);
bleConnect();
@@ -154,7 +189,7 @@ public class SdDataSourceBLE extends SdDataSource {
try {
device = mBluetoothAdapter.getRemoteDevice(mBleDeviceAddr);
} catch (Exception e) {
Log.w(TAG, "bleConnect(): Error connecting to device address "+mBleDeviceAddr+".");
Log.w(TAG, "bleConnect(): Error connecting to device address " + mBleDeviceAddr + ".");
device = null;
}
if (device == null) {
@@ -200,6 +235,7 @@ public class SdDataSourceBLE extends SdDataSource {
mUtil.writeToSysLogFile("SDDataSourceBLE.stop()");
bleDisconnect();
CurrentTimeService.stopServer();
super.stop();
}
@@ -219,14 +255,16 @@ public class SdDataSourceBLE extends SdDataSource {
} 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);
Log.i(TAG, "onConnectionStateChange(): Disconnected from GATT server");
/**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);
*/
}
}
@@ -258,14 +296,53 @@ public class SdDataSourceBLE extends SdDataSource {
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");
Log.i(TAG, "Subscribing to Acceleration Data Change Notifications");
mOsdChar = gattCharacteristic;
setCharacteristicNotification(gattCharacteristic,true);
setCharacteristicNotification(gattCharacteristic, true);
} else if (charUuidStr.equals(CHAR_OSD_STATUS)) {
Log.i(TAG, "Found OSD Status Characteristic");
mStatusChar = gattCharacteristic;
} else if (charUuidStr.equals(CHAR_OSD_BATT_DATA)) {
Log.i(TAG, "Subscribing to battery change Notifications");
executeReadCharacteristic(gattCharacteristic);
setCharacteristicNotification(gattCharacteristic, true);
executeReadCharacteristic(gattCharacteristic);
} else if (charUuidStr.equals(CHAR_OSD_WATCH_ID)) {
Log.i(TAG, "Reading Watch ID");
executeReadCharacteristic(gattCharacteristic);
} else if (charUuidStr.equals(CHAR_OSD_WATCH_FW)) {
Log.i(TAG, "Reading Watch Firmware Version");
executeReadCharacteristic(gattCharacteristic);
} else if (charUuidStr.equals(CHAR_OSD_ACC_FMT)) {
Log.i(TAG, "Reading Acceleration format code");
executeReadCharacteristic(gattCharacteristic);
}
else if (charUuidStr.equals(CHAR_OSD_BATT_DATA)) {
Log.v(TAG,"Saving battery characteristic for later");
Log.v(TAG, "Subscribing to battery change Notifications");
setCharacteristicNotification(gattCharacteristic,true);
}
} else if (uuidStr.equals(SERV_INFINITIME_MOTION)) {
Log.v(TAG, "Infinitime Motion Service Discovered");
foundOsdService = true;
for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
String charUuidStr = gattCharacteristic.getUuid().toString();
if (charUuidStr.equals(CHAR_INFINITIME_ACC_DATA)) {
Log.i(TAG, "Subscribing to Infinitime Acceleration Data Change Notifications");
mOsdChar = gattCharacteristic;
mAccFmt = ACC_FMT_3D; // Infinitime presents x, y, z data
setCharacteristicNotification(gattCharacteristic, true);
} else if (charUuidStr.equals(CHAR_INFINITIME_OSD_STATUS)) {
Log.i(TAG, "Found Infinitime OSD Status Characteristic");
mStatusChar = gattCharacteristic;
}
}
} else if (uuidStr.equals(SERV_BATT)) {
Log.v(TAG, "Battery Data Service Service Discovered");
for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
String charUuidStr = gattCharacteristic.getUuid().toString();
Log.i(TAG, "batt char=" + charUuidStr);
if (charUuidStr.equals(CHAR_BATT_DATA)) {
Log.i(TAG, "Subscribing to Battery Data Change Notifications");
setCharacteristicNotification(gattCharacteristic, true);
Log.i(TAG, "Reading battery level");
executeReadCharacteristic(gattCharacteristic);
}
}
}
@@ -273,7 +350,7 @@ public class SdDataSourceBLE extends SdDataSource {
if (foundOsdService) {
mGatt = gatt;
} else {
Log.v(TAG, "device is not offering the OSD Gatt Service - re-trying connection");
Log.i(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() {
@@ -287,67 +364,258 @@ public class SdDataSourceBLE extends SdDataSource {
}
}
public void onDataReceived(BluetoothGattCharacteristic characteristic) {
Log.v(TAG, "onDataReceived uuid" + characteristic.getUuid().toString());
// FIXME - collect data until we have enough to do analysis, then use onDataReceived to process it.
//Log.v(TAG,"onDataReceived: Characteristic="+characteristic.getUuid().toString());
/**
* executeReadCharacteristic runs the bluetoothGatt readCharacteristic command to read the value
* of a given characteristic.
* Because only one BLE operation can be taking place at a time, it may fail, in which case
* the read is re-tried after a 100ms delay.
* @param gattCharacteristic - the characteristic to be read.
*/
private void executeReadCharacteristic(BluetoothGattCharacteristic gattCharacteristic) {
if (gattCharacteristic == null) {
Log.i(TAG, "ExecuteReadCharacteristic() - gatCharacteristic is null, so not doing anything");
mUtil.showToast("ERROR: gatCharacteristic is null - this should not happen");
mSdDataReceiver.onSdDataFault(mSdData);
return;
}
if (mBluetoothGatt == null) {
Log.e(TAG, "executeReadCharacteristic() - mBluetoothGatt is null - Characteristic=" + gattCharacteristic.getUuid().toString());
mUtil.showToast("ERROR: mGatCharacteristic is null - this should not happen");
mSdDataReceiver.onSdDataFault(mSdData);
return;
}
// To get here both gatCharacteristic and mBluetoothGatt must be non-null
boolean retVal = mBluetoothGatt.readCharacteristic(gattCharacteristic);
if (retVal) {
Log.d(TAG, "executeReadCharacteristic - read initiated successfully");
} else {
Log.d(TAG, "executeReadCharacteristic - read initiation failed - waiting, then re-trying");
mHandler.postDelayed(new Runnable() {
public void run() {
Log.w(TAG, "Executing delayed read of characteristic");
executeReadCharacteristic(gattCharacteristic);
}
}, 100);
}
}
/**
* executeWriteCharacteristic runs the bluetoothGatt writeCharacteristic command to sent the value
* of a given characteristic.
* Because only one BLE operation can be taking place at a time, it may fail, in which case
* the read is re-tried after a 100ms delay.
*
* @param gattCharacteristic - the characteristic to be read.
* @param valBytes[] - array of bytes to send
* @param nBytes - number of bytes to send.
*/
private void executeWriteCharacteristic(BluetoothGattCharacteristic gattCharacteristic, byte[] valBytes) {
if (gattCharacteristic == null) {
Log.i(TAG, "ExecuteWriteCharacteristic() - gatCharacteristic is null, so not doing anything");
mUtil.showToast("ERROR: gatCharacteristic is null - this should not happen");
mSdDataReceiver.onSdDataFault(mSdData);
return;
}
if (mBluetoothGatt == null) {
Log.e(TAG, "executeWriteCharacteristic() - mBluetoothGatt is null - Characteristic=" + gattCharacteristic.getUuid().toString());
mUtil.showToast("ERROR: mGatCharacteristic is null - this should not happen");
mSdDataReceiver.onSdDataFault(mSdData);
return;
}
// To get here both gatCharacteristic and mBluetoothGatt must be non-null
gattCharacteristic.setValue(valBytes);
boolean retVal = mBluetoothGatt.writeCharacteristic(gattCharacteristic);
if (retVal) {
Log.d(TAG, "executeWriteCharacteristic - write initiated successfully");
} else {
Log.d(TAG, "executeWriteCharacteristic - write initiation failed - waiting, then re-trying");
mHandler.postDelayed(new Runnable() {
public void run() {
Log.w(TAG, "Executing delayed write of characteristic");
executeWriteCharacteristic(gattCharacteristic, valBytes);
}
}, 100);
}
}
private boolean permissionsOK() {
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
Log.e(TAG, "permissionsOK() - Bluetooth Permmission Not Granted");
mUtil.showToast("ERROR - Bluetooth Permission not Granted");
return (false);
} else {
return (true);
}
}
public void onDataReceived(BluetoothGattCharacteristic characteristic) {
/*
* onDataReceived - called whenever a BLE characteristic notifies us that its data has changed.
* If the data is acceleration data, we add it to a buffer - it is analysed once the buffer is full.
* Heart rate data is written directly to sdData to be used in future analysis.
*/
Log.v(TAG, "onDataReceived: Characteristic=" + characteristic.getUuid().toString());
if (characteristic.getUuid().toString().equals(CHAR_HEART_RATE_MEASUREMENT)) {
int flag = characteristic.getProperties();
//Log.d(TAG,"onDataReceived() - flag = "+flag);
int format = -1;
if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16;
Log.d(TAG, "Heart rate format UINT16.");
//Log.d(TAG, "onDataReceived(): Heart rate format UINT16.");
} else {
format = BluetoothGattCharacteristic.FORMAT_UINT8;
Log.d(TAG, "Heart rate format UINT8.");
//Log.d(TAG, "onDataReceived(): Heart rate format UINT8.");
}
final int heartRate = characteristic.getIntValue(format, 1);
mSdData.mHR = (double) heartRate;
Log.d(TAG, String.format("Received heart rate: %d", heartRate));
}
else if (characteristic.getUuid().toString().equals(CHAR_OSD_ACC_DATA)) {
final int heartRate = characteristic.getIntValue(format, 1); // heart rate is second byte
// We normally use -1 for fault indication, but the BLE standard is for one byte for heart
// rate services, so we can't send -1, so treat either 0 or 255 as fault.
if (heartRate == 255 || heartRate == 0) {
mSdData.mHR = -1;
} else {
mSdData.mHR = (double) heartRate;
}
Log.d(TAG, String.format("onDataReceived(): CHAR_HEART_RATE_MEASUREMENT: %d", heartRate));
} else if (characteristic.getUuid().toString().equals(CHAR_OSD_ACC_DATA)
|| characteristic.getUuid().toString().equals(CHAR_INFINITIME_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++) {
short[] newAccVals = parseDataToAccVals(rawDataBytes);
Log.v(TAG, "onDataReceived(): CHAR_OSD_ACC_DATA: numSamples = " + rawDataBytes.length + " nRawData=" + nRawData);
//Log.v(TAG, "onDataReceived() - rawDataBytes="+ Arrays.toString(rawDataBytes));
//Log.v(TAG, "onDataReceived() - newAccVals="+Arrays.toString(newAccVals));
for (int i = 0; i < newAccVals.length; i++) {
if (nRawData < MAX_RAW_DATA) {
rawData[nRawData] = 1000 * rawDataBytes[i] / 64; // Scale to mg
nRawData++;
switch (mAccFmt) {
case ACC_FMT_8BIT:
case ACC_FMT_16BIT:
rawData[nRawData] = newAccVals[i];
nRawData++;
break;
case ACC_FMT_3D:
// 3d data is x1,y1,z1, x2,y2,z2 ... xn,yn,zn
// We only do this every third value, then process x, y and z simultaneously.
if (i + 2 < newAccVals.length) {
if (i % 3 == 0) {
short x, y, z;
x = newAccVals[i];
y = newAccVals[i + 1];
z = newAccVals[i + 2];
// Calculate vector magnitude
rawData[nRawData] = Math.sqrt(x * x + y * y + z * z);
// Store 3d values
rawData3d[nRawData * 3] = x;
rawData3d[nRawData * 3 + 1] = y;
rawData3d[nRawData * 3 + 2] = z;
nRawData++;
}
}
break;
default:
Log.e(TAG, "INVALID ACCELERATION FORMAT" + mAccFmt);
mUtil.showToast("INVALID ACCELERATION FORMAT " + mAccFmt);
}
} else {
Log.i(TAG, "RawData Buffer Full - processing data");
// Re-start collecting raw data.
Log.i(TAG, "onDataReceived(): RawData Buffer Full - processing data");
mSdData.watchAppRunning = true;
for (i = 0; i < rawData.length; i++) {
mSdData.rawData[i] = rawData[i];
mSdData.rawData3D[i * 3] = rawData3d[i * 3];
mSdData.rawData3D[i * 3 + 1] = rawData3d[i * 3 + 1];
mSdData.rawData3D[i * 3 + 2] = rawData3d[i * 3 + 2];
//Log.v(TAG,"onDataReceived() i="+i+", "+rawData[i]);
}
mSdData.mNsamp = rawData.length;
//mNSamp = accelVals.length();
mWatchAppRunningCheck = true;
mDataStatusTime = new Time(Time.getCurrentTimezone());
// Process the data to do seizure detection
doAnalysis();
// Re-start collecting raw data.
nRawData = 0;
// Notify the device of the resulting alarm state
if (mStatusChar != null) {
Log.i(TAG,"onDataReceived() - Sending analysis result");
byte[] statusVal = new byte[1];
statusVal[0] = (byte) mSdData.alarmState;
executeWriteCharacteristic(mStatusChar, statusVal);
} else {
Log.i(TAG,"onDataReceived() - mStatusChar is null - not sending result");
}
}
}
}
else if (characteristic.getUuid().toString().equals(CHAR_OSD_BATT_DATA)) {
} else if (characteristic.getUuid().toString().equals(CHAR_OSD_BATT_DATA)) {
byte batteryPc = characteristic.getValue()[0];
mSdData.batteryPc = batteryPc;
Log.v(TAG,"Received Battery Data" + String.format("%d", batteryPc));
Log.v(TAG, "onDataReceived(): CHAR_OSD_BATT_DATA: " + String.format("%d", batteryPc));
mSdData.haveSettings = true;
}
else {
Log.v(TAG,"Unrecognised Characteristic Updated "+
} else if (characteristic.getUuid().toString().equals(CHAR_BATT_DATA)) {
byte batteryPc = characteristic.getValue()[0];
mSdData.batteryPc = batteryPc;
Log.v(TAG, "onDataReceived(): CHAR_BATT_DATA: " + String.format("%d", batteryPc));
mSdData.haveSettings = true;
} else if (characteristic.getUuid().toString().equals(CHAR_OSD_WATCH_ID)) {
byte[] rawDataBytes = characteristic.getValue();
String watchId = new String(rawDataBytes, StandardCharsets.UTF_8);
Log.v(TAG, "Received Watch ID: " + watchId);
mSdData.watchSdName = watchId;
} else if (characteristic.getUuid().toString().equals(CHAR_OSD_WATCH_FW)) {
byte[] rawDataBytes = characteristic.getValue();
String watchFwVer = new String(rawDataBytes, StandardCharsets.UTF_8);
Log.v(TAG, "Received Watch Firmware Version: " + watchFwVer);
mSdData.watchSdVersion = watchFwVer;
} else if (characteristic.getUuid().toString().equals(CHAR_OSD_ACC_FMT)) {
mAccFmt = characteristic.getValue()[0];
Log.v(TAG, "Received Acceleration format code: " + mAccFmt);
} else {
Log.v(TAG, "Unrecognised Characteristic Updated " +
characteristic.getUuid().toString());
}
}
private short[] parseDataToAccVals(byte[] rawDataBytes) {
short[] retArr;
switch (mAccFmt) {
case ACC_FMT_8BIT:
retArr = new short[rawDataBytes.length];
for (int i = 0; i < rawDataBytes.length; i++) {
retArr[i] = (short) (1000 * rawDataBytes[i] / 64); // Scale to mg
}
break;
case ACC_FMT_16BIT:
case ACC_FMT_3D:
// from https://stackoverflow.com/questions/5625573/byte-array-to-short-array-and-back-again-in-java
retArr = new short[rawDataBytes.length / 2];
// to turn bytes to shorts as either big endian or little endian.
ByteBuffer.wrap(rawDataBytes)
.order(ByteOrder.LITTLE_ENDIAN)
.asShortBuffer()
.get(retArr);
break;
default:
Log.e(TAG, "INVALID ACCELERATION FORMAT" + mAccFmt);
mUtil.showToast("INVALID ACCELERATION FORMAT " + mAccFmt);
retArr = new short[0];
}
return (retArr);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
Log.v(TAG,"onCharacteristicRead");
Log.v(TAG, "onCharacteristicRead");
if (status == BluetoothGatt.GATT_SUCCESS) {
onDataReceived(characteristic);
}
@@ -356,13 +624,13 @@ public class SdDataSourceBLE extends SdDataSource {
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
Log.v(TAG,"onCharacteristicChanged(): Characteristic "+characteristic.getUuid()+" changed");
//Log.v(TAG,"onCharacteristicChanged(): Characteristic "+characteristic.getUuid()+" changed");
onDataReceived(characteristic);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
Log.v(TAG,"onDescriptorWrite(): Characteristic " + descriptor.getUuid() + " changed");
Log.v(TAG, "onDescriptorWrite(): Characteristic " + descriptor.getUuid() + " changed");
waitForDescriptorWrite = false;
}
};
@@ -431,5 +699,23 @@ public class SdDataSourceBLE extends SdDataSource {
return mBluetoothGatt.getServices();
}
/**
* Install the watch app on the watch.
*/
/* @Override
public void installWatchApp() {
Log.v(TAG, "installWatchApp");
try {
String url = "http://www.openseizuredetector.org.uk/?page_id=1207";
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(i);
} catch (Exception ex) {
Log.i(TAG, "exception starting install watch app activity " + ex.toString());
showToast("Error Displaying Installation Instructions - try http://www.openseizuredetector.org.uk/?page_id=1207 instead");
}
}
*/
}

View File

@@ -0,0 +1,596 @@
/*
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 static com.welie.blessed.BluetoothBytesParser.asHexString;
import static java.lang.Math.abs;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.text.format.Time;
import android.util.Log;
import com.welie.blessed.BluetoothBytesParser;
import com.welie.blessed.BluetoothCentralManager;
import com.welie.blessed.BluetoothCentralManagerCallback;
import com.welie.blessed.BluetoothPeripheral;
import com.welie.blessed.BluetoothPeripheralCallback;
import com.welie.blessed.ConnectionPriority;
import com.welie.blessed.GattStatus;
import com.welie.blessed.HciStatus;
import com.welie.blessed.PhyOptions;
import com.welie.blessed.PhyType;
import com.welie.blessed.WriteType;
import org.jetbrains.annotations.NotNull;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import co.beeline.android.bluetooth.currenttimeservice.CurrentTimeService;
/**
* A data source that registers for BLE GATT notifications from a device and
* waits to be notified of data being available.
* SdDataSourceBLE2 uses the BLESSED library for the BLE access rather than native Android
* BLE methods to try to improve start-up/shutdown reliability.
*/
public class SdDataSourceBLE2 extends SdDataSource {
private int MAX_RAW_DATA = 125; // 5 seconds at 25 Hz.
private String TAG = "SdDataSourceBLE2";
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 double[] rawData3d = new double[MAX_RAW_DATA * 3];
private int mAccFmt = 0;
private boolean waitForDescriptorWrite = false;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
public static String SERV_DEV_INFO = "0000180a-0000-1000-8000-00805f9b34fb";
public static String CHAR_DEV_MANUF = "00002a29-0000-1000-8000-00805f9b34fb";
public static String CHAR_DEV_MODEL_NO = "00002a24-0000-1000-8000-00805f9b34fb";
public static String CHAR_DEV_SER_NO = "00002a25-0000-1000-8000-00805f9b34fb";
public static String CHAR_DEV_FW_VER = "00002a26-0000-1000-8000-00805f9b34fb";
public static String CHAR_DEV_HW_VER = "00002a27-0000-1000-8000-00805f9b34fb";
public static String CHAR_DEV_FW_NAME = "00002a28-0000-1000-8000-00805f9b34fb";
public static String SERV_HEART_RATE = "0000180d-0000-1000-8000-00805f9b34fb";
public static String CHAR_HEART_RATE_MEASUREMENT = "00002a37-0000-1000-8000-00805f9b34fb";
public static String SERV_OSD = "000085e9-0000-1000-8000-00805f9b34fb";
public static String CHAR_OSD_ACC_DATA = "000085e9-0001-1000-8000-00805f9b34fb";
public static String CHAR_OSD_BATT_DATA = "000085e9-0002-1000-8000-00805f9b34fb";
public static String CHAR_OSD_WATCH_ID = "000085e9-0003-1000-8000-00805f9b34fb";
public static String CHAR_OSD_WATCH_FW = "000085e9-0004-1000-8000-00805f9b34fb";
public static String CHAR_OSD_ACC_FMT = "000085e9-0005-1000-8000-00805f9b34fb";
// Valid values are 0: 8 bit vector magnitude scaled so 1g=44
public final static int ACC_FMT_8BIT = 0;
public final static int ACC_FMT_16BIT = 1;
public final static int ACC_FMT_3D = 3;
public static String CHAR_OSD_STATUS = "000085e9-0006-1000-8000-00805f9b34fb";
public static String SERV_INFINITIME_MOTION = "00030000-78fc-48fe-8e23-433b3a1942d0";
public static String CHAR_INFINITIME_ACC_DATA = "00030002-78fc-48fe-8e23-433b3a1942d0";
public static String CHAR_INFINITIME_OSD_STATUS = "00030078-78fc-48fe-8e23-433b3a1942d0";
public static String CHAR_BATT_DATA = "00002a19-0000-1000-8000-00805f9b34fb";
public static String SERV_BATT = "0000180f-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";
private BluetoothGatt mGatt;
private BluetoothGattCharacteristic mOsdChar;
private BluetoothGattCharacteristic mStatusChar;
BluetoothGattCharacteristic mHrChar;
BluetoothGattCharacteristic mBattChar;
private BluetoothCentralManager mBluetoothCentralManager;
private boolean mShutdown = false;
public SdDataSourceBLE2(Context context, Handler handler,
SdDataReceiver sdDataReceiver) {
super(context, handler, sdDataReceiver);
mName = "BLE2";
}
/**
* Start the datasource updating - initialises from sharedpreferences first to
* make sure any changes to preferences are taken into account.
*/
public void start() {
super.start();
Log.i(TAG, "start() - mBleDeviceAddr="+mBleDeviceAddr);
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);
}
// Note, these values are set in BleScanActivity and written to shared preferences, which
// ae read in SdDataSource.java
// FIXME: Read the shared preferences in this class so SdDataSource does not need to know
// FIXME: about BLE details.
Log.i(TAG, "mBLEDevice is " + mBleDeviceName + ", Addr=" + mBleDeviceAddr);
//mSdData.watchSdName = mBleDeviceName;
mSdData.watchSerNo = mBleDeviceAddr;
boolean success = CurrentTimeService.startServer(mContext);
bleConnect();
}
private void bleConnect() {
// Create BluetoothCentral and receive callbacks on the main thread
mBluetoothCentralManager = new BluetoothCentralManager(mContext,
mBluetoothCentralManagerCallback,
new Handler(Looper.getMainLooper())
);
// Look for the specified device
Log.i(TAG,"bleConnect() - scanning for device: "+mBleDeviceAddr);
mShutdown = false;
mBluetoothCentralManager.scanForPeripheralsWithAddresses(new String[]{mBleDeviceAddr});
}
private final BluetoothCentralManagerCallback mBluetoothCentralManagerCallback = new BluetoothCentralManagerCallback() {
@Override
public void onDiscoveredPeripheral(BluetoothPeripheral peripheral, ScanResult scanResult) {
Log.i(TAG,"BluetoothCentralManagerCallback.onDiscoveredPeripheral()");
mBluetoothCentralManager.stopScan();
mBluetoothCentralManager.autoConnectPeripheral(peripheral, peripheralCallback);
}
@Override
public void onConnectedPeripheral(BluetoothPeripheral peripheral) {
Log.i(TAG,"BluetoothCentralManagerCallback.onConnectedPeripheral()");
mUtil.showToast("Watch Connected");
super.onConnectedPeripheral(peripheral);
}
@Override
public void onConnectionFailed(BluetoothPeripheral peripheral, HciStatus status) {
Log.i(TAG,"BluetoothCentralManagerCallback.onConnectionFailed() - attempting to reconnect");
mUtil.showToast("Failed to Connect to Watch - Retrying");
mBluetoothCentralManager.autoConnectPeripheral(peripheral, peripheralCallback);
super.onConnectionFailed(peripheral, status);
}
@Override
public void onDisconnectedPeripheral(BluetoothPeripheral peripheral, HciStatus status) {
if (mShutdown) {
Log.i(TAG,"BluetoothCentralManagerCallback.onDisonnectedPeripheral - mShutdown is set, so not reconnecting");
} else {
Log.i(TAG,"BluetoothCentralManagerCallback.onDisonnectedPeripheral");
mUtil.showToast("WATCH CONNECTION LOST");
Log.i(TAG, "BluetoothCentralManagerCallback.onDisonnectedPeripheral - attempting to re-connect...");
bleDisconnect();
mShutdown=false;
mBluetoothCentralManager.autoConnectPeripheral(peripheral, peripheralCallback);
}
super.onDisconnectedPeripheral(peripheral, status);
}
};
private @NotNull BluetoothPeripheral mBlePeripheral;
// Callback for peripherals
private final BluetoothPeripheralCallback peripheralCallback = new BluetoothPeripheralCallback() {
@Override // BluetoothPeripheralCallback
public void onServicesDiscovered(@NotNull BluetoothPeripheral peripheral) {
Log.i(TAG,"onServicesDiscovered()");
mBlePeripheral = peripheral;
// Request a higher MTU, iOS always asks for 185 - This is likely to have no effect, as Pinetime uses 23 bytes.
Log.i(TAG,"onServicesDiscovered() - requesting higher MTU");
peripheral.requestMtu(185);
// Request a new connection priority
Log.i(TAG,"onServicesDiscovered() - requesting high priority connection");
peripheral.requestConnectionPriority(ConnectionPriority.HIGH);
Log.i(TAG,"onServicesDiscovered() - requesting Long Range Bluetooth 5 connection");
//peripheral.setPreferredPhy(PhyType.LE_2M, PhyType.LE_2M, PhyOptions.S2);
// Request long range Bluetooth 5 connection if available.
peripheral.setPreferredPhy(PhyType.LE_CODED, PhyType.LE_CODED, PhyOptions.S8);
peripheral.readPhy();
peripheral.readRemoteRssi();
boolean foundOsdService = false;
for (BluetoothGattService service : peripheral.getServices()) {
String servUuidStr = service.getUuid().toString();
Log.d(TAG, "found service: " + servUuidStr);
if (servUuidStr.equals(SERV_OSD)) {
Log.v(TAG, "OpenSeizureDetector Service Discovered");
foundOsdService = true;
} else if (servUuidStr.equals(SERV_INFINITIME_MOTION)) {
Log.v(TAG, "InfiniTime Motion Service Discovered");
foundOsdService = true;
} else if (servUuidStr.equals(SERV_HEART_RATE)) {
Log.v(TAG, "Heart Rate Measurement Service Service Discovered");
} else if (servUuidStr.equals(SERV_BATT)) {
Log.v(TAG, "Battery Data Service Service Discovered");
} else if (servUuidStr.equals(SERV_DEV_INFO)) {
Log.v(TAG, "Device Information Service Service Discovered");
}
// Loop through the available characteristics...
for (BluetoothGattCharacteristic gattCharacteristic : service.getCharacteristics()) {
String charUuidStr = gattCharacteristic.getUuid().toString();
Log.d(TAG, " found characteristic: " + charUuidStr);
// The generic heart rate measurement characteristic
if (charUuidStr.equals(CHAR_HEART_RATE_MEASUREMENT)) {
Log.v(TAG, "Subscribing to Heart Rate Measurement Change Notifications");
mHrChar = gattCharacteristic;
peripheral.setNotify(service.getUuid(), gattCharacteristic.getUuid(), true);
} else if (charUuidStr.equals(CHAR_OSD_ACC_DATA)) {
Log.i(TAG, "Subscribing to OSD Acceleration Data Change Notifications");
peripheral.setNotify(service.getUuid(), gattCharacteristic.getUuid(), true);
mOsdChar = gattCharacteristic;
} else if (charUuidStr.equals(CHAR_OSD_STATUS)) {
Log.i(TAG, "Found OSD Status Characteristic");
mStatusChar = gattCharacteristic;
} else if (charUuidStr.equals(CHAR_OSD_BATT_DATA)) {
Log.i(TAG, "Subscribing to OSD battery change Notifications");
peripheral.readCharacteristic(service.getUuid(), gattCharacteristic.getUuid());
peripheral.setNotify(service.getUuid(), gattCharacteristic.getUuid(), true);
mBattChar = gattCharacteristic;
} else if (charUuidStr.equals(CHAR_OSD_WATCH_ID)) {
Log.i(TAG, "Reading Watch ID");
peripheral.readCharacteristic(service.getUuid(), gattCharacteristic.getUuid());
} else if (charUuidStr.equals(CHAR_OSD_WATCH_FW)) {
Log.i(TAG, "Reading Watch Firmware Version");
peripheral.readCharacteristic(service.getUuid(), gattCharacteristic.getUuid());
} else if (charUuidStr.equals(CHAR_OSD_ACC_FMT)) {
Log.i(TAG, "Reading Acceleration format code");
peripheral.readCharacteristic(service.getUuid(), gattCharacteristic.getUuid());
// Now the Infinitime Motion Service Characteristics
} else if (charUuidStr.equals(CHAR_INFINITIME_ACC_DATA)) {
Log.i(TAG, "Subscribing to Infinitime Acceleration Data Change Notifications");
mOsdChar = gattCharacteristic;
mAccFmt = ACC_FMT_3D; // InfiniTime presents x, y, z data
peripheral.setNotify(service.getUuid(), gattCharacteristic.getUuid(), true);
} else if (charUuidStr.equals(CHAR_INFINITIME_OSD_STATUS)) {
Log.i(TAG, "Found InfiniTime OSD Status Characteristic");
mStatusChar = gattCharacteristic;
// Now the generic battery data characteristic
} else if (charUuidStr.equals(CHAR_BATT_DATA)) {
mBattChar = gattCharacteristic;
Log.i(TAG, "Subscribing to Generic Battery Data Change Notifications");
peripheral.setNotify(service.getUuid(), gattCharacteristic.getUuid(), true);
Log.i(TAG, "Reading battery level");
peripheral.readCharacteristic(service.getUuid(), gattCharacteristic.getUuid());
// Now device info characteristics
} else if (charUuidStr.equals(CHAR_DEV_MANUF)) {
Log.i(TAG, "Reading device manufacturer");
peripheral.readCharacteristic(service.getUuid(), gattCharacteristic.getUuid());
} else if (charUuidStr.equals(CHAR_DEV_MODEL_NO)) {
Log.i(TAG, "Reading device model number");
peripheral.readCharacteristic(service.getUuid(), gattCharacteristic.getUuid());
} else if (charUuidStr.equals(CHAR_DEV_SER_NO)) {
Log.i(TAG, "Reading device serial number");
peripheral.readCharacteristic(service.getUuid(), gattCharacteristic.getUuid());
} else if (charUuidStr.equals(CHAR_DEV_FW_VER)) {
Log.i(TAG, "Reading device firmware version");
peripheral.readCharacteristic(service.getUuid(), gattCharacteristic.getUuid());
} else if (charUuidStr.equals(CHAR_DEV_HW_VER)) {
Log.i(TAG, "Reading device hardware version");
peripheral.readCharacteristic(service.getUuid(), gattCharacteristic.getUuid());
} else if (charUuidStr.equals(CHAR_DEV_FW_NAME)) {
Log.i(TAG, "Reading device firmware name");
peripheral.readCharacteristic(service.getUuid(), gattCharacteristic.getUuid());
}
}
}
if (foundOsdService) {
Log.i(TAG,"Success - found OSD Service");
} else {
Log.e(TAG,"ERROR - device does not provide the OSD service");
mUtil.showToast("ERROR: BLE Device does no provide OSD Servie");
}
}
@Override
public void onNotificationStateUpdate(@NotNull BluetoothPeripheral peripheral, @NotNull BluetoothGattCharacteristic characteristic, @NotNull GattStatus status) {
if (status == GattStatus.SUCCESS) {
final boolean isNotifying = peripheral.isNotifying(characteristic);
Log.i(TAG, String.format("SUCCESS: Notify set to '%s' for %s", isNotifying, characteristic.getUuid()));
} else {
Log.e(TAG, String.format("ERROR: Changing notification state failed for %s (%s)",
characteristic.getUuid(), status));
}
}
@Override
public void onCharacteristicWrite(@NotNull BluetoothPeripheral peripheral, @NotNull byte[] value, @NotNull BluetoothGattCharacteristic characteristic, @NotNull GattStatus status) {
if (status == GattStatus.SUCCESS) {
Log.d(TAG, String.format("SUCCESS: Writing <%s> to <%s>", asHexString(value), characteristic.getUuid()));
} else {
Log.w(TAG, String.format("ERROR: Failed writing <%s> to <%s> (%s)", asHexString(value), characteristic.getUuid(), status));
}
}
@Override
public void onCharacteristicUpdate(@NotNull BluetoothPeripheral peripheral, @NotNull byte[] value, @NotNull BluetoothGattCharacteristic characteristic, @NotNull GattStatus status) {
if (status != GattStatus.SUCCESS) return;
UUID characteristicUUID = characteristic.getUuid();
BluetoothBytesParser parser = new BluetoothBytesParser(value);
String charUuidStr = characteristicUUID.toString();
Log.v(TAG,"onCharacteristicUpdate() - Characteristic "+charUuidStr+" updated");
if (charUuidStr.equals(CHAR_HEART_RATE_MEASUREMENT)) {
Log.v(TAG, String.format("%s", "HR Measurement"));
// Parse the flags
int flags = parser.getUInt8();
final int unit = flags & 0x01;
final int sensorContactStatus = (flags & 0x06) >> 1;
final boolean energyExpenditurePresent = (flags & 0x08) > 0;
final boolean rrIntervalPresent = (flags & 0x10) > 0;
// Parse heart rate
mSdData.mHR = (unit == 0) ? parser.getUInt8() : parser.getUInt16();
Log.d(TAG,"Received HR="+mSdData.mHR);
} else if (charUuidStr.equals(CHAR_OSD_ACC_DATA)
|| charUuidStr.equals(CHAR_INFINITIME_ACC_DATA)) {
//Log.v(TAG,"Received OSD ACC DATA"+characteristic.getValue());
byte[] rawDataBytes = characteristic.getValue();
short[] newAccVals = parseDataToAccVals(rawDataBytes);
Log.v(TAG, "onDataReceived(): CHAR_OSD_ACC_DATA: numSamples = " + rawDataBytes.length + " nRawData=" + nRawData);
for (int i = 0; i < newAccVals.length; i++) {
if (nRawData < MAX_RAW_DATA) {
switch (mAccFmt) {
case ACC_FMT_8BIT:
case ACC_FMT_16BIT:
rawData[nRawData] = newAccVals[i];
nRawData++;
break;
case ACC_FMT_3D:
// 3d data is x1,y1,z1, x2,y2,z2 ... xn,yn,zn
// We only do this every third value, then process x, y and z simultaneously.
if (i + 2 < newAccVals.length) {
if (i % 3 == 0) {
short x, y, z;
x = newAccVals[i];
y = newAccVals[i + 1];
z = newAccVals[i + 2];
// Calculate vector magnitude
rawData[nRawData] = Math.sqrt(x * x + y * y + z * z);
// Store 3d values
rawData3d[nRawData * 3] = x;
rawData3d[nRawData * 3 + 1] = y;
rawData3d[nRawData * 3 + 2] = z;
nRawData++;
}
}
break;
default:
Log.e(TAG, "INVALID ACCELERATION FORMAT" + mAccFmt);
mUtil.showToast("INVALID ACCELERATION FORMAT " + mAccFmt);
}
} else {
Log.i(TAG, "onDataReceived(): RawData Buffer Full - processing data");
mSdData.watchAppRunning = true;
for (i = 0; i < rawData.length; i++) {
mSdData.rawData[i] = rawData[i];
mSdData.rawData3D[i * 3] = rawData3d[i * 3];
mSdData.rawData3D[i * 3 + 1] = rawData3d[i * 3 + 1];
mSdData.rawData3D[i * 3 + 2] = rawData3d[i * 3 + 2];
//Log.v(TAG,"onDataReceived() i="+i+", "+rawData[i]);
}
mSdData.mNsamp = rawData.length;
mWatchAppRunningCheck = true;
mDataStatusTime = new Time(Time.getCurrentTimezone());
// Process the data to do seizure detection
doAnalysis();
mBlePeripheral.readRemoteRssi(); // Update RSSI
// Re-start collecting raw data.
nRawData = 0;
// Notify the device of the resulting alarm state
if (mStatusChar != null) {
Log.i(TAG, "onDataReceived() - Sending analysis result");
byte[] statusVal = new byte[1];
statusVal[0] = (byte) mSdData.alarmState;
peripheral.writeCharacteristic(mStatusChar, statusVal, WriteType.WITH_RESPONSE);
} else {
Log.i(TAG, "onDataReceived() - mStatusChar is null - not sending result");
}
}
}
} else if (charUuidStr.equals(CHAR_BATT_DATA)
|| charUuidStr.equals(CHAR_OSD_BATT_DATA)) {
byte batteryPc = characteristic.getValue()[0];
mSdData.batteryPc = batteryPc;
Log.v(TAG, "onDataReceived(): CHAR_BATT_DATA: " + String.format("%d", batteryPc));
mSdData.haveSettings = true;
} else if (charUuidStr.equals(CHAR_OSD_WATCH_ID) || charUuidStr.equals(CHAR_DEV_FW_NAME)) {
byte[] rawDataBytes = characteristic.getValue();
String watchId = new String(rawDataBytes, StandardCharsets.UTF_8);
Log.i(TAG, "Received Watch ID: " + watchId);
mSdData.watchSdName = watchId;
} else if (charUuidStr.equals(CHAR_OSD_ACC_FMT)) {
mAccFmt = characteristic.getValue()[0];
Log.i(TAG, "Received Acceleration format code: " + mAccFmt);
} else if (charUuidStr.equals(CHAR_DEV_MANUF)) {
byte[] rawDataBytes = characteristic.getValue();
String watchManuf = new String(rawDataBytes, StandardCharsets.UTF_8);
Log.i(TAG, "Received Manufacturer: " + watchManuf);
mSdData.watchManuf = watchManuf;
} else if (charUuidStr.equals(CHAR_DEV_MODEL_NO)) {
byte[] rawDataBytes = characteristic.getValue();
String watchModelNo = new String(rawDataBytes, StandardCharsets.UTF_8);
Log.i(TAG, "Received Watch Model No.: " + watchModelNo);
mSdData.watchPartNo = watchModelNo;
} else if (charUuidStr.equals(CHAR_DEV_SER_NO)) {
byte[] rawDataBytes = characteristic.getValue();
String watchSerNo = new String(rawDataBytes, StandardCharsets.UTF_8);
Log.i(TAG, "Received Watch Serial No.: " + watchSerNo);
//mSdData.watchSerNo = watchSerNo;
// We do not use this serial number because it is zero for PineTime - we set the MAC address at start-up instead.
} else if (charUuidStr.equals(CHAR_DEV_HW_VER)) {
byte[] rawDataBytes = characteristic.getValue();
String watchHwVer = new String(rawDataBytes, StandardCharsets.UTF_8);
Log.i(TAG, "Received Hardware Version: " + watchHwVer);
mSdData.watchFwVersion = watchHwVer;
} else if (charUuidStr.equals(CHAR_DEV_FW_NAME)) {
byte[] rawDataBytes = characteristic.getValue();
String watchFwName = new String(rawDataBytes, StandardCharsets.UTF_8);
Log.i(TAG, "Received Firmware Name: " + watchFwName);
mSdData.watchSdName = watchFwName;
} else if (charUuidStr.equals(CHAR_OSD_WATCH_FW) || charUuidStr.equals(CHAR_DEV_FW_VER)) {
byte[] rawDataBytes = characteristic.getValue();
String watchFwVer = new String(rawDataBytes, StandardCharsets.UTF_8);
Log.i(TAG, "Received Watch Firmware Version: " + watchFwVer);
mSdData.watchSdVersion = watchFwVer;
} else {
byte[] rawDataBytes = characteristic.getValue();
String strVal = new String(rawDataBytes, StandardCharsets.UTF_8);
Log.d(TAG, "Unrecognised Characteristic Updated " +
charUuidStr+" : "+strVal);
}
}
@Override
public void onMtuChanged(@NotNull BluetoothPeripheral peripheral, int mtu, @NotNull GattStatus status) {
Log.i(TAG, String.format("new MTU set: %d", mtu));
}
@Override
public void onReadRemoteRssi(@NotNull BluetoothPeripheral peripheral, int rssi, @NotNull GattStatus status) {
Log.d(TAG, String.format("Rssi = %d", rssi));
mSdData.watchSignalStrength = rssi;
mSdData.watchSignalStrengthBuff.add(rssi);
}
};
private void bleDisconnect() {
try {
Log.i(TAG, "bleDisconnect() - Unregistering notifications");
if (mBlePeripheral != null) {
if (mOsdChar != null) {
Log.i(TAG, "bleDisconnect() - unregistering mOsdChar");
mBlePeripheral.setNotify(mOsdChar, false);
} else {
Log.w(TAG, "bleDisconnect() - mOsdChar is null - not removing notification");
}
if (mHrChar != null) {
Log.i(TAG, "bleDisconnect() - unregistering mHrChar");
mBlePeripheral.setNotify(mHrChar, false);
} else {
Log.w(TAG, "bleDisconnect() - mHrChar is null - not removing notification");
}
if (mBattChar != null) {
Log.i(TAG, "bleDisconnect() - unregistering mBattChar");
mBlePeripheral.setNotify(mBattChar, false);
} else {
Log.w(TAG, "bleDisconnect() - mBattChar is null - not removing notification");
}
} else {
Log.w(TAG, "bleDisconnect() - mBlePeripheral is null - not removing notifications");
}
mShutdown = true;
mBlePeripheral.cancelConnection();
//Log.i(TAG, "bleDisconnect() - closing BluetoothCentralManager");
//mBluetoothCentralManager.close();
} catch (Exception e) {
Log.e(TAG,"bleDisconnect() - Error: "+e.getMessage());
mUtil.showToast("Error disconnecting from watch");
}
}
/**
* Stop the datasource from updating
*/
public void stop() {
Log.i(TAG, "stop()");
mUtil.writeToSysLogFile("SDDataSourceBLE.stop()");
super.stop();
try {
mShutdown = true;
bleDisconnect();
CurrentTimeService.stopServer();
} catch (Exception e) {
Log.e(TAG,"stop() - Error stopping data source: "+e.getMessage());
}
}
private short[] parseDataToAccVals(byte[] rawDataBytes) {
short[] retArr;
switch (mAccFmt) {
case ACC_FMT_8BIT:
retArr = new short[rawDataBytes.length];
for (int i = 0; i < rawDataBytes.length; i++) {
retArr[i] = (short) (1000 * rawDataBytes[i] / 64); // Scale to mg
}
break;
case ACC_FMT_16BIT:
case ACC_FMT_3D:
// from https://stackoverflow.com/questions/5625573/byte-array-to-short-array-and-back-again-in-java
retArr = new short[rawDataBytes.length / 2];
// to turn bytes to shorts as either big endian or little endian.
ByteBuffer.wrap(rawDataBytes)
.order(ByteOrder.LITTLE_ENDIAN)
.asShortBuffer()
.get(retArr);
break;
default:
Log.e(TAG, "INVALID ACCELERATION FORMAT" + mAccFmt);
mUtil.showToast("INVALID ACCELERATION FORMAT " + mAccFmt);
retArr = new short[0];
}
return (retArr);
}
}

View File

@@ -38,17 +38,18 @@ public class SdDataSourceNetwork extends SdDataSource {
mName = "Network";
}
@Override public void start() {
@Override
public void start() {
// Update preferences.
Log.v(TAG,"start(): calling updatePrefs()");
Log.v(TAG, "start(): calling updatePrefs()");
mUtil.writeToSysLogFile("SdDataSourceNetwork().start()");
updatePrefs();
// Start timer to retrieve seizure detector data regularly.
mStatusTime = new Time(Time.getCurrentTimezone());
mStatusTime.setToNow();
if (mDataUpdateTimer ==null) {
Log.v(TAG,"start(): starting data update timer");
if (mDataUpdateTimer == null) {
Log.v(TAG, "start(): starting data update timer");
mDataUpdateTimer = new Timer();
mDataUpdateTimer.schedule(new TimerTask() {
@Override
@@ -57,17 +58,18 @@ public class SdDataSourceNetwork extends SdDataSource {
}
}, 0, mDataUpdatePeriod);
} else {
Log.v(TAG,"start(): data update timer already running.");
Log.v(TAG, "start(): data update timer already running.");
}
}
@Override public void stop() {
@Override
public void stop() {
mUtil.writeToSysLogFile("SdDataSourceNetwork().stop()");
// Stop the data update timer
if (mDataUpdateTimer !=null) {
Log.v(TAG,"stop(): cancelling status timer");
if (mDataUpdateTimer != null) {
Log.v(TAG, "stop(): cancelling status timer");
mDataUpdateTimer.cancel();
mDataUpdateTimer.purge();
mDataUpdateTimer = null;
@@ -76,7 +78,6 @@ public class SdDataSourceNetwork extends SdDataSource {
}
/**
* updatePrefs() - update basic settings from the SharedPreferences
* - defined in res/xml/prefs.xml
@@ -86,21 +87,21 @@ public class SdDataSourceNetwork extends SdDataSource {
mUtil.writeToSysLogFile("SdDataSourceNetwork().updatePrefs()");
SharedPreferences SP = PreferenceManager
.getDefaultSharedPreferences(mContext);
mServerIP = SP.getString("ServerIP","192.168.1.175");
Log.v(TAG,"updatePrefs() - mServerIP = "+mServerIP);
mServerIP = SP.getString("ServerIP", "192.168.1.175");
Log.v(TAG, "updatePrefs() - mServerIP = " + mServerIP);
try {
String dataUpdatePeriodStr = SP.getString("DataUpdatePeriod","2000");
String dataUpdatePeriodStr = SP.getString("DataUpdatePeriod", "2000");
mDataUpdatePeriod = Integer.parseInt(dataUpdatePeriodStr);
Log.v(TAG,"updatePrefs() - mDataUpdatePeriod = "+mDataUpdatePeriod);
String connectTimeoutPeriodStr = SP.getString("ConnectTimeoutPeriod","5000");
Log.v(TAG, "updatePrefs() - mDataUpdatePeriod = " + mDataUpdatePeriod);
String connectTimeoutPeriodStr = SP.getString("ConnectTimeoutPeriod", "5000");
mConnnectTimeoutPeriod = Integer.parseInt(connectTimeoutPeriodStr);
Log.v(TAG,"updatePrefs() - mConnectTimeoutPeriod = "+mConnnectTimeoutPeriod);
String readTimeoutPeriodStr = SP.getString("ReadTimeoutPeriod","5000");
Log.v(TAG, "updatePrefs() - mConnectTimeoutPeriod = " + mConnnectTimeoutPeriod);
String readTimeoutPeriodStr = SP.getString("ReadTimeoutPeriod", "5000");
mReadTimeoutPeriod = Integer.parseInt(readTimeoutPeriodStr);
Log.v(TAG,"updatePrefs() - mReadTimeoutPeriod = "+mReadTimeoutPeriod);
Log.v(TAG, "updatePrefs() - mReadTimeoutPeriod = " + mReadTimeoutPeriod);
} catch (Exception ex) {
Log.v(TAG,"updatePrefs() - Problem parsing preferences!");
mUtil.writeToSysLogFile("SdDataSourceNetwork().updatePrefs() - " +ex.toString());
Log.v(TAG, "updatePrefs() - Problem parsing preferences!");
mUtil.writeToSysLogFile("SdDataSourceNetwork().updatePrefs() - " + ex.toString());
showToast("Problem Parsing Preferences - Something won't work");
}
}
@@ -117,6 +118,7 @@ public class SdDataSourceNetwork extends SdDataSource {
private class DownloadSdDataTask extends AsyncTask<String, Void, SdData> {
private SdData sdData;
@Override
protected SdData doInBackground(String... urls) {
// params comes from the execute() call: params[0] is the url.
@@ -124,23 +126,23 @@ public class SdDataSourceNetwork extends SdDataSource {
try {
String result = downloadUrl(urls[0]);
if (result.startsWith("Unable to retrieve web page")) {
Log.v(TAG,"doInBackground() - Unable to retrieve data");
Log.v(TAG, "doInBackground() - Unable to retrieve data");
sdData.serverOK = false;
sdData.watchConnected = false;
sdData.watchAppRunning = false;
sdData.alarmState = ALARM_STATE_NETFAULT;
sdData.alarmPhrase = "Warning - No Connection to Server";
Log.v(TAG,"doInBackground(): No Connection to Server - sdData = "+sdData.toString());
Log.v(TAG, "doInBackground(): No Connection to Server - sdData = " + sdData.toString());
} else {
Log.v(TAG,"doInBackground - result = "+result);
Log.v(TAG, "doInBackground - result = " + result);
sdData.fromJSON(result);
// Populate mSdData using the received data.
sdData.serverOK = true;
if (sdData.batteryPc>0) {
if (sdData.batteryPc > 0) {
sdData.haveSettings = true;
}
mStatusTime.setToNow();
Log.v(TAG,"doInBackground(): sdData = "+sdData.toString());
Log.v(TAG, "doInBackground(): sdData = " + sdData.toString());
}
return (sdData);
@@ -150,14 +152,15 @@ public class SdDataSourceNetwork extends SdDataSource {
sdData.watchAppRunning = false;
sdData.alarmState = ALARM_STATE_NETFAULT;
sdData.alarmPhrase = "Warning - No Connection to Server";
Log.v(TAG,"doInBackground(): IOException - "+e.toString());
Log.v(TAG, "doInBackground(): IOException - " + e.toString());
return sdData;
}
}
// onPostExecute displays the results of the AsyncTask.
@Override
protected void onPostExecute(SdData sdData) {
Log.v(TAG,"onPostExecute() - sdData = "+sdData.toString());
Log.v(TAG, "onPostExecute() - sdData = " + sdData.toString());
mSdDataReceiver.onSdDataReceived(sdData);
}
}
@@ -178,24 +181,24 @@ public class SdDataSourceNetwork extends SdDataSource {
try {
String result = downloadUrl(urls[0]);
if (result.startsWith("Unable to retrieve web page")) {
Log.v(TAG,"doInBackground() - Error accepting alarm");
Log.v(TAG, "doInBackground() - Error accepting alarm");
} else {
Log.v(TAG,"doInBackground(): Alarm Accepted");
Log.v(TAG, "doInBackground(): Alarm Accepted");
}
} catch (IOException e) {
Log.v(TAG,"doInBackground(): IOException - "+e.toString());
Log.v(TAG, "doInBackground(): IOException - " + e.toString());
}
return "Done";
}
// onPostExecute displays the results of the AsyncTask.
@Override
protected void onPostExecute(String s) {
Log.v(TAG,"onPostExecute() - s="+s);
Log.v(TAG, "onPostExecute() - s=" + s);
}
}
// Given a URL, establishes an HttpUrlConnection and retrieves
// the web page content as a InputStream, which it returns as
// a string.
@@ -241,6 +244,4 @@ public class SdDataSourceNetwork extends SdDataSource {
}
}

View File

@@ -45,7 +45,6 @@ import java.util.TimerTask;
import java.util.UUID;
/**
* Abstract class for a seizure detector data source. Subclasses include a pebble smart watch data source and a
* network data source.
@@ -166,7 +165,7 @@ public class SdDataSourcePebble extends SdDataSource {
// 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 with period "+mDataUpdatePeriod*1000 + " ms");
Log.v(TAG, "start(): starting status timer with period " + mDataUpdatePeriod * 1000 + " ms");
mUtil.writeToSysLogFile("SdDataSourcePebble.start() - starting status timer");
mStatusTimer = new Timer();
mStatusTimer.schedule(new TimerTask() {
@@ -185,7 +184,7 @@ public class SdDataSourcePebble extends SdDataSource {
getPebbleSdSettings();
if (mSettingsTimer == null) {
Log.v(TAG, "start(): starting settings timer");
mUtil.writeToSysLogFile("SdDataSourcePebble.start() - starting settings timer with period "+1000*mSettingsPeriod);
mUtil.writeToSysLogFile("SdDataSourcePebble.start() - starting settings timer with period " + 1000 * mSettingsPeriod);
mSettingsTimer = new Timer();
mSettingsTimer.schedule(new TimerTask() {
@Override
@@ -230,7 +229,7 @@ public class SdDataSourcePebble extends SdDataSource {
} catch (Exception e) {
Log.v(TAG, "Error in stop() - " + e.toString());
mUtil.writeToSysLogFile("SdDataSourcePebble.stop() - error - "+e.toString());
mUtil.writeToSysLogFile("SdDataSourcePebble.stop() - error - " + e.toString());
}
}
@@ -343,8 +342,8 @@ public class SdDataSourcePebble extends SdDataSource {
Log.v(TAG, "updatePrefs() FallWindow = " + mFallWindow);
} catch (Exception ex) {
Log.v(TAG, "updatePrefs() - Problem parsing preferences! - prefStr="+prefStr);
mUtil.writeToSysLogFile("SdDataSourcePebble.updatePrefs() - ERROR "+ex.toString());
Log.v(TAG, "updatePrefs() - Problem parsing preferences! - prefStr=" + prefStr);
mUtil.writeToSysLogFile("SdDataSourcePebble.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();
}
@@ -437,16 +436,16 @@ public class SdDataSourcePebble extends SdDataSource {
byte[] rawDataBytes = data.getBytes(KEY_RAW_DATA);
for (int i = 0; i < rawDataBytes.length - 4; i += 4) { // 4 bytes per sample
int b0 = rawDataBytes[i];
int b1 = rawDataBytes[i+1] & 0xff;
int b2 = rawDataBytes[i+2] & 0xff;
int b3 = rawDataBytes[i+3] & 0xff;
int b1 = rawDataBytes[i + 1] & 0xff;
int b2 = rawDataBytes[i + 2] & 0xff;
int b3 = rawDataBytes[i + 3] & 0xff;
int x = (b3 | b2 << 8 | b1 << 16 | b0 << 24);
//int y = (rawDataBytes[i+2] & 0xff) | (rawDataBytes[i+3] << 8);
//int z = (rawDataBytes[i+4] & 0xff) | (rawDataBytes[i+5] << 8);
//Log.v(TAG,"x="+x+", y="+y+", z="+z);
Log.v(TAG,"b0="+b0+", b1="+b1+", b2="+b2+", b3="+b3+", x="+x);
Log.v(TAG, "b0=" + b0 + ", b1=" + b1 + ", b2=" + b2 + ", b3=" + b3 + ", x=" + x);
if (nRawData < MAX_RAW_DATA) {
rawData[nRawData] = (int)Math.sqrt(x);
rawData[nRawData] = (int) Math.sqrt(x);
} else {
Log.i(TAG, "WARNING - rawData Buffer Full");
}
@@ -461,7 +460,7 @@ public class SdDataSourcePebble extends SdDataSource {
// } else {
// Log.i(TAG, "WARNING - rawData Buffer Full");
// }
// }
// }
}
}
@@ -498,7 +497,7 @@ public class SdDataSourcePebble extends SdDataSource {
// first close the watch app if it is running.
PebbleKit.closeAppOnPebble(mContext, SD_UUID);
Log.v(TAG, "startWatchApp() - starting watch app after 5 seconds delay...");
// Wait 5 seconds then start the app.
// Wait 5 seconds then start the app.
Timer appStartTimer = new Timer();
appStartTimer.schedule(new TimerTask() {
@Override
@@ -727,7 +726,7 @@ public class SdDataSourcePebble extends SdDataSource {
* ignored!
*/
private void analyseRawData() {
Log.v(TAG,"analyserawData()");
Log.v(TAG, "analyserawData()");
//DoubleFFT_1D fft = new DoubleFFT_1D(MAX_RAW_DATA);
//fft.realForward(rawData);
// FIXME - rawData should really be a circular buffer.
@@ -774,7 +773,6 @@ public class SdDataSourcePebble extends SdDataSource {
}
}

View File

@@ -47,8 +47,6 @@ import static java.lang.Math.sqrt;
public class SdDataSourcePhone extends SdDataSource implements SensorEventListener {
private String TAG = "SdDataSourcePhone";
private final static int NSAMP = 250;
private SensorManager mSensorManager;
private Sensor mSensor;
private int mMode = 0; // 0=check data rate, 1=running
@@ -56,6 +54,8 @@ public class SdDataSourcePhone extends SdDataSource implements SensorEventListen
private long mStartTs = 0;
public double mSampleFreq = 0;
private boolean mUseNextSample = true;
private PowerManager.WakeLock mWakeLock;
@@ -79,7 +79,7 @@ public class SdDataSourcePhone extends SdDataSource implements SensorEventListen
mUtil.writeToSysLogFile("SdDataSourcePhone.start()");
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mSensor , SensorManager.SENSOR_DELAY_GAME);
mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_GAME);
super.start();
}
@@ -95,88 +95,91 @@ public class SdDataSourcePhone extends SdDataSource implements SensorEventListen
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// we initially start in mMode=0, which calculates the sample frequency returned by the sensor, then enters mMode=1, which is normal operation.
if (mMode == 0) {
if (mStartEvent==null) {
Log.v(TAG,"onSensorChanged(): mMode=0 - checking Sample Rate - mNSamp = "+mSdData.mNsamp);
Log.v(TAG,"onSensorChanged(): saving initial event data");
if (mStartEvent == null) {
Log.v(TAG, "onSensorChanged(): mMode=0 - Starting Sample Rate Check - mNSamp = " + mSdData.mNsamp);
Log.v(TAG, "onSensorChanged(): saving initial event data");
mStartEvent = event;
mStartTs = event.timestamp;
mSdData.mNsamp = 0;
} else {
mSdData.mNsamp ++;
mSdData.mNsamp++;
}
if (mSdData.mNsamp>=250) {
Log.v(TAG,"onSensorChanged(): Collected Data = final TimeStamp="+event.timestamp+", initial TimeStamp="+mStartTs);
double dT = 1e-9*(event.timestamp - mStartTs);
mSdData.mSampleFreq = (int)(mSdData.mNsamp/dT);
Log.v(TAG, "onSensorChanged - mMode=" + mMode + " mNSamp=" + mSdData.mNsamp);
if (mSdData.mNsamp >= mSdData.rawData.length) {
Log.v(TAG, "onSensorChanged(): Collected Data = final TimeStamp=" + event.timestamp + ", initial TimeStamp=" + mStartTs);
double dT = 1e-9 * (event.timestamp - mStartTs);
mSdData.mSampleFreq = (int) (mSdData.mNsamp / dT);
mSdData.haveSettings = true;
Log.v(TAG,"onSensorChanged(): Collected data for "+dT+" sec - calculated sample rate as "+ mSampleFreq +" Hz");
Log.v(TAG, "onSensorChanged(): Collected data for " + dT + " sec - calculated sample rate as " + mSampleFreq + " Hz");
mMode = 1;
mSdData.mNsamp = 0;
mStartTs = event.timestamp;
}
} else if (mMode==1) {
// mMode=1 is normal operation - collect NSAMP accelerometer data samples, then analyse them by calling doAnalysis().
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
//Log.v(TAG,"Accelerometer Data Received: x="+x+", y="+y+", z="+z);
mSdData.rawData[mSdData.mNsamp] = sqrt(x*x + y*y + z*z);
mSdData.rawData3D[3*mSdData.mNsamp] = x;
mSdData.rawData3D[3*mSdData.mNsamp+1] = y;
mSdData.rawData3D[3*mSdData.mNsamp+2] = z;
mSdData.mNsamp++;
if (mSdData.mNsamp==NSAMP) {
// Calculate the sample frequency for this sample, but do not change mSampleFreq, which is used for
// analysis - this is because sometimes you get a very long delay (e.g. when disconnecting debugger),
// which gives a very low frequency which can make us run off the end of arrays in doAnalysis().
// FIXME - we should do some sort of check and disregard samples with long delays in them.
double dT = 1e-9*(event.timestamp - mStartTs);
int sampleFreq = (int)(mSdData.mNsamp/dT);
Log.v(TAG,"onSensorChanged(): Collected "+NSAMP+" data points in "+dT+" sec (="+sampleFreq+" Hz) - analysing...");
// DownSample from the 50Hz received frequency to 25Hz and convert to mg.
// FIXME - we should really do this properly rather than assume we are really receiving data at 50Hz.
for (int i=0; i<mSdData.mNsamp; i++) {
mSdData.rawData[i/2] = 1000.*mSdData.rawData[i]/9.81;
mSdData.rawData3D[i/2] = 1000.*mSdData.rawData3D[i]/9.81;
mSdData.rawData3D[i/2 +1] = 1000.*mSdData.rawData3D[i+1]/9.81;
mSdData.rawData3D[i/2 +2] = 1000.*mSdData.rawData3D[i+2]/9.81;
//Log.v(TAG,"i="+i+", rawData="+mSdData.rawData[i]+","+mSdData.rawData[i/2]);
} else if (mMode == 1) {
// The phone gives us 50 Hz sample frequency so we do a crude factor of 2 downsampling.
if (mUseNextSample) {
mUseNextSample = false;
// mMode=1 is normal operation - collect NSAMP accelerometer data samples, then analyse them by calling doAnalysis().
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
//Log.v(TAG,"Accelerometer Data Received: x="+x+", y="+y+", z="+z);
mSdData.rawData[mSdData.mNsamp] = sqrt(x * x + y * y + z * z);
mSdData.rawData3D[3 * mSdData.mNsamp] = x;
mSdData.rawData3D[3 * mSdData.mNsamp + 1] = y;
mSdData.rawData3D[3 * mSdData.mNsamp + 2] = z;
mSdData.mNsamp++;
if (mSdData.mNsamp == mSdData.rawData.length) {
// Calculate the sample frequency for this sample, but do not change mSampleFreq, which is used for
// analysis - this is because sometimes you get a very long delay (e.g. when disconnecting debugger),
// which gives a very low frequency which can make us run off the end of arrays in doAnalysis().
// FIXME - we should do some sort of check and disregard samples with long delays in them.
double dT = 1e-9 * (event.timestamp - mStartTs);
int sampleFreq = (int) (mSdData.mNsamp / dT);
Log.v(TAG, "onSensorChanged(): Collected " + mSdData.mNsamp + " data points in " + dT + " sec (=" + sampleFreq + " Hz) - analysing...");
// DownSample from the 50Hz received frequency to 25Hz and convert to mg.
// FIXME - we should really do this properly rather than assume we are really receiving data at 50Hz.
for (int i = 0; i < mSdData.mNsamp; i++) {
mSdData.rawData[i / 2] = 1000. * mSdData.rawData[i] / 9.81;
mSdData.rawData3D[i / 2] = 1000. * mSdData.rawData3D[i] / 9.81;
mSdData.rawData3D[i / 2 + 1] = 1000. * mSdData.rawData3D[i + 1] / 9.81;
mSdData.rawData3D[i / 2 + 2] = 1000. * mSdData.rawData3D[i + 2] / 9.81;
//Log.v(TAG,"i="+i+", rawData="+mSdData.rawData[i]+","+mSdData.rawData[i/2]);
}
mSdData.mNsamp /= 2;
// Set HR and O2Sat values to fault value (-1) to avoid alarms if the user enables HR or O2Sat alarms.
mSdData.mHR = -1;
mSdData.mO2Sat = -1;
doAnalysis();
mSdData.mNsamp = 0;
mStartTs = event.timestamp;
} else if (mSdData.mNsamp > mSdData.rawData.length) {
Log.v(TAG, "onSensorChanged(): Received data during analysis - ignoring sample");
}
mSdData.mNsamp /= 2;
// Set HR and O2Sat values to fault value (-1) to avoid alarms if the user enables HR or O2Sat alarms.
mSdData.mHR = -1;
mSdData.mO2Sat = -1;
doAnalysis();
mSdData.mNsamp = 0;
mStartTs = event.timestamp;
} else if (mSdData.mNsamp>NSAMP) {
Log.v(TAG,"onSensorChanged(): Received data during analysis - ignoring sample");
} else {
mUseNextSample = true;
}
} else {
Log.v(TAG,"onSensorChanged(): ERROR - Mode "+mMode+" unrecognised");
Log.v(TAG, "onSensorChanged(): ERROR - Mode " + mMode + " unrecognised");
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
Log.v(TAG,"onAccuracyChanged()");
Log.v(TAG, "onAccuracyChanged()");
}
}

View File

@@ -157,6 +157,7 @@ public class SdServer extends Service implements SdDataReceiver {
private final IBinder mBinder = new SdBinder();
public LogManager mLm;
private boolean mUseNewUi;
/**
* class to handle binding the MainApp activity to this service
@@ -259,7 +260,7 @@ public class SdServer extends Service implements SdDataReceiver {
Log.v(TAG, "onStartCommand() - calling updatePrefs()");
updatePrefs();
Log.v(TAG, "onStartCommand: Datasource =" + mSdDataSourceName + ", phoneAppVersion="+mUtil.getAppVersionName());
Log.v(TAG, "onStartCommand: Datasource =" + mSdDataSourceName + ", phoneAppVersion=" + mUtil.getAppVersionName());
mSdData.dataSourceName = mSdDataSourceName;
mSdData.phoneAppVersion = mUtil.getAppVersionName();
switch (mSdDataSourceName) {
@@ -277,7 +278,7 @@ public class SdServer extends Service implements SdDataReceiver {
Log.v(TAG, "Selecting Network DataSource");
mUtil.writeToSysLogFile("SdServer.onStartCommand() - creating SdDataSourceNetwork");
mSdDataSource = new SdDataSourceNetwork(this.getApplicationContext(), mHandler, this);
Log.i(TAG,"Disabling remote logging when using network data source");
Log.i(TAG, "Disabling remote logging when using network data source");
mLogDataRemote = false;
break;
case "Garmin":
@@ -290,6 +291,11 @@ public class SdServer extends Service implements SdDataReceiver {
mUtil.writeToSysLogFile("SdServer.onStartCommand() - creating SdDataSourceBLE");
mSdDataSource = new SdDataSourceBLE(this.getApplicationContext(), mHandler, this);
break;
case "BLE2":
Log.v(TAG, "Selecting BLE2 DataSource");
mUtil.writeToSysLogFile("SdServer.onStartCommand() - creating SdDataSourceBLE2");
mSdDataSource = new SdDataSourceBLE2(this.getApplicationContext(), mHandler, this);
break;
case "Phone":
Log.v(TAG, "Selecting Phone Sensor DataSource");
mUtil.writeToSysLogFile("SdServer.onStartCommand() - creating SdDataSourcePhone");
@@ -305,7 +311,7 @@ public class SdServer extends Service implements SdDataReceiver {
// Create our log manager.
mLm = new LogManager(this, mLogDataRemote, mLogDataRemoteMobile, mAuthToken, mEventDuration,
mRemoteLogPeriod, mLogNDA ,mAutoPruneDb, mDataRetentionPeriod, mSdData);
mRemoteLogPeriod, mLogNDA, mAutoPruneDb, mDataRetentionPeriod, mSdData);
if (mSMSAlarm) {
Log.v(TAG, "Creating LocationFinder");
@@ -315,7 +321,6 @@ public class SdServer extends Service implements SdDataReceiver {
mSdDataSource.start();
// Record last time we sent an SMS so we can limit rate of SMS
// sending to one per minute. We set it to one minute ago (60000 milliseconds)
mSMSTime = new Time(Time.getCurrentTimezone());
@@ -477,7 +482,7 @@ public class SdServer extends Service implements SdDataReceiver {
Uri soundUri = null;
if ((alarmLevel == mCurrentNotificationAlarmLevel) && (isNotificationShown(NOTIFICATION_ID))) {
Log.v(TAG,"showNotification - notification already shown at specified alarm level - not doing anything");
Log.v(TAG, "showNotification - notification already shown at specified alarm level - not doing anything");
return;
}
Log.v(TAG, "showNotification() - alarmLevel=" + alarmLevel);
@@ -517,7 +522,12 @@ public class SdServer extends Service implements SdDataReceiver {
soundUri = null;
}
Intent i = new Intent(getApplicationContext(), MainActivity.class);
Intent i;
if (mUseNewUi) {
i = new Intent(getApplicationContext(), MainActivity2.class);
} else {
i = new Intent(getApplicationContext(), MainActivity.class);
}
i.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
PendingIntent contentIntent =
PendingIntent.getActivity(this,
@@ -569,13 +579,18 @@ public class SdServer extends Service implements SdDataReceiver {
mUtil.writeToSysLogFile("SdServer.showMainActivity - Activity is already shown on top, not doing anything");
} else {
Log.i(TAG, "showMainActivity(): Showing Main Activity");
Intent i = new Intent(getApplicationContext(), MainActivity.class);
Intent i;
if (mUseNewUi) {
i = new Intent(getApplicationContext(), MainActivity2.class);
} else {
i = new Intent(getApplicationContext(), MainActivity.class);
}
i.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(i);
}
} else {
mUtil.showToast("OpenSeizureDetector: showMainActvity Failed to Display Activity");
Log.e(TAG,"OpenSeizureDetector: showMainActvity Failed to Display Activity");
Log.e(TAG, "OpenSeizureDetector: showMainActvity Failed to Display Activity");
}
}
@@ -665,7 +680,7 @@ public class SdServer extends Service implements SdDataReceiver {
mUtil.showToast(getString(R.string.SMSAlarmDisabledNotSendingMsg));
Log.v(TAG, "mSMSAlarm is false - not sending");
}
Log.v(TAG,"calling startLatchTimer()");
startLatchTimer();
}
// Handle fall alarm
@@ -772,7 +787,6 @@ public class SdServer extends Service implements SdDataReceiver {
}
}
// Fault
if ((sdData.alarmState) == 4 || (sdData.alarmState == 7) || (sdData.mHRFaultStanding) || (sdData.mHrFrozenFaultStanding)) {
sdData.alarmPhrase = "FAULT";
@@ -807,12 +821,19 @@ public class SdServer extends Service implements SdDataReceiver {
// flag.
if (mFaultTimerCompleted) {
faultWarningBeep();
//mSdDataSource.stop();
//mHandler.postDelayed(new Runnable() {
// public void run() {
// mSdDataSource.start();
// }
//}, 190);
// Disable the data-source re-start for now because it was messing up BLE2 data source by ending up with multiple
// notifications for the same data when it reconnects.
if (false) {
// Re-start the data source to see if that fixes it
Log.w(TAG, "FAULT - stopping data source");
mSdDataSource.stop();
mHandler.postDelayed(new Runnable() {
public void run() {
Log.w(TAG, "FAULT - restarting data source");
mSdDataSource.start();
}
}, 10000);
}
} else {
startFaultTimer();
Log.v(TAG, "onSdDataFault() - starting Fault Timer");
@@ -997,15 +1018,20 @@ public class SdServer extends Service implements SdDataReceiver {
private void startLatchTimer() {
if (mLatchAlarms) {
if (mLatchAlarmTimer != null) {
Log.v(TAG, "startLatchTimer -timer already running - cancelling it");
Log.i(TAG, "startLatchTimer -timer already running - cancelling it");
mLatchAlarmTimer.cancel();
mLatchAlarmTimer = null;
}
Log.v(TAG, "startLatchTimer() - starting alarm latch release timer to time out in " + mLatchAlarmPeriod + " sec");
Log.i(TAG, "startLatchTimer() - starting alarm latch release timer to time out in " + mLatchAlarmPeriod + " sec");
// set timer to timeout after mLatchAlarmPeriod, and Tick() function to be called every second.
mLatchAlarmTimer =
new LatchAlarmTimer(mLatchAlarmPeriod * 1000, 1000);
mLatchAlarmTimer.start();
// We need to start the timer on the UI thread to get it to work for some reason - I don't know why!
runOnUiThread(new Runnable() {
public void run() {
mLatchAlarmTimer =
new LatchAlarmTimer(mLatchAlarmPeriod * 1000, 1000);
mLatchAlarmTimer.start();
}
});
} else {
Log.v(TAG, "startLatchTimer() - Latch Alarms disabled - not doing anything");
}
@@ -1285,6 +1311,8 @@ public class SdServer extends Service implements SdDataReceiver {
mOSDUrl = SP.getString("OSDUrl", "http://openseizuredetector.org.uk/webApi");
Log.v(TAG, "updatePrefs() - mOSDUrl = " + mOSDUrl);
mUtil.writeToSysLogFile("updatePrefs() - mOSDUrl = " + mOSDUrl);
mUseNewUi = SP.getBoolean("UseNewUi", false);
} catch (Exception ex) {
Log.v(TAG, "updatePrefs() - Problem parsing preferences!");
mUtil.writeToSysLogFile("SdServer.updatePrefs() - Error " + ex.toString());
@@ -1332,7 +1360,7 @@ public class SdServer extends Service implements SdDataReceiver {
Log.i(TAG, "SmsTimer.onFinish() - Last Location is Null so sending first SMS without location.");
}
} else {
Log.e(TAG,"SmsTimer.onFinish - mLocationFinder is null - this should not happen!");
Log.e(TAG, "SmsTimer.onFinish - mLocationFinder is null - this should not happen!");
mUtil.showToast(getString(R.string.mLocationFinder_is_null_msg));
}
Log.i(TAG, "SmsTimer.onFinish() - Sending to " + mSMSNumbers.length + " Numbers");
@@ -1360,7 +1388,6 @@ public class SdServer extends Service implements SdDataReceiver {
/**
* onSdLocationReceived - called with the best estimate location after mLocationReceiver times out.
*
*/
private void sendSMS(String phoneNo, String msgStr) {
Log.i(TAG, "sendSMS() - Sending to " + phoneNo);
@@ -1446,7 +1473,7 @@ public class SdServer extends Service implements SdDataReceiver {
// called after startTime ms.
@Override
public void onFinish() {
Log.v(TAG, "LatchAlarmTimer.onFinish()");
Log.i(TAG, "LatchAlarmTimer.onFinish()");
// Do the equivalent of accept alarm push button.
acceptAlarm();
}
@@ -1605,11 +1632,11 @@ public class SdServer extends Service implements SdDataReceiver {
Log.v(TAG, "checkEvents() - haveUnvalidatedEvent = " +
haveUnvalidatedEvent);
if (haveUnvalidatedEvent) {
Log.v(TAG,"checkEvents() - showing event notification and cancelling datashare notification.");
Log.v(TAG, "checkEvents() - showing event notification and cancelling datashare notification.");
showEventNotification();
mNM.cancel(DATASHARE_NOTIFICATION_ID);
} else {
Log.v(TAG,"checkEvents() - cancelling event and datashare notifications");
Log.v(TAG, "checkEvents() - cancelling event and datashare notifications");
mNM.cancel(EVENT_NOTIFICATION_ID);
mNM.cancel(DATASHARE_NOTIFICATION_ID);
}
@@ -1665,7 +1692,7 @@ public class SdServer extends Service implements SdDataReceiver {
Uri soundUri = null;
if (isNotificationShown(EVENT_NOTIFICATION_ID)) {
Log.v(TAG,"showEventNotification() - notification is already shown, so not doing anything");
Log.v(TAG, "showEventNotification() - notification is already shown, so not doing anything");
return;
}
@@ -1714,7 +1741,7 @@ public class SdServer extends Service implements SdDataReceiver {
Uri soundUri = null;
if (isNotificationShown(DATASHARE_NOTIFICATION_ID)) {
Log.v(TAG,"showDataShareNotification() - notification is already shown, so not doing anything");
Log.v(TAG, "showDataShareNotification() - notification is already shown, so not doing anything");
return;
}
@@ -1733,7 +1760,12 @@ public class SdServer extends Service implements SdDataReceiver {
iconId = R.drawable.datasharing_fault_24x24;
titleStr = getString(R.string.datasharing_notification_title);
Intent i = new Intent(getApplicationContext(), MainActivity.class);
Intent i;
if (mUseNewUi) {
i = new Intent(getApplicationContext(), MainActivity2.class);
} else {
i = new Intent(getApplicationContext(), MainActivity.class);
}
i.putExtra("action", "showDataSharingDialog");
i.setAction("showDataSharingDialog");
i.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
@@ -1763,6 +1795,7 @@ public class SdServer extends Service implements SdDataReceiver {
/**
* isNotificationShown - returns true if the specified notificationID is shown, otherwise returns false.
*
* @param notificationId - Notification ID to check
* @return true if the specified notification is displayed, otherwise false.
*/
@@ -1771,10 +1804,10 @@ public class SdServer extends Service implements SdDataReceiver {
StatusBarNotification[] notifications = mNotificationManager.getActiveNotifications();
for (StatusBarNotification notification : notifications) {
if (notification.getId() == notificationId) {
return(true);
return (true);
}
}
return(false);
return (false);
}
}

View File

@@ -31,7 +31,6 @@ import android.os.IBinder;
import android.util.Log;
/**
* Defines callbacks for service binding, passed to bindService()
*/
@@ -68,11 +67,12 @@ public class SdServiceConnection implements ServiceConnection {
/**
* Check if the service has received seizure detector data.
*
* @return true if data has been received.
*/
public boolean hasSdData() {
if (mSdServer!=null) {
if (mSdServer.mSdData!=null) {
if (mSdServer != null) {
if (mSdServer.mSdData != null) {
return mSdServer.mSdData.haveData;
}
}
@@ -81,11 +81,12 @@ public class SdServiceConnection implements ServiceConnection {
/**
* Check if the service has received seizure detector settings.
*
* @return true if settings have been received.
*/
public boolean hasSdSettings() {
if (mSdServer!=null) {
if (mSdServer.mSdData!=null) {
if (mSdServer != null) {
if (mSdServer.mSdData != null) {
if (mSdServer.mSdData.haveSettings) {
return true;
}
@@ -96,11 +97,12 @@ public class SdServiceConnection implements ServiceConnection {
/**
* Check if the pebble watch is connected to the server device via bluetooth.
*
* @return true if watch connected.
*/
public boolean watchConnected() {
if (mSdServer!=null) {
if (mSdServer.mSdData!=null) {
if (mSdServer != null) {
if (mSdServer.mSdData != null) {
if (mSdServer.mSdData.watchConnected) {
return true;
}
@@ -111,11 +113,12 @@ public class SdServiceConnection implements ServiceConnection {
/**
* Check if the openseizuredetector pebble watch app is running..
*
* @return true if watch app running.
*/
public boolean pebbleAppRunning() {
if (mSdServer!=null) {
if (mSdServer.mSdData!=null) {
if (mSdServer != null) {
if (mSdServer.mSdData != null) {
if (mSdServer.mSdData.watchAppRunning) {
return true;
}

View File

@@ -114,7 +114,7 @@ public class SdWebServer extends NanoHTTPD {
answer = mSdServer.mSdDataSource.updateFromJSON(files.get("postData"));
}
} else {
Log.i(TAG,"Web server received data, but datasource is not set to 'Garmin' - Ignoring");
Log.i(TAG, "Web server received data, but datasource is not set to 'Garmin' - Ignoring");
mUtil.showToast("Web server received data, but datasource is not set to 'Garmin' - Ignoring");
}
break;
@@ -152,10 +152,10 @@ public class SdWebServer extends NanoHTTPD {
Log.v(TAG, " files=" + files.toString());
// Send the data to the SdDataSource so the app can pick it up.
if (parameters != null) {
Log.v(TAG,"passing parameters to data source");
Log.v(TAG, "passing parameters to data source");
answer = mSdServer.mSdDataSource.updateFromJSON(parameters.get("dataObj").toString());
} else {
Log.v(TAG,"Passing postData to data source");
Log.v(TAG, "Passing postData to data source");
answer = mSdServer.mSdDataSource.updateFromJSON(files.get("postData"));
}
//mSdServer.mSdDataSource.updateFromJSON(parameters.toString());
@@ -209,12 +209,12 @@ public class SdWebServer extends NanoHTTPD {
} else {
Log.v(TAG, "WebServer.serve() - Unknown uri -" +
uri);
answer = "{'msg' : 'Unknown URI: "+uri+"'}";
answer = "{'msg' : 'Unknown URI: " + uri + "'}";
}
}
res = new NanoHTTPD.Response(answer);
res.setMimeType(responseMimeType);
Log.v(TAG,"WebServer.serve() - returning "+res.getData()+", mime="+res.getMimeType()+", status="+res.getStatus());
Log.v(TAG, "WebServer.serve() - returning " + res.getData() + ", mime=" + res.getMimeType() + ", status=" + res.getStatus());
return (res);
}
@@ -248,13 +248,13 @@ public class SdWebServer extends NanoHTTPD {
try {
JSONObject jsonObj = new JSONObject();
File[] fileList = mUtil.getDataFilesList();
Log.v(TAG, "serveLogFile(): fileList=" + fileList.toString()+", length="+fileList.length);
Log.v(TAG, "serveLogFile(): fileList=" + fileList.toString() + ", length=" + fileList.length);
JSONArray arr = new JSONArray();
for (int i = 0; i < fileList.length; i++) {
Log.v(TAG, "serveLogFile(): file[" + i + "]=" + fileList[i]);
arr.put(fileList[i].getName());
}
jsonObj.put("logFileList", arr);
for (int i = 0; i < fileList.length; i++) {
Log.v(TAG, "serveLogFile(): file[" + i + "]=" + fileList[i]);
arr.put(fileList[i].getName());
}
jsonObj.put("logFileList", arr);
res = new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK,
"text/html", jsonObj.toString());
} catch (Exception ex) {

View File

@@ -53,7 +53,6 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.text.HtmlCompat;
import com.rohitss.uceh.UCEHandler;
@@ -89,10 +88,12 @@ public class StartupActivity extends AppCompatActivity {
private boolean mLocationPermissions2Requested;
private boolean mSmsPermissionsRequested;
private boolean mPermissionsRequested;
private boolean mBindInProgress = false;
public final String[] REQUIRED_PERMISSIONS = {
//Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.WAKE_LOCK,
Manifest.permission.POST_NOTIFICATIONS,
};
public final String[] SMS_PERMISSIONS_1 = {
@@ -112,8 +113,19 @@ public class StartupActivity extends AppCompatActivity {
Manifest.permission.ACCESS_BACKGROUND_LOCATION,
};
private String[] BT_PERMISSIONS;
private boolean mBTPermissionsRequested = false;
private String mSdDataSourceName;
private String mBleDeviceAddr;
private String mBleDeviceName;
private final int MODE_INIT = 0;
private final int MODE_SHUTDOWN_SERVER = 1;
private final int MODE_START_SERVER = 2;
private final int MODE_CONNECT_SERVER = 3;
private final int MODE_WATCH_RUNNING = 4;
private final int MODE_SD_DATA_OK = 5;
private int mMode = MODE_INIT;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -181,6 +193,7 @@ public class StartupActivity extends AppCompatActivity {
});
mConnection = new SdServiceConnection(getApplicationContext());
}
@Override
@@ -197,31 +210,28 @@ public class StartupActivity extends AppCompatActivity {
// Display the DataSource name
SharedPreferences SP = PreferenceManager
.getDefaultSharedPreferences(getBaseContext());
;
String dataSourceName = SP.getString("DataSource", "Pebble");
mSdDataSourceName = SP.getString("DataSource", "Pebble");
mBleDeviceAddr = SP.getString("BLE_Device_Addr", "");
mBleDeviceName = SP.getString("BLE_Device_Name", "");
tv = (TextView) findViewById(R.id.dataSourceTextView);
tv.setText(String.format("%s = %s", getString(R.string.DataSource), dataSourceName));
if (mSdDataSourceName.equals("BLE")) {
tv.setText(String.format("%s = %s (%s - %s)", getString(R.string.DataSource), mSdDataSourceName, mBleDeviceName, mBleDeviceAddr));
} else {
tv.setText(String.format("%s = %s", getString(R.string.DataSource), mSdDataSourceName));
}
if (mUtil.isServerRunning()) {
Log.i(TAG, "onStart() - server running - stopping it - isServerRunning="+mUtil.isServerRunning());
mMode = MODE_SHUTDOWN_SERVER;
Log.i(TAG, "onStart() - server running - stopping it - isServerRunning=" + mUtil.isServerRunning());
mUtil.writeToSysLogFile("StartupActivity.onStart() - server already running - stopping it.");
mUtil.stopServer();
} else {
Log.i(TAG, "onStart() - server not running - isServerRunning="+mUtil.isServerRunning());
mMode = MODE_START_SERVER;
Log.i(TAG, "onStart() - server not running - isServerRunning=" + mUtil.isServerRunning());
}
// Wait 0.1 second to give the server chance to shutdown in case we have just shut it down below, then start it
mHandler.postDelayed(new Runnable() {
public void run() {
mUtil.writeToSysLogFile("StartupActivity.onStart() - starting server after delay - isServerRunning="+mUtil.isServerRunning());
Log.i(TAG, "onStart() - starting server after delay -isServerRunning="+mUtil.isServerRunning());
mUtil.startServer();
// Bind to the service.
Log.i(TAG, "onStart() - binding to server");
mUtil.writeToSysLogFile("StartupActivity.onStart() - binding to server");
mUtil.bindToServer(getApplicationContext(), mConnection);
}
}, 100);
// Check power management settings
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
@@ -231,7 +241,7 @@ public class StartupActivity extends AppCompatActivity {
} else {
boolean preventBatteryOptWarning = SP.getBoolean("PreventBatteryOptWarning", false);
if (preventBatteryOptWarning) {
Log.i(TAG,"PreventBatteryOptWarning is true, so not displaying battery optimisation dialog");
Log.i(TAG, "PreventBatteryOptWarning is true, so not displaying battery optimisation dialog");
} else {
Log.e(TAG, "Power Management Problem - not ignoring Battery Optimisations");
//mUtil.showToast("WARNING - Phone is Optimising OpenSeizureDetector Battery Usage - this is likely to prevent it working correctly when running on battery!");
@@ -300,8 +310,34 @@ public class StartupActivity extends AppCompatActivity {
tv = (TextView) findViewById(R.id.textItem1);
pb = (ProgressBar) findViewById(R.id.progressBar1);
if (arePermissionsOK()) {
if (smsAlarmsActive && !areSMSPermissions1OK()) {
Log.i(TAG,"SMS permissions NOT OK");
Log.i(TAG,"arePermissionsOK=true");
Log.i(TAG,"mSdDataSourceName = "+ mSdDataSourceName);
tv.setText(getString(R.string.AppPermissionsOk));
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
pb.setIndeterminateDrawable(getResources().getDrawable(R.drawable.start_server));
pb.setProgressDrawable(getResources().getDrawable(R.drawable.start_server));
if (mSdDataSourceName.equals("BLE") || mSdDataSourceName.equals("BLE2")) {
if (!mUtil.areBtPermissionsOk()) {
Log.i(TAG, "Bluetooth permissions NOT OK");
tv.setText(getString(R.string.BTPermissionWarning));
tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour);
//pb.setIndeterminateDrawable(getResources().getDrawable(R.drawable.start_server));
//pb.setProgressDrawable(getResources().getDrawable(R.drawable.start_server));
requestBTPermissions();
allOk = false;
} else if (mBleDeviceAddr.equals("")) {
Log.i(TAG,"BLE data source selected, but no device address specified - starting BLEScanActivity");
Intent i;
i = new Intent(getApplicationContext(), BLEScanActivity.class);
startActivity(i);
finish();
return;
}
} else if (smsAlarmsActive && !areSMSPermissions1OK()) {
Log.i(TAG, "SMS permissions NOT OK");
tv.setText(getString(R.string.SmsPermissionWarning));
tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour);
@@ -310,25 +346,19 @@ public class StartupActivity extends AppCompatActivity {
requestSMSPermissions();
allOk = false;
} else if (smsAlarmsActive && !areLocationPermissions1OK()) {
Log.i(TAG,"Location permissions NOT OK");
Log.i(TAG, "Location permissions NOT OK");
tv.setText(getString(R.string.SmsPermissionWarning));
tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour);
requestLocationPermissions1();
allOk = false;
} else if (smsAlarmsActive && !areLocationPermissions2OK()) {
Log.i(TAG,"Location permissions2 NOT OK");
Log.i(TAG, "Location permissions2 NOT OK");
tv.setText(getString(R.string.SmsPermissionWarning));
tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour);
requestLocationPermissions2();
allOk = false;
} else {
tv.setText(getString(R.string.AppPermissionsOk));
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
pb.setIndeterminateDrawable(getResources().getDrawable(R.drawable.start_server));
pb.setProgressDrawable(getResources().getDrawable(R.drawable.start_server));
}
} else {
tv.setText(getString(R.string.AppPermissionsWarning));
@@ -349,6 +379,41 @@ public class StartupActivity extends AppCompatActivity {
allOk = false;
}
if (allOk) {
tv = (TextView) findViewById(R.id.textItem1);
pb = (ProgressBar) findViewById(R.id.progressBar1);
if (!mUtil.isServerRunning()) {
mUtil.writeToSysLogFile("StartupActivity.onStart() - starting server - isServerRunning=" + mUtil.isServerRunning());
Log.i(TAG, "onStart() - starting server -isServerRunning=" + mUtil.isServerRunning());
mUtil.startServer();
mBindInProgress = false;
allOk = false;
tv.setText("Starting Server");
tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour);
pb.setIndeterminateDrawable(getResources().getDrawable(R.drawable.start_server));
pb.setProgressDrawable(getResources().getDrawable(R.drawable.start_server));
mMode = MODE_START_SERVER;
} else {
tv.setText(getString(R.string.ServerRunningOK));
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
pb.setIndeterminateDrawable(getResources().getDrawable(R.drawable.start_server));
pb.setProgressDrawable(getResources().getDrawable(R.drawable.start_server));
if (mBindInProgress) {
Log.i(TAG,"Waiting to bind to server");
} else {
Log.i(TAG, "ServerStatusRunnable() - not starting server - allOk=" + allOk + ", isServerRunning()=" + mUtil.isServerRunning());
// Bind to the service.
Log.i(TAG, "ServerStatusRunnable() - binding to server");
mUtil.writeToSysLogFile("StartupActivity.onStart() - binding to server");
mUtil.bindToServer(getApplicationContext(), mConnection);
mBindInProgress = true;
}
}
}
// Are we Bound to the Service
tv = (TextView) findViewById(R.id.textItem2);
pb = (ProgressBar) findViewById(R.id.progressBar2);
@@ -428,13 +493,22 @@ public class StartupActivity extends AppCompatActivity {
Log.i(TAG, "serverStatusRunnable() - starting main activity...");
mUtil.writeToSysLogFile("StartupActivity.serverStatusRunnable - all checks ok - starting main activity.");
try {
Intent intent = new Intent(
getApplicationContext(),
MainActivity.class);
Boolean useNewUi = SP.getBoolean("UseNewUi", true);
Intent intent;
if (useNewUi) {
intent = new Intent(
getApplicationContext(),
MainActivity2.class);
} else {
intent = new Intent(
getApplicationContext(),
MainActivity.class);
}
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
mStartedMainActivity = true;
finish();
return;
} catch (Exception ex) {
mStartedMainActivity = false;
Log.e(TAG, "exception starting main activity " + ex.toString());
@@ -470,10 +544,10 @@ public class StartupActivity extends AppCompatActivity {
}
}
/**
* checkFirstRun - checks to see if this is the first run of the app after installation or upgrade.
* if it is, the relevant dialog message is displayed. If not, the routine just exists so start-up can continue.
*/
/**
* checkFirstRun - checks to see if this is the first run of the app after installation or upgrade.
* if it is, the relevant dialog message is displayed. If not, the routine just exists so start-up can continue.
*/
public void checkFirstRun() {
String storedVersionName = "";
String versionName;
@@ -631,7 +705,7 @@ public class StartupActivity extends AppCompatActivity {
for (int i = 0; i < SMS_PERMISSIONS_1.length; i++) {
if (ContextCompat.checkSelfPermission(this, SMS_PERMISSIONS_1[i])
!= PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "areSMSPermissions1OK: "+SMS_PERMISSIONS_1[i] + " Permission Not Granted");
Log.i(TAG, "areSMSPermissions1OK: " + SMS_PERMISSIONS_1[i] + " Permission Not Granted");
allOk = false;
}
}
@@ -654,7 +728,7 @@ public class StartupActivity extends AppCompatActivity {
public boolean areLocationPermissions2OK() {
boolean allOk = true;
Log.v(TAG, "areSMSPermissions2OK() - SDK="+android.os.Build.VERSION.SDK_INT);
Log.v(TAG, "areSMSPermissions2OK() - SDK=" + android.os.Build.VERSION.SDK_INT);
if (android.os.Build.VERSION.SDK_INT < 29) {
Log.d(TAG, "areLocationPermission2OK() - SDK <29 (Android 10) so permission not required");
allOk = true;
@@ -670,6 +744,8 @@ public class StartupActivity extends AppCompatActivity {
return allOk;
}
public void requestPermissions(AppCompatActivity activity) {
if (mPermissionsRequested) {
Log.i(TAG, "requestPermissions() - request already sent - not doing anything");
@@ -703,7 +779,7 @@ public class StartupActivity extends AppCompatActivity {
.setPositiveButton(getString(R.string.okBtnTxt), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
Log.i(TAG,"requestSMSPermissions(): Launching ActivityCompat.requestPermissions()");
Log.i(TAG, "requestSMSPermissions(): Launching ActivityCompat.requestPermissions()");
ActivityCompat.requestPermissions(StartupActivity.this,
SMS_PERMISSIONS_1,
45);
@@ -740,9 +816,9 @@ public class StartupActivity extends AppCompatActivity {
}
})
.setNegativeButton(getString(R.string.cancelBtnTxt), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
})
.create().show();
}
@@ -764,7 +840,7 @@ public class StartupActivity extends AppCompatActivity {
.setPositiveButton(getString(R.string.okBtnTxt), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
Log.i(TAG,"requestSMSPermissions(): Launching ActivityCompat.requestPermissions()");
Log.i(TAG, "requestSMSPermissions(): Launching ActivityCompat.requestPermissions()");
ActivityCompat.requestPermissions(StartupActivity.this,
LOCATION_PERMISSIONS_2,
44);
@@ -778,15 +854,41 @@ public class StartupActivity extends AppCompatActivity {
}
}
public void requestBTPermissions() {
if (mBTPermissionsRequested) {
Log.i(TAG, "requestBTPermissions() - request already sent - not doing anything");
} else {
Log.i(TAG, "requestBTPermissions() - requesting permissions");
mBTPermissionsRequested = true;
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
this);
alertDialogBuilder
.setTitle(R.string.BTpermissions_required)
.setMessage(R.string.BT_permissions_rationale)
.setCancelable(false)
.setPositiveButton(getString(R.string.okBtnTxt), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
Log.i(TAG, "requestBTPermissions(): Launching ActivityCompat.requestPermissions()");
ActivityCompat.requestPermissions(StartupActivity.this,
mUtil.getRequiredBtPermissions(),
46);
}
})
.create().show();
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
Log.i(TAG, "onRequestPermissionsResult - Permission" + permissions + " = " + grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Log.i(TAG, "onRequestPermissionsResult - requestCode="+requestCode+" nPermissions="+permissions.length);
Log.i(TAG, "onRequestPermissionsResult: "+permissions[0]+": "+grantResults[0]);
for (int i = 0; i < permissions.length; i++) {
Log.i(TAG, "Permission " + permissions[i] + " = " + grantResults[i]);
Log.i(TAG, String.format("onRequestPermissionsResult: i="+i+", Permission " + permissions[i].toString() + " = " + grantResults[i]));
//Log.i(TAG,"i="+i);
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

View File

@@ -139,19 +139,20 @@ public abstract class WebApiConnection {
/**
* Mark all of the events with IDs contained in eventList as the specified type and subtype.
* @param eventList list of String IDs of the events to mark as unknown.
*
* @param eventList list of String IDs of the events to mark as unknown.
* @param typeStr
* @param subTypeStr
* @return true if request sent successfully or false.
*/
private boolean markEventsAsTypeSubType(ArrayList<String>eventList, String typeStr, String subTypeStr) {
if (eventList.size()>0) {
Log.i(TAG,"markEventsAsTypeSubtype - eventList.size()="+eventList.size());
Log.i(TAG,"markEventsAsSypeSubtype - eventList(0) = "+eventList.get(0));
private boolean markEventsAsTypeSubType(ArrayList<String> eventList, String typeStr, String subTypeStr) {
if (eventList.size() > 0) {
Log.i(TAG, "markEventsAsTypeSubtype - eventList.size()=" + eventList.size());
Log.i(TAG, "markEventsAsSypeSubtype - eventList(0) = " + eventList.get(0));
getEvent(eventList.get(0), new WebApiConnection.JSONObjectCallback() {
@Override
public void accept(JSONObject eventObj) {
Log.v(TAG, "markEventsAsTypeSubtype.getEvent.callback: "+eventObj);
Log.v(TAG, "markEventsAsTypeSubtype.getEvent.callback: " + eventObj);
if (eventObj != null) {
Log.v(TAG, "markEventsAsTypeSubtype.getEvent.callback: eventObj=" + eventObj.toString());
try {
@@ -161,7 +162,7 @@ public abstract class WebApiConnection {
if (notesStr == null) notesStr = new String("");
notesStr = notesStr + " bulk type/subtype set";
eventObj.put("desc", notesStr);
updateEvent(eventObj,new WebApiConnection.JSONObjectCallback() {
updateEvent(eventObj, new WebApiConnection.JSONObjectCallback() {
@Override
public void accept(JSONObject eventObj) {
if (eventObj != null) {
@@ -176,7 +177,7 @@ public abstract class WebApiConnection {
}
});
} catch (JSONException e) {
Log.e(TAG,"markEventsAsTypeSubtype.getEvent.callback: Error editing eventObj");
Log.e(TAG, "markEventsAsTypeSubtype.getEvent.callback: Error editing eventObj");
mUtil.showToast("markEventsAsTypeSubtype.getEvent.callback: Error editing eventObj");
}
} else {
@@ -186,11 +187,11 @@ public abstract class WebApiConnection {
}
});
} else {
Log.i(TAG,"markEventsAsTypeSubtype(): No more events to Modify");
Log.i(TAG, "markEventsAsTypeSubtype(): No more events to Modify");
mUtil.showToast("No more unvalidated events to modify.");
}
return(true);
return (true);
}
/**
@@ -240,6 +241,7 @@ public abstract class WebApiConnection {
markUnverifiedEventsAsTypeSubtype("Unknown", "");
return true;
}
public boolean markUnverifiedEventsAsFalseAlarm() {
markUnverifiedEventsAsTypeSubtype("False Alarm", "");
return true;

View File

@@ -95,7 +95,7 @@ public class WebApiConnection_firebase extends WebApiConnection {
} else {
try {
JSONObject retObj = new JSONObject();
retObj.put("id",auth.getCurrentUser().getUid());
retObj.put("id", auth.getCurrentUser().getUid());
retObj.put("username", auth.getCurrentUser().getDisplayName());
retObj.put("email", auth.getCurrentUser().getEmail());
callback.accept(retObj);
@@ -121,7 +121,7 @@ public class WebApiConnection_firebase extends WebApiConnection {
// passes the newly created documentId to function callback on successful completion, or null on error.
public boolean createEvent(final int osdAlarmState, final Date eventDate, final String type, final String subType,
final String eventDesc, final String dataJSON, StringCallback callback) {
// FIXME - save type, subtype, eventDesc and dataJSON
// FIXME - save type, subtype, eventDesc and dataJSON
Log.v(TAG, "createEvent()");
String userId = null;
@@ -424,7 +424,7 @@ public class WebApiConnection_firebase extends WebApiConnection {
}
public boolean getCnnModelInfo(JSONObjectCallback callback) {
Log.w(TAG,"getCnnModelInfo() - FIXME - not implemented yet!");
Log.w(TAG, "getCnnModelInfo() - FIXME - not implemented yet!");
return false;
}

View File

@@ -41,7 +41,7 @@ public class WebApiConnection_osdapi extends WebApiConnection {
public void close() {
super.close();
Log.i(TAG,"stop()");
Log.i(TAG, "stop()");
mQueue.stop();
}
@@ -110,8 +110,6 @@ public class WebApiConnection_osdapi extends WebApiConnection {
}
public boolean isLoggedIn() {
String authToken = getStoredToken();
Log.v(TAG, "isLoggedIn(): token=" + authToken);
@@ -119,7 +117,7 @@ public class WebApiConnection_osdapi extends WebApiConnection {
Log.v(TAG, "isLogged in - not logged in");
return (false);
} else {
Log.v(TAG,"isLoggedIn - logged in ok");
Log.v(TAG, "isLoggedIn - logged in ok");
return (true);
}
@@ -315,7 +313,7 @@ public class WebApiConnection_osdapi extends WebApiConnection {
} else {
Log.e(TAG, "getEvents(): Error: - request returned null networkResponse");
}
} else{
} else {
Log.e(TAG, "getEvents(): Error: - request returned null response");
}
callback.accept(null);
@@ -517,6 +515,7 @@ public class WebApiConnection_osdapi extends WebApiConnection {
/**
* Retieve the user profile of the authenticated user from the server, and return it to the callback function.
*
* @param callback - function to be called with a JSONObject as a parameter that contains the user profile data.
* @return true if request sent successfully, or else false.
*/
@@ -572,8 +571,6 @@ public class WebApiConnection_osdapi extends WebApiConnection {
}
/**
* Retrieve the file containing the standard event types from the server.
* Calls the specified callback function, passing a JSONObject as a parameter when the data has been received and parsed.
@@ -680,11 +677,10 @@ public class WebApiConnection_osdapi extends WebApiConnection {
}
/**
* Retrieve a trivial file from the server to check we have a good server connection.
* sets mServerConnectionOk.
* sets mServerConnectionOk.
*
* @return true if request sent successfully or else false.
*/
public boolean checkServerConnection() {
@@ -714,7 +710,7 @@ public class WebApiConnection_osdapi extends WebApiConnection {
}
public boolean getCnnModelInfo(JSONObjectCallback callback) {
Log.w(TAG,"getCnnModelInfo() - FIXME - not implemented yet!");
Log.w(TAG, "getCnnModelInfo() - FIXME - not implemented yet!");
return false;
}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<TextView
android:id="@+id/versionTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_name" />
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_common_container_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/fragment_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@@ -6,25 +6,19 @@
android:paddingLeft="8dp"
android:paddingRight="8dp">
<LinearLayout
<TextView
android:id="@+id/ble_scan_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16pt"
android:text="Scan for Bluetooth Data Sources" />
<TextView
android:id="@+id/ble_connection_warning_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:text="@string/ble_connected_warning_text" />
<TextView
android:id="@+id/ble_scan_status_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16pt"
android:text="ble_scan_status_tv" />
<Button
android:id="@+id/startScanButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onScanButtonClick"
android:text="Start Scan"/>
</LinearLayout>
<TextView
android:id="@+id/ble_present_tv"
@@ -47,21 +41,26 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ble_perm1_tv" />
<TextView
android:id="@+id/ble_perm2_tv"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ble_perm2_tv" />
<TextView
android:id="@+id/ble_perm3_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ble_perm3_tv" />
<TextView
android:id="@+id/ble_perm4_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ble_perm4_tv" />
android:orientation="horizontal">
<TextView
android:id="@+id/ble_scan_status_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16pt"
android:text="ble_scan_status_tv" />
<Button
android:id="@+id/startScanButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onScanButtonClick"
android:text="Start Scan"/>
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@@ -74,14 +73,13 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#00FF00"
android:drawSelectorOnTop="false" />
<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF0000"
android:background="#101010"
android:text="No data" />
</LinearLayout>
</LinearLayout>

View File

@@ -34,7 +34,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="View Error Log"
android:textColor="#212121"/>
android:textColor="#212121"
android:visibility="gone"/>
<Button
android:id="@+id/button_copy_error_log"
@@ -42,7 +43,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Copy Error Log"
android:textColor="#212121"/>
android:textColor="#212121"
android:visibility="gone"/>
<Button
android:id="@+id/button_share_error_log"
@@ -50,7 +52,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Share Error Log"
android:textColor="#212121"/>
android:textColor="#212121"
android:visibility="gone"/>
<Button
android:id="@+id/button_email_error_log"
@@ -66,7 +69,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Save Error Log"
android:textColor="#212121"/>
android:textColor="#212121"
android:visibility="gone"/>
<Button
android:id="@+id/button_close_app"

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FragmentOsdBaseClass">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/okBackgroundColor"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/battery_history"
android:textColor="@color/okTextColor" />
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/battLineChart"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</com.github.mikephil.charting.charts.LineChart>
</androidx.appcompat.widget.LinearLayoutCompat>
</FrameLayout>

View File

@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".FragmentOsdBaseClass">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/okBackgroundColor"
>
<TextView
android:id="@+id/serverStatusTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="---" />
<TextView
android:id="@+id/dataSourceInfoTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="---" />
<TextView
android:id="@+id/data_time_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="---" />
<LinearLayout
android:id="@+id/statusLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/algsTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Algorithms: " />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/osdAlgTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="---" />
<TextView
android:id="@+id/cnnAlgTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="---" />
<TextView
android:id="@+id/hrAlgTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="---" />
<TextView
android:id="@+id/o2AlgTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="---" />
<!--<TextView
android:id="@+id/fallAlgTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:visibility="gone"/>
-->
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/alarmTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="---"
android:textSize="32sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false">
<Button
android:id="@+id/acceptAlarmButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/AcceptAlarmBtnTxt" />
<Button
android:id="@+id/cancelAudibleButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/CancelAudibleButtonTxt" />
<Button
android:id="@+id/manualAlarmButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:backgroundTint="#ff0000"
android:text="@string/ManualAlarmBtnTxt" />
</LinearLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
</FrameLayout>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FragmentOsdBaseClass">
<!-- TODO: Update blank fragment layout -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="253dp"
android:orientation="vertical">
<!-- TODO: Update blank fragment layout -->
<TextView
android:id="@+id/fragment_data_sharing_tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Fragment_Data_Sharing" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Data Sharing Status" />
</androidx.appcompat.widget.LinearLayoutCompat>
</FrameLayout>

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FragmentOsdBaseClass">
<!-- TODO: Update blank fragment layout -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/okBackgroundColor"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/heart_rate_algorithm"
android:textColor="@color/okTextColor"/>
<TextView
android:id="@+id/fragment_hr_alg_tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="HR Algorithm Status"
android:textColor="@color/okTextColor" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Current HR: "
android:textColor="@color/okTextColor" />
<TextView
android:id="@+id/current_hr_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="---"
android:textColor="@color/okTextColor" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" bpm"
android:textColor="@color/okTextColor" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Adaptive Threshold Average HR: "
android:textColor="@color/okTextColor" />
<TextView
android:id="@+id/adaptive_avg_hr_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="---"
android:textColor="@color/okTextColor" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" bpm"
android:textColor="@color/okTextColor" />
</androidx.appcompat.widget.LinearLayoutCompat>
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:textColor="@color/okTextColor"/>
<!--
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/hr_average_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show averages"
android:textColor="@color/okTextColor"/>
-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/lineChart"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</com.github.mikephil.charting.charts.LineChart>
</FrameLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
</FrameLayout>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FragmentOsdBaseClass">
<!-- TODO: Update blank fragment layout -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="253dp"
android:orientation="vertical">
<!-- TODO: Update blank fragment layout -->
<TextView
android:id="@+id/fragment_ml_alg_tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Fragment_MlAlg" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Machine Learning Algorithm Status" />
</androidx.appcompat.widget.LinearLayoutCompat>
</FrameLayout>

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FragmentOsdBaseClass">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/okBackgroundColor">
<TextView
android:id="@+id/fragment_osdalg_tv2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/okTextColor"
android:text="OSD Algorithm Status" />
<TextView
android:id="@+id/powerTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/okTextColor"
android:text="---" />
<ProgressBar
android:id="@+id/powerProgressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:minHeight="20dp" />
<TextView
android:id="@+id/spectrumTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/okTextColor"
android:text="---" />
<ProgressBar
android:id="@+id/spectrumProgressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:indeterminate="false"
android:minHeight="20dp" />
<TextView
android:id="@+id/pSeizureTvM2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/seizure_probability"
android:textColor="@color/okTextColor" />
<ProgressBar
android:id="@+id/pSeizureProgressBarM2"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.github.mikephil.charting.charts.BarChart
android:id="@+id/chart1"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.appcompat.widget.LinearLayoutCompat>
</FrameLayout>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FragmentOsdBaseClass">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment"
android:id="@+id/fragment_sddata_viewer_tv1"/>
</FrameLayout>

View File

@@ -0,0 +1,225 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FragmentOsdBaseClass">
<!-- TODO: Update blank fragment layout -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/fragment_ll">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="OpenSeizureDetector System Status"
android:textColor="@color/okTextColor"
android:background="@color/okBackgroundColor"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageButton
android:id="@+id/settingsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/ic_menu_manage" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Edit Settings"
android:textColor="@color/okTextColor"
android:background="@color/okBackgroundColor"/>
</LinearLayout>
<TextView
android:id="@+id/serverStatusTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="---" />
<TextView
android:id="@+id/fragment_bound_to_server_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Fragment_System" />
<TextView
android:id="@+id/data_time_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left"
android:text="---" />
<TextView
android:id="@+id/battTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="---" />
<TextView
android:id="@+id/alarmTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="---" />
<TextView
android:id="@+id/serverIpTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left"
android:text="---" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Watch Status"
android:textColor="@color/okTextColor"
android:background="@color/okBackgroundColor"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/okTextColor"
android:text="Watch Manufacturer: "/>
<TextView
android:id="@+id/watch_manuf_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="---"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/okTextColor"
android:text="Watch Part No: "/>
<TextView
android:id="@+id/watch_partno_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="---"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/okTextColor"
android:text="Watch FW Ver: "/>
<TextView
android:id="@+id/watch_fwver_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="---"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/okTextColor"
android:text="Watch App Name: "/>
<TextView
android:id="@+id/watch_sdname_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="---"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/okTextColor"
android:text="Watch App Ver: "/>
<TextView
android:id="@+id/watch_sdver_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="---"/>
</LinearLayout>
<TextView
android:id="@+id/fragment_watch_app_status_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="---" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/okTextColor"
android:text="Watch Battery Level: "/>
<TextView
android:id="@+id/watch_batt_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="---"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/okTextColor"
android:text="Watch Signal Strength: "/>
<TextView
android:id="@+id/watch_signal_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="---"/>
</LinearLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
</FrameLayout>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FragmentOsdBaseClass">
<!-- TODO: Update blank fragment layout -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/okBackgroundColor"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Watch Signal Strength History"
android:textColor="@color/okTextColor"/>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Current Signal Strength: "
android:textColor="@color/okTextColor" />
<TextView
android:id="@+id/current_sig_strength_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="---"
android:textColor="@color/okTextColor" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" dB"
android:textColor="@color/okTextColor" />
</androidx.appcompat.widget.LinearLayoutCompat>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/sigStrengthLineChart"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</com.github.mikephil.charting.charts.LineChart>
</FrameLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
</FrameLayout>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FragmentOsdBaseClass">
<!-- TODO: Update blank fragment layout -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="253dp"
android:orientation="vertical">
<!-- TODO: Update blank fragment layout -->
<TextView
android:id="@+id/fragment_web_server_tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Fragment_WebServer" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Web Server Status" />
</androidx.appcompat.widget.LinearLayoutCompat>
</FrameLayout>

View File

@@ -107,7 +107,7 @@
android:orientation="vertical">
<TextView
android:id="@+id/pebTimeTv"
android:id="@+id/data_time_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
@@ -135,7 +135,7 @@
android:text="---" />
<TextView
android:id="@+id/appTv"
android:id="@+id/fragment_watch_app_status_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="---" />

View File

@@ -12,6 +12,12 @@
android:id="@+id/action_start_stop"
android:icon="@drawable/stop_server"
app:showAsAction="never|withText"
android:title="@string/restart_server"
android:enabled="false" />
<item
android:id="@+id/action_exit"
android:icon="@drawable/stop_server"
app:showAsAction="never|withText"
android:title="@string/start_stop_server" />
</group>
<group android:id="@+id/grp3">

View File

@@ -3,4 +3,6 @@
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="okBackgroundColor">#0000ff</color>
<color name="okTextColor">#ffffff</color>
</resources>

View File

@@ -2,7 +2,8 @@
<resources>
<string-array name="datasource_list">
<item>"Garmin Watch"</item>
<item>"Bluetooth Device"</item>
<item>"BangleJS (BLE)"</item>
<item>"PineTime (BLE2)"</item>
<item>"Pebble Watch"</item>
<item>"Phone Sensor (for testing)"</item>
<item>"Network (for Wifi Alerts)"</item>
@@ -10,6 +11,7 @@
<string-array name="datasource_list_values">
<item>"Garmin"</item>
<item>"BLE"</item>
<item>"BLE2"</item>
<item>"Pebble"</item>
<item>"Phone"</item>
<item>"Network"</item>

View File

@@ -2,5 +2,6 @@
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<!-- Default screen margins, per the Android Design guidelines. -->
</resources>

View File

@@ -3,11 +3,10 @@
<string name="app_name">OpenSeizureDetector</string>
<string name="changelog">
"\n
\mV4.1.11 - Fixed issue with data export crashing when lots of data requested. Added simple 'Fidget Detector' to detect if watch has fallen off the wrist. Changed target Android Version to 13 (SDK33) (Play Store policy).
\nV4.1.10 - Added warning if heart rate readings freeze and do not change for more than 1 minute.
\nV4.1.9 - Fixed problem with average heart rate alarm
Fixed issue with phone data source generating continuous alarms for Heart Rate or O2Sat
Fixed a small number of user reported issues (https://github.com/OpenSeizureDetector/Android_Pebble_SD/issues?q=is%3Aissue+milestone%3AV4.1.8)
\nV4.2 - Added support for PineTime and BangleJS Watches using Bluetooth data source.
\n - Added support for Version 2 of the Garmin watch app, which reduces battery drain
\n - Added new, swipeable user interface to simplify the main screen.
\n - various bug fixes and stability improvements
"</string>
<string name="UpgradeMsg">
Please enable the new &lt;b>Data Sharing&lt;/b> feature to help improve OpenSeizureDetector!&lt;br/>
@@ -48,7 +47,7 @@
<string name="WaitingForSeizureDetectorSettings">Waiting for Seizure Detector Settings...</string>
<string name="DataSource">DataSource</string>
<string name="AppTitleText">OpenSeizureDetector Android App Version </string>
<string name="ServerRunningOK">Server Running OK\n</string>
<string name="ServerRunningOK">Server Running OK</string>
<string name="AccessServerAt">Access Server at </string>
<string name="ServerStopped">Server Stopped</string>
<string name="Warning">WARNING</string>
@@ -59,7 +58,7 @@
<string name="HRAlarmOff">HR Alarm OFF</string>
<string name="WatchAppOK">Watch App OK</string>
<string name="WatchAppNotRunning">Watch App NOT Running</string>
<string name="WatchBatteryEquals">Watch Battery = </string>
<string name="WatchBatteryEquals">Batteries = </string>
<string name="PowerEquals">Power = </string>
<string name="SpectrumRatioEquals">Spectrum Ratio = </string>
<string name="Threshold">threshold</string>
@@ -100,7 +99,7 @@
<string name="AlarmThreshTitle">Alarm Threshold</string>
<string name="AlarmThreshSummary">Alarm Threshold (Default = 100)</string>
<string name="AlarmRatioThreshTitle">Alarm Ratio Threshold</string>
<string name="AlarmRatioThreshSummary">Alarm Ratio Threshold (Default = 50). Increase to reduce sensitivity.</string>
<string name="AlarmRatioThreshSummary">Alarm Ratio Threshold (Default = 57). Increase to reduce sensitivity.</string>
<string name="AlarmFreqMaxTitle">AlarmFreqMax (Hz)</string>
<string name="AlarmFreqMaxSummary">Maximum Frequency of ROI (Hz) (Default = 8 Hz)</string>
<string name="AlarmFreqMinTitle">AlarmFreqMin (Hz)</string>
@@ -116,7 +115,7 @@
<string name="BasicPrefTitle">Basic</string>
<string name="BasicPrefSummary">Basic Preferences</string>
<string name="accept_alarm">Accept Alarm</string>
<string name="start_stop_server">Start/Stop Server</string>
<string name="start_stop_server">Exit</string>
<string name="install_watch_app">Install Watch App</string>
<string name="test_alarm_beep">Test Alarm Beep</string>
<string name="test_warning_beep">Test Warning Beep</string>
@@ -459,7 +458,7 @@
<string name="NDATimeRemaining">"NDA Logging Time Remaining (hours): "</string>
<string name="stop_nda_menu_title">Stop NDA</string>
<string name="start_nda_menu_title">Start NDA</string>
<string name="seizure_probability">Seizure Probability (%)</string>
<string name="seizure_probability">Seizure Probability (from CNN) (%)</string>
<string name="eventid">EventId</string>
<string name="event_date">Event Date</string>
<string name="alarm_state">Alarm State</string>
@@ -503,6 +502,12 @@
<string name="error_exporting_data">*** ERROR Exporting Data ***</string>
<string name="HrFrozenTitle">Heart Rate measurement Frozen Warning</string>
<string name="HrFrozenSummary">Produce a fault warning if the heart rate measurement freezes and does not change for more than 1 minute.</string>
<string name="ml_sd_settings_title">Machine Learning Seizure Detector Settings</string>
<string name="ml_sd_threshold_summary">Movement Threshold (% standard deviation) to activate the Machine Learning Algorithm</string>
<string name="ml_sd_threshold_title">Movement Threshold (%std)</string>
<string name="ml_sd_modelid_title">AI Model ID</string>
<string name="ml_sd_modelid_summary">ID number of machine learning (AI) model to be used - users should not edit this, but use the model Manager page instead.</string>
<string name="title_activity_ml_model_manager">MlModelManager</string>
<string name="FidgetSettingsTitle">Fidget Detector Settings</string>
<string name="FidgetDetectorEnabledTitle">Enable Fidget Detector</string>
<string name="FidgetDetectorEnabledSummary">Generates a fault if no movement has been detected for a specified period (signifying the watch has been removed)</string>
@@ -510,4 +515,65 @@
<string name="FidgetDetectorThresholdTitle">Fidget Detector Threshold (%)</string>
<string name="FidgetDetectorPeriodTitle">Fidget Detector Period (minutes)</string>
<string name="FidgetDetectorPeriodSummary">A fault is generated if no movement (fidgets) are detected for more than the Fidget Detector Period.</string>
<!-- Strings used for fragments for navigation -->
<string name="first_fragment_label">First Fragment</string>
<string name="second_fragment_label">Second Fragment</string>
<string name="next">Next</string>
<string name="previous">Previous</string>
<string name="lorem_ipsum">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam in scelerisque sem. Mauris
volutpat, dolor id interdum ullamcorper, risus dolor egestas lectus, sit amet mattis purus
dui nec risus. Maecenas non sodales nisi, vel dictum dolor. Class aptent taciti sociosqu ad
litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse blandit eleifend
diam, vel rutrum tellus vulputate quis. Aliquam eget libero aliquet, imperdiet nisl a,
ornare ex. Sed rhoncus est ut libero porta lobortis. Fusce in dictum tellus.\n\n
Suspendisse interdum ornare ante. Aliquam nec cursus lorem. Morbi id magna felis. Vivamus
egestas, est a condimentum egestas, turpis nisl iaculis ipsum, in dictum tellus dolor sed
neque. Morbi tellus erat, dapibus ut sem a, iaculis tincidunt dui. Interdum et malesuada
fames ac ante ipsum primis in faucibus. Curabitur et eros porttitor, ultricies urna vitae,
molestie nibh. Phasellus at commodo eros, non aliquet metus. Sed maximus nisl nec dolor
bibendum, vel congue leo egestas.\n\n
Sed interdum tortor nibh, in sagittis risus mollis quis. Curabitur mi odio, condimentum sit
amet auctor at, mollis non turpis. Nullam pretium libero vestibulum, finibus orci vel,
molestie quam. Fusce blandit tincidunt nulla, quis sollicitudin libero facilisis et. Integer
interdum nunc ligula, et fermentum metus hendrerit id. Vestibulum lectus felis, dictum at
lacinia sit amet, tristique id quam. Cras eu consequat dui. Suspendisse sodales nunc ligula,
in lobortis sem porta sed. Integer id ultrices magna, in luctus elit. Sed a pellentesque
est.\n\n
Aenean nunc velit, lacinia sed dolor sed, ultrices viverra nulla. Etiam a venenatis nibh.
Morbi laoreet, tortor sed facilisis varius, nibh orci rhoncus nulla, id elementum leo dui
non lorem. Nam mollis ipsum quis auctor varius. Quisque elementum eu libero sed commodo. In
eros nisl, imperdiet vel imperdiet et, scelerisque a mauris. Pellentesque varius ex nunc,
quis imperdiet eros placerat ac. Duis finibus orci et est auctor tincidunt. Sed non viverra
ipsum. Nunc quis augue egestas, cursus lorem at, molestie sem. Morbi a consectetur ipsum, a
placerat diam. Etiam vulputate dignissim convallis. Integer faucibus mauris sit amet finibus
convallis.\n\n
Phasellus in aliquet mi. Pellentesque habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. In volutpat arcu ut felis sagittis, in finibus massa
gravida. Pellentesque id tellus orci. Integer dictum, lorem sed efficitur ullamcorper,
libero justo consectetur ipsum, in mollis nisl ex sed nisl. Donec maximus ullamcorper
sodales. Praesent bibendum rhoncus tellus nec feugiat. In a ornare nulla. Donec rhoncus
libero vel nunc consequat, quis tincidunt nisl eleifend. Cras bibendum enim a justo luctus
vestibulum. Fusce dictum libero quis erat maximus, vitae volutpat diam dignissim.
</string>
<string name="title_activity_main2">MainActivity2</string>
<string name="tab_text_1">Tab 1</string>
<string name="tab_text_2">Tab 2</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="use_new_ui_summary">Use new swipeable user interface</string>
<string name="use_new_ui_title">Use New User Interface</string>
<string name="heart_rate_algorithm">Heart Rate Algorithm</string>
<string name="heart_rate_history_bpm">"Heart Rate History (bpm): "</string>
<string name="minutes">minutes</string>
<string name="algorithms">"Algorithms: "</string>
<string name="battery_history">Battery History</string>
<string name="watch_batt_hist">Watch Battery History (%)</string>
<string name="ble_connected_warning_text">NOTE: Devices will not appear on this list if they are already connected - disconnect device from GadgetBridge etc. to allow it to be selected here</string>
<string name="BTPermissionWarning">Bluetooth Permissions Not Granted</string>
<string name="BTpermissions_required">Bluetooth Permissions Required</string>
<string name="BT_permissions_rationale">Bluetooth permissions are required to communicate with the bluetooth (BLE) data source</string>
<string name="restart_server">Restart Server</string>
</resources>

View File

@@ -1,11 +1,16 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">
<!-- Customize your theme here.
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:textColorPrimary">#a0a0a0</item>
-->
</style>
</resources>

View File

@@ -8,6 +8,11 @@
android:entryValues="@array/datasource_list_values"
android:defaultValue="Phone"
android:dialogTitle="@string/select_datasource_title" />
<CheckBoxPreference
android:defaultValue="true"
android:key="UseNewUi"
android:summary="@string/use_new_ui_summary"
android:title="@string/use_new_ui_title" />
<!--
<CheckBoxPreference
android:defaultValue="true"
@@ -50,13 +55,14 @@
android:key="OSDUrl"
android:summary="@string/remote_url_summary"
android:title="@string/remote_url_title" />
-->
<CheckBoxPreference
android:defaultValue="false"
android:key="PreventSleep"
android:summary="@string/prevent_sleep_summary"
android:title="@string/prevent_sleep_title" />
-->
<!--<EditTextPreference
android:defaultValue="1000"
android:key="UpdatePeriod"

View File

@@ -7,7 +7,7 @@
android:summary="@string/OsdAlarmEnabledSummary"
android:title="@string/OsdAlarmEnabledTitle" />
<CheckBoxPreference
android:defaultValue="true"
android:defaultValue="false"
android:key="CnnAlarmActive"
android:summary="@string/CnnAlarmEnabledSummary"
android:title="@string/CnnAlarmEnabledTitle" />
@@ -38,6 +38,11 @@
android:title="@string/fall_detect_active_title" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/SeizureDetectorSettingsTitle">
<CheckBoxPreference
android:defaultValue="true"
android:key="OsdAlarmActive"
android:summary="@string/OsdAlarmEnabledSummary"
android:title="@string/OsdAlarmEnabledTitle" />
<EditTextPreference
android:defaultValue="5"
android:key="WarnTime"
@@ -72,11 +77,31 @@
<EditTextPreference
android:defaultValue="5"
android:enabled="false"
android:key="SamplePeriod"
android:summary="@string/sample_period_summary"
android:title="@string/sample_period_title" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/ml_sd_settings_title">
<CheckBoxPreference
android:defaultValue="false"
android:key="CnnAlarmActive"
android:summary="@string/CnnAlarmEnabledSummary"
android:title="@string/CnnAlarmEnabledTitle" />
<EditTextPreference
android:defaultValue="5"
android:key="CnnAlarmThreshold"
android:summary="@string/ml_sd_threshold_summary"
android:title="@string/ml_sd_threshold_title" />
<EditTextPreference
android:defaultValue="1"
android:key="CnnModelId"
android:enabled="false"
android:summary="@string/ml_sd_modelid_summary"
android:title="@string/ml_sd_modelid_title" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/HeartRateAlarmSettingsTitle">
<CheckBoxPreference
android:defaultValue="false"
@@ -170,12 +195,12 @@
android:summary=""
android:title="@string/fall_detect_active_title" />
<EditTextPreference
android:defaultValue="200"
android:defaultValue="1500"
android:key="FallThreshMin"
android:summary=""
android:title="@string/fall_thresh_min_title" />
<EditTextPreference
android:defaultValue="1200"
android:defaultValue="3500"
android:key="FallThreshMax"
android:summary=""
android:title="@string/fall_thresh_max_title" />

View File

@@ -10,7 +10,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.0.2'
classpath 'com.android.tools.build:gradle:8.1.2'
classpath 'com.google.gms:google-services:4.3.15'
}
}

View File

@@ -0,0 +1,20 @@
BLE Data Source Specification
=============================
The BLE data source allows the use of devices which provide accelerometer and heart rate data as BLE services.
This document describes the services and characteristics that must be provided for the BLE data source
to work correclty.
Required Services and Characteristics
=====================================
| ID | UUID | Description |
|-----------------------------|--------------------------------------------|---------------------------------------------------------------------------------------------------------|
| ***SERV_OSD*** | ***000085e9-0000-1000-8000-00805f9b34fb*** | ***Bespoke OSD Service - contains several characteristics*** |
| CHAR_OSD_ACC_DATA | 000085e9-0001-1000-8000-00805f9b34fb | Acceleration data - array of 20 bytes representing acceleration vector magnitude at 25 Hz sample frequency |
| CHAR_OSD_BATT_DATA | 000085e9-0002-1000-8000-00805f9b34fb | Watch Battery remaining - one byte which is battery level in percent |
| CHAR_OSD_WATCH_ID | 000085e9-0003-1000-8000-00805f9b34fb | String identifier for watch (e.g. "BangleJs") |
| CHAR_OSD_WATCH_FW | 000085e9-0004-1000-8000-00805f9b34fb | String identifier for watch firmware version (e.g. "V0.10.0") |
| ***SERV_HEART_RATE *** | ***0000180d-0000-1000-8000-00805f9b34fb*** | ***Generic Heart Rate Service*** |
| CHAR_HEART_RATE_MEASUREMENT | 00002a37-0000-1000-8000-00805f9b34fb | Heart rate data. 2 bytes. First is ignored, second is heart rate in bpm |

Binary file not shown.

Binary file not shown.