From 86d4b755eadc72e17139d5f9c3a2b4ca8b050154 Mon Sep 17 00:00:00 2001 From: fabriziocasellato Date: Thu, 7 Nov 2019 10:39:17 +0100 Subject: [PATCH] 1) Added SMS commands: - SMS DISABLE/STOP to disable SMS Service; - TARGET MEAL/ACTIVITY/HYPO to switch to the standard temp targets; - BOLUS 0.60 MEAL, to do a bolus and set the stadard meal TT (ie 90 mg/dL for 45'). 2) Modified the SMS Timeout in a range from 3' to 60'. This preference is useful for parents that will manage many boluses in a single meal (because often, eaten foods are unpredictable with babies, so parents need to perform many closed boluses). Due to safety reasons (prevent multiple boluses for stolen phones, cloned SIM, ...): - Added a further SMS command to disable SMS Services (SMS DISABLE/STOP); - To modify the default timeout of 15', 2 Phone Numbers are mandatory In this way, if unwanted/suspicious boluses are notified, the other parent can disable Remote Management (till to re-enable it directly with the AAPS in the master smartphone). --- .../activities/PreferencesActivity.java | 35 ++++ .../SmsCommunicatorPlugin.java | 180 +++++++++++++++++- app/src/main/res/values/strings.xml | 11 ++ app/src/main/res/xml/pref_smscommunicator.xml | 13 +- 4 files changed, 234 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java b/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java index 367429dbbf..bf69624c40 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java @@ -13,6 +13,7 @@ import android.preference.PreferenceScreen; import android.text.TextUtils; import info.nightscout.androidaps.Config; +import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.plugins.bus.RxBus; @@ -51,6 +52,8 @@ import info.nightscout.androidaps.utils.OKDialog; import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin; +import com.andreabaccega.widget.ValidatingEditTextPreference; + public class PreferencesActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener { MyPreferenceFragment myPreferenceFragment; @@ -218,6 +221,38 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre TidepoolUploader.INSTANCE.testLogin(getActivity()); return false; }); + + final ValidatingEditTextPreference distance = (ValidatingEditTextPreference)findPreference(getString(R.string.key_smscommunicator_remotebolusmindistance)); + final EditTextPreference allowedNumbers = (EditTextPreference)findPreference(getString(R.string.key_smscommunicator_allowednumbers)); + if (distance != null && allowedNumbers != null) { + if (!SmsCommunicatorPlugin.areMoreNumbers(allowedNumbers.getText())) { + distance.setTitle(getString(R.string.smscommunicator_remotebolusmindistance) + + ".\n" + + getString(R.string.smscommunicator_remotebolusmindistance_caveat)); + distance.setEnabled(false); + } else { + distance.setTitle(getString(R.string.smscommunicator_remotebolusmindistance)); + distance.setEnabled(true); + } + + allowedNumbers.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (!SmsCommunicatorPlugin.areMoreNumbers(((String)newValue))) { + distance.setText(String.valueOf(Constants.remoteBolusMinDistance/(60 * 1000L))); + distance.setTitle(getString(R.string.smscommunicator_remotebolusmindistance) + + ".\n" + + getString(R.string.smscommunicator_remotebolusmindistance_caveat)); + distance.setEnabled(false); + } else { + distance.setTitle(getString(R.string.smscommunicator_remotebolusmindistance)); + distance.setEnabled(true); + } + return true; + } + }); + } + } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.java index 264d7c3e52..f34a66ca4b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.java @@ -23,6 +23,7 @@ import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.DatabaseHelper; import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventRefreshOverview; import info.nightscout.androidaps.interfaces.Constraint; @@ -136,6 +137,8 @@ public class SmsCommunicatorPlugin extends PluginBase { case "EXTENDED": case "CAL": case "PROFILE": + case "TARGET": + case "SMS": return true; } if (messageToConfirm != null && messageToConfirm.requester.phoneNumber.equals(number)) @@ -242,7 +245,7 @@ public class SmsCommunicatorPlugin extends PluginBase { sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotebolusnotallowed)); else if (splitted.length == 2 && ConfigBuilderPlugin.getPlugin().getActivePump().isSuspended()) sendSMS(new Sms(receivedSms.phoneNumber, R.string.pumpsuspended)); - else if (splitted.length == 2) + else if (splitted.length == 2 || splitted.length == 3) processBOLUS(splitted, receivedSms); else sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); @@ -255,6 +258,22 @@ public class SmsCommunicatorPlugin extends PluginBase { else sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); break; + case "TARGET": + if (!remoteCommandsAllowed) + sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)); + else if (splitted.length == 2) + processTARGET(splitted, receivedSms); + else + sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); + break; + case "SMS": + if (!remoteCommandsAllowed) + sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)); + else if (splitted.length == 2) + processSMS(splitted, receivedSms); + else + sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); + break; default: // expect passCode here if (messageToConfirm != null && messageToConfirm.requester.phoneNumber.equals(receivedSms.phoneNumber)) { messageToConfirm.action(splitted[0]); @@ -680,10 +699,19 @@ public class SmsCommunicatorPlugin extends PluginBase { private void processBOLUS(String[] splitted, Sms receivedSms) { Double bolus = SafeParse.stringToDouble(splitted[1]); + final boolean isMeal = splitted.length > 2 && splitted[2].equalsIgnoreCase("MEAL"); bolus = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(bolus)).value(); - if (bolus > 0d) { + + if (splitted.length == 3 && !isMeal) { + sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); + } else if (bolus > 0d) { String passCode = generatePasscode(); - String reply = String.format(MainApp.gs(R.string.smscommunicator_bolusreplywithcode), bolus, passCode); + String reply = ""; + if (isMeal) { + reply = String.format(MainApp.gs(R.string.smscommunicator_mealbolusreplywithcode), bolus, passCode); + } else { + reply = String.format(MainApp.gs(R.string.smscommunicator_bolusreplywithcode), bolus, passCode); + } receivedSms.processed = true; messageToConfirm = new AuthRequest(this, receivedSms, reply, passCode, new SmsAction(bolus) { @Override @@ -701,10 +729,40 @@ public class SmsCommunicatorPlugin extends PluginBase { public void run() { PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); if (resultSuccess) { - String reply = String.format(MainApp.gs(R.string.smscommunicator_bolusdelivered), resultBolusDelivered); + String reply = ""; + if (isMeal) { + reply = String.format(MainApp.gs(R.string.smscommunicator_mealbolusdelivered), resultBolusDelivered); + } else { + reply = String.format(MainApp.gs(R.string.smscommunicator_bolusdelivered), resultBolusDelivered); + } if (pump != null) reply += "\n" + pump.shortStatus(true); lastRemoteBolusTime = DateUtil.now(); + if (isMeal) { + Profile currentProfile = ProfileFunctions.getInstance().getProfile(); + int eatingSoonTTDuration = SP.getInt(R.string.key_eatingsoon_duration, Constants.defaultEatingSoonTTDuration); + eatingSoonTTDuration = eatingSoonTTDuration > 0 ? eatingSoonTTDuration : Constants.defaultEatingSoonTTDuration; + double eatingSoonTT = SP.getDouble(R.string.key_eatingsoon_target, currentProfile.getUnits().equals(Constants.MMOL) ? Constants.defaultEatingSoonTTmmol : Constants.defaultEatingSoonTTmgdl); + eatingSoonTT = eatingSoonTT > 0 ? eatingSoonTT : currentProfile.getUnits().equals(Constants.MMOL) ? Constants.defaultEatingSoonTTmmol : Constants.defaultEatingSoonTTmgdl; + + TempTarget tempTarget = new TempTarget() + .date(System.currentTimeMillis()) + .duration(eatingSoonTTDuration) + .reason(MainApp.gs(R.string.eatingsoon)) + .source(Source.USER) + .low(Profile.toMgdl(eatingSoonTT, currentProfile.getUnits())) + .high(Profile.toMgdl(eatingSoonTT, currentProfile.getUnits())); + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget); + String tt = ""; + if (currentProfile.getUnits().equals(Constants.MMOL)) { + tt = DecimalFormatter.to1Decimal(eatingSoonTT); + } else + tt = DecimalFormatter.to0Decimal(eatingSoonTT); + + reply += String.format(MainApp.gs(R.string.smscommunicator_mealbolusdelivered_tt), tt, eatingSoonTTDuration); + + + } sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply)); } else { String reply = MainApp.gs(R.string.smscommunicator_bolusfailed); @@ -722,6 +780,105 @@ public class SmsCommunicatorPlugin extends PluginBase { sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); } + private void processTARGET(String[] splitted, Sms receivedSms) { + boolean isMeal = splitted[1].equalsIgnoreCase("MEAL"); + boolean isActivity = splitted[1].equalsIgnoreCase("ACTIVITY"); + boolean isHypo = splitted[1].equalsIgnoreCase("HYPO"); + + if (isMeal || isActivity || isHypo) { + String passCode = generatePasscode(); + String reply = String.format(MainApp.gs(R.string.smscommunicator_temptargetwithcode), splitted[1].toUpperCase(), passCode); + receivedSms.processed = true; + messageToConfirm = new AuthRequest(this, receivedSms, reply, passCode, new SmsAction() { + @Override + public void run() { + try { + Profile currentProfile = ProfileFunctions.getInstance().getProfile(); + + int keyDuration = 0; + Integer defaultTargetDuration = 0; + int keyTarget = 0; + double defaultTargetMMOL = 0d; + double defaultTargetMGDL = 0d; + + if (isMeal) { + keyDuration = R.string.key_eatingsoon_duration; + defaultTargetDuration = Constants.defaultEatingSoonTTDuration; + keyTarget = R.string.key_eatingsoon_target; + defaultTargetMMOL = Constants.defaultEatingSoonTTmmol; + defaultTargetMGDL = Constants.defaultEatingSoonTTmgdl; + } else if (isActivity) { + keyDuration = R.string.key_activity_duration; + defaultTargetDuration = Constants.defaultActivityTTDuration; + keyTarget = R.string.key_activity_target; + defaultTargetMMOL = Constants.defaultActivityTTmmol; + defaultTargetMGDL = Constants.defaultActivityTTmgdl; + + } else if (isHypo) { + keyDuration = R.string.key_hypo_duration; + defaultTargetDuration = Constants.defaultHypoTTDuration; + keyTarget = R.string.key_hypo_target; + defaultTargetMMOL = Constants.defaultHypoTTmmol; + defaultTargetMGDL = Constants.defaultHypoTTmgdl; + } + + int ttDuration = SP.getInt(keyDuration, defaultTargetDuration); + ttDuration = ttDuration > 0 ? ttDuration : defaultTargetDuration; + double tt = SP.getDouble(keyTarget, currentProfile.getUnits().equals(Constants.MMOL) ? defaultTargetMMOL : defaultTargetMGDL); + tt = tt > 0 ? tt : currentProfile.getUnits().equals(Constants.MMOL) ? defaultTargetMMOL : defaultTargetMGDL; + + TempTarget tempTarget = new TempTarget() + .date(System.currentTimeMillis()) + .duration(ttDuration) + .reason(MainApp.gs(R.string.eatingsoon)) + .source(Source.USER) + .low(Profile.toMgdl(tt, currentProfile.getUnits())) + .high(Profile.toMgdl(tt, currentProfile.getUnits())); + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget); + String ttString = ""; + if (currentProfile.getUnits().equals(Constants.MMOL)) + ttString = DecimalFormatter.to1Decimal(tt); + else + ttString = DecimalFormatter.to0Decimal(tt); + + String reply = String.format(MainApp.gs(R.string.smscommunicator_tt_set), ttString, ttDuration); + sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply)); + } catch (Exception e) { + sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_unknowncommand)); + return; + } + } + }); + } else + sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); + } + + private void processSMS(String[] splitted, Sms receivedSms) { + boolean isStop = splitted[1].equalsIgnoreCase("STOP") + || splitted[1].equalsIgnoreCase("DISABLE"); + + if (isStop) { + String passCode = generatePasscode(); + String reply = String.format(MainApp.gs(R.string.smscommunicator_stopsmswithcode), passCode); + receivedSms.processed = true; + messageToConfirm = new AuthRequest(this, receivedSms, reply, passCode, new SmsAction() { + @Override + public void run() { + try { + SP.putBoolean(R.string.key_smscommunicator_remotecommandsallowed, false); + String reply = String.format(MainApp.gs(R.string.smscommunicator_stoppedsms)); + sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply)); + } catch (Exception e) { + e.printStackTrace(); + sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_unknowncommand)); + return; + } + } + }); + } else + sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); + } + private void processCAL(String[] splitted, Sms receivedSms) { Double cal = SafeParse.stringToDouble(splitted[1]); if (cal > 0d) { @@ -809,4 +966,19 @@ public class SmsCommunicatorPlugin extends PluginBase { s = s.replaceAll("[\\p{InCombiningDiacriticalMarks}]", ""); return s; } + + public static boolean areMoreNumbers(String allowednumbers) { + int countNumbers = 0; + String[] substrings = allowednumbers.split(";"); + + for (String number : substrings) { + String cleaned = number.replaceAll("\\s+", ""); + if (cleaned.length()<4) continue; + if (cleaned.substring(0,1).compareTo("+")!=0) continue; + cleaned = cleaned.replace("+",""); + if (!cleaned.matches("[0-9]+")) continue; + countNumbers++; + } + return countNumbers > 1; + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4c43ba1193..9f37cc9a85 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -292,11 +292,22 @@ Allowed phone numbers +XXXXXXXXXX;+YYYYYYYYYY To deliver bolus %1$.2fU reply with code %2$s + To deliver meal bolus %1$.2fU reply with code %2$s + To set the Temp Target %1$s reply with code %2$s + To disable the SMS Remote Service reply with code %1$s.\n\nKeep in mind that you\'ll able to reactivate it directly from the AAPS master smartphone only. + SMS Remote Service stopped. To reactivate it, use AAPS on master smartphone. To send calibration %1$.2f reply with code %2$s Bolus failed + smscommunicator_remotebolusmindistance + Minimum number of minutes that must elapse between one remote bolus and the next + How many minutes must elapse, at least, between one bolus and the next + For your safety, to edit this preference you need to add at least 2 phone numbers. Bolus %1$.2fU delivered successfully Going to deliver %1$.2fU Bolus %1$.2fU delivered successfully + Meal Bolus %1$.2fU delivered successfully + Target %1$s for %2$d minutes + Target %1$s for %2$d minutes set succesfully Delivering %1$.2fU Allow remote commands via SMS Finger diff --git a/app/src/main/res/xml/pref_smscommunicator.xml b/app/src/main/res/xml/pref_smscommunicator.xml index 202e7348c5..e76b1b15b2 100644 --- a/app/src/main/res/xml/pref_smscommunicator.xml +++ b/app/src/main/res/xml/pref_smscommunicator.xml @@ -1,5 +1,6 @@ - + @@ -8,6 +9,16 @@ android:key="@string/key_smscommunicator_allowednumbers" android:summary="@string/smscommunicator_allowednumbers_summary" android:title="@string/smscommunicator_allowednumbers" /> +