Added support for watch info settins in SdDataSourceGarmin. Started converting to sqlite database data logging rather than text file.

This commit is contained in:
Graham Jones
2020-01-06 20:51:14 +00:00
parent f40c363808
commit 5df5ef66c6
10 changed files with 450 additions and 12 deletions

View File

@@ -1,8 +1,13 @@
OpenSeizureDetector Android App - Change Log OpenSeizureDetector Android App - Change Log
============================================ ============================================
V3.2.0 - (NEXT VERSION!) V3.2.0 - jan2020
- Added neural network based data analysis. - Modified data logging to use sqlite database rather than text files.
- Added facility to upload data to remote server.
- Added support for additional GarminSD settings data fields to record the watch app version number etc and
log that info to the SysLog file.
V3.1.11 - 23oct2019 V3.1.11 - 23oct2019
- Updated network data source so it displays heart rate data if it is available. - Updated network data source so it displays heart rate data if it is available.

View File

@@ -1,13 +1,114 @@
package uk.org.openseizuredetector; package uk.org.openseizuredetector;
import android.app.DatePickerDialog;
import android.app.TimePickerDialog;
import android.content.Context;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.TimePicker;
import java.util.Calendar;
public class DBQueryActivity extends AppCompatActivity
implements View.OnClickListener {
String TAG = "DBQueryActivity";
Button mDateBtn;
Button mTimeBtn;
Button mExportBtn;
EditText mDateTxt;
EditText mTimeTxt;
EditText mDurationTxt;
int mYear;
int mMonth;
int mDay;
int mHour;
int mMinute;
double mDuration;
OsdUtil mUtil;
Handler mHandler;
public class DBQueryActivity extends AppCompatActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dbquery); setContentView(R.layout.activity_dbquery);
mHandler = new Handler();
mUtil = new OsdUtil(this, mHandler);
mDateBtn = (Button)findViewById(R.id.dateBtn);
mDateBtn.setOnClickListener(this);
mTimeBtn = (Button)findViewById(R.id.timeBtn);
mTimeBtn.setOnClickListener(this);
mExportBtn = (Button)findViewById(R.id.exportBtn);
mExportBtn.setOnClickListener(this);
mDateTxt = (EditText)findViewById(R.id.endDateText);
mTimeTxt = (EditText)findViewById(R.id.endTimeText);
mDurationTxt = (EditText)findViewById(R.id.durationText);
// Get Current Date
final Calendar c = Calendar.getInstance();
mYear = c.get(Calendar.YEAR);
mMonth = c.get(Calendar.MONTH);
mDay = c.get(Calendar.DAY_OF_MONTH);
mHour = c.get(Calendar.HOUR_OF_DAY);
mMinute = c.get(Calendar.MINUTE);
mDateTxt.setText(String.format("%02d-%02d-%04d",mDay, mMonth+1, mYear));
mTimeTxt.setText(String.format("%02d:%02d:%02d", mHour, mMinute, 00));
mDuration = 2.0;
mDurationTxt.setText(String.format("%03.1f", mDuration));
}
@Override
public void onClick(View view) {
Log.v(TAG, "onClick()");
if (view == mDateBtn) {
DatePickerDialog datePickerDialog = new DatePickerDialog(this,
new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int year,
int monthOfYear, int dayOfMonth) {
mDay = dayOfMonth;
mMonth = monthOfYear;
mYear = year;
mDateTxt.setText(String.format("%02d-%02d-%04d",mDay, mMonth+1, mYear));
}
}, mYear, mMonth, mDay);
datePickerDialog.show();
}
if (view == mTimeBtn) {
// Launch Time Picker Dialog
TimePickerDialog timePickerDialog = new TimePickerDialog(this,
new TimePickerDialog.OnTimeSetListener() {
@Override
public void onTimeSet(TimePicker view, int hourOfDay,
int minute) {
mHour = hourOfDay;
mMinute = minute;
mTimeTxt.setText(String.format("%02d:%02d:%02d", mHour, mMinute, 00));
}
}, mHour, mMinute, false);
timePickerDialog.show();
}
if (view == mExportBtn) {
mDateTxt.setText(String.format("%02d-%02d-%04d",mDay, mMonth+1, mYear));
mTimeTxt.setText(String.format("%02d:%02d:%02d", mHour, mMinute, 00));
mDuration = Double.parseDouble(mDurationTxt.getText().toString());
mUtil.showToast(String.format("EndDate=%s %s, Duration=%3.1f hrs",
mDateTxt.getText().toString(), mTimeTxt.getText().toString() ,mDuration));
}
} }
} }

View File

