sms communicator: remote bolus, status

This commit is contained in:
Milos Kozak 2016-07-14 23:29:21 +02:00
parent 2ca75b52e5
commit 90fee59e8a
13 changed files with 359 additions and 9 deletions

View file

@ -37,7 +37,7 @@
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View file

@ -65,6 +65,16 @@
<!-- Receiver keepalive, scheduled every 30 min -->
<receiver android:name=".receivers.KeepAliveReceiver" />
<!-- Receive new SMS messages -->
<receiver
android:name=".plugins.SmsCommunicator.Receivers.SmsReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
<!-- Service processing incomming data -->
<service
android:name=".Services.DataService"
@ -83,7 +93,8 @@
android:enabled="true"
android:exported="false" />
<activity android:name=".AgreementActivity"></activity>
<activity android:name=".AgreementActivity" />
</application>
</manifest>

View file

@ -31,6 +31,7 @@ public class Config {
public static final boolean logTempBasalsCut = true;
public static final boolean logNSUpload = true;
public static final boolean logPumpActions = true;
public static final boolean logSMSComm = true;
// DanaR specific
public static final boolean logDanaBTComm = true;

View file

@ -22,4 +22,6 @@ public class Constants {
public static final int hoursToKeepInDatabase = 24;
public static final long keepAliveMsecs = 30 * 60 * 1000L;
public static final long remoteBolusMinDistance = 15 * 60 * 1000L;
}

View file

@ -49,7 +49,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
if (pref.getTitle().toString().toLowerCase().contains("password"))
{
pref.setSummary("******");
} else {
} else if (editTextPref.getText() != null && !editTextPref.getText().equals("")){
pref.setSummary(editTextPref.getText());
}
}
@ -75,6 +75,8 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_language);
if (Config.CAREPORTALENABLED)
addPreferencesFromResource(R.xml.pref_careportal);
addPreferencesFromResource(R.xml.pref_treatments);
if (Config.APS)
addPreferencesFromResource(R.xml.pref_closedmode);
@ -87,8 +89,8 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
addPreferencesFromResource(R.xml.pref_danar);
if (Config.MM640G)
addPreferencesFromResource(R.xml.pref_mm640g);
if (Config.CAREPORTALENABLED)
addPreferencesFromResource(R.xml.pref_careportal);
if (Config.SMSCOMMUNICATORENABLED)
addPreferencesFromResource(R.xml.pref_smscommunicator);
initSummary(getPreferenceScreen());
}

View file

@ -5,7 +5,9 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.Telephony;
import android.support.annotation.Nullable;
import android.telephony.SmsMessage;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.stmt.PreparedQuery;
@ -32,10 +34,13 @@ import info.nightscout.androidaps.events.EventNewBG;
import info.nightscout.androidaps.events.EventNewBasalProfile;
import info.nightscout.androidaps.events.EventTreatmentChange;
import info.nightscout.androidaps.Config;
import info.nightscout.androidaps.interfaces.PluginBase;
import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderFragment;
import info.nightscout.androidaps.plugins.Objectives.ObjectivesFragment;
import info.nightscout.androidaps.plugins.Overview.OverviewFragment;
import info.nightscout.androidaps.plugins.SmsCommunicator.Events.EventNewSMS;
import info.nightscout.androidaps.plugins.SmsCommunicator.SmsCommunicatorFragment;
import info.nightscout.androidaps.plugins.SourceNSClient.SourceNSClientFragment;
import info.nightscout.androidaps.plugins.SourceXdrip.SourceXdripFragment;
import info.nightscout.androidaps.receivers.NSClientDataReceiver;
@ -50,6 +55,7 @@ public class DataService extends IntentService {
boolean xDripEnabled = false;
boolean nsClientEnabled = true;
SmsCommunicatorFragment smsCommunicatorFragment = null;
public DataService() {
super("DataService");
@ -69,6 +75,10 @@ public class DataService extends IntentService {
xDripEnabled = false;
nsClientEnabled = true;
}
if (MainActivity.getSpecificPlugin(SmsCommunicatorFragment.class) != null) {
smsCommunicatorFragment = (SmsCommunicatorFragment) MainActivity.getSpecificPlugin(SmsCommunicatorFragment.class);
}
}
if (intent != null) {
@ -89,6 +99,9 @@ public class DataService extends IntentService {
) {
handleNewDataFromNSClient(intent);
NSClientDataReceiver.completeWakefulIntent(intent);
} else if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(action)) {
handleNewSMS(intent);
NSClientDataReceiver.completeWakefulIntent(intent);
}
}
}
@ -482,4 +495,11 @@ public class DataService extends IntentService {
log.debug("REMOVE: Not stored treatment (ignoring): " + _id);
}
}
private void handleNewSMS(Intent intent) {
Bundle bundle = intent.getExtras();
if (bundle == null) return;
MainApp.bus().post(new EventNewSMS(bundle));
}
}

