diff --git a/.gitignore b/.gitignore index 128a207..326ee92 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ build .gradle .idea +local.properties +*.iml +app/app-release.apk app/build app/app.iml diff --git a/CHANGELOG.md b/CHANGELOG.md index 9150234..b758317 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,25 @@ OpenSeizureDetector Android App - Change Log ============================================ + V2.0.6 - 25 July 2016 + - Added main activity menu option to view log files (via web browser). + - Added options to switch off spectrum display on watch to save battery. + - Changed main screen graph to bar chart and highlights frequency + region of interest. + - Fixed problem with log files not showing on web interface. + - Added system log file to help with de-bugging start-up/shutdown issues. + - Improved handling of watch app settings to make sure + they are loaded correctly without having to re-start app (but I'd still recommend re-starting the watch app manually to be sure :) ) + - Reduced ammount of bluetooth comms to the watch to save battery. + - Added support for future watch app features (such as raw mode and digital + filter mode). + - Added watch app to Android phone app package so watch app can be + installed directly from phone rather than using pebble store - to make sure that watch app and Android app are always compatible. + V2.0.3 - 23 April 2016 Further modification to beep code to avoid occasional crashes if system tries to beep during a re-start. - Log faults to alarm log on SD Card. + Log faults to alarm log on SD Card. V2.0.2 - 13 April 2016 Modified 'beep' code to try to avoid crashes on some systems. diff --git a/app/app-release-2.0.4.apk b/app/app-release-2.0.4.apk new file mode 100644 index 0000000..059fc7c Binary files /dev/null and b/app/app-release-2.0.4.apk differ diff --git a/app/app-release-2.0.6.apk b/app/app-release-2.0.6.apk new file mode 100644 index 0000000..7f56461 Binary files /dev/null and b/app/app-release-2.0.6.apk differ diff --git a/app/build.gradle b/app/build.gradle index 4675763..7bdee66 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,7 +25,7 @@ android { dependencies { compile files('libs/mpandroidchartlibrary-2-0-7.jar') - compile 'com.getpebble:pebblekit:3.0.0@aar' + compile 'com.getpebble:pebblekit:3.1.0@aar' // Unit testing dependencies testCompile 'junit:junit:4.12' // Set this dependency if you want to use Mockito @@ -34,6 +34,9 @@ dependencies { testCompile 'org.hamcrest:hamcrest-library:1.1' compile 'com.android.support:appcompat-v7:22.2.1' compile 'com.android.support:support-v4:22.2.1' + //compile files('libs/JTransforms-3.1-with-dependencies.jar') + compile 'org.apache.commons:commons-math3:3.6.1' + } repositories { diff --git a/app/libs/JTransforms-3.1-with-dependencies.jar b/app/libs/JTransforms-3.1-with-dependencies.jar new file mode 100644 index 0000000..c6d5e1a Binary files /dev/null and b/app/libs/JTransforms-3.1-with-dependencies.jar differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4658dc6..fdcdd7d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,10 +1,10 @@ + android:versionCode="27" + android:versionName="2.0.6"> - + @@ -23,30 +23,28 @@ - + android:label="@string/app_name"> + + - - + android:icon="@drawable/star_of_life_48x48" + android:label="@string/app_name"> - + android:label="OpenSeizureDetector Preferences"> + diff --git a/app/src/main/assets/pebble_sd.pbw b/app/src/main/assets/pebble_sd.pbw new file mode 100644 index 0000000..b508c2f Binary files /dev/null and b/app/src/main/assets/pebble_sd.pbw differ diff --git a/app/src/main/assets/www/index.html b/app/src/main/assets/www/index.html index c464145..0ce2ad3 100644 --- a/app/src/main/assets/www/index.html +++ b/app/src/main/assets/www/index.html @@ -1,15 +1,13 @@ - Ben's Pebble Watch Seizure Detector + Open Seizure Detector - - @@ -77,23 +75,11 @@
- Logs + View Log Files
-
-
- logo -

Open Seizure Detector - Logs

-
-
-
-
-
- Home -
-
diff --git a/app/src/main/assets/www/index_orig.html b/app/src/main/assets/www/index_orig.html new file mode 100644 index 0000000..c464145 --- /dev/null +++ b/app/src/main/assets/www/index_orig.html @@ -0,0 +1,99 @@ + + + + Ben's Pebble Watch Seizure Detector + + + + + + + + + + + +
+ +
+
+ OK +
+
+
+ Pebble Watch Connected +
+
+ Pebble App Running +
+
+ Pebble Battery +
+
+ +
+ + +
+ +
+

Seizure Detector Data

+
+ Maximum Frequency: +
+
+ Maximum Value: +
+
+ Spectrum Power: +
+
+ Region Of Interest Power: +
+
+ Alarm Status: +
+
+ Alarm Phrase: +
+
+
+

Seizure Detector Settings

+ +
+ SD Settings +
+
+
+

+ For details see + + OpenSeizureDetector.org.uk + +

+
+
+
+ Logs +
+
+
+ +
+
+ logo +

Open Seizure Detector - Logs

+
+
+
+
+
+ Home +
+
+ + + diff --git a/app/src/main/assets/www/js/osd_logs.js b/app/src/main/assets/www/js/osd_logs.js index 9685376..05a1ea7 100644 --- a/app/src/main/assets/www/js/osd_logs.js +++ b/app/src/main/assets/www/js/osd_logs.js @@ -21,7 +21,7 @@ function populate_filelist(dataStr) { -$(document).delegate("#logs","pageinit", function() { +$(document).ready(function() { //alert("logs page opened"); get_filelist(); }); diff --git a/app/src/main/assets/www/logfiles.html b/app/src/main/assets/www/logfiles.html new file mode 100644 index 0000000..59b7ebf --- /dev/null +++ b/app/src/main/assets/www/logfiles.html @@ -0,0 +1,28 @@ + + + + Open Seizure Detector + + + + + + + + + +
+
+ logo +

Open Seizure Detector - Logs

+
+
+
+
+
+ Home +
+
+ + + diff --git a/app/src/main/java/uk/org/openseizuredetector/AccelData.java b/app/src/main/java/uk/org/openseizuredetector/AccelData.java new file mode 100644 index 0000000..2eabed3 --- /dev/null +++ b/app/src/main/java/uk/org/openseizuredetector/AccelData.java @@ -0,0 +1,85 @@ +package uk.org.openseizuredetector; + +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.TimeZone; + +/** + * Created by graham on 27/06/16. + */ +/* From https://github.com/kramimus/pebble-accel-analyzer */ +public class AccelData { + private final String TAG = AccelData.class.getSimpleName(); + + final private int x; + final private int y; + final private int z; + + private long timestamp = 0; + final private boolean didVibrate; + + public AccelData(byte[] data) { + x = (data[0] & 0xff) | (data[1] << 8); + y = (data[2] & 0xff) | (data[3] << 8); + z = (data[4] & 0xff) | (data[5] << 8); + didVibrate = data[6] != 0; + + for (int i = 0; i < 8; i++) { + timestamp |= ((long)(data[i+7] & 0xff)) << (i * 8); + } + } + + public JSONObject toJson() { + JSONObject json = new JSONObject(); + try { + json.put("x", x); + json.put("y", y); + json.put("z", z); + json.put("ts", timestamp); + json.put("v", didVibrate); + return json; + } catch (JSONException e) { + Log.w(TAG, "problem constructing accel data, skipping " + e); + } + return null; + } + + public static List fromDataArray(byte[] data) { + List accels = new ArrayList(); + for (int i = 0; i < data.length; i += 15) { + accels.add(new AccelData(Arrays.copyOfRange(data, i, i + 15))); + } + return accels; + } + + public long getTimestamp() { + return timestamp; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + + public int getMagnitude() { + return (int)Math.sqrt(x*x + y*y + z*z); + } + + public void applyTimezone(TimeZone tz) { + timestamp -= tz.getOffset(timestamp); + } +} + diff --git a/app/src/main/java/uk/org/openseizuredetector/CircularArrayList.java b/app/src/main/java/uk/org/openseizuredetector/CircularArrayList.java new file mode 100644 index 0000000..08d7d10 --- /dev/null +++ b/app/src/main/java/uk/org/openseizuredetector/CircularArrayList.java @@ -0,0 +1,107 @@ +package uk.org.openseizuredetector; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.RandomAccess; + +/** + * Created by graham on 28/06/16. + */ +public class CircularArrayList + extends AbstractList implements RandomAccess { + /** + * If you use this code, please consider notifying isak at du-preez dot com + * with a brief description of your application. + * + * This is free and unencumbered software released into the public domain. + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + */ + + private final int n; // buffer length + private final List buf; // a List implementing RandomAccess + private int head = 0; + private int tail = 0; + + public CircularArrayList(int capacity) { + n = capacity + 1; + buf = new ArrayList(Collections.nCopies(n, (E) null)); + } + + public int capacity() { + return n - 1; + } + + private int wrapIndex(int i) { + int m = i % n; + if (m < 0) { // java modulus can be negative + m += n; + } + return m; + } + + // This method is O(n) but will never be called if the + // CircularArrayList is used in its typical/intended role. + private void shiftBlock(int startIndex, int endIndex) { + assert (endIndex > startIndex); + for (int i = endIndex - 1; i >= startIndex; i--) { + set(i + 1, get(i)); + } + } + + @Override + public int size() { + return tail - head + (tail < head ? n : 0); + } + + @Override + public E get(int i) { + if (i < 0 || i >= size()) { + throw new IndexOutOfBoundsException(); + } + return buf.get(wrapIndex(head + i)); + } + + @Override + public E set(int i, E e) { + if (i < 0 || i >= size()) { + throw new IndexOutOfBoundsException(); + } + return buf.set(wrapIndex(head + i), e); + } + + @Override + public void add(int i, E e) { + int s = size(); + if (s == n - 1) { + throw new IllegalStateException("Cannot add element." + + " CircularArrayList is filled to capacity."); + } + if (i < 0 || i > s) { + throw new IndexOutOfBoundsException(); + } + tail = wrapIndex(tail + 1); + if (i < s) { + shiftBlock(i, s); + } + set(i, e); + } + + @Override + public E remove(int i) { + int s = size(); + if (i < 0 || i >= s) { + throw new IndexOutOfBoundsException(); + } + E e = get(i); + if (i > 0) { + shiftBlock(0, i); + } + head = wrapIndex(head + 1); + return e; + } +} diff --git a/app/src/main/java/uk/org/openseizuredetector/EventLogManager/EventLogListAdapter.java b/app/src/main/java/uk/org/openseizuredetector/EventLogManager/EventLogListAdapter.java new file mode 100644 index 0000000..91db4ef --- /dev/null +++ b/app/src/main/java/uk/org/openseizuredetector/EventLogManager/EventLogListAdapter.java @@ -0,0 +1,98 @@ +package uk.org.openseizuredetector.EventLogManager; + +import java.util.ArrayList; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import uk.org.openseizuredetector.R; + +public class EventLogListAdapter extends BaseAdapter { + EventLogManager dm; + ArrayList logEntryModelList; + LayoutInflater inflater; + Context _context; + + public EventLogListAdapter(Context context) { + + logEntryModelList = new ArrayList(); + _context = context; + inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + dm = new EventLogManager(_context); + logEntryModelList = dm.getAllData(); + + } + + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + //refetching the new data from database + logEntryModelList = dm.getAllData(); + + } + + public void delRow(int delPosition) { + + dm.deleteRow(logEntryModelList.get(delPosition).getId()); + logEntryModelList.remove(delPosition); + + } + + @Override + public int getCount() { + return logEntryModelList.size(); + } + + @Override + public Object getItem(int position) { + return logEntryModelList.get(position); + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder vHolder; + if (convertView == null) { + convertView = inflater.inflate(R.layout.log_entry_layout, null); + vHolder = new ViewHolder(); + + vHolder.date = (TextView) convertView + .findViewById(R.id.event_date); + vHolder.alarmState = (TextView) convertView + .findViewById(R.id.event_alarmState); + vHolder.note = (TextView) convertView + .findViewById(R.id.event_note); + vHolder.dataJSON = (TextView) convertView + .findViewById(R.id.event_dataJSON); + convertView.setTag(vHolder); + } else { + vHolder = (ViewHolder) convertView.getTag(); + } + + LogEntryModel eventObj = logEntryModelList.get(position); + + //vHolder.date.setText(eventObj.getDate().toString()); + vHolder.alarmState.setText(eventObj.getAlarmState()); + vHolder.note.setText(eventObj.getNote()); + vHolder.dataJSON.setText(eventObj.getDataJSON()); + + return convertView; + } + + class ViewHolder { + TextView date,alarmState,note,dataJSON; + } + +} diff --git a/app/src/main/java/uk/org/openseizuredetector/EventLogManager/EventLogManager.java b/app/src/main/java/uk/org/openseizuredetector/EventLogManager/EventLogManager.java new file mode 100644 index 0000000..daebfca --- /dev/null +++ b/app/src/main/java/uk/org/openseizuredetector/EventLogManager/EventLogManager.java @@ -0,0 +1,210 @@ +/** + * Database manager for logging events and associated seizure detector data. + */ +package uk.org.openseizuredetector.EventLogManager; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Locale; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +public class EventLogManager { + final static String TAG = "EventLogManager"; + private SQLiteDatabase db; // a reference to the database manager class. + private static final String DB_NAME = "eventlog"; // the name of our database + private static final int DB_VERSION = 1; // the version of the database + + private static final String TABLE_NAME = "events";// table name + + // the names for our database columns + private static final String TABLE_ROW_ID = "_id"; + private static final String TABLE_ROW_DATE = "event_date"; + private static final String TABLE_ROW_ALARM_STATE = "alarm_state"; + private static final String TABLE_ROW_DATA_JSON = "data_json"; + private static final String TABLE_ROW_NOTE = "note"; + private Context context; + + public EventLogManager(Context context) { + this.context = context; + + // create or open the database + CustomSQLiteOpenHelper helper = new CustomSQLiteOpenHelper(context); + this.db = helper.getWritableDatabase(); + + helper.onCreate(this.db); + } + + // the beginnings our SQLiteOpenHelper class + private class CustomSQLiteOpenHelper extends SQLiteOpenHelper { + + public CustomSQLiteOpenHelper(Context context) { + super(context, DB_NAME, null, DB_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + // the SQLite query string that will create our column database + // table. + String newTableQueryString = "create table " + TABLE_NAME + " (" + + TABLE_ROW_ID + + " integer primary key autoincrement not null," + + TABLE_ROW_DATE + " timestamp not null," + TABLE_ROW_ALARM_STATE + + " integer not null," + TABLE_ROW_NOTE + " text not null," + + TABLE_ROW_DATA_JSON + " text not null" + ");"; + + // execute the query string to the database. + db.execSQL(newTableQueryString); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + + // LATER, WE WOULD SPECIFIY HOW TO UPGRADE THE DATABASE + // FROM OLDER VERSIONS. + String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLE_NAME; + db.execSQL(DROP_TABLE); + onCreate(db); + + } + + } + + public void addRow(LogEntryModel eventObj) { + ContentValues values = prepareData(eventObj); + // ask the database object to insert the new data + try { + db.insert(TABLE_NAME, null, values); + } catch (Exception e) { + Log.e("DB ERROR", e.toString()); // prints the error message to + // the log + e.printStackTrace(); // prints the stack trace to the log + } + } + + private String getDateTime(Date date) { + SimpleDateFormat dateFormat = new SimpleDateFormat( + "yyyy-MM-dd HH:mm:ss", Locale.getDefault()); + if (date==null) + return ""; + else + return dateFormat.format(date); + } + + private ContentValues prepareData(LogEntryModel eventObj) { + + ContentValues values = new ContentValues(); + values.put(TABLE_ROW_ALARM_STATE, eventObj.getAlarmState()); + values.put(TABLE_ROW_DATE, getDateTime(eventObj.getDate())); + values.put(TABLE_ROW_NOTE, eventObj.getNote()); + values.put(TABLE_ROW_DATA_JSON, eventObj.getDataJSON()); + return values; + } + + // Returns row data in form of LogEntryModel object + public LogEntryModel getRowAsObject(int rowID) { + + LogEntryModel rowContactObj = new LogEntryModel(); + Cursor cursor; + + try { + + cursor = db.query(TABLE_NAME, new String[] { TABLE_ROW_ID, + TABLE_ROW_ALARM_STATE, TABLE_ROW_DATE, TABLE_ROW_NOTE, + TABLE_ROW_DATA_JSON }, TABLE_ROW_ID + "=" + rowID, null, + null, null, null, null); + + cursor.moveToFirst(); + prepareSendObject(rowContactObj, cursor); + + } catch (SQLException e) { + Log.e("DB ERROR", e.toString()); + e.printStackTrace(); + } + + return rowContactObj; + } + + // Returns all the rows data in form of LogEntryModel object list + + public ArrayList getAllData() { + + ArrayList allRowsObj = new ArrayList(); + Cursor cursor; + LogEntryModel rowContactObj; + + String[] columns = new String[] { TABLE_ROW_ID, TABLE_ROW_ALARM_STATE, + TABLE_ROW_DATE, TABLE_ROW_NOTE, TABLE_ROW_DATA_JSON }; + + try { + + cursor = db + .query(TABLE_NAME, columns, null, null, null, null, null); + cursor.moveToFirst(); + + if (!cursor.isAfterLast()) { + do { + rowContactObj = new LogEntryModel(); + rowContactObj.setId(cursor.getInt(0)); + prepareSendObject(rowContactObj, cursor); + allRowsObj.add(rowContactObj); + + } while (cursor.moveToNext()); // try to move the cursor's + // pointer forward one position. + } + } catch (SQLException e) { + Log.e("DB ERROR", e.toString()); + e.printStackTrace(); + } + + return allRowsObj; + + } + + private void prepareSendObject(LogEntryModel rowObj, Cursor cursor) { + rowObj.setId(cursor.getInt(cursor.getColumnIndexOrThrow(TABLE_ROW_ID))); + rowObj.setAlarmState(cursor.getInt(cursor + .getColumnIndexOrThrow(TABLE_ROW_ALARM_STATE))); + String dateTimeStr = cursor.getString(cursor + .getColumnIndexOrThrow(TABLE_ROW_DATE)); + Log.v(TAG,"dateTimeStr = "+dateTimeStr); + Date dateVal; + try { dateVal = new Date(dateTimeStr); } + catch (IllegalArgumentException e) { dateVal = null; } + rowObj.setDate(dateVal); + rowObj.setNote(cursor.getString(cursor + .getColumnIndexOrThrow(TABLE_ROW_NOTE))); + rowObj.setDataJSON(cursor.getString(cursor + .getColumnIndexOrThrow(TABLE_ROW_DATA_JSON))); + } + + public void deleteRow(int rowID) { + // ask the database manager to delete the row of given id + try { + db.delete(TABLE_NAME, TABLE_ROW_ID + "=" + rowID, null); + } catch (Exception e) { + Log.e("DB ERROR", e.toString()); + e.printStackTrace(); + } + } + + public void updateRow(int rowId, LogEntryModel contactObj) { + + ContentValues values = prepareData(contactObj); + + String whereClause = TABLE_ROW_ID + "=?"; + String whereArgs[] = new String[] { String.valueOf(rowId) }; + + db.update(TABLE_NAME, values, whereClause, whereArgs); + + } + + +} diff --git a/app/src/main/java/uk/org/openseizuredetector/EventLogManager/LogEntryModel.java b/app/src/main/java/uk/org/openseizuredetector/EventLogManager/LogEntryModel.java new file mode 100644 index 0000000..9502239 --- /dev/null +++ b/app/src/main/java/uk/org/openseizuredetector/EventLogManager/LogEntryModel.java @@ -0,0 +1,53 @@ +package uk.org.openseizuredetector.EventLogManager; + +import java.util.Date; + +/** + * Our LogEntryModel class which will have fields like id, name, contact number + * and email and corresponding getter and setter methods. + * **/ +public class LogEntryModel { + + private int id; + private Date date; + private int alarmState; + private String dataJSON; + private String note; + + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getAlarmState() { + return alarmState; + } + + public void setAlarmState(int alarmState) { + this.alarmState = alarmState; + } + + public String getNote() { + return note; + } + + public void setNote(String note) { + this.note = note; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public String getDataJSON() { return dataJSON; } + + public void setDataJSON(String dataJSON) { this.dataJSON = dataJSON; } +} diff --git a/app/src/main/java/uk/org/openseizuredetector/LogManagerActivity.java b/app/src/main/java/uk/org/openseizuredetector/LogManagerActivity.java new file mode 100644 index 0000000..5da2319 --- /dev/null +++ b/app/src/main/java/uk/org/openseizuredetector/LogManagerActivity.java @@ -0,0 +1,42 @@ +package uk.org.openseizuredetector; + +import android.app.Activity; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.widget.ListView; + +import java.util.Date; + +import uk.org.openseizuredetector.EventLogManager.EventLogListAdapter; +import uk.org.openseizuredetector.EventLogManager.EventLogManager; +import uk.org.openseizuredetector.EventLogManager.LogEntryModel; + +public class LogManagerActivity extends Activity { + + private EventLogListAdapter mEventLogListAdapter; + private ListView mEventLogListView; + private EventLogManager mElm; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_log_manager); + + LogEntryModel lem = new LogEntryModel(); + //lem.setDate(new Date()); + lem.setNote("Test Entry"); + lem.setDataJSON("[]"); + lem.setAlarmState(1); + + mElm = new EventLogManager(this); + mElm.addRow(lem); + + mEventLogListAdapter = new EventLogListAdapter(this); + mEventLogListView = (ListView) findViewById(R.id.eventLogListView); + mEventLogListView.setAdapter(mEventLogListAdapter); + + + } + + +} diff --git a/app/src/main/java/uk/org/openseizuredetector/MainActivity.java b/app/src/main/java/uk/org/openseizuredetector/MainActivity.java index 7a8cc45..69c8bb6 100644 --- a/app/src/main/java/uk/org/openseizuredetector/MainActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/MainActivity.java @@ -49,15 +49,25 @@ import android.widget.TextView; import android.widget.Button; import java.lang.reflect.Field; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Timer; import java.util.TimerTask; //MPAndroidChart +import com.github.mikephil.charting.charts.BarChart; import com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.components.YAxis; +import com.github.mikephil.charting.data.BarData; +import com.github.mikephil.charting.data.BarDataSet; +import com.github.mikephil.charting.data.BarEntry; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.LineDataSet; +import com.github.mikephil.charting.utils.ColorTemplate; +import com.github.mikephil.charting.utils.LargeValueFormatter; +import com.github.mikephil.charting.utils.ValueFormatter; public class MainActivity extends Activity { static final String TAG = "MainActivity"; @@ -86,8 +96,11 @@ public class MainActivity extends Activity { // Set our custom uncaught exception handler to report issues. Thread.setDefaultUncaughtExceptionHandler(new OsdUncaughtExceptionHandler(MainActivity.this)); //int i = 5/0; // Force exception to test handler. - mUtil = new OsdUtil(this); + mUtil = new OsdUtil(this,serverStatusHandler); mConnection = new SdServiceConnection(this); + mUtil.writeToSysLogFile(""); + mUtil.writeToSysLogFile("* MainActivity Started *"); + mUtil.writeToSysLogFile("MainActivity.onCreate()"); // Initialise the User Interface setContentView(R.layout.main); @@ -160,8 +173,11 @@ public class MainActivity extends Activity { switch (item.getItemId()) { case R.id.action_launch_pebble_app: Log.v(TAG, "action_launch_pebble_app"); - mUtil.startPebbleApp(); + mConnection.mSdServer.mSdDataSource.startPebbleApp(); return true; + case R.id.action_instal_watch_app: + Log.v(TAG, "action_install_watch_app"); + mConnection.mSdServer.mSdDataSource.installWatchApp(); case R.id.action_accept_alarm: Log.v(TAG, "action_accept_alarm"); @@ -209,6 +225,23 @@ public class MainActivity extends Activity { mConnection.mSdServer.sendSMSAlarm(); } return true; + case R.id.action_logs: + Log.v(TAG, "action_logs"); + try { + String url = "http://" + + mUtil.getLocalIpAddress() + + ":8080/logfiles.html"; + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(url)); + startActivity(i); + //Intent prefsIntent = new Intent( + // MainActivity.this, + // LogManagerActivity.class); + //this.startActivity(prefsIntent); + } catch (Exception ex) { + Log.v(TAG, "exception starting log manager activity " + ex.toString()); + } + return true; case R.id.action_settings: Log.v(TAG, "action_settings"); try { @@ -233,6 +266,7 @@ public class MainActivity extends Activity { @Override protected void onStart() { super.onStart(); + mUtil.writeToSysLogFile("MainActivity.onStart()"); SharedPreferences SP = PreferenceManager .getDefaultSharedPreferences(getBaseContext()); boolean audibleAlarm = SP.getBoolean("AudibleAlarm", true); @@ -243,18 +277,21 @@ public class MainActivity extends Activity { String versionName = mUtil.getAppVersionName(); tv.setText("OpenSeizureDetector Server Version " + versionName); + mUtil.writeToSysLogFile("MainActivity.onStart - Binding to Server"); mUtil.bindToServer(this, mConnection); - } @Override protected void onStop() { super.onStop(); + mUtil.writeToSysLogFile("MainActivity.onStop()"); mUtil.unbindFromServer(this, mConnection); } private void startServer() { + mUtil.writeToSysLogFile("MainActivity.startServer()"); + Log.v(TAG, "starting Server..."); mUtil.startServer(); // Change the action bar icon to show the option to stop the service. if (mOptionsMenu != null) { @@ -268,6 +305,7 @@ public class MainActivity extends Activity { } private void stopServer() { + mUtil.writeToSysLogFile("MainActivity.stopServer()"); Log.v(TAG, "stopping Server..."); mUtil.stopServer(); // Change the action bar icon to show the option to start the service. @@ -499,33 +537,86 @@ public class MainActivity extends Activity { //////////////////////////////////////////////////////////// // Produce graph - LineChart mChart = (LineChart) findViewById(R.id.chart1); - mChart.setDescription(""); + BarChart mChart = (BarChart) findViewById(R.id.chart1); + mChart.setDrawBarShadow(false); mChart.setNoDataTextDescription("You need to provide data for the chart."); - // X Values + mChart.setDescription(""); + + // X and Y Values ArrayList xVals = new ArrayList(); + ArrayList yBarVals = new ArrayList(); for (int i = 0; i < 10; i++) { - xVals.add((i) + ""); - } - // Y Values - ArrayList yVals = new ArrayList(); - for (int i = 0; i < 10; i++) { - if (mConnection.mSdServer != null) - yVals.add(new Entry(mConnection.mSdServer.mSdData.simpleSpec[i], i)); - else - yVals.add(new Entry(i, i)); + xVals.add(i+"-"+(i+1)+" Hz"); + if (mConnection.mSdServer != null) { + yBarVals.add(new BarEntry(mConnection.mSdServer.mSdData.simpleSpec[i], i)); + } + else { + yBarVals.add(new BarEntry(i,i)); + } } // create a dataset and give it a type - LineDataSet set1 = new LineDataSet(yVals, "DataSet 1"); - set1.setColor(Color.BLACK); - set1.setLineWidth(1f); + BarDataSet barDataSet = new BarDataSet(yBarVals,"Spectrum"); + try { + int[] barColours = new int[10]; + for (int i = 0; i < 10; i++) { + if ((i < mConnection.mSdServer.mSdData.alarmFreqMin) || + (i > mConnection.mSdServer.mSdData.alarmFreqMax)) { + barColours[i] = Color.GRAY; + } else { + barColours[i] = Color.RED; + } + } + barDataSet.setColors(barColours); + } catch (NullPointerException e){ + Log.v(TAG,"Null pointer exception setting bar colours"); + } + barDataSet.setBarSpacePercent(20f); + barDataSet.setBarShadowColor(Color.WHITE); + BarData barData = new BarData(xVals,barDataSet); + barData.setValueFormatter(new ValueFormatter() { + @Override + public String getFormattedValue(float v) { + DecimalFormat format = new DecimalFormat("####"); + return format.format(v); + } + }); + mChart.setData(barData); + + // format the axes + XAxis xAxis = mChart.getXAxis(); + xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); + xAxis.setTextSize(10f); + xAxis.setDrawAxisLine(true); + xAxis.setDrawLabels(true); + // Note: the default text colour is BLACK, so does not show up on black background!!! + // This took a lot of finding.... + xAxis.setTextColor(Color.WHITE); + xAxis.setDrawGridLines(false); + + YAxis yAxis = mChart.getAxisLeft(); + yAxis.setAxisMinValue(0f); + yAxis.setAxisMaxValue(3000f); + yAxis.setDrawGridLines(true); + yAxis.setDrawLabels(true); + yAxis.setTextColor(Color.WHITE); + yAxis.setValueFormatter(new ValueFormatter() { + @Override + public String getFormattedValue(float v) { + DecimalFormat format = new DecimalFormat("#####"); + return format.format(v); + } + }); + + YAxis yAxis2 = mChart.getAxisRight(); + yAxis2.setDrawGridLines(false); + + try { + mChart.getLegend().setEnabled(false); + } catch (NullPointerException e) { + Log.v(TAG,"Null Pointer Exception setting legend"); + } - ArrayList dataSets = new ArrayList(); - dataSets.add(set1); // add the datasets - LineData data = new LineData(xVals, dataSets); - //data.setValueTextSize(10f); - mChart.setData(data); mChart.invalidate(); } }; @@ -534,15 +625,18 @@ public class MainActivity extends Activity { @Override protected void onPause() { super.onPause(); + mUtil.writeToSysLogFile("MainActivity.onPause()"); } @Override protected void onResume() { super.onResume(); + mUtil.writeToSysLogFile("MainActivity.onResume()"); } private void showAbout() { + mUtil.writeToSysLogFile("MainActivity.showAbout()"); View aboutView = getLayoutInflater().inflate(R.layout.about_layout, null, false); String versionName = mUtil.getAppVersionName(); Log.v(TAG, "showAbout() - version name = " + versionName); @@ -554,11 +648,11 @@ public class MainActivity extends Activity { builder.show(); } - class ResponseHandler extends Handler { + static class ResponseHandler extends Handler { @Override public void handleMessage(Message message) { Log.v(TAG, "Message=" + message.toString()); } - } + } } diff --git a/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java b/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java index e7b5d6e..266d8d7 100644 --- a/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java +++ b/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java @@ -30,35 +30,91 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Environment; +import android.os.Handler; import android.os.IBinder; +import android.preference.PreferenceManager; +import android.text.format.Time; import android.util.Log; import android.view.MenuItem; import android.widget.Toast; import org.apache.http.conn.util.InetAddressUtils; +import java.io.File; +import java.io.FileWriter; import java.net.InetAddress; import java.net.NetworkInterface; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; +import java.util.List; +import java.util.RandomAccess; +import java.util.concurrent.RunnableFuture; /** * OsdUtil - OpenSeizureDetector Utilities * Deals with starting and stopping the background service and binding to it to receive data. */ public class OsdUtil { + private final String SYSLOG = "SysLog"; + private final String ALARMLOG = "AlarmLog"; + private final String DATALOG = "DataLog"; + /** * Based on http://stackoverflow.com/questions/7440473/android-how-to-check-if-the-intent-service-is-still-running-or-has-stopped-running */ private Context mContext; + private Handler mHandler; private String TAG = "OsdUtil"; + private boolean mLogAlarms = true; + private boolean mLogSystem = true; + private boolean mLogData = true; - public OsdUtil(Context context) { + public OsdUtil(Context context, Handler handler) { mContext = context; + mHandler = handler; + updatePrefs(); + writeToSysLogFile("OsdUtil() - initialised"); + } + /** + * updatePrefs() - update basic settings from the SharedPreferences + * - defined in res/xml/prefs.xml + */ + public void updatePrefs() { + Log.v(TAG, "updatePrefs()"); + SharedPreferences SP = PreferenceManager + .getDefaultSharedPreferences(mContext); + try { + mLogAlarms = SP.getBoolean("LogAlarms", true); + Log.v(TAG, "updatePrefs() - mLogAlarms = " + mLogAlarms); + mLogData = SP.getBoolean("LogData", false); + Log.v(TAG, "updatePrefs() - mLogData = " + mLogData); + mLogSystem = SP.getBoolean("LogSystem", true); + Log.v(TAG, "updatePrefs() - mLogSystem = " + mLogSystem); + + } catch (Exception ex) { + Log.v(TAG, "updatePrefs() - Problem parsing preferences!"); + showToast("Problem Parsing Preferences - Something won't work - Please go back to Settings and correct it!"); + } + } + + + /** + * used to make sure timers etc. run on UI thread + */ + public void runOnUiThread(Runnable runnable) { + mHandler.post(runnable); + } + + public boolean isServerRunning() { //Log.v(TAG,"isServerRunning()................"); ActivityManager manager = @@ -81,6 +137,8 @@ public class OsdUtil { */ public void startServer() { // Start the server + Log.v(TAG,"startServer()"); + writeToSysLogFile("startServer() - starting server"); Intent sdServerIntent; sdServerIntent = new Intent(mContext, SdServer.class); sdServerIntent.setData(Uri.parse("Start")); @@ -92,6 +150,7 @@ public class OsdUtil { */ public void stopServer() { Log.v(TAG, "stopping Server..."); + writeToSysLogFile("stopserver() - stopping server"); // then send an Intent to stop the service. Intent sdServerIntent; @@ -106,6 +165,7 @@ public class OsdUtil { */ public void bindToServer(Activity activity, SdServiceConnection sdServiceConnection) { Log.v(TAG, "bindToServer() - binding to SdServer"); + writeToSysLogFile("bindToServer() - binding to SdServer"); Intent intent = new Intent(sdServiceConnection.mContext, SdServer.class); activity.bindService(intent, sdServiceConnection, Context.BIND_AUTO_CREATE); } @@ -117,14 +177,18 @@ public class OsdUtil { // unbind this activity from the service if it is bound. if (sdServiceConnection.mBound) { Log.v(TAG, "unbindFromServer() - unbinding"); + writeToSysLogFile("unbindFromServer() - unbinding"); try { activity.unbindService(sdServiceConnection); sdServiceConnection.mBound = false; } catch (Exception ex) { Log.e(TAG, "unbindFromServer() - error unbinding service - " + ex.toString()); + writeToSysLogFile("unbindFromServer() - error unbinding service - " +ex.toString()); } } else { Log.v(TAG, "unbindFromServer() - not bound to server - ignoring"); + writeToSysLogFile("unbindFromServer() - not bound to server - ignoring"); + } } @@ -180,55 +244,106 @@ public class OsdUtil { /** * Display a Toast message on screen. + * * @param msg - message to display. */ - public void showToast(String msg) { - Toast.makeText(mContext, msg, - Toast.LENGTH_LONG).show(); - } - - - /** - * Open Pebble or Pebble Time app. If it is not installed, open Play store so the user can install it. - */ - public void startPebbleApp() { - // first try to launch the original pebble app - Intent pebbleAppIntent; - PackageManager pm = mContext.getPackageManager(); - try { - pebbleAppIntent = pm.getLaunchIntentForPackage("com.getpebble.android"); - mContext.startActivity(pebbleAppIntent); - } catch (Exception ex1) { - // and if original pebble app fails, try Pebble Time app... - Log.v(TAG, "exception starting original pebble App - trying pebble time..." + ex1.toString()); - try { - pebbleAppIntent = pm.getLaunchIntentForPackage("com.getpebble.android.basalt"); - mContext.startActivity(pebbleAppIntent); - } catch (Exception ex2) { - // and if that fails, open play store so the user can install it: - Log.v(TAG, "exception starting Pebble Time App." + ex2.toString()); - this.showToast("Error Launching Pebble or Pebble Time App - Please make sure it is installed..."); - final String appPackageName = "com.getpebble.android.basalt"; - try { - // try using play store app. - mContext.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appPackageName))); - } catch (android.content.ActivityNotFoundException anfe) { - // and if play store app is not installed, use browser to open app page. - mContext.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + appPackageName))); - } + public void showToast(final String msg) { + runOnUiThread(new Runnable() { + public void run() { + Toast.makeText(mContext, msg, + Toast.LENGTH_LONG).show(); } - } + }); + } + + /** + * Write a message to the system log file, provided mLogSystem is true. + * @param msgStr + */ + public void writeToSysLogFile(String msgStr) { + if (mLogSystem) + writeToLogFile(SYSLOG,msgStr); + else + Log.v(TAG,"writeToSysLogFile - mLogSystem False so not writing"); } /** - * Install the OpenSeizureDetector watch app onto the watch. - * based on https://forums.getpebble.com/discussion/13128/install-watch-app-pebble-store-from-android-companion-app + * Write a message to the alarm log file, provided mLogAlarms is true. + * @param msgStr */ - public void installOsdWatchApp() { - Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("pebble://appstore/54d28a43e4d94c043f000008")); - myIntent.setFlags( Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_NEW_TASK ); - mContext.startActivity(myIntent); + public void writeToAlarmLogFile(String msgStr) { + if (mLogAlarms) + writeToLogFile(ALARMLOG,msgStr); + else + Log.v(TAG,"writeToAlarmLogFile - mLogAlarms False so not writing"); } + /** + * Write a message to the data log file, provided mLogData is true. + * @param msgStr + */ + public void writeToDataLogFile(String msgStr) { + if (mLogData) + writeToLogFile(DATALOG,msgStr); + else + Log.v(TAG,"writeToDataLogFile - mLogData False so not writing"); + } + + + /** + * Write data to SD card - writes to data log file unless alarm=true, + * in which case writes to alarm log file. + */ + public void writeToLogFile(String fname, String msgStr) { + Log.v(TAG, "writeToLogFile(" + fname + "," + msgStr + ")"); + //showToast("Logging " + msgStr); + Time tnow = new Time(Time.getCurrentTimezone()); + tnow.setToNow(); + String dateStr = tnow.format("%Y-%m-%d"); + + fname = fname + "_" + dateStr + ".txt"; + // Open output directory on SD Card. + if (isExternalStorageWritable()) { + try { + FileWriter of = new FileWriter(getDataStorageDir().toString() + + "/" + fname, true); + if (msgStr != null) { + String dateTimeStr = tnow.format("%Y-%m-%d %H:%M:%S"); + Log.v(TAG, "writing msgStr"); + of.append(dateTimeStr+", " + +tnow.toMillis(true)+", " + +msgStr+"
\n"); + } + of.close(); + } catch (Exception ex) { + Log.e(TAG, "writeToLogFile - error " + ex.toString()); + showToast("ERROR Writing to Log File"); + } + } else { + Log.e(TAG, "ERROR - Can not Write to External Folder"); + } + } + + /* Checks if external storage is available for read and write */ + public boolean isExternalStorageWritable() { + String state = Environment.getExternalStorageState(); + if (Environment.MEDIA_MOUNTED.equals(state)) { + return true; + } + return false; + } + + public File getDataStorageDir() { + // Get the directory for the user's public directory. + File file = + new File(Environment.getExternalStorageDirectory() + , "OpenSeizureDetector"); + if (!file.mkdirs()) { + Log.e(TAG, "Directory not created"); + } + return file; + } + + } diff --git a/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java b/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java index b79eeb4..0fff372 100644 --- a/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/PrefActivity.java @@ -28,6 +28,7 @@ package uk.org.openseizuredetector; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.os.Handler; import android.preference.PreferenceActivity; import android.os.Bundle; import android.preference.PreferenceFragment; @@ -42,6 +43,7 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference private OsdUtil mUtil; private boolean mPrefChanged = false; private Context mContext; + private Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { @@ -51,9 +53,11 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference Thread.setDefaultUncaughtExceptionHandler(new OsdUncaughtExceptionHandler(PrefActivity.this)); //int i = 5/0; // Force exception to test handler. - - mUtil = new OsdUtil(getApplicationContext()); + mHandler = new Handler(); mContext = getApplicationContext(); + + mUtil = new OsdUtil(mContext,mHandler); + mUtil.writeToSysLogFile("PrefActvity.onCreate()"); } /** @@ -105,6 +109,7 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference @Override public void onStart() { super.onStart(); + mUtil.writeToSysLogFile("PrefActvity.onStart()"); invalidateHeaders(); Log.v(TAG, "onStart()"); } @@ -120,6 +125,7 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference @Override public void onResume() { super.onResume(); + mUtil.writeToSysLogFile("PrefActvity.onResume()"); Log.v(TAG, "onResume()"); SharedPreferences SP = PreferenceManager .getDefaultSharedPreferences(getBaseContext()); @@ -129,6 +135,7 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference @Override protected void onPause() { super.onPause(); + mUtil.writeToSysLogFile("PrefActvity.onPause()"); SharedPreferences SP = PreferenceManager .getDefaultSharedPreferences(getBaseContext()); SP.unregisterOnSharedPreferenceChangeListener(this); @@ -137,8 +144,10 @@ public class PrefActivity extends PreferenceActivity implements SharedPreference @Override protected void onDestroy() { super.onDestroy(); + mUtil.writeToSysLogFile("PrefActvity.onDestroy()"); Log.v(TAG, "onDestroy. mPrefChanged=" + mPrefChanged); if (mPrefChanged) { + mUtil.writeToSysLogFile("PrefActvity.onDestroy() - settings changed - re-starting...."); mUtil.showToast("Settings Changed - re-starting OpenSeizureDetector...."); Intent intent = new Intent(getApplicationContext(), StartupActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); diff --git a/app/src/main/java/uk/org/openseizuredetector/SdData.java b/app/src/main/java/uk/org/openseizuredetector/SdData.java index 282710e..30f3e56 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdData.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdData.java @@ -43,6 +43,15 @@ public class SdData implements Parcelable { /* Analysis settings */ public boolean haveSettings = false; // flag to say if we have received settings or not. public boolean haveData = false; // flag to say we have received data. + public short mDataUpdatePeriod; + public short mMutePeriod; + public short mManAlarmPeriod; + public boolean mFallActive; + public short mFallThreshMin; + public short mFallThreshMax; + public short mFallWindow; + public long mSdMode; + public long mSampleFreq; public long alarmFreqMin; public long alarmFreqMax; public long nMin; @@ -141,6 +150,10 @@ public class SdData implements Parcelable { jsonObj.put("haveSettings", haveSettings); jsonObj.put("alarmState", alarmState); jsonObj.put("alarmPhrase", alarmPhrase); + jsonObj.put("sdMode",mSdMode); + jsonObj.put("sampleFreq",mSampleFreq); + jsonObj.put("alarmFreqMin",alarmFreqMin); + jsonObj.put("alarmFreqMax",alarmFreqMax); jsonObj.put("alarmThresh", alarmThresh); jsonObj.put("alarmRatioThresh", alarmRatioThresh); JSONArray arr = new JSONArray(); diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java index ce62048..2fc65b2 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java @@ -24,6 +24,7 @@ package uk.org.openseizuredetector; import android.content.Context; +import android.os.Handler; import android.util.Log; import android.widget.Toast; @@ -39,13 +40,17 @@ interface SdDataReceiver { public abstract class SdDataSource { public SdData mSdData; public String mName = "undefined"; + protected OsdUtil mUtil; protected Context mContext; + protected Handler mHandler; protected SdDataReceiver mSdDataReceiver; private String TAG = "SdDataSource"; - public SdDataSource(Context context, SdDataReceiver sdDataReceiver) { + public SdDataSource(Context context, Handler handler, SdDataReceiver sdDataReceiver) { Log.v(TAG, "SdDataSource() Constructor"); mContext = context; + mHandler = handler; + mUtil = new OsdUtil(mContext, mHandler); mSdDataReceiver = sdDataReceiver; mSdData = new SdData(); } @@ -73,6 +78,13 @@ public abstract class SdDataSource { Log.v(TAG, "stop()"); } + /** + * Install the watch app on the watch. + */ + public void installWatchApp() { Log.v(TAG,"installWatchApp"); } + + public void startPebbleApp() { Log.v(TAG,"startPebbleApp()"); } + /** * Display a Toast message on screen. * @param msg - message to display. diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceNetwork.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceNetwork.java index bb124d6..23b2ca0 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceNetwork.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceNetwork.java @@ -5,6 +5,7 @@ import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.AsyncTask; +import android.os.Handler; import android.preference.PreferenceManager; import android.text.format.Time; import android.util.Log; @@ -32,14 +33,15 @@ public class SdDataSourceNetwork extends SdDataSource { private int ALARM_STATE_NETFAULT = 7; - public SdDataSourceNetwork(Context context, SdDataReceiver sdDataReceiver) { - super(context,sdDataReceiver); + public SdDataSourceNetwork(Context context, Handler handler, SdDataReceiver sdDataReceiver) { + super(context, handler, sdDataReceiver); mName = "Network"; } @Override public void start() { // Update preferences. Log.v(TAG,"start(): calling updatePrefs()"); + mUtil.writeToSysLogFile("SdDataSourceNetwork().start()"); updatePrefs(); // Start timer to retrieve seizure detector data regularly. @@ -62,6 +64,7 @@ public class SdDataSourceNetwork extends SdDataSource { } @Override public void stop() { + mUtil.writeToSysLogFile("SdDataSourceNetwork().stop()"); // Stop the data update timer if (mDataUpdateTimer !=null) { Log.v(TAG,"stop(): cancelling status timer"); @@ -80,6 +83,7 @@ public class SdDataSourceNetwork extends SdDataSource { */ public void updatePrefs() { Log.v(TAG, "updatePrefs()"); + mUtil.writeToSysLogFile("SdDataSourceNetwork().updatePrefs()"); SharedPreferences SP = PreferenceManager .getDefaultSharedPreferences(mContext); mServerIP = SP.getString("ServerIP","192.168.1.175"); @@ -90,14 +94,14 @@ public class SdDataSourceNetwork extends SdDataSource { Log.v(TAG,"updatePrefs() - mDataUpdatePeriod = "+mDataUpdatePeriod); } catch (Exception ex) { Log.v(TAG,"updatePrefs() - Problem parsing preferences!"); + mUtil.writeToSysLogFile("SdDataSourceNetwork().updatePrefs() - " +ex.toString()); showToast("Problem Parsing Preferences - Something won't work"); } - } /** * Retrive the current Seizure Detector Data from the server. - * Uses teh DownloadSdDataTask class to download the data in the + * Uses the DownloadSdDataTask class to download the data in the * background. The data is processed in DownloadSdDataTask.onPostExecute(). */ public void downloadSdData() { diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSourcePebble.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSourcePebble.java index 8314be0..e7788fe 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdDataSourcePebble.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSourcePebble.java @@ -3,7 +3,7 @@ See http://openseizuredetector.org for more information. - Copyright Graham Jones, 2015. + Copyright Graham Jones, 2015, 2016 This file is part of pebble_sd. @@ -24,18 +24,24 @@ package uk.org.openseizuredetector; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Handler; -import android.os.Looper; import android.preference.PreferenceManager; import android.text.format.Time; import android.util.Log; import android.widget.Toast; -import com.getpebble.android.kit.Constants; 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; @@ -50,15 +56,16 @@ import java.util.UUID; * network data source. */ public class SdDataSourcePebble extends SdDataSource { + private Handler mHandler = new Handler(); private Timer mSettingsTimer; private Timer mStatusTimer; private Time mPebbleStatusTime; private boolean mPebbleAppRunningCheck = false; - private int mDataPeriod = 5; // Period at which data is sent from watch to phone (sec) private int mAppRestartTimeout = 10; // Timeout before re-starting watch app (sec) if we have not received - // data after mDataPeriod + // data after mDataUpdatePeriod //private Looper mServiceLooper; private int mFaultTimerPeriod = 30; // Fault Timer Period in sec + private int mSettingsPeriod = 60; // period between requesting settings in seconds. private PebbleKit.PebbleDataReceiver msgDataHandler = null; @@ -94,14 +101,54 @@ public class SdDataSourcePebble extends SdDataSource { private int KEY_DATA_UPDATE_PERIOD = 25; private int KEY_MUTE_PERIOD = 26; private int KEY_MAN_ALARM_PERIOD = 27; + private int KEY_SD_MODE = 28; + private int KEY_SAMPLE_FREQ = 29; + private int KEY_RAW_DATA = 30; + private int KEY_NUM_RAW_DATA = 31; + private int KEY_DEBUG = 32; + private int KEY_DISPLAY_SPECTRUM = 33; // Values of the KEY_DATA_TYPE entry in a message private int DATA_TYPE_RESULTS = 1; // Analysis Results private int DATA_TYPE_SETTINGS = 2; // Settings private int DATA_TYPE_SPEC = 3; // FFT Spectrum (or part of a spectrum) - public SdDataSourcePebble(Context context, SdDataReceiver sdDataReceiver) { - super(context,sdDataReceiver); + private int DATA_TYPE_RAW = 4; // raw accelerometer data. + + // Values for SD_MODE + private int SD_MODE_FFT = 0; // The original OpenSeizureDetector mode (FFT based) + private int SD_MODE_RAW = 1; // Send raw, unprocessed data to the phone. + private int SD_MODE_FILTER = 2; // Use digital filter rather than FFT. + + private short mDebug; + private short mDisplaySpectrum; + private short mDataUpdatePeriod; + private short mMutePeriod; + private short mManAlarmPeriod; + private short mPebbleSdMode; + private short mSampleFreq; + private short mAlarmFreqMin; + private short mAlarmFreqMax; + private short mWarnTime; + private short mAlarmTime; + private short mAlarmThresh; + private short mAlarmRatioThresh; + private boolean mFallActive; + private short mFallThreshMin; + private short mFallThreshMax; + private short mFallWindow; + + // raw data storage for SD_MODE_RAW + private int MAX_RAW_DATA = 500; + private double[] rawData = new double[MAX_RAW_DATA]; + private int nRawData = 0; + + public SdDataSourcePebble(Context context, Handler handler, + SdDataReceiver sdDataReceiver) { + super(context, handler, sdDataReceiver); mName = "Pebble"; + // Set default settings from XML files (mContext is set by super(). + PreferenceManager.setDefaultValues(mContext, + R.xml.pebble_datasource_prefs, true); } @@ -111,6 +158,7 @@ public class SdDataSourcePebble extends SdDataSource { */ public void start() { Log.v(TAG, "start()"); + mUtil.writeToSysLogFile("SdDataSourcePebble.start()"); updatePrefs(); startPebbleServer(); // Start timer to check status of pebble regularly. @@ -118,35 +166,38 @@ public class SdDataSourcePebble extends SdDataSource { // use a timer to check the status of the pebble app on the same frequency // as we get app data. if (mStatusTimer == null) { - Log.v(TAG, "onCreate(): starting status timer"); + Log.v(TAG, "start(): starting status timer"); + mUtil.writeToSysLogFile("SdDataSourcePebble.start() - starting status timer"); mStatusTimer = new Timer(); mStatusTimer.schedule(new TimerTask() { @Override public void run() { getPebbleStatus(); } - }, 0, mDataPeriod * 1000); + }, 0, mDataUpdatePeriod * 1000); } else { - Log.v(TAG, "onCreate(): status timer already running."); + Log.v(TAG, "start(): status timer already running."); + mUtil.writeToSysLogFile("SdDataSourcePebble.start() - status timer already running??"); } // make sure we get some data when we first start. getPebbleData(); // Start timer to retrieve pebble settings regularly. getPebbleSdSettings(); if (mSettingsTimer == null) { - Log.v(TAG, "onCreate(): starting settings timer"); + Log.v(TAG, "start(): starting settings timer"); + mUtil.writeToSysLogFile("SdDataSourcePebble.start() - starting settings timer"); mSettingsTimer = new Timer(); mSettingsTimer.schedule(new TimerTask() { @Override public void run() { + //mUtil.writeToSysLogFile("SdDataSourcePebble.mSettingsTimer timed out."); getPebbleSdSettings(); } - }, 0, 1000 * (mDataPeriod + 60)); // ask for settings less frequently than we get data + }, 0, 1000 * mSettingsPeriod); // ask for settings less frequently than we get data } else { - Log.v(TAG, "onCreate(): settings timer already running."); + Log.v(TAG, "start(): settings timer already running."); + mUtil.writeToSysLogFile("SdDataSourcePebble.start() - settings timer already running??"); } - - } /** @@ -154,30 +205,33 @@ public class SdDataSourcePebble extends SdDataSource { */ public void stop() { Log.v(TAG, "stop()"); + mUtil.writeToSysLogFile("SdDataSourcePebble.stop()"); try { // Stop the status timer if (mStatusTimer != null) { - Log.v(TAG, "onDestroy(): cancelling status timer"); + Log.v(TAG, "stop(): cancelling status timer"); + mUtil.writeToSysLogFile("SdDataSourcePebble.stop() - cancelling status timer"); mStatusTimer.cancel(); mStatusTimer.purge(); mStatusTimer = null; } // Stop the settings timer if (mSettingsTimer != null) { - Log.v(TAG, "onDestroy(): cancelling settings timer"); + Log.v(TAG, "stop(): cancelling settings timer"); + mUtil.writeToSysLogFile("SdDataSourcePebble.stop() - cancelling settings timer"); mSettingsTimer.cancel(); mSettingsTimer.purge(); mSettingsTimer = null; } // Stop pebble message handler. - Log.v(TAG, "onDestroy(): stopping pebble server"); + Log.v(TAG, "stop(): stopping pebble server"); + mUtil.writeToSysLogFile("SdDataSourcePebble.stop() - stopping pebble server"); stopPebbleServer(); } catch (Exception e) { Log.v(TAG, "Error in stop() - " + e.toString()); + mUtil.writeToSysLogFile("SdDataSourcePebble.stop() - error - "+e.toString()); } - - } /** @@ -186,6 +240,7 @@ public class SdDataSourcePebble extends SdDataSource { */ public void updatePrefs() { Log.v(TAG, "updatePrefs()"); + mUtil.writeToSysLogFile("SdDataSourcePebble.updatePrefs()"); SharedPreferences SP = PreferenceManager .getDefaultSharedPreferences(mContext); try { @@ -200,17 +255,6 @@ public class SdDataSourcePebble extends SdDataSource { toast.show(); } - // Parse the DataPeriod setting. - try { - String dataPeriodStr = SP.getString("DataPeriod", "5"); - mDataPeriod = Integer.parseInt(dataPeriodStr); - Log.v(TAG, "updatePrefs() - mDataPeriod = " + mDataPeriod); - } catch (Exception ex) { - Log.v(TAG, "updatePrefs() - Problem with DataPeriod preference!"); - Toast toast = Toast.makeText(mContext, "Problem Parsing DataPeriod Preference", Toast.LENGTH_SHORT); - toast.show(); - } - // Parse the FaultTimer period setting. try { String faultTimerPeriodStr = SP.getString("FaultTimerPeriod", "30"); @@ -224,84 +268,78 @@ public class SdDataSourcePebble extends SdDataSource { // Watch Settings - PebbleDictionary setDict = new PebbleDictionary(); - short intVal; String prefStr; - prefStr = SP.getString("DataUpdatePeriod", "5"); - intVal = (short) Integer.parseInt(prefStr); - Log.v(TAG, "updatePrefs() DataUpdatePeriod = " + intVal); - setDict.addInt16(KEY_DATA_UPDATE_PERIOD, intVal); + prefStr = SP.getString("PebbleDebug", "SET_FROM_XML"); + mDebug = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() Debug = " + mDebug); - prefStr = SP.getString("MutePeriod", "300"); - intVal = (short) Integer.parseInt(prefStr); - Log.v(TAG, "updatePrefs() MutePeriod = " + intVal); - setDict.addInt16(KEY_MUTE_PERIOD, intVal); + prefStr = SP.getString("PebbleDisplaySpectrum", "SET_FROM_XML"); + mDisplaySpectrum = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() DisplaySpectrum = " + mDisplaySpectrum); - prefStr = SP.getString("ManAlarmPeriod", "30"); - intVal = (short) Integer.parseInt(prefStr); - Log.v(TAG, "updatePrefs() ManAlarmPeriod = " + intVal); - setDict.addInt16(KEY_MAN_ALARM_PERIOD, intVal); + prefStr = SP.getString("PebbleUpdatePeriod", "SET_FROM_XML"); + mDataUpdatePeriod = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() DataUpdatePeriod = " + mDataUpdatePeriod); + prefStr = SP.getString("MutePeriod", "SET_FROM_XML"); + mMutePeriod = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() MutePeriod = " + mMutePeriod); - prefStr = SP.getString("AlarmFreqMin", "5"); - intVal = (short) Integer.parseInt(prefStr); - Log.v(TAG, "updatePrefs() AlarmFreqMin = " + intVal); - setDict.addInt16(KEY_ALARM_FREQ_MIN, intVal); + prefStr = SP.getString("ManAlarmPeriod", "SET_FROM_XML"); + mManAlarmPeriod = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() ManAlarmPeriod = " + mManAlarmPeriod); - prefStr = SP.getString("AlarmFreqMax", "10"); - intVal = (short) Integer.parseInt(prefStr); - Log.v(TAG, "updatePrefs() AlarmFreqMax = " + intVal); - setDict.addUint16(KEY_ALARM_FREQ_MAX, (short) intVal); + prefStr = SP.getString("PebbleSdMode", "SET_FROM_XML"); + mPebbleSdMode = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() PebbleSdMode = " + mPebbleSdMode); - prefStr = SP.getString("WarnTime", "5"); - intVal = (short) Integer.parseInt(prefStr); - Log.v(TAG, "updatePrefs() WarnTime = " + intVal); - setDict.addUint16(KEY_WARN_TIME, (short) intVal); + prefStr = SP.getString("SampleFreq", "SET_FROM_XML"); + mSampleFreq = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() SampleFreq = " + mSampleFreq); - prefStr = SP.getString("AlarmTime", "10"); - intVal = (short) Integer.parseInt(prefStr); - Log.v(TAG, "updatePrefs() AlarmTime = " + intVal); - setDict.addUint16(KEY_ALARM_TIME, (short) intVal); + prefStr = SP.getString("AlarmFreqMin", "SET_FROM_XML"); + mAlarmFreqMin = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() AlarmFreqMin = " + mAlarmFreqMin); - prefStr = SP.getString("AlarmThresh", "70"); - intVal = (short) Integer.parseInt(prefStr); - Log.v(TAG, "updatePrefs() AlarmThresh = " + intVal); - setDict.addUint16(KEY_ALARM_THRESH, (short) intVal); + prefStr = SP.getString("AlarmFreqMax", "SET_FROM_XML"); + mAlarmFreqMax = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() AlarmFreqMax = " + mAlarmFreqMax); - prefStr = SP.getString("AlarmRatioThresh", "30"); - intVal = (short) Integer.parseInt(prefStr); - Log.v(TAG, "updatePrefs() AlarmRatioThresh = " + intVal); - setDict.addUint16(KEY_ALARM_RATIO_THRESH, (short) intVal); + prefStr = SP.getString("WarnTime", "SET_FROM_XML"); + mWarnTime = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() WarnTime = " + mWarnTime); - boolean fallActiveBool = SP.getBoolean("FallActive", false); - Log.v(TAG, "updatePrefs() FallActive = " + fallActiveBool); - if (fallActiveBool) - setDict.addUint16(KEY_FALL_ACTIVE, (short) 1); - else - setDict.addUint16(KEY_FALL_ACTIVE, (short) 0); + prefStr = SP.getString("AlarmTime", "SET_FROM_XML"); + mAlarmTime = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() AlarmTime = " + mAlarmTime); - prefStr = SP.getString("FallThreshMin", "200"); - intVal = (short) Integer.parseInt(prefStr); - Log.v(TAG, "updatePrefs() FallThreshMin = " + intVal); - setDict.addUint16(KEY_FALL_THRESH_MIN, (short) intVal); + prefStr = SP.getString("AlarmThresh", "SET_FROM_XML"); + mAlarmThresh = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() AlarmThresh = " + mAlarmThresh); - prefStr = SP.getString("FallThreshMax", "1200"); - intVal = (short) Integer.parseInt(prefStr); - Log.v(TAG, "updatePrefs() FallThreshMax = " + intVal); - setDict.addUint16(KEY_FALL_THRESH_MAX, (short) intVal); + prefStr = SP.getString("AlarmRatioThresh", "SET_FROM_XML"); + mAlarmRatioThresh = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() AlarmRatioThresh = " + mAlarmRatioThresh); - prefStr = SP.getString("FallWindow", "1500"); - intVal = (short) Integer.parseInt(prefStr); - Log.v(TAG, "updatePrefs() FallWindow = " + intVal); - setDict.addUint16(KEY_FALL_WINDOW, (short) intVal); + mFallActive = SP.getBoolean("FallActive", false); + Log.v(TAG, "updatePrefs() FallActive = " + mFallActive); + prefStr = SP.getString("FallThreshMin", "SET_FROM_XML"); + mFallThreshMin = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() FallThreshMin = " + mFallThreshMin); + + prefStr = SP.getString("FallThreshMax", "SET_FROM_XML"); + mFallThreshMax = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() FallThreshMax = " + mFallThreshMax); + + prefStr = SP.getString("FallWindow", "SET_FROM_XML"); + mFallWindow = (short) Integer.parseInt(prefStr); + Log.v(TAG, "updatePrefs() FallWindow = " + mFallWindow); - // Send Watch Settings to Pebble - Log.v(TAG, "updatePrefs() - setDict = " + setDict.toJsonString()); - PebbleKit.sendDataToPebble(mContext, SD_UUID, setDict); } catch (Exception ex) { Log.v(TAG, "updatePrefs() - Problem parsing preferences!"); + mUtil.writeToSysLogFile("SdDataSourcePebble.updatePrefs() - ERROR "+ex.toString()); Toast toast = Toast.makeText(mContext, "Problem Parsing Preferences - Something won't work - Please go back to Settings and correct it!", Toast.LENGTH_SHORT); toast.show(); } @@ -314,6 +352,7 @@ public class SdDataSourcePebble extends SdDataSource { */ private void startPebbleServer() { Log.v(TAG, "StartPebbleServer()"); + mUtil.writeToSysLogFile("SdDataSourcePebble.startPebbleServer()"); final Handler handler = new Handler(); msgDataHandler = new PebbleKit.PebbleDataReceiver(SD_UUID) { @Override @@ -322,8 +361,8 @@ public class SdDataSourcePebble extends SdDataSource { final PebbleDictionary data) { Log.v(TAG, "Received message from Pebble - data type=" + data.getUnsignedIntegerAsLong(KEY_DATA_TYPE)); - // If we ha ve a message, the app must be running - Log.v(TAG,"Setting mPebbleAppRunningCheck to true"); + // If we have a message, the app must be running + Log.v(TAG, "Setting mPebbleAppRunningCheck to true"); mPebbleAppRunningCheck = true; PebbleKit.sendAckToPebble(context, transactionId); //Log.v(TAG,"Message is: "+data.toJsonString()); @@ -342,13 +381,12 @@ public class SdDataSourcePebble extends SdDataSource { mSdData.alarmPhrase = "Unknown"; mSdData.haveData = true; mSdDataReceiver.onSdDataReceived(mSdData); - } // Read the data that has been sent, and convert it into // an integer array. byte[] byteArr = data.getBytes(KEY_SPEC_DATA); - if ((byteArr!=null) && (byteArr.length!=0)) { + if ((byteArr != null) && (byteArr.length != 0)) { IntBuffer intBuf = ByteBuffer.wrap(byteArr) .order(ByteOrder.LITTLE_ENDIAN) .asIntBuffer(); @@ -358,9 +396,9 @@ public class SdDataSourcePebble extends SdDataSource { mSdData.simpleSpec[i] = intArray[i]; } } else { - Log.v(TAG,"***** zero length spectrum received - error!!!!"); + Log.v(TAG, "***** zero length spectrum received - error!!!!"); } - + } if (data.getUnsignedIntegerAsLong(KEY_DATA_TYPE) == DATA_TYPE_SETTINGS) { @@ -376,12 +414,45 @@ public class SdDataSourcePebble extends SdDataSource { mSdData.batteryPc = data.getUnsignedIntegerAsLong(KEY_BATTERY_PC); mSdData.haveSettings = true; } + if (data.getUnsignedIntegerAsLong(KEY_DATA_TYPE) + == DATA_TYPE_RAW) { + Log.v(TAG, "DATA_TYPE = Raw"); + long numSamples; + numSamples = data.getUnsignedIntegerAsLong(KEY_NUM_RAW_DATA); + Log.v(TAG, "numSamples = " + numSamples); + byte[] rawDataBytes = data.getBytes(KEY_RAW_DATA); + for (int i = 0; i < rawDataBytes.length - 4; i += 4) { // 4 bytes per sample + int x = (rawDataBytes[i]); + //int y = (rawDataBytes[i+2] & 0xff) | (rawDataBytes[i+3] << 8); + //int z = (rawDataBytes[i+4] & 0xff) | (rawDataBytes[i+5] << 8); + //Log.v(TAG,"x="+x+", y="+y+", z="+z); + Log.v(TAG,"x="+x); + if (nRawData < MAX_RAW_DATA) { + rawData[nRawData] = (int)Math.sqrt(x); + } else { + Log.i(TAG, "WARNING - rawData Buffer Full"); + } + + } + + + //for (AccelData reading : AccelData.fromDataArray(rawDataBytes)) { + // if (nRawData < MAX_RAW_DATA) { + // rawData[nRawData] = reading.getMagnitude(); + // nRawData++; + // } else { + // Log.i(TAG, "WARNING - rawData Buffer Full"); + // } + // } + + } } }; PebbleKit.registerReceivedDataHandler(mContext, msgDataHandler); - // We struggle to connect to pebble time if app is already running, so stop app so we can - // re-connect to it. - stopWatchApp(); + // We struggle to connect to pebble time if app is already running, + // so stop app so we can re-connect to it. + //stopWatchApp(); + startWatchApp(); } /** @@ -390,10 +461,13 @@ public class SdDataSourcePebble extends SdDataSource { public void stopPebbleServer() { Log.v(TAG, "stopPebbleServer(): Stopping Pebble Server"); Log.v(TAG, "stopPebbleServer(): msgDataHandler = " + msgDataHandler.toString()); + mUtil.writeToSysLogFile("SdDataSourcePebble.stopPebbleServer()"); try { mContext.unregisterReceiver(msgDataHandler); + stopWatchApp(); } catch (Exception e) { Log.v(TAG, "stopPebbleServer() - error " + e.toString()); + mUtil.writeToSysLogFile("SdDataSourcePebble.stopPebbleServer() - error " + e.toString()); } } @@ -402,11 +476,20 @@ public class SdDataSourcePebble extends SdDataSource { */ public void startWatchApp() { Log.v(TAG, "startWatchApp() - closing app first"); + mUtil.writeToSysLogFile("SdDataSourcePebble.startWatchApp() - closing app first"); // first close the watch app if it is running. PebbleKit.closeAppOnPebble(mContext, SD_UUID); - // then start it. - Log.v(TAG, "startWatchApp() - starting watch app..."); - PebbleKit.startAppOnPebble(mContext, SD_UUID); + Log.v(TAG, "startWatchApp() - starting watch app after 5 seconds delay..."); + // Wait 5 seconds then start the app. + Timer appStartTimer = new Timer(); + appStartTimer.schedule(new TimerTask() { + @Override + public void run() { + Log.v(TAG, "startWatchApp() - starting watch app..."); + mUtil.writeToSysLogFile("SdDataSourcePebble.startWatchApp() - starting watch app"); + PebbleKit.startAppOnPebble(mContext, SD_UUID); + } + }, 5000); } /** @@ -414,16 +497,22 @@ public class SdDataSourcePebble extends SdDataSource { */ public void stopWatchApp() { Log.v(TAG, "stopWatchApp()"); + mUtil.writeToSysLogFile("SdDataSourcePebble.stopWatchApp()"); PebbleKit.closeAppOnPebble(mContext, SD_UUID); } /** - * Request Pebble App to send us its latest settings. + * Send our latest settings to the watch, then request Pebble App to send + * us its latest settings so we can check it has been set up correctly.. * Will be received as a message by the receiveData handler */ public void getPebbleSdSettings() { - Log.v(TAG, "getPebbleSdSettings() - requesting settings from pebble"); + Log.v(TAG, "getPebbleSdSettings() - sending required settings to pebble"); + mUtil.writeToSysLogFile("SdDataSourcePebble.getPebbleSdSettings()"); + sendPebbleSdSettings(); + //Log.v(TAG, "getPebbleSdSettings() - requesting settings from pebble"); + //mUtil.writeToSysLogFile("SdDataSourcePebble.getPebbleSdSettings() - and request settings from pebble"); PebbleDictionary data = new PebbleDictionary(); data.addUint8(KEY_SETTINGS, (byte) 1); PebbleKit.sendDataToPebble( @@ -432,12 +521,114 @@ public class SdDataSourcePebble extends SdDataSource { data); } + /** + * Send the pebble watch settings that are stored as class member + * variables to the watch. + */ + public void sendPebbleSdSettings() { + Log.v(TAG, "sendPebblSdSettings() - preparing settings dictionary.. mSampleFreq=" + mSampleFreq); + mUtil.writeToSysLogFile("SdDataSourcePebble.sendPebbleSdSettings()"); + + // Watch Settings + final PebbleDictionary setDict = new PebbleDictionary(); + setDict.addInt16(KEY_DEBUG, mDebug); + setDict.addInt16(KEY_DISPLAY_SPECTRUM, mDisplaySpectrum); + setDict.addInt16(KEY_DATA_UPDATE_PERIOD, mDataUpdatePeriod); + setDict.addInt16(KEY_MUTE_PERIOD, mMutePeriod); + setDict.addInt16(KEY_MAN_ALARM_PERIOD, mManAlarmPeriod); + setDict.addInt16(KEY_SD_MODE, mPebbleSdMode); + setDict.addInt16(KEY_SAMPLE_FREQ, mSampleFreq); + setDict.addInt16(KEY_ALARM_FREQ_MIN, mAlarmFreqMin); + setDict.addInt16(KEY_ALARM_FREQ_MAX, mAlarmFreqMax); + setDict.addUint16(KEY_WARN_TIME, mWarnTime); + setDict.addUint16(KEY_ALARM_TIME, mAlarmTime); + setDict.addUint16(KEY_ALARM_THRESH, mAlarmThresh); + setDict.addUint16(KEY_ALARM_RATIO_THRESH, mAlarmRatioThresh); + if (mFallActive) + setDict.addUint16(KEY_FALL_ACTIVE, (short) 1); + else + setDict.addUint16(KEY_FALL_ACTIVE, (short) 0); + setDict.addUint16(KEY_FALL_THRESH_MIN, mFallThreshMin); + setDict.addUint16(KEY_FALL_THRESH_MAX, mFallThreshMax); + setDict.addUint16(KEY_FALL_WINDOW, mFallWindow); + + // Send Watch Settings to Pebble + Log.v(TAG, "sendPebbleSdSettings() - setDict = " + setDict.toJsonString()); + PebbleKit.sendDataToPebble(mContext, SD_UUID, setDict); + } + + + /** + * Compares the watch settings retrieved from the watch (stored in mSdData) + * to the required settings stored as member variables to this class. + * + * @return true if they are all the same, or false if there are discrepancies. + */ + public boolean checkWatchSettings() { + boolean settingsOk = true; + if (mDataUpdatePeriod != mSdData.mDataUpdatePeriod) { + Log.v(TAG, "checkWatchSettings - mDataUpdatePeriod Wrong"); + settingsOk = false; + } + if (mMutePeriod != mSdData.mMutePeriod) { + Log.v(TAG, "checkWatchSettings - mMutePeriod Wrong"); + settingsOk = false; + } + if (mManAlarmPeriod != mSdData.mManAlarmPeriod) { + Log.v(TAG, "checkWatchSettings - mManAlarmPeriod Wrong"); + settingsOk = false; + } + if (mAlarmFreqMin != mSdData.alarmFreqMin) { + Log.v(TAG, "checkWatchSettings - mAlarmFreqMin Wrong"); + settingsOk = false; + } + if (mAlarmFreqMax != mSdData.alarmFreqMax) { + Log.v(TAG, "checkWatchSettings - mAlarmFreqMax Wrong"); + settingsOk = false; + } + if (mWarnTime != mSdData.warnTime) { + Log.v(TAG, "checkWatchSettings - mWarnTime Wrong"); + settingsOk = false; + } + if (mAlarmTime != mSdData.alarmTime) { + Log.v(TAG, "checkWatchSettings - mAlarmTime Wrong"); + settingsOk = false; + } + if (mAlarmThresh != mSdData.alarmThresh) { + Log.v(TAG, "checkWatchSettings - mAlarmThresh Wrong"); + settingsOk = false; + } + if (mAlarmRatioThresh != mSdData.alarmRatioThresh) { + Log.v(TAG, "checkWatchSettings - mAlarmRatioThresh Wrong"); + settingsOk = false; + } + if (mFallActive != mSdData.mFallActive) { + Log.v(TAG, "checkWatchSettings - mAlarmFreqMin Wrong"); + settingsOk = false; + } + if (mFallThreshMin != mSdData.mFallThreshMin) { + Log.v(TAG, "checkWatchSettings - mFallThreshMin Wrong"); + settingsOk = false; + } + if (mFallThreshMax != mSdData.mFallThreshMax) { + Log.v(TAG, "checkWatchSettings - mFallThreshMax Wrong"); + settingsOk = false; + } + if (mFallWindow != mSdData.mFallWindow) { + Log.v(TAG, "checkWatchSettings - mFallWindow Wrong"); + settingsOk = false; + } + + return settingsOk; + } + /** * Request Pebble App to send us its latest data. * Will be received as a message by the receiveData handler */ public void getPebbleData() { Log.v(TAG, "getPebbleData() - requesting data from pebble"); + mUtil.writeToSysLogFile("SdDataSourcePebble.getPebbleData() - requesting data from pebble"); PebbleDictionary data = new PebbleDictionary(); data.addUint8(KEY_DATA_TYPE, (byte) 1); PebbleKit.sendDataToPebble( @@ -458,7 +649,7 @@ public class SdDataSourcePebble extends SdDataSource { tnow.setToNow(); // get time since the last data was received from the Pebble watch. tdiff = (tnow.toMillis(false) - mPebbleStatusTime.toMillis(false)); - Log.v(TAG, "getPebbleStatus() - mPebbleAppRunningCheck="+mPebbleAppRunningCheck+" tdiff="+tdiff); + Log.v(TAG, "getPebbleStatus() - mPebbleAppRunningCheck=" + mPebbleAppRunningCheck + " tdiff=" + tdiff); // Check we are actually connected to the pebble. mSdData.pebbleConnected = PebbleKit.isWatchConnected(mContext); if (!mSdData.pebbleConnected) mPebbleAppRunningCheck = false; @@ -467,15 +658,20 @@ public class SdDataSourcePebble extends SdDataSource { // the app is not talking to us // mPebbleAppRunningCheck is set to true in the receiveData handler. if (!mPebbleAppRunningCheck && - (tdiff > (mDataPeriod+mAppRestartTimeout) * 1000)) { + (tdiff > (mDataUpdatePeriod + mAppRestartTimeout) * 1000)) { Log.v(TAG, "getPebbleStatus() - tdiff = " + tdiff); mSdData.pebbleAppRunning = false; - Log.v(TAG, "getPebbleStatus() - Pebble App Not Running - Attempting to Re-Start"); - startWatchApp(); + //Log.v(TAG, "getPebbleStatus() - Pebble App Not Running - Attempting to Re-Start"); + //mUtil.writeToSysLogFile("SdDataSourcePebble.getPebbleStatus() - Pebble App not Running - Attempting to Re-Start"); + //startWatchApp(); //mPebbleStatusTime = tnow; // set status time to now so we do not re-start app repeatedly. - getPebbleSdSettings(); + //getPebbleSdSettings(); // Only make audible warning beep if we have not received data for more than mFaultTimerPeriod seconds. - if (tdiff > (mDataPeriod+mFaultTimerPeriod) * 1000) { + if (tdiff > (mDataUpdatePeriod + mFaultTimerPeriod) * 1000) { + Log.v(TAG, "getPebbleStatus() - Pebble App Not Running - Attempting to Re-Start"); + mUtil.writeToSysLogFile("SdDataSourcePebble.getPebbleStatus() - Pebble App not Running - Attempting to Re-Start"); + startWatchApp(); + mPebbleStatusTime.setToNow(); mSdDataReceiver.onSdDataFault(mSdData); } else { Log.v(TAG, "getPebbleStatus() - Waiting for mFaultTimerPeriod before issuing audible warning..."); @@ -496,10 +692,115 @@ public class SdDataSourcePebble extends SdDataSource { getPebbleSdSettings(); getPebbleData(); } + + if (mPebbleSdMode == SD_MODE_RAW) { + analyseRawData(); + } + } + + /** + * analyseRawData() - called when raw data is received. + * FIXME - this does not do anything at the moment so raw data is + * ignored! + */ + private void analyseRawData() { + Log.v(TAG,"analyserawData()"); + //DoubleFFT_1D fft = new DoubleFFT_1D(MAX_RAW_DATA); + //fft.realForward(rawData); + // FIXME - rawData should really be a circular buffer. + nRawData = 0; + } + + /** + * Install the wach app that is bundled in the 'assets' folder of this + * phone app. + * from https://github.com/pebble-examples/pebblekit-android-example/blob/master/android/Eclipse/src/com/getpebble/pebblekitexample/MainActivity.java#L148 + */ + @Override + public void installWatchApp() { + Log.v(TAG, "SdDataSourcePebble.installWatchApp()"); + mUtil.writeToSysLogFile("SdDataSourcePebble.installWatchApp()"); + final String WATCHAPP_FILENAME = "pebble_sd.pbw"; + + try { + // Read .pbw from assets/ + Intent intent = new Intent(Intent.ACTION_VIEW); + File file = new File(mContext.getExternalFilesDir(null), WATCHAPP_FILENAME); + InputStream is = mContext.getResources().getAssets().open(WATCHAPP_FILENAME); + OutputStream os = new FileOutputStream(file); + byte[] pbw = new byte[is.available()]; + is.read(pbw); + os.write(pbw); + is.close(); + os.close(); + + // Install via Pebble Android app + intent.setDataAndType(Uri.fromFile(file), "application/pbw"); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } catch (IOException e) { + mUtil.writeToSysLogFile("SdDataSourcePebble.installWatchApp() - app install failed"+e.toString()); + Toast.makeText(mContext, "App install failed: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); + } + + } + + /** + * Install the OpenSeizureDetector watch app onto the watch from Pebble AppStore + * based on https://forums.getpebble.com/discussion/13128/install-watch-app-pebble-store-from-android-companion-app + */ + public void installWatchAppFromPebbleAppStore() { + Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("pebble://appstore/54d28a43e4d94c043f000008")); + myIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(myIntent); } + /** + * Open Pebble or Pebble Time app. If it is not installed, open Play store so the user can install it. + */ + @Override + public void startPebbleApp() { + mUtil.writeToSysLogFile("SdDataSourcePebble.startPebbleApp()"); + // first try to launch the original pebble app + Intent pebbleAppIntent; + PackageManager pm = mContext.getPackageManager(); + try { + pebbleAppIntent = pm.getLaunchIntentForPackage("com.getpebble.android"); + mContext.startActivity(pebbleAppIntent); + } catch (Exception ex1) { + // and if original pebble app fails, try Pebble Time app... + Log.v(TAG, "exception starting original pebble App - trying pebble time..." + ex1.toString()); + mUtil.writeToSysLogFile("SdDataSourcePebble.startPebbleApp() - Error starting original pebble app - trying Pebble Time App instead"); + try { + pebbleAppIntent = pm.getLaunchIntentForPackage("com.getpebble.android.basalt"); + mContext.startActivity(pebbleAppIntent); + } catch (Exception ex2) { + // and if that fails, open play store so the user can install it: + Log.v(TAG, "exception starting Pebble Time App." + ex2.toString()); + mUtil.writeToSysLogFile("SdDataSourcePebble.startPebbleApp() - Error starting Pebble Time App - Is it installed?"); + this.showToast("Error Launching Pebble or Pebble Time App - Please make sure it is installed..."); + final String appPackageName = "com.getpebble.android.basalt"; + try { + mUtil.writeToSysLogFile("SdDataSourcePebble.startPebbleApp() - Opening Play Store to install Pebble App"); + // try using play store app. + mContext.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appPackageName))); + } catch (android.content.ActivityNotFoundException anfe) { + // and if play store app is not installed, use browser to open app page. + mContext.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + appPackageName))); + } + } + } + + } + + + } + + + + diff --git a/app/src/main/java/uk/org/openseizuredetector/SdServer.java b/app/src/main/java/uk/org/openseizuredetector/SdServer.java index 442350e..27a184a 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdServer.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdServer.java @@ -130,10 +130,9 @@ public class SdServer extends Service implements SdDataReceiver { */ public SdServer() { super(); + Log.v(TAG, "SdServer Created"); mSdData = new SdData(); mToneGenerator = new ToneGenerator(AudioManager.STREAM_ALARM, 100); - - Log.v(TAG, "SdServer Created"); } @@ -144,7 +143,7 @@ public class SdServer extends Service implements SdDataReceiver { } /** - * used to make suer timers run on UI thread + * used to make sure timers run on UI thread */ private void runOnUiThread(Runnable runnable) { mHandler.post(runnable); @@ -157,6 +156,9 @@ public class SdServer extends Service implements SdDataReceiver { @Override public void onCreate() { Log.v(TAG, "onCreate()"); + mHandler = new Handler(); + mUtil = new OsdUtil(getApplicationContext(),mHandler); + mUtil.writeToSysLogFile("SdServer.onCreate()"); // Set our custom uncaught exception handler to report issues. Thread.setDefaultUncaughtExceptionHandler( @@ -164,9 +166,6 @@ public class SdServer extends Service implements SdDataReceiver { //int i = 5/0; // Force exception to test handler. - mHandler = new Handler(); - - mUtil = new OsdUtil(getApplicationContext()); // Create a wake lock, but don't use it until the service is started. PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); @@ -181,6 +180,7 @@ public class SdServer extends Service implements SdDataReceiver { @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.v(TAG, "onStartCommand() - SdServer service starting"); + mUtil.writeToSysLogFile("SdServer.onStartCommand()"); // Update preferences. Log.v(TAG, "onStartCommand() - calling updatePrefs()"); @@ -190,23 +190,28 @@ public class SdServer extends Service implements SdDataReceiver { switch (mSdDataSourceName) { case "Pebble": Log.v(TAG, "Selecting Pebble DataSource"); - mSdDataSource = new SdDataSourcePebble(this.getApplicationContext(), this); + mUtil.writeToSysLogFile("SdServer.onStartCommand() - creating SdDataSourcePebble"); + mSdDataSource = new SdDataSourcePebble(this.getApplicationContext(), mHandler, this); break; case "Network": Log.v(TAG, "Selecting Network DataSource"); - mSdDataSource = new SdDataSourceNetwork(this.getApplicationContext(), this); + mUtil.writeToSysLogFile("SdServer.onStartCommand() - creating SdDataSourceNetwork"); + mSdDataSource = new SdDataSourceNetwork(this.getApplicationContext(), mHandler, this); break; default: Log.v(TAG, "Datasource " + mSdDataSourceName + " not recognised - Exiting"); + mUtil.writeToSysLogFile("SdServer.onStartCommand() - Datasource "+mSdDataSourceName+" not recognised - exiting"); mUtil.showToast("Datasource " + mSdDataSourceName + " not recognised - Exiting"); return 1; } + mUtil.writeToSysLogFile("SdServer.onStartCommand() - starting SdDataSource"); mSdDataSource.start(); // Display a notification icon in the status bar of the phone to // show the service is running. Log.v(TAG, "showing Notification"); + mUtil.writeToSysLogFile("SdServer.onStartCommand() - showing Notification"); showNotification(0); // Record last time we sent an SMS so we can limit rate of SMS @@ -216,7 +221,8 @@ public class SdServer extends Service implements SdDataReceiver { // Start timer to log data regularly.. if (dataLogTimer == null) { - Log.v(TAG, "onCreate(): starting dataLog timer"); + Log.v(TAG, "onStartCommand(): starting dataLog timer"); + mUtil.writeToSysLogFile("SdServer.onStartCommand() - starting dataLog timer"); dataLogTimer = new Timer(); dataLogTimer.schedule(new TimerTask() { @Override @@ -225,19 +231,23 @@ public class SdServer extends Service implements SdDataReceiver { } }, 0, 1000 * 60); } else { - Log.v(TAG, "onCreate(): dataLog timer already running."); + Log.v(TAG, "onStartCommand(): dataLog timer already running."); + mUtil.writeToSysLogFile("SdServer.onStartCommand() - dataLog timer already running???"); } // Start the web server + mUtil.writeToSysLogFile("SdServer.onStartCommand() - starting web server"); startWebServer(); // Apply the wake-lock to prevent CPU sleeping (very battery intensive!) if (mWakeLock != null) { mWakeLock.acquire(); Log.v(TAG, "Applied Wake Lock to prevent device sleeping"); + mUtil.writeToSysLogFile("SdServer.onStartCommand() - applying wake lock"); } else { Log.d(TAG, "mmm...mWakeLock is null, so not aquiring lock. This shouldn't happen!"); + mUtil.writeToSysLogFile("SdServer.onStartCommand() - mWakeLock is not null - this shouldn't happen???"); } return START_STICKY; @@ -246,6 +256,7 @@ public class SdServer extends Service implements SdDataReceiver { @Override public void onDestroy() { Log.v(TAG, "onDestroy(): SdServer Service stopping"); + mUtil.writeToSysLogFile("SdServer.onDestroy() - releasing wakelock"); // release the wake lock to allow CPU to sleep and reduce // battery drain. if (mWakeLock != null) { @@ -254,22 +265,26 @@ public class SdServer extends Service implements SdDataReceiver { Log.v(TAG, "Released Wake Lock to allow device to sleep."); } catch (Exception e) { Log.e(TAG, "Error Releasing Wakelock - " + e.toString()); + mUtil.writeToSysLogFile("SdServer.onDestroy() - Error releasing wakelock."); mUtil.showToast("Error Releasing Wakelock"); } } else { Log.d(TAG, "mmm...mWakeLock is null, so not releasing lock. This shouldn't happen!"); + mUtil.writeToSysLogFile("SdServer.onDestroy() - mWakeLock is null so not releasing lock - this Shouldn't happen???"); } if (mSdDataSource != null) { Log.v(TAG, "stopping mSdDataSource"); + mUtil.writeToSysLogFile("SdServer.onDestroy() - stopping mSdDataSource"); mSdDataSource.stop(); } else { Log.e(TAG, "ERROR - mSdDataSource is null - why????"); + mUtil.writeToSysLogFile("SdServer.onDestroy() - mSdDataSource is null - why???"); } - // Stop the data update timer + // Stop the Cancel Audible timer if (mCancelAudibleTimer != null) { - Log.v(TAG, "stop(): cancelling Cancel_Audible timer"); + Log.v(TAG, "onDestroy(): cancelling Cancel_Audible timer"); mCancelAudibleTimer.cancel(); //mCancelAudibleTimer.purge(); mCancelAudibleTimer = null; @@ -279,18 +294,23 @@ public class SdServer extends Service implements SdDataReceiver { try { // Cancel the notification. Log.v(TAG, "onDestroy(): cancelling notification"); + mUtil.writeToSysLogFile("SdServer.onDestroy - cancelling notification"); mNM.cancel(NOTIFICATION_ID); // Stop web server Log.v(TAG, "onDestroy(): stopping web server"); + mUtil.writeToSysLogFile("SdServer.onDestroy() - stopping Web Server"); stopWebServer(); // stop this service. Log.v(TAG, "onDestroy(): calling stopSelf()"); + mUtil.writeToSysLogFile("SdServer.onDestroy() - stopping self"); stopSelf(); } catch (Exception e) { Log.v(TAG, "Error in onDestroy() - " + e.toString()); + mUtil.writeToSysLogFile("SdServer.onDestroy() -error "+e.toString()); } + mUtil.writeToSysLogFile("SdServer.onDestroy() - releasing mToneGenerator"); mToneGenerator.release(); mToneGenerator = null; } @@ -336,6 +356,7 @@ public class SdServer extends Service implements SdDataReceiver { // Show the main activity on the user's screen. private void showMainActivity() { Log.v(TAG, "showMainActivity()"); + mUtil.writeToSysLogFile("SdServer.showMainActivity()"); ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); List runningTaskInfo = manager.getRunningTasks(1); @@ -343,6 +364,7 @@ public class SdServer extends Service implements SdDataReceiver { if (componentInfo.getPackageName().equals("uk.org.openseizuredetector")) { Log.v(TAG,"showMainActivity(): OpenSeizureDetector Activity is already shown on top - not doing anything"); + mUtil.writeToSysLogFile("SdServer.showMainActivity - Activity is already shown on top, not doing anything"); } else { Log.v(TAG,"showMainActivity(): Showing Main Activity"); Intent i = new Intent(getApplicationContext(), MainActivity.class); @@ -485,6 +507,7 @@ public class SdServer extends Service implements SdDataReceiver { } else { mUtil.showToast("Warming mToneGenerator is null - not beeping!!!"); Log.v(TAG, "beep() - Warming mToneGenerator is null - not beeping!!!"); + mUtil.writeToSysLogFile("SdServer.beep() - mToneGenerator is null???"); } } @@ -499,6 +522,7 @@ public class SdServer extends Service implements SdDataReceiver { if (mAudibleFaultWarning) { beep(10); Log.v(TAG, "faultWarningBeep()"); + mUtil.writeToSysLogFile("SdServer.faultWarningBeep() - beeping"); } else { Log.v(TAG, "faultWarningBeep() - silent..."); } @@ -521,6 +545,7 @@ public class SdServer extends Service implements SdDataReceiver { if (mAudibleAlarm) { beep(3000); Log.v(TAG, "alarmBeep()"); + mUtil.writeToSysLogFile("SdServer.alarmBeep() - beeping"); } else { Log.v(TAG, "alarmBeep() - silent..."); } @@ -537,6 +562,7 @@ public class SdServer extends Service implements SdDataReceiver { if (mAudibleWarning) { beep(100); Log.v(TAG, "warningBeep()"); + mUtil.writeToSysLogFile("SdServer.warningBeep() - beeping"); } else { Log.v(TAG, "warningBeep() - silent..."); } @@ -550,6 +576,7 @@ public class SdServer extends Service implements SdDataReceiver { public void sendSMSAlarm() { if (mSMSAlarm) { Log.v(TAG, "sendSMSAlarm() - Sending to " + mSMSNumbers.length + " Numbers"); + mUtil.writeToSysLogFile("SdServer.sendSMSAlarm()"); Time tnow = new Time(Time.getCurrentTimezone()); tnow.setToNow(); String dateStr = tnow.format("%Y-%m-%d %H-%M-%S"); @@ -614,8 +641,9 @@ public class SdServer extends Service implements SdDataReceiver { */ protected void startWebServer() { Log.v(TAG, "startWebServer()"); + mUtil.writeToSysLogFile("SdServer.Start Web Server."); if (webServer == null) { - webServer = new SdWebServer(getApplicationContext(), getDataStorageDir(), mSdData); + webServer = new SdWebServer(getApplicationContext(), mUtil.getDataStorageDir(), mSdData); try { webServer.start(); } catch (IOException ioe) { @@ -631,7 +659,7 @@ public class SdServer extends Service implements SdDataReceiver { * Stop the web server - FIXME - doesn't seem to do anything! */ protected void stopWebServer() { - Log.v(TAG, "stopWebServer()"); + Log.v(TAG, "SdServer.stopWebServer()"); if (webServer != null) { webServer.stop(); if (webServer.isAlive()) { @@ -660,6 +688,8 @@ public class SdServer extends Service implements SdDataReceiver { */ public void updatePrefs() { Log.v(TAG, "updatePrefs()"); + mUtil.writeToSysLogFile("SdServer.updatePrefs()"); + SharedPreferences SP = PreferenceManager .getDefaultSharedPreferences(getBaseContext()); try { @@ -698,31 +728,13 @@ public class SdServer extends Service implements SdDataReceiver { } catch (Exception ex) { Log.v(TAG, "updatePrefs() - Problem parsing preferences!"); + mUtil.writeToSysLogFile("SdServer.updatePrefs() - Error "+ex.toString()); Toast toast = Toast.makeText(getApplicationContext(), "Problem Parsing Preferences - Something won't work - Please go back to Settings and correct it!", Toast.LENGTH_SHORT); toast.show(); } } - /* Checks if external storage is available for read and write */ - public boolean isExternalStorageWritable() { - String state = Environment.getExternalStorageState(); - if (Environment.MEDIA_MOUNTED.equals(state)) { - return true; - } - return false; - } - - public File getDataStorageDir() { - // Get the directory for the user's public pictures directory. - File file = - new File(Environment.getExternalStorageDirectory() - , "OpenSeizureDetector"); - if (!file.mkdirs()) { - Log.e(TAG, "Directory not created"); - } - return file; - } /** * Write data to SD card alarm log @@ -757,9 +769,9 @@ public class SdServer extends Service implements SdDataReceiver { fname = fname + "_" + dateStr + ".txt"; // Open output directory on SD Card. - if (isExternalStorageWritable()) { + if (mUtil.isExternalStorageWritable()) { try { - FileWriter of = new FileWriter(getDataStorageDir().toString() + FileWriter of = new FileWriter(mUtil.getDataStorageDir().toString() + "/" + fname, true); if (mSdData != null) { Log.v(TAG, "writing mSdData.toString()"); diff --git a/app/src/main/java/uk/org/openseizuredetector/SdWebServer.java b/app/src/main/java/uk/org/openseizuredetector/SdWebServer.java index 2113716..9206594 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdWebServer.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdWebServer.java @@ -112,6 +112,7 @@ public class SdWebServer extends NanoHTTPD { default: if (uri.startsWith("/index.html") || + uri.startsWith("/logfiles.html") || uri.startsWith("/favicon.ico") || uri.startsWith("/js/") || uri.startsWith("/css/") || diff --git a/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java b/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java index 66a64d8..dfa64e9 100644 --- a/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java @@ -35,6 +35,7 @@ import android.os.Handler; import android.preference.PreferenceManager; import android.util.Log; import android.view.View; +import android.view.WindowManager; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; @@ -61,7 +62,7 @@ public class StartupActivity extends Activity { private Timer mUiTimer; private SdServiceConnection mConnection; private boolean mStartedMainActivity = false; - final Handler mServerStatusHandler = new Handler(); // used to update ui from mUiTimer + private Handler mHandler = new Handler(); // used to update ui from mUiTimer @Override @@ -70,10 +71,26 @@ public class StartupActivity extends Activity { // Set our custom uncaught exception handler to report issues. Thread.setDefaultUncaughtExceptionHandler(new OsdUncaughtExceptionHandler(StartupActivity.this)); - //int i = 5/0; // Force exception to test handler. + + mHandler = new Handler(); + mUtil = new OsdUtil(this,mHandler); + mUtil.writeToSysLogFile(""); + mUtil.writeToSysLogFile("*******************************"); + mUtil.writeToSysLogFile("* StartUpActivity Started *"); + mUtil.writeToSysLogFile("*******************************"); + + // Force the screen to stay on when the app is running + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + setContentView(R.layout.startup_activity); - mUtil = new OsdUtil(this); + + // Read the default settings from the xml preferences files, so we do + // not have to use the hard coded ones in the java files. + PreferenceManager.setDefaultValues(this, R.xml.alarm_prefs, true); + PreferenceManager.setDefaultValues(this, R.xml.camera_prefs, true); + PreferenceManager.setDefaultValues(this, R.xml.general_prefs, true); + PreferenceManager.setDefaultValues(this, R.xml.network_datasource_prefs, true); Button b = (Button)findViewById(R.id.settingsButton); b.setOnClickListener(new View.OnClickListener() { @@ -81,12 +98,14 @@ public class StartupActivity extends Activity { public void onClick(View view) { Log.v(TAG, "settings button clicked"); try { + mUtil.writeToSysLogFile("Starting Settings Activity"); Intent intent = new Intent( StartupActivity.this, PrefActivity.class); startActivity(intent); } catch (Exception ex) { Log.v(TAG, "exception starting settings activity " + ex.toString()); + mUtil.writeToSysLogFile("ERROR Starting Settings Activity"); } } @@ -97,7 +116,8 @@ public class StartupActivity extends Activity { @Override public void onClick(View view) { Log.v(TAG, "pebble button clicked"); - mUtil.startPebbleApp(); + mUtil.writeToSysLogFile("Starting Pebble Phone App"); + mConnection.mSdServer.mSdDataSource.startPebbleApp(); } }); @@ -106,7 +126,8 @@ public class StartupActivity extends Activity { @Override public void onClick(View view) { Log.v(TAG, "install Osd Watch App button clicked"); - mUtil.installOsdWatchApp(); + mUtil.writeToSysLogFile("Installing Watch App"); + mConnection.mSdServer.mSdDataSource.installWatchApp(); } }); @@ -115,6 +136,8 @@ public class StartupActivity extends Activity { @Override protected void onStart() { super.onStart(); + mUtil.writeToSysLogFile("StartupActivity.onStart()"); + // Display the DataSource name SharedPreferences SP = PreferenceManager @@ -134,11 +157,14 @@ public class StartupActivity extends Activity { if (mUtil.isServerRunning()) { Log.v(TAG, "onStart() - server running - stopping it"); + mUtil.writeToSysLogFile("StartupActivity.onStart() - server already running - stopping it."); mUtil.stopServer(); } + mUtil.writeToSysLogFile("StartupActivity.onStart() - starting server"); mUtil.startServer(); // Bind to the service. + mUtil.writeToSysLogFile("StartupActivity.onStart() - binding to server"); mConnection = new SdServiceConnection(this); mUtil.bindToServer(this, mConnection); @@ -147,7 +173,7 @@ public class StartupActivity extends Activity { mUiTimer.schedule(new TimerTask() { @Override public void run() { - mServerStatusHandler.post(serverStatusRunnable); + mHandler.post(serverStatusRunnable); //updateServerStatus(); } }, 0, 1000); @@ -156,8 +182,9 @@ public class StartupActivity extends Activity { @Override protected void onStop() { - Log.v(TAG, "onStop()"); super.onStop(); + Log.v(TAG, "onStop()"); + mUtil.writeToSysLogFile("StartupActivity.onStop() - unbinding from server"); mUtil.unbindFromServer(this, mConnection); mUiTimer.cancel(); } @@ -285,6 +312,7 @@ public class StartupActivity extends Activity { if (allOk) { if (!mStartedMainActivity) { Log.v(TAG, "starting main activity..."); + mUtil.writeToSysLogFile("StartupActivity.serverStatusRunnable - all checks ok - starting main activity."); try { Intent intent = new Intent( getApplicationContext(), @@ -296,9 +324,11 @@ public class StartupActivity extends Activity { } catch (Exception ex) { mStartedMainActivity = false; Log.v(TAG, "exception starting main activity " + ex.toString()); + mUtil.writeToSysLogFile("StartupActivity.serverStatusRunnable - exception starting main activity "+ex.toString()); } } else { Log.v(TAG,"allOk, but already started MainActivity so not doing anything"); + mUtil.writeToSysLogFile("StartupActivity.serverStatusRunnable - allOk, but already started MainActivity so not doing anything"); } } } diff --git a/app/src/main/res/layout/activity_log_manager.xml b/app/src/main/res/layout/activity_log_manager.xml new file mode 100644 index 0000000..5143735 --- /dev/null +++ b/app/src/main/res/layout/activity_log_manager.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/app/src/main/res/layout/log_entry_layout.xml b/app/src/main/res/layout/log_entry_layout.xml new file mode 100644 index 0000000..2c8fa5d --- /dev/null +++ b/app/src/main/res/layout/log_entry_layout.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml index 591abb3..0cfbf24 100644 --- a/app/src/main/res/layout/main.xml +++ b/app/src/main/res/layout/main.xml @@ -131,7 +131,7 @@ android:text="Cancel Audible (temporarily)" /> - diff --git a/app/src/main/res/menu/main_activity_actions.xml b/app/src/main/res/menu/main_activity_actions.xml index 33f47e6..9f74b14 100644 --- a/app/src/main/res/menu/main_activity_actions.xml +++ b/app/src/main/res/menu/main_activity_actions.xml @@ -5,12 +5,6 @@ android:title="Accept Alarm" /> - - --> + + + + + + + android:title="Settings" + /> + + + + "Debug OFF" + "Debug ON" + + + "0" + "1" + + + + "Spectrum Display OFF" + "Spectrum Display ON" + + + "0" + "1" + + + + "Normal - OpenSeizureDetector FFT" + "Raw" + "Digital Filter" + + + "0" + "1" + "2" + + + + "100 Hz" + "50 Hz" + "25 Hz" + "10 Hz" + + + "100" + "50" + "25" + "10" + + + \ No newline at end of file diff --git a/app/src/main/res/xml/general_prefs.xml b/app/src/main/res/xml/general_prefs.xml index 642117c..a9f7381 100644 --- a/app/src/main/res/xml/general_prefs.xml +++ b/app/src/main/res/xml/general_prefs.xml @@ -6,6 +6,7 @@ android:summary="Select whether to use a Pebble Watch or network connection as the seizure detector data source." android:entries="@array/datasource_list" android:entryValues="@array/datasource_list_values" + android:defaultValue="Pebble" android:dialogTitle="Select Data Source" /> + + + + + @@ -76,6 +104,14 @@ +