@@ -28,9 +28,25 @@ import android.database.DatabaseUtils;
import android.database.SQLException; import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.os.AsyncTask;
import android.os.CountDownTimer;
import android.os.Handler;
import android.text.format.Time; import android.text.format.Time;
import android.util.Log; import android.util.Log;
import org.apache.commons.codec.binary.Base64;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import static android.database.sqlite.SQLiteDatabase.openOrCreateDatabase; import static android.database.sqlite.SQLiteDatabase.openOrCreateDatabase;
/** /**
@@ -47,7 +63,10 @@ public class LogManager {
private int mOSDWearerId; private int mOSDWearerId;
private String mOSDUrl; private String mOSDUrl;
private OsdDbHelper mOSDDb; private OsdDbHelper mOSDDb;
private RemoteLogTimer mRemoteLogTimer;
private Context mContext; private Context mContext;
private OsdUtil mUtil;
public LogManager(boolean logRemote, public LogManager(boolean logRemote,
boolean logRemoteMobile, boolean logRemoteMobile,
@@ -64,6 +83,9 @@ public class LogManager {
mOSDUrl = OSDUrl; mOSDUrl = OSDUrl;
mContext = context; mContext = context;
Handler handler = new Handler();
mUtil = new OsdUtil(mContext, handler);
try { try {
mOSDDb = new OsdDbHelper(mDbTableName, mContext); mOSDDb = new OsdDbHelper(mDbTableName, mContext);
if (!checkTableExists(mOSDDb, mDbTableName)) { if (!checkTableExists(mOSDDb, mDbTableName)) {
@@ -72,9 +94,12 @@ public class LogManager {
} catch (SQLException e) { } catch (SQLException e) {
Log.e(TAG, "Failed to open Database: " + e.toString()); Log.e(TAG, "Failed to open Database: " + e.toString());
} }
startRemoteLogTimer();
} }
private boolean checkTableExists(OsdDbHelper osdDb, String osdTableName) { private boolean checkTableExists(OsdDbHelper osdDb, String osdTableName) {
Cursor c = null; Cursor c = null;
boolean tableExists = false; boolean tableExists = false;
@@ -126,8 +151,142 @@ public class LogManager {
} }
public void writeToRemoteServer() {
Log.v(TAG,"writeToRemoteServer()");
if (!mLogRemote) {
Log.v(TAG,"mLogRemote not set, not doing anything");
return;
}
if (!mLogRemoteMobile) {
// Check network state - are we using mobile data?
if (mUtil.isMobileDataActive()) {
Log.v(TAG,"Using mobile data, so not doing anything");
return;
}
}
if (!mUtil.isNetworkConnected()) {
Log.v(TAG,"No network connection - doing nothing");
return;
}
Log.v(TAG,"Requirements for remote logging met!");
uploadSdData();
}
/**
* Upload a batch of seizure detector data records to the server..
* Uses the UploadSdDataTask class to upload the data in the
* background. DownloadSdDataTask.onPostExecute() is called on completion.
*/
public void uploadSdData() {
Log.v(TAG, "uploadSdData()");
String dataStr = "data string to upload";
//new PostDataTask().execute("http://" + mOSDUrl + ":8080/data", dataStr, mOSDUname, mOSDPasswd);
new PostDataTask().execute("http://192.168.43.175:8765/datapoints/add", dataStr, mOSDUname, mOSDPasswd);
}
private class PostDataTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
// params comes from the execute() call:
// params[0] is the url,
// params[1] is the data to send.
// params[2] is the user name
// params[3] is the password
int MAXLEN = 500; // Maximum length of response that we will accept (bytes)
InputStream is = null;
String urlStr = params[0];
String dataStr = params[1];
String uname = params[2];
String passwd = params[3];
String resultStr = "Not Initialised";
Log.v(TAG,"doInBackgound(): url="+urlStr+" data="+dataStr+" uname="+uname+" passwd="+passwd);
try {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(2000 /* milliseconds */);
conn.setConnectTimeout(5000 /* milliseconds */);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json; utf-8");
conn.setRequestProperty("Accept", "application/json");
String auth = uname + ":" + passwd;
byte[] encodedAuth = Base64.encodeBase64(auth.getBytes("utf-8"));
String authHeaderValue = "Basic " + new String(encodedAuth);
conn.setRequestProperty("Authorization", authHeaderValue);
conn.setDoInput(true);
// Put our data into the outputstream associated with the connection.
OutputStream os = conn.getOutputStream();
byte[] input = dataStr.getBytes("utf-8");
os.write(input, 0, input.length);
// Starts the query
conn.connect();
int response = conn.getResponseCode();
Log.d(TAG, "The response code is: " + response);
is = conn.getInputStream();
// Convert the InputStream into a string
Reader reader = new InputStreamReader(is, "UTF-8");
char[] buffer = new char[MAXLEN];
reader.read(buffer);
resultStr = new String(buffer);
} catch (IOException e) {
Log.v(TAG,"doInBackground(): IOException - "+e.toString());
resultStr = "Error"+e.toString();
// Makes sure that the InputStream is closed after the app is
// finished using it.
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Log.v(TAG,"doInBackground(): IOException - "+e.toString());
resultStr = "Error"+e.toString();
}
}
}
if (resultStr.startsWith("Unable to retrieve web page")) {
Log.v(TAG,"doInBackground() - Unable to retrieve data");
} else {
Log.v(TAG,"doInBackground(): result = "+resultStr);
}
return (resultStr);
}
// onPostExecute displays the results of the AsyncTask.
@Override
protected void onPostExecute(String result) {
Log.v(TAG,"onPostExecute() - result = "+result);
}
}
public void close() { public void close() {
mOSDDb.close(); mOSDDb.close();
stopRemoteLogTimer();
}
public JSONObject queryDatapoints(String endDateStr, Double duration) {
Log.d(TAG,"queryDatapoints() - endDateStr="+endDateStr);
Cursor c = null;
try {
c = mOSDDb.getWritableDatabase().query(mDbTableName, null,
null, null, null, null, null);
//c.query("Select * from ? where DataTime < ?", mDbTableName, endDateStr);
}
catch (Exception e) {
Log.d(TAG, mDbTableName+" doesn't exist :(((");
}
return(null);
} }
public class OsdDbHelper extends SQLiteOpenHelper { public class OsdDbHelper extends SQLiteOpenHelper {
@@ -170,4 +329,54 @@ public class LogManager {
onUpgrade(db, oldVersion, newVersion); onUpgrade(db, oldVersion, newVersion);
} }
} }
/*
* Start the timer that will send and SMS alert after a given period.
*/
private void startRemoteLogTimer() {
if (mRemoteLogTimer != null) {
Log.v(TAG, "startRemoteLogTimer -timer already running - cancelling it");
mRemoteLogTimer.cancel();
mRemoteLogTimer = null;
}
Log.v(TAG, "startRemoteLogTimer() - starting RemoteLogTimer");
mRemoteLogTimer =
new RemoteLogTimer(10 * 1000, 1000);
mRemoteLogTimer.start();
}
/*
* Cancel the SMS timer to prevent the SMS message being sent..
*/
public void stopRemoteLogTimer() {
if (mRemoteLogTimer != null) {
Log.v(TAG, "stopRemoteLogTimer(): cancelling Remote Log timer");
mRemoteLogTimer.cancel();
mRemoteLogTimer = null;
}
}
/**
* Inhibit fault alarm initiation for a period to avoid spurious warning
* beeps caused by short term network interruptions.
*/
private class RemoteLogTimer extends CountDownTimer {
public RemoteLogTimer(long startTime, long interval) {
super(startTime, interval);
}
@Override
public void onTick(long l) {
// Do Nothing
}
@Override
public void onFinish() {
Log.v(TAG, "mRemoteLogTimer - onFinish");
writeToRemoteServer();
start();
}
}
} }