View file

@ -746,7 +746,7 @@ public class DanaRFragment extends Fragment implements PluginBase, PumpInterface
}
if (getDanaRPump().lastBolusTime.getTime() != 0) {
Long agoMsec = new Date().getTime() - getDanaRPump().lastBolusTime.getTime();
int agoHours = (int) (agoMsec / 60d / 60d / 1000d);
double agoHours = (int) (agoMsec / 60d / 60d / 1000d);
if (agoHours < 6) // max 6h back
lastBolusView.setText(formatTime.format(getDanaRPump().lastBolusTime) + " (" + DecimalFormatter.to1Decimal(agoHours) + " " + getString(R.string.hoursago) + ") " + DecimalFormatter.to2Decimal(getDanaRPump().lastBolusAmount) + " U");
else lastBolusView.setText("");
@ -854,5 +854,30 @@ public class DanaRFragment extends Fragment implements PluginBase, PumpInterface
return pump.convertedProfile;
}
// Reply for sms communicator
public String shortStatus() {
final DateFormat formatTime = DateFormat.getTimeInstance(DateFormat.SHORT);
String ret = "";
if (getDanaRPump().lastConnection.getTime() != 0) {
Long agoMsec = new Date().getTime() - getDanaRPump().lastConnection.getTime();
int agoMin = (int) (agoMsec / 60d / 1000d);
ret += "LastConn: " + agoMin + " minago\n";
}
if (getDanaRPump().lastBolusTime.getTime() != 0) {
Long agoMsec = new Date().getTime() - getDanaRPump().lastBolusTime.getTime();
long agoHours = (int) (agoMsec / 60d / 60d / 1000d);
ret += "LastBolus: " + DecimalFormatter.to2Decimal(getDanaRPump().lastBolusAmount) + "U @" + formatTime.format(getDanaRPump().lastBolusTime) + "\n";
}
if (isRealTempBasalInProgress()) {
ret += "Temp: " + getRealTempBasal().toString() + "\n";
}
if (isExtendedBoluslInProgress()) {
ret += "Extended: " + getExtendedBolus().toString() + "\n";
}
ret += "IOB: " + getDanaRPump().iob + "U\n";
ret += "Reserv: " + DecimalFormatter.to0Decimal(getDanaRPump().reservoirRemainingUnits) + "U\n";
ret += "Batt: " + getDanaRPump().batteryRemaining + "\n";
return ret;
}
// TODO: daily total constraint
}

View file

@ -0,0 +1,13 @@
package info.nightscout.androidaps.plugins.SmsCommunicator.Events;
import android.os.Bundle;
/**
* Created by mike on 13.07.2016.
*/
public class EventNewSMS {
public Bundle bundle;
public EventNewSMS(Bundle bundle) {
this.bundle = bundle;
}
}

View file

@ -0,0 +1,29 @@
package info.nightscout.androidaps.plugins.SmsCommunicator.Receivers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.content.WakefulBroadcastReceiver;
import android.telephony.SmsMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import info.nightscout.androidaps.Config;
import info.nightscout.androidaps.MainActivity;
import info.nightscout.androidaps.Services.DataService;
import info.nightscout.androidaps.plugins.SmsCommunicator.SmsCommunicatorFragment;
public class SmsReceiver extends WakefulBroadcastReceiver {
private static Logger log = LoggerFactory.getLogger(SmsReceiver.class);
@Override
public void onReceive(Context context, Intent intent) {
if (Config.logFunctionCalls)
log.debug("onReceive " + intent);
startWakefulService(context, new Intent(context, DataService.class)
.setAction(intent.getAction())
.putExtras(intent));
}
}

View file

