V3.1.9 - quite a few changes - see Changelog.md

This commit is contained in:
Graham Jones
2019-06-22 11:41:58 +01:00
parent 54ce96aabb
commit 238b3c504f
16 changed files with 435 additions and 169 deletions

View File

@@ -4,6 +4,11 @@
V3.2.0 - (NEXT VERSION!) V3.2.0 - (NEXT VERSION!)
- Added neural network based data analysis. - Added neural network based data analysis.
V3.1.9 - 14jun2019
- Now requests READ_PHONE_STATE along with SMS permissions (required for some phones to send SMS messages)
- Fixed issue with Garmin Seizure Detector not producing warnings.
- Added faut pips for missing heart rate data if heart rate alarm active
V3.1.8 - 06jun2019 V3.1.8 - 06jun2019
- Added READ_PHONE_STATE permission which seems to be needed for some phones to send SMS (but not many). - Added READ_PHONE_STATE permission which seems to be needed for some phones to send SMS (but not many).

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="uk.org.openseizuredetector" package="uk.org.openseizuredetector"
android:versionCode="62" android:versionCode="63"
android:versionName="3.1.8" android:versionName="3.1.9"
> >
<!--android:allowBackup="false"--> <!--android:allowBackup="false"-->

View File

@@ -63,8 +63,6 @@ import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.utils.ValueFormatter; import com.github.mikephil.charting.utils.ValueFormatter;
import com.rohitss.uceh.UCEHandler; import com.rohitss.uceh.UCEHandler;
import static java.util.Objects.isNull;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
static final String TAG = "MainActivity"; static final String TAG = "MainActivity";
private int okColour = Color.BLUE; private int okColour = Color.BLUE;
@@ -435,15 +433,18 @@ public class MainActivity extends AppCompatActivity {
tv = (TextView) findViewById(R.id.pebbleTv); tv = (TextView) findViewById(R.id.pebbleTv);
if (mConnection.mSdServer.mSdData.mHRAlarmActive) { if (mConnection.mSdServer.mSdData.mHRAlarmActive) {
tv.setText("HR = " + mConnection.mSdServer.mSdData.mHR); tv.setText("HR = " + mConnection.mSdServer.mSdData.mHR);
if (!mConnection.mSdServer.mSdData.mHRAlarmStanding) { if (mConnection.mSdServer.mSdData.mHRAlarmStanding) {
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
} else {
tv.setBackgroundColor(alarmColour); tv.setBackgroundColor(alarmColour);
tv.setTextColor(alarmTextColour); tv.setTextColor(alarmTextColour);
} else if (mConnection.mSdServer.mSdData.mHRFaultStanding) {
tv.setBackgroundColor(warnColour);
tv.setTextColor(warnTextColour);
} else {
tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour);
} }
} else { } else {
if (mConnection.mSdServer.mSdData.pebbleConnected) { if (mConnection.mSdServer.mSdData.watchConnected) {
tv.setText("HR Alarm OFF"); tv.setText("HR Alarm OFF");
tv.setBackgroundColor(okColour); tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour); tv.setTextColor(okTextColour);
@@ -455,7 +456,7 @@ public class MainActivity extends AppCompatActivity {
} }
} }
tv = (TextView) findViewById(R.id.appTv); tv = (TextView) findViewById(R.id.appTv);
if (mConnection.mSdServer.mSdData.pebbleAppRunning) { if (mConnection.mSdServer.mSdData.watchAppRunning) {
tv.setText("Watch App OK"); tv.setText("Watch App OK");
tv.setBackgroundColor(okColour); tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour); tv.setTextColor(okTextColour);

View File

@@ -96,6 +96,7 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac
private final String[] SMS_PERMISSIONS = { private final String[] SMS_PERMISSIONS = {
Manifest.permission.SEND_SMS, Manifest.permission.SEND_SMS,
Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.READ_PHONE_STATE,
}; };

View File

@@ -0,0 +1,195 @@
/*
Android_Pebble_SD - a simple accelerometer based seizure detector that runs on a
Pebble smart watch (http://getpebble.com).
See http://openseizuredetector.org for more information.
Copyright Graham Jones, 2015, 2016, 2017, 2018, 2019.
This file is part of android_pebble_sd.
Android_pebble_sd is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Android_pebble_sd is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with android_pebble_sd. If not, see <http://www.gnu.org/licenses/>.
*/
package uk.org.openseizuredetector;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.jtransforms.fft.DoubleFFT_1D;
/**
* SdAnalyser implements the seizure detection algorithm on an Android Phone - it is used by SdDatasourceGarmin
*/
public class SdAnalyser {
private String TAG = "SdAnalyser";
private double mSampleFreq;
private double mAlarmFreqMin;
private double mAlarmFreqMax;
private double mSamplePeriod;
private double mWarnTime;
private double mAlarmTime;
private double mAlarmThresh;
private double mAlarmRatioThresh;
private double mFreqRes;
private int mAlarmCount;
private double mFreqCutoff;
private int mNSamp;
double roiPower;
double specPower;
double roiRatio;
SdAnalyser(double sampleFreq,
double alarmFreqMin,
double alarmFreqMax,
double samplePeriod,
double warnTime,
double alarmThresh,
double alarmRatioThresh) {
mSampleFreq = sampleFreq;
mAlarmFreqMin = alarmFreqMin;
mAlarmFreqMax = alarmFreqMax;
mSamplePeriod = samplePeriod;
mWarnTime = warnTime;
mAlarmThresh = alarmThresh;
mAlarmRatioThresh = alarmRatioThresh;
mFreqRes = 1.0 / mSamplePeriod;
mFreqCutoff = mSampleFreq / 2.0;
mNSamp = (int)(mSamplePeriod * mSampleFreq);
}
int freq2fftBin(double freq) {
int n = (int)(freq/mFreqRes);
return(n);
}
/**
* doAnalysis() - analyse the data if the accelerometer data array mAccData
* and populate the output data structure mSdData
*/
void calculateSpectralPowers(double[] rawData) {
// Set the frequency bounds for the analysis in fft output bin numbers.
int nMin = freq2fftBin(mAlarmFreqMin);
int nMax = freq2fftBin(mAlarmFreqMax);
int nFreqCutoff = freq2fftBin(mFreqCutoff);
DoubleFFT_1D fftDo = new DoubleFFT_1D(mNSamp);
double[] fft = new double[mNSamp * 2];
System.arraycopy(rawData, 0, fft, 0, mNSamp);
fftDo.realForward(fft);
// Calculate the whole spectrum power (well a value equivalent to it that avoids square root calculations
// and zero any readings that are above the frequency cutoff.
double specPower = 0;
for (int i = 1; i < mNSamp / 2; i++) {
if (i <= nFreqCutoff) {
specPower = specPower + getMagnitude(fft, i);
} else {
fft[2 * i] = 0.;
fft[2 * i + 1] = 0.;
}
}
specPower = specPower / (mNSamp / 2);
// Calculate the Region of Interest power and power ratio.
double roiPower = 0;
for (int i = nMin; i < nMax; i++) {
roiPower = roiPower + getMagnitude(fft, i);
}
roiPower = roiPower / (nMax - nMin);
double roiRatio = 10 * roiPower / specPower;
}
/**
* Calculate the magnitude of entry i in the fft array fft
*
* @param fft
* @param i
* @return magnitude ( Re*Re + Im*Im )
*/
double getMagnitude(double[] fft, int i) {
double mag;
mag = (fft[2 * i] * fft[2 * i] + fft[2 * i + 1] * fft[2 * i + 1]);
return mag;
}
// Force the data stored in this datasource to update in line with the JSON string encoded data provided.
// Used by webServer to update the GarminDatasource.
// Returns a message string that is passed back to the watch.
public String updateFromJSON(String jsonStr) {
String retVal = "undefined";
Log.v(TAG,"updateFromJSON - "+jsonStr);
try {
JSONObject mainObject = new JSONObject(jsonStr);
//JSONObject dataObject = mainObject.getJSONObject("dataObj");
JSONObject dataObject = mainObject;
String dataTypeStr = dataObject.getString("dataType");
Log.v(TAG,"updateFromJSON - dataType="+dataTypeStr);
if (dataTypeStr.equals("raw")) {
Log.v(TAG,"updateFromJSON - processing raw data");
try {
mSdData.mHR = dataObject.getDouble("HR");
} catch (JSONException e) {
// if we get 'null' HR (For example if the heart rate is not working)
mSdData.mHR = -1;
}
try {
mMute = dataObject.getInt("Mute");
} catch (JSONException e) {
// if we get 'null' HR (For example if the heart rate is not working)
mMute = 0;
}
JSONArray accelVals = dataObject.getJSONArray("data");
Log.v(TAG, "Received " + accelVals.length() + " acceleration values");
int i;
for (i = 0; i < accelVals.length(); i++) {
mSdData.rawData[i] = accelVals.getInt(i);
}
mSdData.mNsamp = accelVals.length();
//mNSamp = accelVals.length();
mWatchAppRunningCheck = true;
doAnalysis();
if (mSdData.haveSettings == false) {
retVal = "sendSettings";
} else {
retVal = "OK";
}
} else if (dataTypeStr.equals("settings")){
Log.v(TAG,"updateFromJSON - processing settings");
mSamplePeriod = (short)dataObject.getInt("analysisPeriod");
mSampleFreq = (short)dataObject.getInt("sampleFreq");
mSdData.batteryPc = (short)dataObject.getInt("battery");
Log.v(TAG,"updateFromJSON - mSamplePeriod="+mSamplePeriod+" mSampleFreq="+mSampleFreq);
mSdData.haveSettings = true;
mSdData.mSampleFreq = mSampleFreq;
mWatchAppRunningCheck = true;
retVal = "OK";
} else {
Log.e(TAG,"updateFromJSON - unrecognised dataType "+dataTypeStr);
retVal = "ERROR";
}
} catch (Exception e) {
Log.e(TAG,"updateFromJSON - Error Parsing JSON String - "+e.toString());
e.printStackTrace();
retVal = "ERROR";
}
return(retVal);
}
}

View File

@@ -29,10 +29,6 @@ import android.os.Parcel;
import android.text.format.Time; import android.text.format.Time;
import android.util.Log; import android.util.Log;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import org.json.JSONObject; import org.json.JSONObject;
import org.json.JSONArray; import org.json.JSONArray;
@@ -82,11 +78,12 @@ public class SdData implements Parcelable {
public long roiPower; public long roiPower;
public String alarmPhrase; public String alarmPhrase;
public int simpleSpec[]; public int simpleSpec[];
public boolean pebbleConnected = false; public boolean watchConnected = false;
public boolean pebbleAppRunning = false; public boolean watchAppRunning = false;
public boolean serverOK = false; public boolean serverOK = false;
public boolean mHRAlarmStanding = false; public boolean mHRAlarmStanding = false;
public boolean mHRFaultStanding = false;
public double mHR = 0; public double mHR = 0;
public SdData() { public SdData() {
@@ -116,8 +113,8 @@ public class SdData implements Parcelable {
specPower = jo.optInt("specPower"); specPower = jo.optInt("specPower");
roiPower = jo.optInt("roiPower"); roiPower = jo.optInt("roiPower");
batteryPc = jo.optInt("batteryPc"); batteryPc = jo.optInt("batteryPc");
pebbleConnected = jo.optBoolean("pebbleConnected"); watchConnected = jo.optBoolean("watchConnected");
pebbleAppRunning = jo.optBoolean("pebbleAppRunning"); watchAppRunning = jo.optBoolean("watchAppRunning");
alarmState = jo.optInt("alarmState"); alarmState = jo.optInt("alarmState");
alarmPhrase = jo.optString("alarmPhrase"); alarmPhrase = jo.optString("alarmPhrase");
alarmThresh = jo.optInt("alarmThresh"); alarmThresh = jo.optInt("alarmThresh");
@@ -159,8 +156,8 @@ public class SdData implements Parcelable {
jsonObj.put("specPower", specPower); jsonObj.put("specPower", specPower);
jsonObj.put("roiPower", roiPower); jsonObj.put("roiPower", roiPower);
jsonObj.put("batteryPc", batteryPc); jsonObj.put("batteryPc", batteryPc);
jsonObj.put("pebbleConnected", pebbleConnected); jsonObj.put("watchConnected", watchConnected);
jsonObj.put("pebbleAppRunning", pebbleAppRunning); jsonObj.put("watchAppRunning", watchAppRunning);
jsonObj.put("haveSettings", haveSettings); jsonObj.put("haveSettings", haveSettings);
jsonObj.put("alarmState", alarmState); jsonObj.put("alarmState", alarmState);
jsonObj.put("alarmPhrase", alarmPhrase); jsonObj.put("alarmPhrase", alarmPhrase);

View File

@@ -683,8 +683,8 @@ public class SdDataSourceAw extends SdDataSource {
tdiff = (tnow.toMillis(false) - mStatusTime.toMillis(false)); tdiff = (tnow.toMillis(false) - mStatusTime.toMillis(false));
Log.v(TAG, "getStatus() - mWatchAppRunningCheck=" + mWatchAppRunningCheck + " tdiff=" + tdiff); Log.v(TAG, "getStatus() - mWatchAppRunningCheck=" + mWatchAppRunningCheck + " tdiff=" + tdiff);
// Check we are actually connected to the pebble. // Check we are actually connected to the pebble.
mSdData.pebbleConnected = PebbleKit.isWatchConnected(mContext); mSdData.watchConnected = PebbleKit.isWatchConnected(mContext);
if (!mSdData.pebbleConnected) mWatchAppRunningCheck = false; if (!mSdData.watchConnected) mWatchAppRunningCheck = false;
// And is the pebble_sd app running? // And is the pebble_sd app running?
// set mWatchAppRunningCheck has been false for more than 10 seconds // set mWatchAppRunningCheck has been false for more than 10 seconds
// the app is not talking to us // the app is not talking to us
@@ -692,7 +692,7 @@ public class SdDataSourceAw extends SdDataSource {
if (!mWatchAppRunningCheck && if (!mWatchAppRunningCheck &&
(tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) { (tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) {
Log.v(TAG, "getStatus() - tdiff = " + tdiff); Log.v(TAG, "getStatus() - tdiff = " + tdiff);
mSdData.pebbleAppRunning = false; mSdData.watchAppRunning = false;
//Log.v(TAG, "getStatus() - Pebble App Not Running - Attempting to Re-Start"); //Log.v(TAG, "getStatus() - Pebble App Not Running - Attempting to Re-Start");
//mUtil.writeToSysLogFile("SdDataSourceAw.getStatus() - Pebble App not Running - Attempting to Re-Start"); //mUtil.writeToSysLogFile("SdDataSourceAw.getStatus() - Pebble App not Running - Attempting to Re-Start");
//startWatchApp(); //startWatchApp();
@@ -709,7 +709,7 @@ public class SdDataSourceAw extends SdDataSource {
Log.v(TAG, "getStatus() - Waiting for mFaultTimerPeriod before issuing audible warning..."); Log.v(TAG, "getStatus() - Waiting for mFaultTimerPeriod before issuing audible warning...");
} }
} else { } else {
mSdData.pebbleAppRunning = true; mSdData.watchAppRunning = true;
} }
// if we have confirmation that the app is running, reset the // if we have confirmation that the app is running, reset the

View File

@@ -28,32 +28,22 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.os.BatteryManager;
import android.os.Handler; import android.os.Handler;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.text.format.Time; import android.text.format.Time;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import com.getpebble.android.kit.PebbleKit;
import com.getpebble.android.kit.util.PebbleDictionary;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.jtransforms.fft.DoubleFFT_1D; import org.jtransforms.fft.DoubleFFT_1D;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.Arrays; import java.util.Arrays;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.UUID;
import static java.lang.Long.parseLong; import static java.lang.Long.parseLong;
import static java.lang.Math.sqrt;
/** /**
@@ -66,7 +56,7 @@ public class SdDataSourceGarmin extends SdDataSource {
private Handler mHandler = new Handler(); private Handler mHandler = new Handler();
private Timer mStatusTimer; private Timer mStatusTimer;
private Timer mSettingsTimer; private Timer mSettingsTimer;
private Timer mAlarmCheckTimer; private Timer mFaultCheckTimer;
private Time mDataStatusTime; private Time mDataStatusTime;
private boolean mWatchAppRunningCheck = false; private boolean mWatchAppRunningCheck = false;
private int mAppRestartTimeout = 10; // Timeout before re-starting watch app (sec) if we have not received private int mAppRestartTimeout = 10; // Timeout before re-starting watch app (sec) if we have not received
@@ -145,14 +135,14 @@ public class SdDataSourceGarmin extends SdDataSource {
Log.v(TAG, "start(): status timer already running."); Log.v(TAG, "start(): status timer already running.");
mUtil.writeToSysLogFile("SdDataSourceGarmin.start() - status timer already running??"); mUtil.writeToSysLogFile("SdDataSourceGarmin.start() - status timer already running??");
} }
if (mAlarmCheckTimer == null) { if (mFaultCheckTimer == null) {
Log.v(TAG, "start(): starting alarm check timer"); Log.v(TAG, "start(): starting alarm check timer");
mUtil.writeToSysLogFile("SdDataSourceGarmin.start() - starting alarm check timer"); mUtil.writeToSysLogFile("SdDataSourceGarmin.start() - starting alarm check timer");
mAlarmCheckTimer = new Timer(); mFaultCheckTimer = new Timer();
mAlarmCheckTimer.schedule(new TimerTask() { mFaultCheckTimer.schedule(new TimerTask() {
@Override @Override
public void run() { public void run() {
alarmCheck(); faultCheck();
} }
}, 0, 1000); }, 0, 1000);
} else { } else {
@@ -206,12 +196,12 @@ public class SdDataSourceGarmin extends SdDataSource {
mSettingsTimer = null; mSettingsTimer = null;
} }
// Stop the alarm check timer // Stop the alarm check timer
if (mAlarmCheckTimer != null) { if (mFaultCheckTimer != null) {
Log.v(TAG, "stop(): cancelling alarm check timer"); Log.v(TAG, "stop(): cancelling alarm check timer");
mUtil.writeToSysLogFile("SdDataSourceGarmin.stop() - cancelling alarm check timer"); mUtil.writeToSysLogFile("SdDataSourceGarmin.stop() - cancelling alarm check timer");
mAlarmCheckTimer.cancel(); mFaultCheckTimer.cancel();
mAlarmCheckTimer.purge(); mFaultCheckTimer.purge();
mAlarmCheckTimer = null; mFaultCheckTimer = null;
} }
} catch (Exception e) { } catch (Exception e) {
@@ -467,7 +457,10 @@ public class SdDataSourceGarmin extends SdDataSource {
fft[2*i+1] = 0.; fft[2*i+1] = 0.;
} }
} }
//Log.v(TAG,"specPower = "+specPower);
//specPower = specPower/(mSdData.mNsamp/2);
specPower = specPower/mSdData.mNsamp/2; specPower = specPower/mSdData.mNsamp/2;
//Log.v(TAG,"specPower = "+specPower);
// Calculate the Region of Interest power and power ratio. // Calculate the Region of Interest power and power ratio.
double roiPower = 0; double roiPower = 0;
@@ -489,8 +482,6 @@ public class SdDataSourceGarmin extends SdDataSource {
simpleSpec[ifreq] = simpleSpec[ifreq] / (binMax-binMin); simpleSpec[ifreq] = simpleSpec[ifreq] / (binMax-binMin);
} }
checkFall();
// Populate the mSdData structure to communicate with the main SdServer service. // Populate the mSdData structure to communicate with the main SdServer service.
mDataStatusTime.setToNow(); mDataStatusTime.setToNow();
mSdData.specPower = (long)specPower / ACCEL_SCALE_FACTOR; mSdData.specPower = (long)specPower / ACCEL_SCALE_FACTOR;
@@ -512,15 +503,102 @@ public class SdDataSourceGarmin extends SdDataSource {
// Because we have received data, set flag to show watch app running. // Because we have received data, set flag to show watch app running.
mWatchAppRunningCheck = true; mWatchAppRunningCheck = true;
// Check this data to see if it represents an alarm state.
alarmCheck();
hrCheck();
fallCheck();
muteCheck();
mSdDataReceiver.onSdDataReceived(mSdData); // and tell SdServer we have received data. mSdDataReceiver.onSdDataReceived(mSdData); // and tell SdServer we have received data.
} }
/****************************************************************
* checkAlarm() - checks the current accelerometer data and uses
* historical data to determine if we are in a fault, warning or ok
* state.
* Sets mSdData.alarmState and mSdData.hrAlarmStanding
*/
private void alarmCheck() {
boolean inAlarm;
Log.v(TAG, "alarmCheck()");
// Is the current set of data representing an alarm state?
if ((mSdData.roiPower > mAlarmThresh) && (10 * (mSdData.roiPower / mSdData.specPower) > mAlarmRatioThresh)) {
inAlarm = true;
} else {
inAlarm = false;
}
// set the alarmState to Alarm, Warning or OK, depending on the current state and previous ones.
if (inAlarm) {
mAlarmCount += mSamplePeriod;
if (mAlarmCount > mAlarmTime) {
// full alarm
mSdData.alarmState = 2;
} else if (mAlarmCount > mWarnTime) {
// warning
mSdData.alarmState = 1;
}
} else {
// If we are not in an ALARM state, revert back to WARNING, otherwise
// revert back to OK.
if (mSdData.alarmState == 2) {
// revert to warning
mSdData.alarmState = 1;
mAlarmCount = mWarnTime + 1; // pretend we have only just entered warning state.
} else {
// revert to OK
mSdData.alarmState = 0;
mAlarmCount = 0;
}
}
Log.v(TAG, "alarmCheck(): inAlarm=" + inAlarm + ", alarmState = " + mSdData.alarmState + " alarmCount=" + mAlarmCount + " mAlarmTime=" + mAlarmTime);
}
public void muteCheck() {
if (mMute != 0) {
Log.v(TAG, "Mute Active - setting alarms to mute");
mSdData.alarmState = 6;
mSdData.alarmPhrase = "MUTE";
mSdData.mHRAlarmStanding = false;
}
}
/**
* hrCheck - check the Heart rate data in mSdData to see if it represents an alarm condition.
* Sets mSdData.mHRAlarmStanding
*/
public void hrCheck() {
Log.v(TAG, "hrCheck()");
/* Check Heart Rate against alarm settings */
if (mSdData.mHRAlarmActive) {
if (mSdData.mHR < 0) {
Log.i(TAG,"Heart Rate Fault (HR<0)");
mSdData.mHRFaultStanding = true;
mSdData.mHRAlarmStanding = false;
}
else if ((mSdData.mHR > mSdData.mHRTreshMax) || (mSdData.mHR < mSdData.mHRThreshMin)) {
Log.i(TAG, "Heart Rate Abnormal - " + mSdData.mHR + " bpm");
mSdData.mHRFaultStanding = false;
mSdData.mHRAlarmStanding = true;
}
else {
mSdData.mHRFaultStanding = false;
mSdData.mHRAlarmStanding = false;
}
}
}
/**************************************************************** /****************************************************************
* Simple threshold analysis to chech for fall. * Simple threshold analysis to chech for fall.
* Called from clock_tick_handler() * Called from clock_tick_handler()
*/ */
public void checkFall() { public void fallCheck() {
int i,j; int i,j;
double minAcc, maxAcc; double minAcc, maxAcc;
@@ -575,7 +653,7 @@ public class SdDataSourceGarmin extends SdDataSource {
Log.v(TAG, "getStatus() - mWatchAppRunningCheck=" + mWatchAppRunningCheck + " tdiff=" + tdiff); Log.v(TAG, "getStatus() - mWatchAppRunningCheck=" + mWatchAppRunningCheck + " tdiff=" + tdiff);
Log.v(TAG,"getStatus() - tdiff="+tdiff+", mDataUpatePeriod="+mDataUpdatePeriod+", mAppRestartTimeout="+mAppRestartTimeout); Log.v(TAG,"getStatus() - tdiff="+tdiff+", mDataUpatePeriod="+mDataUpdatePeriod+", mAppRestartTimeout="+mAppRestartTimeout);
mSdData.pebbleConnected = true; // We can't check connection for passive network connection, so set it to true to avoid errors. mSdData.watchConnected = true; // We can't check connection for passive network connection, so set it to true to avoid errors.
// And is the watch app running? // And is the watch app running?
// set mWatchAppRunningCheck has been false for more than 10 seconds // set mWatchAppRunningCheck has been false for more than 10 seconds
// the app is not talking to us // the app is not talking to us
@@ -583,7 +661,7 @@ public class SdDataSourceGarmin extends SdDataSource {
if (!mWatchAppRunningCheck && if (!mWatchAppRunningCheck &&
(tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) { (tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) {
Log.v(TAG, "getStatus() - tdiff = " + tdiff); Log.v(TAG, "getStatus() - tdiff = " + tdiff);
mSdData.pebbleAppRunning = false; mSdData.watchAppRunning = false;
// Only make audible warning beep if we have not received data for more than mFaultTimerPeriod seconds. // Only make audible warning beep if we have not received data for more than mFaultTimerPeriod seconds.
if (tdiff > (mDataUpdatePeriod + mFaultTimerPeriod) * 1000) { if (tdiff > (mDataUpdatePeriod + mFaultTimerPeriod) * 1000) {
Log.v(TAG, "getStatus() - Watch App Not Running"); Log.v(TAG, "getStatus() - Watch App Not Running");
@@ -596,7 +674,7 @@ public class SdDataSourceGarmin extends SdDataSource {
Log.v(TAG, "getStatus() - Waiting for mFaultTimerPeriod before issuing audible warning..."); Log.v(TAG, "getStatus() - Waiting for mFaultTimerPeriod before issuing audible warning...");
} }
} else { } else {
mSdData.pebbleAppRunning = true; mSdData.watchAppRunning = true;
} }
// if we have confirmation that the app is running, reset the // if we have confirmation that the app is running, reset the
@@ -612,71 +690,21 @@ public class SdDataSourceGarmin extends SdDataSource {
} }
/** /**
* alarmCheck - determines alarm state based on seizure detector data SdData. Called every second. * faultCheck - determines alarm state based on seizure detector data SdData. Called every second.
*/ */
private void alarmCheck() { private void faultCheck() {
boolean inAlarm;
Time tnow = new Time(Time.getCurrentTimezone()); Time tnow = new Time(Time.getCurrentTimezone());
long tdiff; long tdiff;
tnow.setToNow(); tnow.setToNow();
// get time since the last data was received from the watch. // get time since the last data was received from the watch.
tdiff = (tnow.toMillis(false) - mDataStatusTime.toMillis(false)); tdiff = (tnow.toMillis(false) - mDataStatusTime.toMillis(false));
Log.v(TAG, "alarmCheck() - tdiff=" + tdiff + ", mDataUpatePeriod=" + mDataUpdatePeriod + ", mAppRestartTimeout=" + mAppRestartTimeout Log.v(TAG, "faultCheck() - tdiff=" + tdiff + ", mDataUpatePeriod=" + mDataUpdatePeriod + ", mAppRestartTimeout=" + mAppRestartTimeout
+ ", combined = " + (mDataUpdatePeriod + mAppRestartTimeout) * 1000); + ", combined = " + (mDataUpdatePeriod + mAppRestartTimeout) * 1000);
if (!mWatchAppRunningCheck && if (!mWatchAppRunningCheck &&
(tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) { (tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) {
Log.v(TAG, "alarmCheck() - watch app not running so not doing anything"); Log.v(TAG, "faultCheck() - watch app not running so not doing anything");
mAlarmCount = 0; mAlarmCount = 0;
} else {
Log.v(TAG, "alarmCheck()");
if ((mSdData.roiPower > mAlarmThresh) && (10 * (mSdData.roiPower / mSdData.specPower) > mAlarmRatioThresh)) {
inAlarm = true;
} else {
inAlarm = false;
}
if (inAlarm) {
mAlarmCount += mSamplePeriod;
if (mAlarmCount > mAlarmTime) {
// full alarm
mSdData.alarmState = 2;
} else if (mAlarmCount > mWarnTime) {
// warning
mSdData.alarmState = 1;
}
} else {
// If we are not in an ALARM state, revert back to WARNING, otherwise
// revert back to OK.
if (mSdData.alarmState == 2) {
// revert to warning
mSdData.alarmState = 1;
mAlarmCount = mWarnTime + 1; // pretend we have only just entered warning state.
} else {
// revert to OK
mSdData.alarmState = 0;
mAlarmCount = 0;
}
}
Log.v(TAG, "inAlarm=" + inAlarm + ", alarmState = " + mSdData.alarmState + " alarmCount=" + mAlarmCount + " mAlarmTime=" + mAlarmTime);
/* Check Heart Rate against alarm settings */
if (mSdData.mHRAlarmActive) {
Log.v(TAG,"Checking HR Alarm");
if ((mSdData.mHR > mSdData.mHRTreshMax) || (mSdData.mHR < mSdData.mHRThreshMin)) {
Log.i(TAG, "Heart Rate Abnormal - " + mSdData.mHR + " bpm");
mSdData.mHRAlarmStanding = true;
} else {
mSdData.mHRAlarmStanding = false;
}
}
if (mMute != 0) {
Log.v(TAG,"Mute Active - setting alarms to mute");
mSdData.alarmState = 6;
mSdData.alarmPhrase = "MUTE";
mSdData.mHRAlarmStanding = false;
}
} }
} }

View File

@@ -2,8 +2,6 @@ package uk.org.openseizuredetector;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Handler; import android.os.Handler;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
@@ -128,8 +126,8 @@ public class SdDataSourceNetwork extends SdDataSource {
if (result.startsWith("Unable to retrieve web page")) { 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.serverOK = false;
sdData.pebbleConnected = false; sdData.watchConnected = false;
sdData.pebbleAppRunning = false; sdData.watchAppRunning = false;
sdData.alarmState = ALARM_STATE_NETFAULT; sdData.alarmState = ALARM_STATE_NETFAULT;
sdData.alarmPhrase = "Warning - No Connection to Server"; 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());
@@ -147,8 +145,8 @@ public class SdDataSourceNetwork extends SdDataSource {
} catch (IOException e) { } catch (IOException e) {
sdData.serverOK = false; sdData.serverOK = false;
sdData.pebbleConnected = false; sdData.watchConnected = false;
sdData.pebbleAppRunning = false; sdData.watchAppRunning = false;
sdData.alarmState = ALARM_STATE_NETFAULT; sdData.alarmState = ALARM_STATE_NETFAULT;
sdData.alarmPhrase = "Warning - No Connection to Server"; sdData.alarmPhrase = "Warning - No Connection to Server";
Log.v(TAG,"doInBackground(): IOException - "+e.toString()); Log.v(TAG,"doInBackground(): IOException - "+e.toString());

View File

@@ -37,11 +37,6 @@ import android.widget.Toast;
import com.getpebble.android.kit.PebbleKit; import com.getpebble.android.kit.PebbleKit;
import com.getpebble.android.kit.util.PebbleDictionary; import com.getpebble.android.kit.util.PebbleDictionary;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.nio.IntBuffer; import java.nio.IntBuffer;
@@ -679,8 +674,8 @@ public class SdDataSourcePebble extends SdDataSource {
tdiff = (tnow.toMillis(false) - mPebbleStatusTime.toMillis(false)); tdiff = (tnow.toMillis(false) - mPebbleStatusTime.toMillis(false));
Log.v(TAG, "getStatus() - mPebbleAppRunningCheck=" + mPebbleAppRunningCheck + " tdiff=" + tdiff); Log.v(TAG, "getStatus() - mPebbleAppRunningCheck=" + mPebbleAppRunningCheck + " tdiff=" + tdiff);
// Check we are actually connected to the pebble. // Check we are actually connected to the pebble.
mSdData.pebbleConnected = PebbleKit.isWatchConnected(mContext); mSdData.watchConnected = PebbleKit.isWatchConnected(mContext);
if (!mSdData.pebbleConnected) mPebbleAppRunningCheck = false; if (!mSdData.watchConnected) mPebbleAppRunningCheck = false;
// And is the pebble_sd app running? // And is the pebble_sd app running?
// set mPebbleAppRunningCheck has been false for more than 10 seconds // set mPebbleAppRunningCheck has been false for more than 10 seconds
// the app is not talking to us // the app is not talking to us
@@ -688,7 +683,7 @@ public class SdDataSourcePebble extends SdDataSource {
if (!mPebbleAppRunningCheck && if (!mPebbleAppRunningCheck &&
(tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) { (tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) {
Log.v(TAG, "getStatus() - tdiff = " + tdiff); Log.v(TAG, "getStatus() - tdiff = " + tdiff);
mSdData.pebbleAppRunning = false; mSdData.watchAppRunning = false;
//Log.v(TAG, "getStatus() - Pebble App Not Running - Attempting to Re-Start"); //Log.v(TAG, "getStatus() - Pebble App Not Running - Attempting to Re-Start");
//mUtil.writeToSysLogFile("SdDataSourcePebble.getStatus() - Pebble App not Running - Attempting to Re-Start"); //mUtil.writeToSysLogFile("SdDataSourcePebble.getStatus() - Pebble App not Running - Attempting to Re-Start");
//startWatchApp(); //startWatchApp();
@@ -705,7 +700,7 @@ public class SdDataSourcePebble extends SdDataSource {
Log.v(TAG, "getStatus() - Waiting for mFaultTimerPeriod before issuing audible warning..."); Log.v(TAG, "getStatus() - Waiting for mFaultTimerPeriod before issuing audible warning...");
} }
} else { } else {
mSdData.pebbleAppRunning = true; mSdData.watchAppRunning = true;
} }
// if we have confirmation that the app is running, reset the // if we have confirmation that the app is running, reset the

View File

@@ -629,7 +629,7 @@ public class SdServer extends Service implements SdDataReceiver {
} }
// Fault // Fault
if ((sdData.alarmState) == 4 || (sdData.alarmState == 7)) { if ((sdData.alarmState) == 4 || (sdData.alarmState == 7) || (sdData.mHRFaultStanding)) {
sdData.alarmPhrase = "FAULT"; sdData.alarmPhrase = "FAULT";
writeAlarmToSD(); writeAlarmToSD();
faultWarningBeep(); faultWarningBeep();

View File

@@ -101,7 +101,7 @@ public class SdServiceConnection implements ServiceConnection {
public boolean pebbleConnected() { public boolean pebbleConnected() {
if (mSdServer!=null) { if (mSdServer!=null) {
if (mSdServer.mSdData!=null) { if (mSdServer.mSdData!=null) {
if (mSdServer.mSdData.pebbleConnected) { if (mSdServer.mSdData.watchConnected) {
return true; return true;
} }
} }
@@ -116,7 +116,7 @@ public class SdServiceConnection implements ServiceConnection {
public boolean pebbleAppRunning() { public boolean pebbleAppRunning() {
if (mSdServer!=null) { if (mSdServer!=null) {
if (mSdServer.mSdData!=null) { if (mSdServer.mSdData!=null) {
if (mSdServer.mSdData.pebbleAppRunning) { if (mSdServer.mSdData.watchAppRunning) {
return true; return true;
} }
} }

View File

@@ -340,7 +340,7 @@ public class StartupActivity extends Activity {
// Is Pebble Watch App Running? // Is Pebble Watch App Running?
tv = (TextView) findViewById(R.id.textItem4); tv = (TextView) findViewById(R.id.textItem4);
pb = (ProgressBar) findViewById(R.id.progressBar4); pb = (ProgressBar) findViewById(R.id.progressBar4);
if (mConnection.pebbleAppRunning()) { if (mConnection.watchAppRunning()) {
tv.setText("Watch App Running OK"); tv.setText("Watch App Running OK");
tv.setBackgroundColor(okColour); tv.setBackgroundColor(okColour);
tv.setTextColor(okTextColour); tv.setTextColor(okTextColour);
@@ -506,6 +506,7 @@ public class StartupActivity extends Activity {
+ "\n V3.1.5 - Added repeat alarm beeps during SMS delay to alert user.." + "\n V3.1.5 - Added repeat alarm beeps during SMS delay to alert user.."
+ "\n V3.1.6 - Made Cancel Audible button inhibit sending SMS alarms as well as audible beeps" + "\n V3.1.6 - Made Cancel Audible button inhibit sending SMS alarms as well as audible beeps"
+ "\n V3.1.8 - Added READ_PHONE_STATE permission, which seems to be needed for some phones" + "\n V3.1.8 - Added READ_PHONE_STATE permission, which seems to be needed for some phones"
+ "\n V3.1.9 - Fixed issue with Garmin Seizure Detector not producing warnings. Added faut pips for missing heart rate data if heart rate alarm active"
); );
// This makes the links display as links, but they do not respond to clicks for some reason... // This makes the links display as links, but they do not respond to clicks for some reason...
Linkify.addLinks(s, Linkify.ALL); Linkify.addLinks(s, Linkify.ALL);
@@ -540,6 +541,7 @@ public class StartupActivity extends Activity {
+ "\n V3.1.5 - Added repeat alarm beeps during SMS delay to alert user.." + "\n V3.1.5 - Added repeat alarm beeps during SMS delay to alert user.."
+ "\n V3.1.6 - Made Cancel Audible button inhibit sending SMS alarms as well as audible beeps" + "\n V3.1.6 - Made Cancel Audible button inhibit sending SMS alarms as well as audible beeps"
+ "\n V3.1.8 - Added READ_PHONE_STATE permission, which seems to be needed for some phones" + "\n V3.1.8 - Added READ_PHONE_STATE permission, which seems to be needed for some phones"
+ "\n V3.1.9 - Fixed issue with Garmin Seizure Detector not producing warnings. Added faut pips for missing heart rate data if heart rate alarm active"
+ "\n " + "\n "
); );
// This makes the links display as links, but they do not respond to clicks for some reason... // This makes the links display as links, but they do not respond to clicks for some reason...

View File

@@ -1,46 +0,0 @@
package uk.org.openseizuredetector;
import android.content.Context;
import android.util.EventLog;
import android.util.Log;
import junit.framework.TestCase;
import android.test.mock.MockContext;
import org.junit.Test;
import org.mockito.internal.exceptions.ExceptionIncludingMockitoWarnings;
import uk.org.openseizuredetector.EventLogManager.EventLogManager;
import uk.org.openseizuredetector.EventLogManager.LogEntryModel;
/**
* Created by graham on 12/05/16.
*/
public class EventLogManagerTest extends TestCase {
private final static String TAG = "EventLogManagerTest";
Context mContext;
protected void setUp() throws Exception {
super.setUp();
Log.v(TAG,"setUp()");
mContext = new MockContext();
}
@Test
public void testOpenDb() throws Exception {
Log.v(TAG,"testOpenDb()");
EventLogManager em = new EventLogManager(mContext);
assertNotNull(em);
LogEntryModel lem = new LogEntryModel();
//lem.setDate(new Date());
lem.setNote("Test Entry");
lem.setDataJSON("[]");
lem.setAlarmState(1);
em.addRow(lem);
}
}

View File

@@ -0,0 +1,90 @@
package uk.org.openseizuredetector;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class SdAnalyserTest {
public SdAnalyser sda;
public String alarmJSON = "{ dataType: 'raw', " +
"data: [" +
"1644, 1316, 1144, 1332, 1716, 1716, 1392, 1148, 1276, 1660, " +
"1716, 1496, 1196, 1232, 1572, 1684, 1552, 1236, 1228, 1528, " +
"1648, 1572, 1268, 1208, 1492, 1680, 1596, 1272, 1192, 1424, " +
"1668, 1636, 1300, 1200, 1356, 1652, 1684, 1420, 1208, 1304, " +
"1620, 1672, 1448, 1232, 1248, 1536, 1676, 1540, 1256, 1244, " +
"1544, 1644, 1512, 1252, 1236, 1504, 1684, 1540, 1252, 1200, " +
"1436, 1664, 1624, 1344, 1204, 1396, 1616, 1596, 1344, 1216, " +
"1368, 1648, 1660, 1388, 1220, 1316, 1588, 1672, 1460, 1232, " +
"1256, 1580, 1672, 1500, 1256, 1288, 1540, 1688, 1516, 1252, " +
"1212, 1464, 1684, 1584, 1288, 1224, 1468, 1692, 1616, 1316, " +
"1188, 1360, 1680, 1724, 1424, 1192, 1224, 1556, 1744, 1588, " +
"1260, 1220, 1472, 1692, 1608, 1328, 1192, 1412, 1668, 1656, " +
"1356, 1216, 1304, 1636, 1712], " +
"HR:54, " +
"Mute:0 " +
"}";
private String okJSON = "{ " +
"dataType: 'raw', " +
"data: [" +
"1140, 1188, 1144, 1172, 1228, 1212, 1236, 1236, 1256, 1320, " +
"1316, 1280, 1240, 1280, 1324, 1284, 1292, 1268, 1284, 1276, " +
"1296, 1324, 1308, 1288, 1304, 1276, 1304, 1304, 1276, 1296, " +
"1280, 1284, 1296, 1300, 1284, 1288, 1296, 1284, 1300, 1280, " +
"1300, 1292, 1276, 1304, 1276, 1316, 1280, 1288, 1296, 1280, " +
"1284, 1272, 1300, 1284, 1288, 1292, 1276, 1296, 1276, 1292, " +
"1280, 1284, 1284, 1284, 1284, 1284, 1288, 1284, 1304, 1284, " +
"1288, 1280, 1296, 1284, 1292, 1296, 1280, 1276, 1288, 1296, " +
"1276, 1292, 1288, 1276, 1288, 1276, 1272, 1272, 1292, 1284, " +
"1292, 1288, 1280, 1284, 1284, 1268, 1288, 1268, 1276, 1300, " +
"1268, 1292, 1292, 1304, 1288, 1284, 1280, 1276, 1288, 1280, " +
"1300, 1288, 1320, 1268, 1288, 1280, 1304, 1280, 1280, 1288, " +
"1292, 1308, 1268, 1292, 1280], " +
"HR:57, " +
"Mute:0 " +
"}";
@Before
public void setUp() throws Exception {
sda = new SdAnalyser(25.0,
3.0,
8.0,
5.0,
5.0,
100.0,
54.0
);
}
@After
public void tearDown() throws Exception {
}
@Test
public void freq2fftBin() {
int n;
n = sda.freq2fftBin(0.0);
assertEquals(0,n);
n = sda.freq2fftBin(5.0);
assertEquals(25,n);
}
@Test
public void getMagnitude() {
double[] fft = {1, 1,
2, 1,
2, 2};
double m;
m = sda.getMagnitude(fft,0);
assertEquals(2.0, m, m * 1e-4);
m = sda.getMagnitude(fft,1);
assertEquals(5.0, m, m * 1e-4);
m = sda.getMagnitude(fft,2);
assertEquals(8.0, m, m * 1e-4);
}
}

View File

@@ -9,7 +9,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.4.0' classpath 'com.android.tools.build:gradle:3.4.1'
} }
} }
allprojects { allprojects {