View File

@@ -238,6 +238,17 @@ public class MainActivity extends AppCompatActivity {
mConnection.mSdServer.sendSMSAlarm(); mConnection.mSdServer.sendSMSAlarm();
} }
return true; return true;
case R.id.action_export:
Log.i(TAG, "action_export");
try {
Intent i = new Intent(
MainActivity.this,
DBQueryActivity.class);
this.startActivity(i);
} catch (Exception ex) {
Log.i(TAG, "exception starting export activity " + ex.toString());
}
return true;
case R.id.action_logs: case R.id.action_logs:
Log.i(TAG, "action_logs"); Log.i(TAG, "action_logs");
try { try {

View File

@@ -49,6 +49,8 @@ import android.content.res.Resources;
import android.content.res.XmlResourceParser; import android.content.res.XmlResourceParser;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
@@ -295,6 +297,24 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac
return null; return null;
} }
public boolean isMobileDataActive() {
// return true if we are using mobile data, otherwise return false
ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
return true;
} else {
return false;
}
}
public boolean isNetworkConnected() {
// return true if we have a network connection, otherwise false.
ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return (activeNetwork.isConnected());
}
/** /**
* Display a Toast message on screen. * Display a Toast message on screen.
* *

View File

@@ -354,6 +354,10 @@ public class SdDataSourceGarmin extends SdDataSource {
// Returns a message string that is passed back to the watch. // Returns a message string that is passed back to the watch.
public String updateFromJSON(String jsonStr) { public String updateFromJSON(String jsonStr) {
String retVal = "undefined"; String retVal = "undefined";
String watchPartNo;
String watchFwVersion;
String sdVersion;
String sdName;
Log.v(TAG,"updateFromJSON - "+jsonStr); Log.v(TAG,"updateFromJSON - "+jsonStr);
try { try {
@@ -397,6 +401,23 @@ public class SdDataSourceGarmin extends SdDataSource {
mSampleFreq = (short)dataObject.getInt("sampleFreq"); mSampleFreq = (short)dataObject.getInt("sampleFreq");
mSdData.batteryPc = (short)dataObject.getInt("battery"); mSdData.batteryPc = (short)dataObject.getInt("battery");
Log.v(TAG,"updateFromJSON - mSamplePeriod="+mSamplePeriod+" mSampleFreq="+mSampleFreq); Log.v(TAG,"updateFromJSON - mSamplePeriod="+mSamplePeriod+" mSampleFreq="+mSampleFreq);
mUtil.writeToSysLogFile("SdDataSourceGarmin.updateFromJSON - Settings Received");
mUtil.writeToSysLogFile(" * mSamplePeriod="+mSamplePeriod+" mSampleFreq="+mSampleFreq);
mUtil.writeToSysLogFile(" * batteryPc = "+mSdData.batteryPc);
try {
watchPartNo = dataObject.getString("watchPartNo");
watchFwVersion = dataObject.getString("watchFwVersion");
sdVersion = dataObject.getString("sdVersion");
sdName = dataObject.getString("sdName");
mUtil.writeToSysLogFile(" * sdName = "+sdName+" version "+sdVersion);
mUtil.writeToSysLogFile(" * watchPartNo = "+watchPartNo+" fwVersion "+watchFwVersion);
} catch (Exception e) {
Log.e(TAG,"updateFromJSON - Error Parsing V3.2 JSON String - "+e.toString());
mUtil.writeToSysLogFile("updateFromJSON - Error Parsing V3.2 JSON String - "+e.toString());
mUtil.writeToSysLogFile(" This is probably because of an out of date watch app - please upgrade!");
e.printStackTrace();
}
mSdData.haveSettings = true; mSdData.haveSettings = true;
mSdData.mSampleFreq = mSampleFreq; mSdData.mSampleFreq = mSampleFreq;
mWatchAppRunningCheck = true; mWatchAppRunningCheck = true;
@@ -407,6 +428,7 @@ public class SdDataSourceGarmin extends SdDataSource {
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG,"updateFromJSON - Error Parsing JSON String - "+e.toString()); Log.e(TAG,"updateFromJSON - Error Parsing JSON String - "+e.toString());
mUtil.writeToSysLogFile("updateFromJSON - Error Parsing JSON String - "+e.toString());
e.printStackTrace(); e.printStackTrace();
retVal = "ERROR"; retVal = "ERROR";
} }

View File

@@ -371,6 +371,9 @@ public class SdServer extends Service implements SdDataReceiver {
} }
*/ */
// Stop the log Manager
mLm.close();
try { try {
// Cancel the notification. // Cancel the notification.
@@ -386,9 +389,6 @@ public class SdServer extends Service implements SdDataReceiver {
mToneGenerator.release(); mToneGenerator.release();
mToneGenerator = null; mToneGenerator = null;
// Stop the log Manager
mLm.close();
// stop this service. // stop this service.
Log.v(TAG, "onDestroy(): calling stopSelf()"); Log.v(TAG, "onDestroy(): calling stopSelf()");
mUtil.writeToSysLogFile("SdServer.onDestroy() - stopping self"); mUtil.writeToSysLogFile("SdServer.onDestroy() - stopping self");

View File

@@ -1,9 +1,71 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".DBQueryActivity"> android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="End Date/Time (dd-mm-yyyy hh:mm)"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
</android.support.constraint.ConstraintLayout> <EditText
android:id="@+id/endDateText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:text="(end date)" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Select Date"
android:id="@+id/dateBtn"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/endTimeText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:text="(end time)" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Select Time"
android:id="@+id/timeBtn"
/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Duration (hrs)"
/>
<EditText
android:id="@+id/durationText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:ems="10"
android:text="1.0" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Export Data"
android:id="@+id/exportBtn"
/>
</LinearLayout>

View File

@@ -55,6 +55,14 @@
android:enabled="true" android:enabled="true"
/> />
<item
android:id="@+id/action_export"
android:icon="@drawable/ic_action_settings"
android:showAsAction="never|withText"
android:title="Export Data"
android:enabled="true"
/>
<item <item
android:id="@+id/action_settings" android:id="@+id/action_settings"
android:icon="@drawable/ic_action_settings" android:icon="@drawable/ic_action_settings"

View File

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