@ -1,20 +1,52 @@
package info.nightscout.androidaps.plugins.SmsCommunicator;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.squareup.otto.Subscribe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import info.nightscout.androidaps.Constants;
import info.nightscout.androidaps.MainActivity;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.Services.Intents;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.interfaces.PluginBase;
import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.plugins.DanaR.DanaRFragment;
import info.nightscout.androidaps.plugins.SmsCommunicator.Events.EventNewSMS;
import info.nightscout.utils.SafeParse;
/**
* A simple {@link Fragment} subclass.
*/
public class SmsCommunicatorFragment extends Fragment implements PluginBase {
private static Logger log = LoggerFactory.getLogger(SmsCommunicatorFragment.class);
TextView logView;
boolean fragmentEnabled = false;
boolean fragmentVisible = true;
@ -29,12 +61,50 @@ public class SmsCommunicatorFragment extends Fragment implements PluginBase {
return fragment;
}
public class Sms {
String phoneNumber;
String text;
Date date;
boolean received = false;
boolean sent = false;
boolean processed = false;
String confirmCode;
double bolusRequested = 0d;
public Sms(SmsMessage message) {
phoneNumber = message.getOriginatingAddress();
text = message.getMessageBody();
date = new Date(message.getTimestampMillis());
received = true;
}
public Sms(String phoneNumber, String text, Date date) {
this.phoneNumber = phoneNumber;
this.text = text;
this.date = date;
sent = true;
}
public String toString() {
return "SMS from " + phoneNumber + ": " + text;
}
}
Sms bolusWaitingForConfirmation = null;
Date lastRemoteBolusTime = new Date(0);
ArrayList<Sms> messages = new ArrayList<Sms>();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.smscommunicator_fragment, container, false);
View view = inflater.inflate(R.layout.smscommunicator_fragment, container, false);
logView = (TextView) view.findViewById(R.id.smscommunicator_log);
updateGUI();
return view;
}
private void registerBus() {
@ -80,4 +150,143 @@ public class SmsCommunicatorFragment extends Fragment implements PluginBase {
public void setFragmentVisible(int type, boolean fragmentVisible) {
this.fragmentVisible = fragmentVisible;
}
@Subscribe
public void onStatusEvent(final EventNewSMS ev) {
Object[] pdus = (Object[]) ev.bundle.get("pdus");
// For every SMS message received
for (int i = 0; i < pdus.length; i++) {
SmsMessage message = SmsMessage.createFromPdu((byte[]) pdus[i]);
processSms(new Sms(message));
}
}
private void processSms(Sms receivedSms) {
if (!isEnabled(PluginBase.GENERAL)) {
log.debug("Ignoring SMS. Plugin disabled.");
return;
}
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext());
String allowedNumbers = sharedPreferences.getString("smscommunicator_allowednumbers", "");
if (allowedNumbers.indexOf(receivedSms.phoneNumber) < 0) {
log.debug("Ignoring SMS from: " + receivedSms.phoneNumber + ". Sender not allowed");
return;
}
String reply = "";
messages.add(receivedSms);
log.debug(receivedSms.toString());
String[] splited = receivedSms.text.split("\\s+");
double amount = 0d;
String passCode = "";
if (splited.length > 0) {
switch (splited[0].toUpperCase()) {
case "RNSC":
Intent restartNSClient = new Intent(Intents.ACTION_RESTART);
MainApp.instance().getApplicationContext().sendBroadcast(restartNSClient);
List<ResolveInfo> q = MainApp.instance().getApplicationContext().getPackageManager().queryBroadcastReceivers(restartNSClient, 0);
reply = "RNSC " + q.size() + " receivers";
receivedSms.processed = true;
break;
case "DANAR":
DanaRFragment danaRFragment = (DanaRFragment) MainActivity.getSpecificPlugin(DanaRFragment.class);
if (danaRFragment != null) reply = danaRFragment.shortStatus();
receivedSms.processed = true;
break;
case "BOLUS":
if (new Date().getTime() - lastRemoteBolusTime.getTime() < Constants.remoteBolusMinDistance) {
reply = MainApp.sResources.getString(R.string.remotebolusnotallowed);
} else if (splited.length > 1) {
amount = SafeParse.stringToDouble(splited[1]);
amount = MainApp.getConfigBuilder().applyBolusConstraints(amount);
boolean remoteBolusingAllowed = sharedPreferences.getBoolean("smscommunicator_remotebolusingallowed", false);
if (amount > 0d && remoteBolusingAllowed) {
int startChar1 = 'A'; // on iphone 1st char is uppercase :)
passCode = Character.toString((char) (startChar1 + Math.random() * ('z' - 'a' + 1)));
int startChar2 = Math.random() > 0.5 ? 'a' : 'A';
passCode += Character.toString((char) (startChar2 + Math.random() * ('z' - 'a' + 1)));
int startChar3 = Math.random() > 0.5 ? 'a' : 'A';
passCode += Character.toString((char) (startChar3 + Math.random() * ('z' - 'a' + 1)));
reply = String.format(MainApp.sResources.getString(R.string.replywithcode), amount, passCode);
receivedSms.processed = true;
} else {
reply = MainApp.sResources.getString(R.string.remotebolusnotallowed);
}
}
break;
default: // expect passCode here
if (bolusWaitingForConfirmation != null && !bolusWaitingForConfirmation.processed &&
bolusWaitingForConfirmation.confirmCode.equals(splited[0]) && new Date().getTime() - bolusWaitingForConfirmation.date.getTime() < 5 * 60 * 1000L) {
bolusWaitingForConfirmation.processed = true;
PumpInterface pumpInterface = MainApp.getConfigBuilder().getActivePump();
if (pumpInterface != null) {
danaRFragment = (DanaRFragment) MainActivity.getSpecificPlugin(DanaRFragment.class);
PumpEnactResult result = pumpInterface.deliverTreatment(bolusWaitingForConfirmation.bolusRequested, 0);
if (result.success) {
reply = String.format(MainApp.sResources.getString(R.string.bolusdelivered), bolusWaitingForConfirmation.bolusRequested);
if (danaRFragment != null) reply += "\n" + danaRFragment.shortStatus();
lastRemoteBolusTime = new Date();
} else {
reply = MainApp.sResources.getString(R.string.bolusfailed);
if (danaRFragment != null) reply += "\n" + danaRFragment.shortStatus();
}
}
}
break;
}
}
if (!reply.equals("")) {
SmsManager smsManager = SmsManager.getDefault();
Sms newSms = new Sms(receivedSms.phoneNumber, reply, new Date());
if (amount > 0d) {
newSms.bolusRequested = amount;
newSms.confirmCode = passCode;
bolusWaitingForConfirmation = newSms;
} else {
bolusWaitingForConfirmation = null;
newSms.processed = true;
}
smsManager.sendTextMessage(newSms.phoneNumber, null, newSms.text, null, null);
messages.add(newSms);
}
updateGUI();
}
private void updateGUI() {
Activity activity = getActivity();
if (activity != null)
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
class CustomComparator implements Comparator<Sms> {
public int compare(Sms object1, Sms object2) {
return (int) (object1.date.getTime() - object2.date.getTime());
}
}
Collections.sort(messages, new CustomComparator());
int messagesToShow = 40;
int start = Math.max(0, messages.size() - messagesToShow);
DateFormat df = DateFormat.getTimeInstance(DateFormat.SHORT);
String logText = "";
for (int x = start; x < messages.size(); x++) {
Sms sms = messages.get(x);
if (sms.received) {
logText += df.format(sms.date) + " &lt;&lt;&lt; " + (sms.processed ? "" : "") + sms.phoneNumber + " <b>" + sms.text + "</b><br>";
} else if (sms.sent) {
logText += df.format(sms.date) + " &gt;&gt;&gt; " + (sms.processed ? "" : "") + sms.phoneNumber + " <b>" + sms.text + "</b><br>";
}
}
logView.setText(Html.fromHtml(logText));
}
});
}
}

