V4.1.11 - Fixed crash when exporting larger amounts of data and added simple fidget detector to detect if watch is not being worn.
This commit is contained in:
Binary file not shown.
@@ -2,7 +2,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:versionCode="125"
|
||||
android:versionName="4.1.11a">
|
||||
android:versionName="4.1.11">
|
||||
<!-- android:allowBackup="false" -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
|
||||
@@ -209,17 +209,34 @@ public class ExportDataActivity extends AppCompatActivity
|
||||
// mDateTxt.getText().toString(), mTimeTxt.getText().toString(), mDuration));
|
||||
Log.d(TAG, String.format("EndDate=%s %s, Duration=%3.1f hrs",
|
||||
mDateTxt.getText().toString(), mTimeTxt.getText().toString(), mDuration));
|
||||
ProgressBar pb = (ProgressBar) findViewById(R.id.exportPb);
|
||||
pb.setIndeterminate(true);
|
||||
pb.setVisibility(View.VISIBLE);
|
||||
|
||||
mExportBtn.setEnabled(false);
|
||||
mExportBtn.setVisibility(View.INVISIBLE);
|
||||
showProgressBar();
|
||||
this.openFile();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void showProgressBar() {
|
||||
ProgressBar pb = (ProgressBar) findViewById(R.id.exportPb);
|
||||
pb.setIndeterminate(true);
|
||||
pb.setVisibility(View.VISIBLE);
|
||||
mExportBtn.setEnabled(false);
|
||||
mExportBtn.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void openFile() {
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||
@@ -248,96 +265,14 @@ 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());
|
||||
exportToFile(uri);
|
||||
mLm.exportToCsvFile(mEndDate, mDuration,uri, (boolean b)-> {
|
||||
Log.v(TAG,"onActivityResult callback");
|
||||
hideProgressBar();
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, resultData);
|
||||
}
|
||||
|
||||
private void exportToFile(Uri uri) {
|
||||
Log.v(TAG, "exportToFile(): uri=" + uri.toString());
|
||||
long endDateMillis = mEndDate.getTime();
|
||||
long durationMillis = (long) (mDuration * 3600. * 1000);
|
||||
long startDateMillis = endDateMillis - durationMillis;
|
||||
Log.v(TAG, "exportToFile() - endDateMillis=" + endDateMillis + ", startDateMillis=" + startDateMillis + ", durationMillis=" + durationMillis);
|
||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String sDateStr = dateFormat.format(new Date(startDateMillis));
|
||||
String eDateStr = dateFormat.format(new Date(endDateMillis));
|
||||
Log.v(TAG, "exportToFile() - sDateStr=" + sDateStr + " eDateStr=" + eDateStr);
|
||||
mLm.getDatapointsByDate(
|
||||
sDateStr, eDateStr, (String datapointsJsonStr) -> {
|
||||
Log.v(TAG, "exportToFile() - datapoints=" + datapointsJsonStr);
|
||||
// Open file for writing
|
||||
try {
|
||||
ParcelFileDescriptor pfd = this.getContentResolver().
|
||||
openFileDescriptor(uri, "w");
|
||||
FileOutputStream fileOutputStream =
|
||||
new FileOutputStream(pfd.getFileDescriptor());
|
||||
fileOutputStream.write(("# dataTime, alarmState, hr, o2sat, accel*125\n").getBytes());
|
||||
JSONArray dataObj;
|
||||
try {
|
||||
dataObj = new JSONArray(datapointsJsonStr);
|
||||
Log.v(TAG, "exportToFile() - dataObj length=" + dataObj.length());
|
||||
for (int i = 0; i < dataObj.length(); i++) {
|
||||
JSONObject datapointJsonObj = dataObj.getJSONObject(i);
|
||||
String dataJsonStr = datapointJsonObj.getString("dataJSON");
|
||||
Log.v(TAG, "exportToFile() - i=" + i + "dataJsonStr=" + dataJsonStr);
|
||||
JSONObject dataJsonObj = new JSONObject(dataJsonStr);
|
||||
JSONArray rawDataArr = dataJsonObj.getJSONArray("rawData");
|
||||
try {
|
||||
fileOutputStream.write(dataJsonObj.getString("dataTime").getBytes(StandardCharsets.UTF_8));
|
||||
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
|
||||
fileOutputStream.write(dataJsonObj.getString("alarmState").getBytes(StandardCharsets.UTF_8));
|
||||
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
|
||||
fileOutputStream.write(dataJsonObj.getString("hr").getBytes(StandardCharsets.UTF_8));
|
||||
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
|
||||
fileOutputStream.write(dataJsonObj.getString("o2Sat").getBytes(StandardCharsets.UTF_8));
|
||||
for (int j = 0; j < rawDataArr.length(); j++) {
|
||||
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
|
||||
fileOutputStream.write(rawDataArr.getString(j).getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
fileOutputStream.write("\n".getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "exportToFile() - ERROR Writing File: " + e.toString());
|
||||
//mUtil.showToast("ERROR WRITING FILE");
|
||||
}
|
||||
|
||||
}
|
||||
} catch (JSONException | NullPointerException e) {
|
||||
Log.v(TAG, "createEventCallback(): Error Creating JSON Object from string " + datapointsJsonStr);
|
||||
dataObj = null;
|
||||
mUtil.showToast(getString(R.string.error_exporting_data));
|
||||
Log.e(TAG, "exportToFile() - JSONException: " + e.toString());
|
||||
}
|
||||
// Let the document provider know you're done by closing the stream.
|
||||
fileOutputStream.close();
|
||||
pfd.close();
|
||||
mUtil.showToast(getString(R.string.data_exported_ok));
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
mUtil.showToast(getString(R.string.error_exporting_data));
|
||||
Log.e(TAG, "exportToFile() - FileNotFoundException: " + e.toString());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
mUtil.showToast(getString(R.string.error_exporting_data));
|
||||
Log.e(TAG, "exportToFile() - IOException: " + e.toString());
|
||||
}
|
||||
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);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -30,17 +30,25 @@ import android.database.DatabaseUtils;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.format.Time;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -66,20 +74,20 @@ import java.util.HashMap;
|
||||
* - Query the local database to return all datapoints within +/- EventDuration/2 minutes of the event.
|
||||
* - Upload the datapoints, linking them to the new eventID.
|
||||
* - Mark all the uploaded datapoints as uploaded.
|
||||
*
|
||||
* <p>
|
||||
* Event statuses:
|
||||
* 0 - OK
|
||||
* 1 - WARNING
|
||||
* 2 - ALARM
|
||||
* 3 - FALL
|
||||
* 4 - FAULT
|
||||
* 5 - Manual Alarm
|
||||
* 6 - NDA (Normal Daily Activities)
|
||||
*
|
||||
* NDA Timer creates an event periodically to record Normal Daily Activities (NDA),
|
||||
* irrespective of the alarm state. This will upload a lot of data, so it will only run
|
||||
* for 24 hours after being activated before shutting down requring the user to re-select
|
||||
* the option to log NDA to re-start it.
|
||||
* 0 - OK
|
||||
* 1 - WARNING
|
||||
* 2 - ALARM
|
||||
* 3 - FALL
|
||||
* 4 - FAULT
|
||||
* 5 - Manual Alarm
|
||||
* 6 - NDA (Normal Daily Activities)
|
||||
* <p>
|
||||
* NDA Timer creates an event periodically to record Normal Daily Activities (NDA),
|
||||
* irrespective of the alarm state. This will upload a lot of data, so it will only run
|
||||
* for 24 hours after being activated before shutting down requring the user to re-select
|
||||
* the option to log NDA to re-start it.
|
||||
*/
|
||||
public class LogManager {
|
||||
static final private String TAG = "LogManager";
|
||||
@@ -122,6 +130,11 @@ public class LogManager {
|
||||
void accept(ArrayList<HashMap<String, String>> retVal);
|
||||
}
|
||||
|
||||
public interface BooleanCallback {
|
||||
void accept(boolean retVal);
|
||||
}
|
||||
|
||||
|
||||
public LogManager(Context context,
|
||||
boolean logRemote, boolean logRemoteMobile, String authToken,
|
||||
long eventDuration, long remoteLogPeriod,
|
||||
@@ -242,21 +255,21 @@ public class LogManager {
|
||||
String val;
|
||||
val = c.getString(c.getColumnIndex("id"));
|
||||
// We replace null values with empty string, otherwise they are completely excluded from output JSON.
|
||||
event.put("id", val==null ? "" : val );
|
||||
event.put("id", val == null ? "" : val);
|
||||
val = c.getString(c.getColumnIndex("dataTime"));
|
||||
event.put("dataTime", val==null ? "" : val);
|
||||
event.put("dataTime", val == null ? "" : val);
|
||||
val = c.getString(c.getColumnIndex("status"));
|
||||
event.put("status", val==null ? "" : val);
|
||||
event.put("status", val == null ? "" : val);
|
||||
val = c.getString(c.getColumnIndex("type"));
|
||||
event.put("type", val==null ? "" : val);
|
||||
event.put("type", val == null ? "" : val);
|
||||
val = c.getString(c.getColumnIndex("subType"));
|
||||
event.put("subType", val==null ? "" : val);
|
||||
event.put("subType", val == null ? "" : val);
|
||||
val = c.getString(c.getColumnIndex("notes"));
|
||||
event.put("desc", val==null ? "" : val);
|
||||
event.put("desc", val == null ? "" : val);
|
||||
val = c.getString(c.getColumnIndex("dataJSON"));
|
||||
event.put("dataJSON", val==null ? "" : val);
|
||||
event.put("dataJSON", val == null ? "" : val);
|
||||
val = c.getString(c.getColumnIndex("uploaded"));
|
||||
event.put("uploaded", val==null ? "" : val);
|
||||
event.put("uploaded", val == null ? "" : val);
|
||||
c.moveToNext();
|
||||
eventsArray.put(i, event);
|
||||
i++;
|
||||
@@ -342,7 +355,7 @@ public class LogManager {
|
||||
|
||||
if (sdData.alarmState != 0) {
|
||||
Log.i(TAG, "writeDatapointToLocalDb(): adding event to local DB");
|
||||
createLocalEvent(dateStr,sdData.alarmState,null, null, null, sdData.toSettingsJSON());
|
||||
createLocalEvent(dateStr, sdData.alarmState, null, null, null, sdData.toSettingsJSON());
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
Log.e(TAG, "writeToLocalDb(): Error Writing Data: " + e.toString());
|
||||
@@ -358,18 +371,18 @@ public class LogManager {
|
||||
|
||||
public boolean createLocalEvent(String dataTime, long status, String type, String subType, String desc, String dataJSON) {
|
||||
// Expects dataTime to be in format: SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
Log.d(TAG, "createLocalEvent() - dataTime=" + dataTime + ", status=" + status + ", dataJSON="+dataJSON);
|
||||
Log.d(TAG, "createLocalEvent() - dataTime=" + dataTime + ", status=" + status + ", dataJSON=" + dataJSON);
|
||||
// Write Event to database
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("dataTime", dataTime);
|
||||
values.put("status", status);
|
||||
values.put("type", type);
|
||||
values.put("subType",subType);
|
||||
values.put("notes",desc);
|
||||
values.put("subType", subType);
|
||||
values.put("notes", desc);
|
||||
values.put("dataJSON", dataJSON);
|
||||
|
||||
long newRowId = mOsdDb.insert(mEventsTableName, null, values);
|
||||
Log.d(TAG, "createLocalEvent(): Created Row ID"+newRowId);
|
||||
Log.d(TAG, "createLocalEvent(): Created Row ID" + newRowId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -472,13 +485,118 @@ public class LogManager {
|
||||
if (cursor != null) {
|
||||
callback.accept(cursor2Json(cursor));
|
||||
} else {
|
||||
Log.w(TAG,"getDatapointsByDate() - returned null result");
|
||||
Log.w(TAG, "getDatapointsByDate() - returned null result");
|
||||
callback.accept(null);
|
||||
}
|
||||
}).execute();
|
||||
return (true);
|
||||
}
|
||||
|
||||
/**
|
||||
* exportToFile - export datapoints data to a csv file on the android device.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public void exportToCsvFile(Date endDate, double duration, Uri uri, BooleanCallback callback) {
|
||||
Log.v(TAG, "exportToCsvFile(): uri=" + uri.toString());
|
||||
long endDateMillis = endDate.getTime();
|
||||
long durationMillis = (long) (duration * 3600. * 1000);
|
||||
long startDateMillis = endDateMillis - durationMillis;
|
||||
Log.v(TAG, "exportToCsvFile() - endDateMillis=" + endDateMillis + ", startDateMillis=" + startDateMillis + ", durationMillis=" + durationMillis);
|
||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String sDateStr = dateFormat.format(new Date(startDateMillis));
|
||||
String eDateStr = dateFormat.format(new Date(endDateMillis));
|
||||
Log.v(TAG, "exportToFile() - sDateStr=" + sDateStr + " eDateStr=" + eDateStr);
|
||||
String[] columns = {"*"};
|
||||
String whereClause = "DataTime>? AND DataTime<?";
|
||||
String[] whereArgs = {sDateStr, eDateStr};
|
||||
new SelectQueryTask(mDpTableName, columns, whereClause, whereArgs,
|
||||
null, null, "dataTime DESC", (Cursor cursor) -> {
|
||||
Log.v(TAG, "exportToCsvFile - returned " + cursor);
|
||||
if (cursor != null) {
|
||||
Log.d(TAG, "we got a cursor!");
|
||||
try {
|
||||
ParcelFileDescriptor pfd = mContext.getContentResolver().
|
||||
openFileDescriptor(uri, "w");
|
||||
FileOutputStream fileOutputStream =
|
||||
new FileOutputStream(pfd.getFileDescriptor());
|
||||
fileOutputStream.write(("# dataTime, alarmState, hr, o2sat, accel*125\n").getBytes());
|
||||
writeDatapointsToFile(cursor, fileOutputStream);
|
||||
// Let the document provider know you're done by closing the stream.
|
||||
fileOutputStream.close();
|
||||
pfd.close();
|
||||
callback.accept(true);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
mUtil.showToast(mContext.getString(R.string.error_exporting_data));
|
||||
Log.e(TAG, "exportToFile() - FileNotFoundException: " + e.toString());
|
||||
callback.accept(false);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
mUtil.showToast(mContext.getString(R.string.error_exporting_data));
|
||||
Log.e(TAG, "exportToFile() - IOException: " + e.toString());
|
||||
callback.accept(false);
|
||||
}
|
||||
|
||||
} else {
|
||||
Log.w(TAG, "exportToCsvFile() - returned null result");
|
||||
callback.accept(false);
|
||||
}
|
||||
}).execute();
|
||||
return;
|
||||
}
|
||||
|
||||
private void writeDatapointsToFile(Cursor c, FileOutputStream fileOutputStream) {
|
||||
Log.v(TAG, "writeDatapointsToFile()");
|
||||
JSONArray dataObj;
|
||||
String dataJsonStr;
|
||||
JSONObject dataJsonObj;
|
||||
JSONArray rawDataArr;
|
||||
Log.d(TAG,"writeDatapointsToFile()" + c.getColumnNames());
|
||||
//for (int i=0;i<c.getColumnCount();i++) {
|
||||
// Log.d(TAG," Column"+i+" = "+c.getColumnName(i));
|
||||
//}
|
||||
try {
|
||||
Log.d(TAG,"writeDatapointsToFile() - writing query result to csv file....");
|
||||
while (c.moveToNext()) {
|
||||
//Log.d(TAG,"writeDatapointsToFile - row="+c.getString(0)+", "+c.getString(1));
|
||||
dataJsonStr = c.getString(3); // dataJSON is index 3
|
||||
//Log.v(TAG, "exportToFile() - i=" + i + "dataJsonStr=" + dataJsonStr);
|
||||
dataJsonObj = new JSONObject(dataJsonStr);
|
||||
rawDataArr = dataJsonObj.getJSONArray("rawData");
|
||||
try {
|
||||
//fileOutputStream.write(dataJsonObj.getString("dataTime").getBytes(StandardCharsets.UTF_8));
|
||||
fileOutputStream.write(c.getString(1).getBytes(StandardCharsets.UTF_8)); // We use the database record date rather than datajson date because it is formatted yyyy-mm-dd
|
||||
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
|
||||
fileOutputStream.write(dataJsonObj.getString("alarmState").getBytes(StandardCharsets.UTF_8));
|
||||
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
|
||||
fileOutputStream.write(dataJsonObj.getString("hr").getBytes(StandardCharsets.UTF_8));
|
||||
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
|
||||
fileOutputStream.write(dataJsonObj.getString("o2Sat").getBytes(StandardCharsets.UTF_8));
|
||||
for (int j = 0; j < 125; j++) { // FIXME Hard Coded array length, but rawDataArr.length() is 125*3 so we don't want to use that.
|
||||
fileOutputStream.write(", ".getBytes(StandardCharsets.UTF_8));
|
||||
fileOutputStream.write(rawDataArr.getString(j).getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
fileOutputStream.write("\n".getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "exportToFile() - ERROR Writing File: " + e.toString());
|
||||
mUtil.showToast("ERROR WRITING FILE");
|
||||
}
|
||||
|
||||
}
|
||||
Log.d(TAG,"writeDatapointsToFile() - data written to file ok");
|
||||
mUtil.showToast(mContext.getString(R.string.data_exported_ok));
|
||||
|
||||
} catch (JSONException | NullPointerException e) {
|
||||
Log.v(TAG, "createEventCallback(): Error Creating JSON Object from string ");
|
||||
dataObj = null;
|
||||
mUtil.showToast(mContext.getString(R.string.error_exporting_data));
|
||||
Log.e(TAG, "exportToFile() - JSONException: " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return an array list of objects representing the events in the database by calling the specified callback function.
|
||||
@@ -486,7 +604,8 @@ public class LogManager {
|
||||
* @param includeWarnings - whether to include warnings in the list of events, or just alarm conditions.
|
||||
* @return True on successful start or false if call fails.
|
||||
*/
|
||||
public boolean getEventsList(boolean includeWarnings, ArrayListCallback callback) {
|
||||
public boolean getEventsList(boolean includeWarnings, ArrayListCallback
|
||||
callback) {
|
||||
Log.v(TAG, "getEventsList - includeWarnings=" + includeWarnings);
|
||||
ArrayList<HashMap<String, String>> eventsList = new ArrayList<>();
|
||||
|
||||
@@ -572,7 +691,8 @@ public class LogManager {
|
||||
* @param includeWarnings - whether to include warnings in the list of events, or just alarm conditions.
|
||||
* @return True on successful start or false if call fails.
|
||||
*/
|
||||
public boolean getNextEventToUpload(boolean includeWarnings, WebApiConnection.LongCallback callback) {
|
||||
public boolean getNextEventToUpload(boolean includeWarnings, WebApiConnection.
|
||||
LongCallback callback) {
|
||||
Log.v(TAG, "getNextEventToUpload - includeWarnings=" + includeWarnings);
|
||||
|
||||
String[] whereArgsStatus = getEventWhereArgs(includeWarnings);
|
||||
@@ -619,7 +739,8 @@ public class LogManager {
|
||||
*
|
||||
* @return True on successful start or false if call fails.
|
||||
*/
|
||||
public boolean getNearestDatapointToDate(String dateStr, WebApiConnection.LongCallback callback) {
|
||||
public boolean getNearestDatapointToDate(String
|
||||
dateStr, WebApiConnection.LongCallback callback) {
|
||||
Log.v(TAG, "getNextEventToDate - dateStr=" + dateStr);
|
||||
String[] columns = {"*", "(julianday(dataTime)-julianday(datetime('" + dateStr + "'))) as ddiff"};
|
||||
//SQLStr = "SELECT *, (julianday(dataTime)-julianday(datetime('" + dateStr + "'))) as ddiff from " + mDbTableName + " order by ABS(ddiff) asc;";
|
||||
@@ -652,7 +773,8 @@ public class LogManager {
|
||||
* @param includeWarnings - whether to include warnings in the list of events, or just alarm conditions.
|
||||
* @return True on successful start or false if call fails.
|
||||
*/
|
||||
public boolean getLocalEventsCount(boolean includeWarnings, WebApiConnection.LongCallback callback) {
|
||||
public boolean getLocalEventsCount(boolean includeWarnings, WebApiConnection.
|
||||
LongCallback callback) {
|
||||
//Log.v(TAG, "getLocalEventsCount- includeWarnings=" + includeWarnings);
|
||||
String[] whereArgs = getEventWhereArgs(includeWarnings);
|
||||
String whereClause = getEventWhereClause(includeWarnings);
|
||||
@@ -967,7 +1089,8 @@ public class LogManager {
|
||||
for (int i = 0; i < dataObj.length(); i++) {
|
||||
mDatapointsToUploadList.add(dataObj.getJSONObject(i));
|
||||
}
|
||||
} catch (JSONException | NullPointerException e) {
|
||||
} catch (JSONException |
|
||||
NullPointerException e) {
|
||||
Log.v(TAG, "createEventCallback(): Error Creating JSON Object from string " + datapointsJsonStr);
|
||||
dataObj = null;
|
||||
finishUpload();
|
||||
@@ -1122,7 +1245,7 @@ public class LogManager {
|
||||
timeNow.setToNow();
|
||||
long tNow = timeNow.toMillis(true);
|
||||
long tDiffMillis = (tNow - mNDATimerStartTime);
|
||||
mNDATimeRemaining = mNDALogPeriodHours - tDiffMillis / (3600.*1000.);
|
||||
mNDATimeRemaining = mNDALogPeriodHours - tDiffMillis / (3600. * 1000.);
|
||||
|
||||
|
||||
}
|
||||
@@ -1183,11 +1306,11 @@ public class LogManager {
|
||||
|
||||
|
||||
public void createNDAEvent() {
|
||||
Log.i(TAG,"createNDAEvent()");
|
||||
Log.i(TAG, "createNDAEvent()");
|
||||
Date curDate = new Date();
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String dateStr = dateFormat.format(curDate);
|
||||
createLocalEvent(dateStr,6,"nda", null, null,
|
||||
createLocalEvent(dateStr, 6, "nda", null, null,
|
||||
mSdSettingsData.toSettingsJSON());
|
||||
}
|
||||
|
||||
@@ -1196,7 +1319,6 @@ public class LogManager {
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class OsdDbHelper extends SQLiteOpenHelper {
|
||||
// If you change the database schema, you must increment the database version.
|
||||
public static final int DATABASE_VERSION = 1;
|
||||
@@ -1275,6 +1397,7 @@ public class LogManager {
|
||||
*/
|
||||
private class NDATimer extends CountDownTimer {
|
||||
double mNDALogPeriodHours = 0;
|
||||
|
||||
public NDATimer(long startTime, long interval, double logPeriod) {
|
||||
super(startTime, interval);
|
||||
mNDALogPeriodHours = logPeriod;
|
||||
@@ -1295,7 +1418,7 @@ public class LogManager {
|
||||
timeNow.setToNow();
|
||||
long tNow = timeNow.toMillis(true);
|
||||
long tDiffMillis = (tNow - mNDATimerStartTime);
|
||||
double tDiffHrs = tDiffMillis / (3600.*1000.);
|
||||
double tDiffHrs = tDiffMillis / (3600. * 1000.);
|
||||
mNDATimeRemaining = mNDALogPeriodHours - tDiffHrs;
|
||||
if (tDiffHrs >= mNDALogPeriodHours) {
|
||||
Log.i(TAG, "mNDATimer - onFinish - NDA logging period completed - switching off NDA Logging");
|
||||
@@ -1307,8 +1430,8 @@ public class LogManager {
|
||||
editor.apply();
|
||||
} else {
|
||||
// Restart this timer.
|
||||
Log.i(TAG,"NDATimer - tDiffMillis="+tDiffMillis+", tdiffHrs = "+tDiffHrs+ ", tnow="+tNow+", tstart="+mNDATimerStartTime+", NDALogPeriod="+mNDALogPeriodHours);
|
||||
Log.i(TAG,"NDATimer - re-starting NDA timer");
|
||||
Log.i(TAG, "NDATimer - tDiffMillis=" + tDiffMillis + ", tdiffHrs = " + tDiffHrs + ", tnow=" + tNow + ", tstart=" + mNDATimerStartTime + ", NDALogPeriod=" + mNDALogPeriodHours);
|
||||
Log.i(TAG, "NDATimer - re-starting NDA timer");
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,10 @@ public abstract class SdDataSource {
|
||||
private Time mHrStatusTime;
|
||||
private double mHrFrozenPeriod = 60; // seconds
|
||||
private boolean mHrFrozenAlarm;
|
||||
private boolean mFidgetDetectorEnabled;
|
||||
private double mFidgetPeriod;
|
||||
private double mFidgetThreshold;
|
||||
private Time mLastFidget = null;
|
||||
|
||||
|
||||
public SdDataSource(Context context, Handler handler, SdDataReceiver sdDataReceiver) {
|
||||
@@ -723,6 +727,31 @@ public abstract class SdDataSource {
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the status of the connection to the watch,
|
||||
* and sets class variables for use by other functions.
|
||||
@@ -758,6 +787,23 @@ public abstract class SdDataSource {
|
||||
}
|
||||
} else {
|
||||
mSdData.watchAppRunning = true;
|
||||
|
||||
// Check we have seen a fidget within the required period, or else assume a fault because watch is not being worn
|
||||
if (mFidgetDetectorEnabled) {
|
||||
if (mLastFidget == null) mLastFidget = tnow; // Initialise last fidget time on startup.
|
||||
|
||||
double accStd = calcRawDataStd(mSdData);
|
||||
if (accStd > mFidgetThreshold) {
|
||||
mLastFidget = tnow;
|
||||
} else {
|
||||
Log.d(TAG,"onStatus() - Fidget Detector - low movement - is watch being worn?");
|
||||
tdiff = (tnow.toMillis(false) - mLastFidget.toMillis(false));
|
||||
if (tdiff > (mFidgetPeriod) * 60 * 1000) {
|
||||
Log.e(TAG, "onStatus() - Fidget Not Detected - is watch being worn?");
|
||||
mSdDataReceiver.onSdDataFault(mSdData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we have confirmation that the app is running, reset the
|
||||
@@ -877,6 +923,20 @@ public abstract class SdDataSource {
|
||||
toast.show();
|
||||
}
|
||||
|
||||
// Parse the Fidget Detector settings.
|
||||
try {
|
||||
mFidgetDetectorEnabled = SP.getBoolean("FidgetDetectorEnabled", false);
|
||||
mFidgetPeriod = readDoublePref(SP, "FidgetDetectorPeriod", "20"); // minutes
|
||||
Log.v(TAG, "updatePrefs() - mFidgetPeriod = " + mFidgetPeriod);
|
||||
mFidgetThreshold = readDoublePref(SP, "FidgetDetectorThreshold", "5");
|
||||
Log.d(TAG,"updatePrefs(): mFidgetThreshold="+mFidgetThreshold);
|
||||
|
||||
} catch (Exception ex) {
|
||||
Log.v(TAG, "updatePrefs() - Problem with FidgetDetector preferences!");
|
||||
Toast toast = Toast.makeText(mContext, "Problem Parsing FidgetPeriod Preference", Toast.LENGTH_SHORT);
|
||||
toast.show();
|
||||
}
|
||||
|
||||
|
||||
// Watch Settings
|
||||
String prefStr;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="app_name">OpenSeizureDetector</string>
|
||||
<string name="changelog">
|
||||
"\n
|
||||
\mV4.1.11 - Changed target Android Version to 13 (SDK33) (Play Store policy).
|
||||
\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
|
||||
@@ -503,4 +503,11 @@
|
||||
<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="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>
|
||||
<string name="FidgetDetectorThresholdSummary">The threshold (as % standard deviation) applied to each set of accelerometer data to determine if a \'Fidget\' has occurred.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -186,5 +186,22 @@
|
||||
android:title="@string/fall_window_title" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/FidgetSettingsTitle">
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="FidgetDetectorEnabled"
|
||||
android:summary="@string/FidgetDetectorEnabledSummary"
|
||||
android:title="@string/FidgetDetectorEnabledTitle" />
|
||||
<EditTextPreference
|
||||
android:defaultValue="5"
|
||||
android:key="FidgetDetectorThreshold"
|
||||
android:summary="@string/FidgetDetectorThresholdSummary"
|
||||
android:title="@string/FidgetDetectorThresholdTitle" />
|
||||
<EditTextPreference
|
||||
android:defaultValue="20"
|
||||
android:key="FidgetDetectorPeriod"
|
||||
android:summary="@string/FidgetDetectorPeriodSummary"
|
||||
android:title="@string/FidgetDetectorPeriodTitle" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
||||
Reference in New Issue
Block a user