diff --git a/README.md b/README.md index 7be37f2..4079783 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ # Esel -1. Chose a phone where sqlite3 is available. Easiest solution: Use LineageOS -2. Root your phone (su needs to work - so allow it for this app in the developer options or whatever tool you use to manage access to su) -3. Install the Eversense app and use it as described by the vendor -4. Build https://github.com/AdrianLxM/Esel and install it on your phone. -5. Configuration: +1. Uninstall the Eversense App (Warning: your local historical data (older than 1 week) will be lost!) +2. Install the patched Eversense app (mod_com.senseonics.gen12androidapp-release.apk) and use it as described by the vendor + * You need to enable installation of Apps from unknown sources + * Start the Eversense App, login, connect to your transmitter and use it just like the normal app. +3. Build https://github.com/BernhardRo/Esel and install it on your phone. +4. Configuration: * Allow ESEL to run in the background (it will ask for it) - * Don't touch the "DB Path" setting unless you changed the default data path when installing the Eversense app and know what you are doing! * Upload to Nightscout: Activate "Send to NightScout" in the preferences. It needs a configured AndroidAPS with internal NSClient or NSClient itself installed on the same phone * Inter-App-Broadcasts: Activate "Send to AAPS and xDrip". In xDrip and/or AndroidAPS activate the input method "640g/Eversense". - * As this app is very experimental, please contact @AdrianLxM for positive/negative feedback and to register for future updates. -6. For the modification of the Eversense App, see: https://github.com/BernhardRo/Esel/wiki/How-to-modify-the-Android-Eversense-App + * "Smooth Data" applies a smoothing algorithm to the raw values and provides these smoothed values instead of the raw readings. Smoothing is per default disabled. + * For feedback contact @BernhardRo +4. For the modification of the Eversense App, see: https://github.com/BernhardRo/Esel/wiki/How-to-modify-the-Android-Eversense-App +If you run esel with a fresh installation of Eversense for the first time, it can take up to 15min until your first values appear in xDrip! \ No newline at end of file diff --git a/apk/README.md b/apk/README.md new file mode 100644 index 0000000..2a29650 --- /dev/null +++ b/apk/README.md @@ -0,0 +1,18 @@ +# Eversense + +1. Uninstall the Eversense App (Warning: your local historical data (older than 1 week) will be lost!) +2. Install the patched Eversense app (mod_com.senseonics.gen12androidapp-release.apk) and use it as described by the vendor + * You need to enable installation of Apps from unknown sources + * Start the Eversense App, login, connect to your transmitter and use it just like the normal app. +3. Install esel.apk on your phone. +4. Configuration: + * Allow ESEL to run in the background (it will ask for it) + * Upload to Nightscout: Activate "Send to NightScout" in the preferences. It needs a configured AndroidAPS with internal NSClient or NSClient itself installed on the same phone + * Inter-App-Broadcasts: Activate "Send to AAPS and xDrip". In xDrip and/or AndroidAPS activate the input method "640g/Eversense". + * "Smooth Data" applies a smoothing algorithm to the raw values and provides these smoothed values instead of the raw readings. Smoothing is per default disabled. +5. Install xDrip: https://jamorham.github.io/#xdrip-plus (Download latest APK) + * Use as Datasource 640G / EverSense + +If you run esel with a fresh installation of Eversense for the first time, it can take up to 15min until your first values appear in xDrip! + + diff --git a/apk/esel.apk b/apk/esel.apk new file mode 100644 index 0000000..7f61fcd Binary files /dev/null and b/apk/esel.apk differ diff --git a/apk/mod_com.senseonics.gen12androidapp-release.apk b/apk/mod_com.senseonics.gen12androidapp-release.apk new file mode 100644 index 0000000..5b6f2cf Binary files /dev/null and b/apk/mod_com.senseonics.gen12androidapp-release.apk differ diff --git a/app/debug/app-debug.apk b/app/debug/app-debug.apk new file mode 100644 index 0000000..af0b715 Binary files /dev/null and b/app/debug/app-debug.apk differ diff --git a/app/debug/esel.apk b/app/debug/esel.apk new file mode 100644 index 0000000..97b7f46 Binary files /dev/null and b/app/debug/esel.apk differ diff --git a/app/debug/output.json b/app/debug/output.json new file mode 100644 index 0000000..f20a39f --- /dev/null +++ b/app/debug/output.json @@ -0,0 +1 @@ +[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-debug.apk","fullName":"debug","baseName":"debug"},"path":"app-debug.apk","properties":{}}] \ No newline at end of file diff --git a/app/release/release/app.aab b/app/release/release/app.aab new file mode 100644 index 0000000..2a84a25 Binary files /dev/null and b/app/release/release/app.aab differ diff --git a/app/src/main/java/esel/esel/esel/MainActivity.java b/app/src/main/java/esel/esel/esel/MainActivity.java index ca3fe87..fade69d 100644 --- a/app/src/main/java/esel/esel/esel/MainActivity.java +++ b/app/src/main/java/esel/esel/esel/MainActivity.java @@ -33,6 +33,7 @@ import esel.esel.esel.datareader.SGV; import esel.esel.esel.preferences.Preferences; import esel.esel.esel.preferences.PrefsFragment; +import esel.esel.esel.receivers.ReadReceiver; import esel.esel.esel.util.LocalBroadcaster; import esel.esel.esel.util.SP; import esel.esel.esel.util.ToastUtils; @@ -40,6 +41,7 @@ public class MainActivity extends MenuActivity { private Button buttonReadValue; + private Button buttonSync; private TextView textViewValue; @Override @@ -48,6 +50,7 @@ protected void onCreate(Bundle savedInstanceState) { setupView(R.layout.activity_main); askForBatteryOptimizationPermission(); buttonReadValue = (Button) findViewById(R.id.button_readvalue); + buttonSync = (Button) findViewById(R.id.button_manualsync); textViewValue = (TextView) findViewById(R.id.textview_main); /*FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); @@ -62,30 +65,53 @@ public void onClick(View view) { @Override public void onClick(View view) { try { - // String datastring = Datareader.readData(); - List valueArray = Datareader.readDataFromContentProvider(getBaseContext()); + long currentTime = System.currentTimeMillis(); - if(valueArray !=null && valueArray.size() > 0) { - SGV sgv = valueArray.get(0); - textViewValue.setText(sgv.toString()); - LocalBroadcaster.broadcast(sgv); + long syncTime = 30 * 60 * 1000L; + + List valueArray = Datareader.readDataFromContentProvider(getBaseContext(), 6, currentTime - syncTime); + + if (valueArray != null && valueArray.size() > 0) { + textViewValue.setText(""); + for (int i = 0; i < valueArray.size(); i++) { + SGV sgv = valueArray.get(i); + textViewValue.append(sgv.toString() + "\n"); + //LocalBroadcaster.broadcast(sgv); + } } else { ToastUtils.makeToast("DB not readable!"); } - //sgv.timestamp = System.currentTimeMillis(); - //LocalBroadcaster.broadcast(sgv); - - //} catch (IOException e) { - // e.printStackTrace(); - //} catch (InterruptedException e) { - // e.printStackTrace(); - }catch (Exception e){ + + + }catch (android.database.CursorIndexOutOfBoundsException eb) { + eb.printStackTrace(); + ToastUtils.makeToast("DB is empty!\nIt can take up to 15min with running Eversense App until values are available!"); + } catch (Exception e) { e.printStackTrace(); } } }); + buttonSync.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + int sync = 8; + try { + + sync = SP.getInt("max-sync-hours", sync); + + } catch (Exception e) { + e.printStackTrace(); + } + + ReadReceiver receiver = new ReadReceiver(); + int written = receiver.FullSync(getBaseContext(), sync); + textViewValue.setText("Read " + written + " values from DB\n(last " + sync + " hours)"); + + } + }); + } private void askForBatteryOptimizationPermission() { diff --git a/app/src/main/java/esel/esel/esel/datareader/Datareader.java b/app/src/main/java/esel/esel/esel/datareader/Datareader.java index 7213799..d462df1 100644 --- a/app/src/main/java/esel/esel/esel/datareader/Datareader.java +++ b/app/src/main/java/esel/esel/esel/datareader/Datareader.java @@ -24,17 +24,30 @@ public class Datareader { public static String uriGlucose = "content://com.senseonics.gen12androidapp.glucose"; public static String uriTransmitter = "content://com.senseonics.gen12androidapp.transmitter"; - public static List readDataFromContentProvider(Context context) { - + public static List readDataFromContentProvider(Context context ) { + return readDataFromContentProvider(context, 2,0); + } + public static List readDataFromContentProvider(Context context, int number, long lastReadingTime) { Uri uri = Uri.parse(uriGlucose);; String[] selection = {"timestamp","glucoseLevel"}; - Cursor item = context.getContentResolver().query(uri, null, null, null, "timestamp desc LIMIT 2"); + String where = null; + String sortOder = "timestamp asc LIMIT " + number; + if (lastReadingTime > 0) { + where = "timestamp > " + (lastReadingTime-1L); + } + + Cursor item = context.getContentResolver().query(uri, null, where, null, sortOder); - String datastring = null; + boolean reverseorder = false; + + if(item == null || item.getCount()==0){ + item = context.getContentResolver().query(uri, null, null, null, "timestamp desc LIMIT 2"); + reverseorder = true; + } item.moveToFirst(); @@ -81,38 +94,12 @@ public static List readDataFromContentProvider(Context context) { // // Do work... // } while (transmitter.moveToNext()); - return valueArray; - } - - public static String readData() throws IOException, InterruptedException { - Process p = Runtime.getRuntime().exec("su"); - DataOutputStream dos = new DataOutputStream(p.getOutputStream()); - - BufferedReader bufferedReader = new BufferedReader( - new InputStreamReader(p.getInputStream())); - BufferedReader bufferedErrorReader = new BufferedReader( - new InputStreamReader(p.getErrorStream())); - - String path = SP.getString("db-path-string", Esel.getsResources().getString(R.string.default_db_path)); - dos.writeBytes("sqlite3 -csv " + path + " \"select timestamp, glucoseLevel from glucosereadings order by timestamp desc LIMIT 2;\"\n"); - dos.writeBytes("exit\n"); - dos.flush(); - dos.close(); - p.waitFor(); - String data; - - StringBuilder sb = new StringBuilder(""); - while((data = bufferedErrorReader.readLine()) != null) { - sb.append(data + "\n"); + if(reverseorder && valueArray.size()==2){ + valueArray.add(valueArray.get(0)); + valueArray.remove(0); } - SP.putString("last-error", sb.toString()); - - if ((data = bufferedReader.readLine()) != null) { - return data; - } else { - return null; - } + return valueArray; } public static SGV generateSGV(String dataString){ diff --git a/app/src/main/java/esel/esel/esel/datareader/SGV.java b/app/src/main/java/esel/esel/esel/datareader/SGV.java index d331a55..9af25af 100644 --- a/app/src/main/java/esel/esel/esel/datareader/SGV.java +++ b/app/src/main/java/esel/esel/esel/datareader/SGV.java @@ -4,6 +4,8 @@ import java.text.SimpleDateFormat; import java.util.Date; +import esel.esel.esel.util.SP; + /** * Created by adrian on 04/08/17. */ @@ -47,4 +49,25 @@ public void setDirection(double slope_by_minute) { direction = "DoubleUp"; } } + + public void smooth(int last){ + double smooth = this.value; + + double lastSmooth = SP.getInt("readingSmooth",last*1000)/1000; + double factor = SP.getDouble("smooth_factor",0.3); + double correction = SP.getDouble("correction_factor",0.5); + int lastRaw = SP.getInt("lastReadingRaw", value); + + SP.putInt("lastReadingRaw", this.value); + + double a=lastSmooth+(factor*(this.value-lastSmooth)); + smooth=a+correction*((lastRaw-lastSmooth)+(this.value-a))/2; + + SP.putInt("readingSmooth",(int)Math.round(smooth*1000)); + + if(this.value > SP.getInt("lower_limit",65)){ + this.value = (int)Math.round(smooth); + } + + } } diff --git a/app/src/main/java/esel/esel/esel/receivers/ReadReceiver.java b/app/src/main/java/esel/esel/esel/receivers/ReadReceiver.java index 44e224e..9f5ce6c 100644 --- a/app/src/main/java/esel/esel/esel/receivers/ReadReceiver.java +++ b/app/src/main/java/esel/esel/esel/receivers/ReadReceiver.java @@ -15,6 +15,7 @@ import java.util.List; import esel.esel.esel.Esel; +import esel.esel.esel.R; import esel.esel.esel.datareader.Datareader; import esel.esel.esel.datareader.SGV; import esel.esel.esel.util.LocalBroadcaster; @@ -41,6 +42,17 @@ public synchronized void onReceive(Context context, Intent intent) { setAlarm(Esel.getsInstance()); + int sync = 8; + try { + + sync = SP.getInt("max-sync-hours", sync); + + } catch (Exception e) { + e.printStackTrace(); + } + long syncTime = sync * 60 * 60 * 1000L; + long currentTime = System.currentTimeMillis(); + try { SP.putLong("readReceiver-called", System.currentTimeMillis()); @@ -48,61 +60,128 @@ public synchronized void onReceive(Context context, Intent intent) { //String datastring = Datareader.readData(); - List valueArray = Datareader.readDataFromContentProvider(context); - if(valueArray == null || valueArray.size() == 0){ - ToastUtils.makeToast("DB not readable!"); - wl.release(); - return; + long lastReadingTime = SP.getLong("lastReadingTime", currentTime); + + if (lastReadingTime + syncTime < currentTime) { + lastReadingTime = currentTime - syncTime; } - for(int i = 0; i < valueArray.size(); i++) - { - SGV sgv = valueArray.get(i); + broadcastData(context, lastReadingTime, true); - long oldTime = SP.getLong("lastReadingTime", -1L); - int oldValue = SP.getInt("lastReadingValue", -1); - if(oldTime != sgv.timestamp || oldValue != sgv.value){ + } catch (Exception e) { + ToastUtils.makeToast("Exception: " + e.getMessage()); + } - double slopeByMinute = 0d; - if(oldTime != sgv.timestamp){ - slopeByMinute = (sgv.value - oldValue)*60000.0d/((sgv.timestamp-oldTime)*1.0d); - } - sgv.setDirection(slopeByMinute); - - SP.putLong("lastReadingTime", sgv.timestamp); - SP.putInt("lastReadingValue", sgv.value); - if(sgv.value > 38){ - //ToastUtils.makeToast(sgv.toString()); - LocalBroadcaster.broadcast(sgv); - } else { - ToastUtils.makeToast("NOT A READING!"); - } + + //auto full sync in specific time intervals + long autoSycInterval = SP.getInt("auto-sync-interval",3)* 60 * 60 * 1000L ; + long lastFullSync = SP.getLong("last_full_sync", currentTime - autoSycInterval - 1); + + if(autoSycInterval > 0 && (currentTime - lastFullSync) > autoSycInterval){ + FullSync(context,sync); + } + + + wl.release(); + } + + public int FullSync(Context context, int syncHours){ + long currentTime = System.currentTimeMillis(); + long syncTime = syncHours * 60 * 60 * 1000L; + long lastTimestamp = currentTime - syncTime; + + //disable smoothing as historical data will be overwritten + int written = broadcastData(context, lastTimestamp, false); + + SP.putLong("last_full_sync", currentTime); + + ToastUtils.makeToast("Full Sync done: Read " + written + " values from DB\n(last " + syncHours + " hours)"); + + return written; + } + + public int broadcastData(Context context, long lastReadingTime, boolean smoothEnabled) { + int result = 0; + try { + + + SP.putLong("readReceiver-called", System.currentTimeMillis()); + + int size = 2; + long updatedReadingTime = lastReadingTime; + + do { + lastReadingTime = updatedReadingTime; + + List valueArray = Datareader.readDataFromContentProvider(context, size, lastReadingTime); + + if (valueArray == null || valueArray.size() == 0) { + ToastUtils.makeToast("DB not readable!"); + //wl.release(); + return result; } - } + if (valueArray.size() != size) { + //ToastUtils.makeToast("DB not readable!"); + //wl.release(); + return result; + } + + for (int i = 0; i < valueArray.size(); i++) { + SGV sgv = valueArray.get(i); + + long oldTime = SP.getLong("lastReadingTime", -1L); + int oldValue = SP.getInt("lastReadingValue", -1); + if (oldTime != sgv.timestamp) { + double slopeByMinute = 0d; + if (oldTime != sgv.timestamp) { + slopeByMinute = (oldValue - sgv.value) * 60000.0d / ((oldTime - sgv.timestamp) * 1.0d); + } + sgv.setDirection(slopeByMinute); + if (sgv.value >= 39 && oldValue >= 39) { + //ToastUtils.makeToast(sgv.toString()); + if(SP.getBoolean("smooth_data",false) && smoothEnabled){ + sgv.smooth(oldValue); + } + + LocalBroadcaster.broadcast(sgv); + result++; + } else { + ToastUtils.makeToast("NOT A READING!"); + } + SP.putLong("lastReadingTime", sgv.timestamp); + SP.putInt("lastReadingValue", sgv.value); + } + } + + updatedReadingTime = SP.getLong("lastReadingTime", lastReadingTime); + } while (updatedReadingTime != lastReadingTime); //} catch (IOException e) { // ToastUtils.makeToast("IOException"); //} catch (InterruptedException e) { // ToastUtils.makeToast("InterruptedException"); - }catch (Exception e){ - ToastUtils.makeToast("Exception: " + e.getMessage()); + } catch (android.database.CursorIndexOutOfBoundsException eb) { + eb.printStackTrace(); + ToastUtils.makeToast("DB is empty!\nIt can take up to 15min with running Eversense App until values are available!"); + } catch (Exception e) { + e.printStackTrace(); } + //wl.release(); - - - wl.release(); + return result; } + public void setAlarm(Context context) { AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent i = new Intent(context, ReadReceiver.class); @@ -123,6 +202,4 @@ public void cancelAlarm(Context context) { } - - } diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml index 4b09e43..f2eb52f 100644 --- a/app/src/main/res/layout/content_main.xml +++ b/app/src/main/res/layout/content_main.xml @@ -32,6 +32,14 @@ android:textAlignment="center" android:textAppearance="@style/Base.TextAppearance.AppCompat.Large" android:text="MANUAL READ" /> - + +