View file

@ -4,4 +4,15 @@
android:layout_height="match_parent"
tools:context="info.nightscout.androidaps.plugins.SmsCommunicator.SmsCommunicatorFragment">
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="@+id/smscommunicator_log"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text=""/>
</ScrollView>
</FrameLayout>

View file

@ -242,8 +242,15 @@
<string name="percent">Percent</string>
<string name="absolute">Absolute</string>
<string name="canceltemp">Cancel temp basal</string>
<string name="smscommunicator">Sms Communicator</string>
<string name="smscommunicator">SMS Communicator</string>
<string name="mm640g">MM 640g</string>
<string name="waitingforpumpresult">Waiting for result</string>
<string name="smscommunicator_allowednumbers">Allowed phone numbers</string>
<string name="smscommunicator_allowednumbers_summary">+XXXXXXXXXX;+YYYYYYYYYY</string>
<string formatted="false" name="replywithcode">To deliver bolus %.2fU reply with code %s</string>
<string name="bolusfailed">Bolus failed</string>
<string formatted="false" name="bolusdelivered">Bolus %.2fU delivered successfully</string>
<string name="smscommunicator_remotebolusingallowed">Allow remote bolusing via SMS</string>
<string name="remotebolusnotallowed">Remote bolus not allowed</string>
</resources>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="smscommunicator"
android:title="@string/smscommunicator">
<EditTextPreference
android:title="@string/smscommunicator_allowednumbers"
android:summary="@string/smscommunicator_allowednumbers_summary"
android:key="smscommunicator_allowednumbers"
android:defaultValue="">
</EditTextPreference>
<SwitchPreference
android:defaultValue="false"
android:key="smscommunicator_remotebolusingallowed"
android:title="@string/smscommunicator_remotebolusingallowed" />
</PreferenceCategory>
</PreferenceScreen>