V3.1.9 - quite a few changes - see Changelog.md
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="uk.org.openseizuredetector"
|
||||
android:versionCode="62"
|
||||
android:versionName="3.1.8"
|
||||
android:versionCode="63"
|
||||
android:versionName="3.1.9"
|
||||
>
|
||||
<!--android:allowBackup="false"-->
|
||||
|
||||
|
||||
@@ -63,8 +63,6 @@ import com.github.mikephil.charting.data.BarEntry;
|
||||
import com.github.mikephil.charting.utils.ValueFormatter;
|
||||
import com.rohitss.uceh.UCEHandler;
|
||||
|
||||
import static java.util.Objects.isNull;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
static final String TAG = "MainActivity";
|
||||
private int okColour = Color.BLUE;
|
||||
@@ -435,15 +433,18 @@ public class MainActivity extends AppCompatActivity {
|
||||
tv = (TextView) findViewById(R.id.pebbleTv);
|
||||
if (mConnection.mSdServer.mSdData.mHRAlarmActive) {
|
||||
tv.setText("HR = " + mConnection.mSdServer.mSdData.mHR);
|
||||
if (!mConnection.mSdServer.mSdData.mHRAlarmStanding) {
|
||||
tv.setBackgroundColor(okColour);
|
||||
tv.setTextColor(okTextColour);
|
||||
} else {
|
||||
if (mConnection.mSdServer.mSdData.mHRAlarmStanding) {
|
||||
tv.setBackgroundColor(alarmColour);
|
||||
tv.setTextColor(alarmTextColour);
|
||||
} else if (mConnection.mSdServer.mSdData.mHRFaultStanding) {
|
||||
tv.setBackgroundColor(warnColour);
|
||||
tv.setTextColor(warnTextColour);
|
||||
} else {
|
||||
tv.setBackgroundColor(okColour);
|
||||
tv.setTextColor(okTextColour);
|
||||
}
|
||||
} else {
|
||||
if (mConnection.mSdServer.mSdData.pebbleConnected) {
|
||||
if (mConnection.mSdServer.mSdData.watchConnected) {
|
||||
tv.setText("HR Alarm OFF");
|
||||
tv.setBackgroundColor(okColour);
|
||||
tv.setTextColor(okTextColour);
|
||||
@@ -455,7 +456,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
tv = (TextView) findViewById(R.id.appTv);
|
||||
if (mConnection.mSdServer.mSdData.pebbleAppRunning) {
|
||||
if (mConnection.mSdServer.mSdData.watchAppRunning) {
|
||||
tv.setText("Watch App OK");
|
||||
tv.setBackgroundColor(okColour);
|
||||
tv.setTextColor(okTextColour);
|
||||
|
||||
@@ -96,6 +96,7 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac
|
||||
private final String[] SMS_PERMISSIONS = {
|
||||
Manifest.permission.SEND_SMS,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
Manifest.permission.READ_PHONE_STATE,
|
||||
};
|
||||
|
||||
|
||||
|
||||
195
app/src/main/java/uk/org/openseizuredetector/SdAnalyser.java
Normal file
195
app/src/main/java/uk/org/openseizuredetector/SdAnalyser.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -29,10 +29,6 @@ import android.os.Parcel;
|
||||
import android.text.format.Time;
|
||||
import android.util.Log;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONArray;
|
||||
|
||||
@@ -82,11 +78,12 @@ public class SdData implements Parcelable {
|
||||
public long roiPower;
|
||||
public String alarmPhrase;
|
||||
public int simpleSpec[];
|
||||
public boolean pebbleConnected = false;
|
||||
public boolean pebbleAppRunning = false;
|
||||
public boolean watchConnected = false;
|
||||
public boolean watchAppRunning = false;
|
||||
public boolean serverOK = false;
|
||||
|
||||
public boolean mHRAlarmStanding = false;
|
||||
public boolean mHRFaultStanding = false;
|
||||
public double mHR = 0;
|
||||
|
||||
public SdData() {
|
||||
@@ -116,8 +113,8 @@ public class SdData implements Parcelable {
|
||||
specPower = jo.optInt("specPower");
|
||||
roiPower = jo.optInt("roiPower");
|
||||
batteryPc = jo.optInt("batteryPc");
|
||||
pebbleConnected = jo.optBoolean("pebbleConnected");
|
||||
pebbleAppRunning = jo.optBoolean("pebbleAppRunning");
|
||||
watchConnected = jo.optBoolean("watchConnected");
|
||||
watchAppRunning = jo.optBoolean("watchAppRunning");
|
||||
alarmState = jo.optInt("alarmState");
|
||||
alarmPhrase = jo.optString("alarmPhrase");
|
||||
alarmThresh = jo.optInt("alarmThresh");
|
||||
@@ -159,8 +156,8 @@ public class SdData implements Parcelable {
|
||||
jsonObj.put("specPower", specPower);
|
||||
jsonObj.put("roiPower", roiPower);
|
||||
jsonObj.put("batteryPc", batteryPc);
|
||||
jsonObj.put("pebbleConnected", pebbleConnected);
|
||||
jsonObj.put("pebbleAppRunning", pebbleAppRunning);
|
||||
jsonObj.put("watchConnected", watchConnected);
|
||||
jsonObj.put("watchAppRunning", watchAppRunning);
|
||||
jsonObj.put("haveSettings", haveSettings);
|
||||
jsonObj.put("alarmState", alarmState);
|
||||
jsonObj.put("alarmPhrase", alarmPhrase);
|
||||
|
||||
@@ -683,8 +683,8 @@ public class SdDataSourceAw extends SdDataSource {
|
||||
tdiff = (tnow.toMillis(false) - mStatusTime.toMillis(false));
|
||||
Log.v(TAG, "getStatus() - mWatchAppRunningCheck=" + mWatchAppRunningCheck + " tdiff=" + tdiff);
|
||||
// Check we are actually connected to the pebble.
|
||||
mSdData.pebbleConnected = PebbleKit.isWatchConnected(mContext);
|
||||
if (!mSdData.pebbleConnected) mWatchAppRunningCheck = false;
|
||||
mSdData.watchConnected = PebbleKit.isWatchConnected(mContext);
|
||||
if (!mSdData.watchConnected) mWatchAppRunningCheck = false;
|
||||
// And is the pebble_sd app running?
|
||||
// set mWatchAppRunningCheck has been false for more than 10 seconds
|
||||
// the app is not talking to us
|
||||
@@ -692,7 +692,7 @@ public class SdDataSourceAw extends SdDataSource {
|
||||
if (!mWatchAppRunningCheck &&
|
||||
(tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) {
|
||||
Log.v(TAG, "getStatus() - tdiff = " + tdiff);
|
||||
mSdData.pebbleAppRunning = false;
|
||||
mSdData.watchAppRunning = false;
|
||||
//Log.v(TAG, "getStatus() - Pebble App Not Running - Attempting to Re-Start");
|
||||
//mUtil.writeToSysLogFile("SdDataSourceAw.getStatus() - Pebble App not Running - Attempting to Re-Start");
|
||||
//startWatchApp();
|
||||
@@ -709,7 +709,7 @@ public class SdDataSourceAw extends SdDataSource {
|
||||
Log.v(TAG, "getStatus() - Waiting for mFaultTimerPeriod before issuing audible warning...");
|
||||
}
|
||||
} else {
|
||||
mSdData.pebbleAppRunning = true;
|
||||
mSdData.watchAppRunning = true;
|
||||
}
|
||||
|
||||
// if we have confirmation that the app is running, reset the
|
||||
|
||||
@@ -28,32 +28,22 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.format.Time;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.getpebble.android.kit.PebbleKit;
|
||||
import com.getpebble.android.kit.util.PebbleDictionary;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
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.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
|
||||
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 Timer mStatusTimer;
|
||||
private Timer mSettingsTimer;
|
||||
private Timer mAlarmCheckTimer;
|
||||
private Timer mFaultCheckTimer;
|
||||
private Time mDataStatusTime;
|
||||
private boolean mWatchAppRunningCheck = false;
|
||||
private int mAppRestartTimeout = 10; // Timeout before re-starting watch app (sec) if we have not received
|
||||
@@ -145,14 +135,14 @@ public class SdDataSourceGarmin extends SdDataSource {
|
||||
Log.v(TAG, "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");
|
||||
mUtil.writeToSysLogFile("SdDataSourceGarmin.start() - starting alarm check timer");
|
||||
mAlarmCheckTimer = new Timer();
|
||||
mAlarmCheckTimer.schedule(new TimerTask() {
|
||||
mFaultCheckTimer = new Timer();
|
||||
mFaultCheckTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
alarmCheck();
|
||||
faultCheck();
|
||||
}
|
||||
}, 0, 1000);
|
||||
} else {
|
||||
@@ -206,12 +196,12 @@ public class SdDataSourceGarmin extends SdDataSource {
|
||||
mSettingsTimer = null;
|
||||
}
|
||||
// Stop the alarm check timer
|
||||
if (mAlarmCheckTimer != null) {
|
||||
if (mFaultCheckTimer != null) {
|
||||
Log.v(TAG, "stop(): cancelling alarm check timer");
|
||||
mUtil.writeToSysLogFile("SdDataSourceGarmin.stop() - cancelling alarm check timer");
|
||||
mAlarmCheckTimer.cancel();
|
||||
mAlarmCheckTimer.purge();
|
||||
mAlarmCheckTimer = null;
|
||||
mFaultCheckTimer.cancel();
|
||||
mFaultCheckTimer.purge();
|
||||
mFaultCheckTimer = null;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -467,7 +457,10 @@ public class SdDataSourceGarmin extends SdDataSource {
|
||||
fft[2*i+1] = 0.;
|
||||
}
|
||||
}
|
||||
//Log.v(TAG,"specPower = "+specPower);
|
||||
//specPower = specPower/(mSdData.mNsamp/2);
|
||||
specPower = specPower/mSdData.mNsamp/2;
|
||||
//Log.v(TAG,"specPower = "+specPower);
|
||||
|
||||
// Calculate the Region of Interest power and power ratio.
|
||||
double roiPower = 0;
|
||||
@@ -489,8 +482,6 @@ public class SdDataSourceGarmin extends SdDataSource {
|
||||
simpleSpec[ifreq] = simpleSpec[ifreq] / (binMax-binMin);
|
||||
}
|
||||
|
||||
checkFall();
|
||||
|
||||
// Populate the mSdData structure to communicate with the main SdServer service.
|
||||
mDataStatusTime.setToNow();
|
||||
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.
|
||||
mWatchAppRunningCheck = true;
|
||||
|
||||
// Check this data to see if it represents an alarm state.
|
||||
alarmCheck();
|
||||
hrCheck();
|
||||
fallCheck();
|
||||
muteCheck();
|
||||
|
||||
mSdDataReceiver.onSdDataReceived(mSdData); // and tell SdServer we have received data.
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* checkAlarm() - checks the current accelerometer data and uses
|
||||
* historical data to determine if we are in a fault, warning or ok
|
||||
* state.
|
||||
* Sets mSdData.alarmState and mSdData.hrAlarmStanding
|
||||
*/
|
||||
private void alarmCheck() {
|
||||
boolean inAlarm;
|
||||
Log.v(TAG, "alarmCheck()");
|
||||
// Is the current set of data representing an alarm state?
|
||||
if ((mSdData.roiPower > mAlarmThresh) && (10 * (mSdData.roiPower / mSdData.specPower) > mAlarmRatioThresh)) {
|
||||
inAlarm = true;
|
||||
} else {
|
||||
inAlarm = false;
|
||||
}
|
||||
|
||||
// set the alarmState to Alarm, Warning or OK, depending on the current state and previous ones.
|
||||
if (inAlarm) {
|
||||
mAlarmCount += mSamplePeriod;
|
||||
if (mAlarmCount > mAlarmTime) {
|
||||
// full alarm
|
||||
mSdData.alarmState = 2;
|
||||
} else if (mAlarmCount > mWarnTime) {
|
||||
// warning
|
||||
mSdData.alarmState = 1;
|
||||
}
|
||||
} else {
|
||||
// If we are not in an ALARM state, revert back to WARNING, otherwise
|
||||
// revert back to OK.
|
||||
if (mSdData.alarmState == 2) {
|
||||
// revert to warning
|
||||
mSdData.alarmState = 1;
|
||||
mAlarmCount = mWarnTime + 1; // pretend we have only just entered warning state.
|
||||
} else {
|
||||
// revert to OK
|
||||
mSdData.alarmState = 0;
|
||||
mAlarmCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Log.v(TAG, "alarmCheck(): inAlarm=" + inAlarm + ", alarmState = " + mSdData.alarmState + " alarmCount=" + mAlarmCount + " mAlarmTime=" + mAlarmTime);
|
||||
|
||||
}
|
||||
|
||||
public void muteCheck() {
|
||||
if (mMute != 0) {
|
||||
Log.v(TAG, "Mute Active - setting alarms to mute");
|
||||
mSdData.alarmState = 6;
|
||||
mSdData.alarmPhrase = "MUTE";
|
||||
mSdData.mHRAlarmStanding = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* hrCheck - check the Heart rate data in mSdData to see if it represents an alarm condition.
|
||||
* Sets mSdData.mHRAlarmStanding
|
||||
*/
|
||||
public void hrCheck() {
|
||||
Log.v(TAG, "hrCheck()");
|
||||
/* Check Heart Rate against alarm settings */
|
||||
if (mSdData.mHRAlarmActive) {
|
||||
if (mSdData.mHR < 0) {
|
||||
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.
|
||||
* Called from clock_tick_handler()
|
||||
*/
|
||||
public void checkFall() {
|
||||
public void fallCheck() {
|
||||
int i,j;
|
||||
double minAcc, maxAcc;
|
||||
|
||||
@@ -575,7 +653,7 @@ public class SdDataSourceGarmin extends SdDataSource {
|
||||
Log.v(TAG, "getStatus() - mWatchAppRunningCheck=" + mWatchAppRunningCheck + " tdiff=" + tdiff);
|
||||
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?
|
||||
// set mWatchAppRunningCheck has been false for more than 10 seconds
|
||||
// the app is not talking to us
|
||||
@@ -583,7 +661,7 @@ public class SdDataSourceGarmin extends SdDataSource {
|
||||
if (!mWatchAppRunningCheck &&
|
||||
(tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) {
|
||||
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.
|
||||
if (tdiff > (mDataUpdatePeriod + mFaultTimerPeriod) * 1000) {
|
||||
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...");
|
||||
}
|
||||
} else {
|
||||
mSdData.pebbleAppRunning = true;
|
||||
mSdData.watchAppRunning = true;
|
||||
}
|
||||
|
||||
// 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() {
|
||||
boolean inAlarm;
|
||||
private void faultCheck() {
|
||||
Time tnow = new Time(Time.getCurrentTimezone());
|
||||
long tdiff;
|
||||
tnow.setToNow();
|
||||
|
||||
// get time since the last data was received from the watch.
|
||||
tdiff = (tnow.toMillis(false) - mDataStatusTime.toMillis(false));
|
||||
Log.v(TAG, "alarmCheck() - tdiff=" + tdiff + ", mDataUpatePeriod=" + mDataUpdatePeriod + ", mAppRestartTimeout=" + mAppRestartTimeout
|
||||
Log.v(TAG, "faultCheck() - tdiff=" + tdiff + ", mDataUpatePeriod=" + mDataUpdatePeriod + ", mAppRestartTimeout=" + mAppRestartTimeout
|
||||
+ ", combined = " + (mDataUpdatePeriod + mAppRestartTimeout) * 1000);
|
||||
if (!mWatchAppRunningCheck &&
|
||||
(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;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ package uk.org.openseizuredetector;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
@@ -128,8 +126,8 @@ public class SdDataSourceNetwork extends SdDataSource {
|
||||
if (result.startsWith("Unable to retrieve web page")) {
|
||||
Log.v(TAG,"doInBackground() - Unable to retrieve data");
|
||||
sdData.serverOK = false;
|
||||
sdData.pebbleConnected = false;
|
||||
sdData.pebbleAppRunning = 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());
|
||||
@@ -147,8 +145,8 @@ public class SdDataSourceNetwork extends SdDataSource {
|
||||
|
||||
} catch (IOException e) {
|
||||
sdData.serverOK = false;
|
||||
sdData.pebbleConnected = false;
|
||||
sdData.pebbleAppRunning = false;
|
||||
sdData.watchConnected = false;
|
||||
sdData.watchAppRunning = false;
|
||||
sdData.alarmState = ALARM_STATE_NETFAULT;
|
||||
sdData.alarmPhrase = "Warning - No Connection to Server";
|
||||
Log.v(TAG,"doInBackground(): IOException - "+e.toString());
|
||||
|
||||
@@ -37,11 +37,6 @@ import android.widget.Toast;
|
||||
import com.getpebble.android.kit.PebbleKit;
|
||||
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.ByteOrder;
|
||||
import java.nio.IntBuffer;
|
||||
@@ -679,8 +674,8 @@ public class SdDataSourcePebble extends SdDataSource {
|
||||
tdiff = (tnow.toMillis(false) - mPebbleStatusTime.toMillis(false));
|
||||
Log.v(TAG, "getStatus() - mPebbleAppRunningCheck=" + mPebbleAppRunningCheck + " tdiff=" + tdiff);
|
||||
// Check we are actually connected to the pebble.
|
||||
mSdData.pebbleConnected = PebbleKit.isWatchConnected(mContext);
|
||||
if (!mSdData.pebbleConnected) mPebbleAppRunningCheck = false;
|
||||
mSdData.watchConnected = PebbleKit.isWatchConnected(mContext);
|
||||
if (!mSdData.watchConnected) mPebbleAppRunningCheck = false;
|
||||
// And is the pebble_sd app running?
|
||||
// set mPebbleAppRunningCheck has been false for more than 10 seconds
|
||||
// the app is not talking to us
|
||||
@@ -688,7 +683,7 @@ public class SdDataSourcePebble extends SdDataSource {
|
||||
if (!mPebbleAppRunningCheck &&
|
||||
(tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) {
|
||||
Log.v(TAG, "getStatus() - tdiff = " + tdiff);
|
||||
mSdData.pebbleAppRunning = false;
|
||||
mSdData.watchAppRunning = false;
|
||||
//Log.v(TAG, "getStatus() - Pebble App Not Running - Attempting to Re-Start");
|
||||
//mUtil.writeToSysLogFile("SdDataSourcePebble.getStatus() - Pebble App not Running - Attempting to Re-Start");
|
||||
//startWatchApp();
|
||||
@@ -705,7 +700,7 @@ public class SdDataSourcePebble extends SdDataSource {
|
||||
Log.v(TAG, "getStatus() - Waiting for mFaultTimerPeriod before issuing audible warning...");
|
||||
}
|
||||
} else {
|
||||
mSdData.pebbleAppRunning = true;
|
||||
mSdData.watchAppRunning = true;
|
||||
}
|
||||
|
||||
// if we have confirmation that the app is running, reset the
|
||||
|
||||
@@ -629,7 +629,7 @@ public class SdServer extends Service implements SdDataReceiver {
|
||||
}
|
||||
|
||||
// Fault
|
||||
if ((sdData.alarmState) == 4 || (sdData.alarmState == 7)) {
|
||||
if ((sdData.alarmState) == 4 || (sdData.alarmState == 7) || (sdData.mHRFaultStanding)) {
|
||||
sdData.alarmPhrase = "FAULT";
|
||||
writeAlarmToSD();
|
||||
faultWarningBeep();
|
||||
|
||||
@@ -101,7 +101,7 @@ public class SdServiceConnection implements ServiceConnection {
|
||||
public boolean pebbleConnected() {
|
||||
if (mSdServer!=null) {
|
||||
if (mSdServer.mSdData!=null) {
|
||||
if (mSdServer.mSdData.pebbleConnected) {
|
||||
if (mSdServer.mSdData.watchConnected) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,7 @@ public class SdServiceConnection implements ServiceConnection {
|
||||
public boolean pebbleAppRunning() {
|
||||
if (mSdServer!=null) {
|
||||
if (mSdServer.mSdData!=null) {
|
||||
if (mSdServer.mSdData.pebbleAppRunning) {
|
||||
if (mSdServer.mSdData.watchAppRunning) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,7 +340,7 @@ public class StartupActivity extends Activity {
|
||||
// Is Pebble Watch App Running?
|
||||
tv = (TextView) findViewById(R.id.textItem4);
|
||||
pb = (ProgressBar) findViewById(R.id.progressBar4);
|
||||
if (mConnection.pebbleAppRunning()) {
|
||||
if (mConnection.watchAppRunning()) {
|
||||
tv.setText("Watch App Running OK");
|
||||
tv.setBackgroundColor(okColour);
|
||||
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.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.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...
|
||||
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.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.9 - Fixed issue with Garmin Seizure Detector not producing warnings. Added faut pips for missing heart rate data if heart rate alarm active"
|
||||
+ "\n "
|
||||
);
|
||||
// This makes the links display as links, but they do not respond to clicks for some reason...
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user