From 99544e1f2b6fe0c2f381fff334368a380f40d2fb Mon Sep 17 00:00:00 2001 From: Andy Rozman Date: Mon, 23 Apr 2018 19:47:51 +0100 Subject: [PATCH 0001/1120] Pre start for Medtronic plugin --- .../plugins/PumpMedtronic/MedtronicPumpPlugin.java | 8 ++++++++ .../nightscout/androidaps/plugins/PumpPluginAbstract.java | 8 ++++++++ .../plugins/PumpRileyLink/RileyLinkPumpAbstract.java | 8 ++++++++ 3 files changed, 24 insertions(+) create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/MedtronicPumpPlugin.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpPluginAbstract.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpRileyLink/RileyLinkPumpAbstract.java diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/MedtronicPumpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/MedtronicPumpPlugin.java new file mode 100644 index 0000000000..9302caff25 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/MedtronicPumpPlugin.java @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.plugins.PumpMedtronic; + +/** + * Created by andy on 23.04.18. + */ + +public class MedtronicPumpPlugin { +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpPluginAbstract.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpPluginAbstract.java new file mode 100644 index 0000000000..ddce470f65 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpPluginAbstract.java @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.plugins; + +/** + * Created by andy on 23.04.18. + */ + +public class PumpPluginAbstract { +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpRileyLink/RileyLinkPumpAbstract.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpRileyLink/RileyLinkPumpAbstract.java new file mode 100644 index 0000000000..22d2321f81 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpRileyLink/RileyLinkPumpAbstract.java @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.plugins.PumpRileyLink; + +/** + * Created by andy on 23.04.18. + */ + +public class RileyLinkPumpAbstract { +} From 4b121ae892d6bc085908ace0b51624f22c279c99 Mon Sep 17 00:00:00 2001 From: Andy Rozman Date: Tue, 1 May 2018 22:44:53 +0100 Subject: [PATCH 0002/1120] - copied roundtrip2 project into here (for easier use), some of it might be reused - added PumpCommon - added PumpMedtronic --- app/build.gradle | 8 + .../AppCompatPreferenceActivity.java | 109 +++ .../com/gxwtech/roundtrip2/AutoStart.java | 21 + .../CommunicationService.java | 279 ++++++++ .../CommunicationService/Objects/Bolus.java | 91 +++ .../Objects/DateDeserializer.java | 20 + .../Objects/Integration.java | 216 ++++++ .../Objects/IntegrationSerializer.java | 35 + .../Objects/RealmManager.java | 23 + .../Objects/TempBasal.java | 179 +++++ .../HistoryPageDetailActivity.txt | 83 +++ .../HistoryPageDetailFragment.txt | 70 ++ .../HistoryPageListActivity.txt | 230 +++++++ .../HistoryPageListContent.java | 94 +++ .../com/gxwtech/roundtrip2/MainActivity.txt | 614 +++++++++++++++++ .../java/com/gxwtech/roundtrip2/MainApp.txt | 48 ++ .../java/com/gxwtech/roundtrip2/RT2Const.java | 135 ++++ .../com/gxwtech/roundtrip2/RileyLinkScan.txt | 343 ++++++++++ .../RileyLink/FrequencyScanResults.java | 21 + .../RileyLink/FrequencyTrial.java | 11 + .../RileyLink/PumpManager.java | 598 ++++++++++++++++ .../RileyLink/PumpManagerStatus.java | 61 ++ .../RileyLink/RawHistoryPage.java | 45 ++ .../BLECommOperations/BLECommOperation.java | 20 + .../BLECommOperationResult.java | 16 + .../CharacteristicReadOperation.java | 62 ++ .../CharacteristicWriteOperation.java | 66 ++ .../DescriptorWriteOperation.java | 56 ++ .../RileyLinkBLE/GattAttributes.java | 52 ++ .../RoundtripService/RileyLinkBLE/RFSpy.java | 203 ++++++ .../RileyLinkBLE/RFSpyReader.java | 117 ++++ .../RileyLinkBLE/RFSpyResponse.java | 75 ++ .../RileyLinkBLE/RFTools.java | 295 ++++++++ .../RileyLinkBLE/RadioPacket.java | 24 + .../RileyLinkBLE/RadioResponse.java | 67 ++ .../RileyLinkBLE/RileyLinkBLE.java | 428 ++++++++++++ .../RoundtripService/RoundtripService.java | 638 ++++++++++++++++++ .../RoundtripServiceIPCConnection.java | 165 +++++ .../Tasks/DiscoverGattServicesTask.java | 15 + .../Tasks/FetchPumpHistoryTask.java | 40 ++ .../Tasks/InitializePumpManagerTask.java | 42 ++ .../RoundtripService/Tasks/PumpTask.java | 13 + .../Tasks/ReadBolusWizardCarbProfileTask.java | 24 + .../Tasks/ReadISFProfileTask.java | 37 + .../Tasks/ReadPumpClockTask.java | 29 + .../Tasks/RetrieveHistoryPageTask.java | 37 + .../RoundtripService/Tasks/ServiceTask.java | 50 ++ .../Tasks/ServiceTaskExecutor.java | 42 ++ .../Tasks/UpdatePumpStatusTask.java | 58 ++ .../Tasks/WakeAndTuneTask.java | 22 + .../ButtonPressCarelinkMessageBody.java | 23 + .../Messages/CarelinkLongMessageBody.java | 36 + .../Messages/CarelinkShortMessageBody.java | 38 ++ .../GetHistoryPageCarelinkMessageBody.java | 50 ++ .../GetPumpModelCarelinkMessageBody.java | 31 + .../medtronic/Messages/MessageBody.java | 10 + .../medtronic/Messages/MessageType.java | 105 +++ .../Messages/PumpAckMessageBody.java | 11 + .../Messages/UnknownMessageBody.java | 35 + .../medtronic/PacketType.java | 20 + .../medtronic/PumpData/BasalProfile.java | 175 +++++ .../medtronic/PumpData/BasalProfileEntry.java | 28 + .../PumpData/BasalProfileTypeEnum.java | 8 + .../medtronic/PumpData/HistoryReport.java | 31 + .../medtronic/PumpData/HtmlByte.java | 11 + .../medtronic/PumpData/HtmlCodeTagEnd.java | 9 + .../medtronic/PumpData/HtmlCodeTagStart.java | 30 + .../medtronic/PumpData/HtmlElement.java | 9 + .../PumpData/HtmlElementGeneric.java | 15 + .../PumpData/HtmlHistoryPageStart.java | 15 + .../medtronic/PumpData/ISFTable.java | 102 +++ .../medtronic/PumpData/Page.java | 409 +++++++++++ .../PumpData/PumpHistoryDatabaseEntry.java | 51 ++ .../PumpData/PumpHistoryDatabaseHandler.java | 57 ++ .../PumpData/PumpHistoryManager.java | 341 ++++++++++ .../medtronic/PumpData/PumpHistoryPage.java | 43 ++ .../medtronic/PumpData/PumpHistoryParser.java | 263 ++++++++ .../medtronic/PumpData/PumpSettings.java | 168 +++++ .../medtronic/PumpData/TempBasalPair.java | 45 ++ .../records/AlarmClockReminderPumpEvent.java | 13 + .../records/AlarmSensorPumpEvent.java | 19 + .../PumpData/records/BGReceivedPumpEvent.java | 47 ++ .../PumpData/records/BasalProfileStart.java | 51 ++ .../PumpData/records/BatteryPumpEvent.java | 10 + .../records/BolusNormalPumpEvent.java | 86 +++ .../BolusWizardBolusEstimatePumpEvent.java | 118 ++++ .../PumpData/records/CalBgForPhPumpEvent.java | 39 ++ .../ChangeAlarmClockEnablePumpEvent.java | 13 + .../ChangeAlarmNotifyModePumpEvent.java | 10 + .../records/ChangeAudioBolusPumpEvent.java | 13 + .../ChangeBGReminderEnablePumpEvent.java | 13 + .../ChangeBasalProfilePatternPumpEvent.java | 21 + .../records/ChangeBasalProfilePumpEvent.java | 15 + .../ChangeBolusReminderEnablePumpEvent.java | 19 + .../ChangeBolusReminderTimePumpEvent.java | 19 + .../ChangeBolusScrollStepSizePumpEvent.java | 13 + .../ChangeBolusWizardSetupPumpEvent.java | 21 + .../ChangeCaptureEventEnablePumpEvent.java | 13 + .../records/ChangeCarbUnitsPumpEvent.java | 13 + .../ChangeChildBlockEnablePumpEvent.java | 13 + .../records/ChangeMaxBolusPumpEvent.java | 13 + .../records/ChangeOtherDeviceIDPumpEvent.java | 19 + .../ChangeReservoirWarningTimePumpEvent.java | 13 + ...SensorRateOfChangeAlertSetupPumpEvent.java | 19 + .../records/ChangeSensorSetup2PumpEvent.java | 19 + .../records/ChangeTempBasalTypePumpEvent.java | 44 ++ .../records/ChangeTimeFormatPumpEvent.java | 11 + .../PumpData/records/ChangeTimePumpEvent.java | 18 + .../records/ChangeVariableBolusPumpEvent.java | 13 + .../ChangeWatchdogEnablePumpEvent.java | 13 + ...hangeWatchdogMarriageProfilePumpEvent.java | 19 + .../PumpData/records/ClearAlarmPumpEvent.java | 10 + .../DeleteAlarmClockTimePumpEvent.java | 19 + .../DeleteBolusReminderTimePumpEvent.java | 19 + .../records/DeleteOtherDeviceIDPumpEvent.java | 19 + .../records/EnableDisableRemotePumpEvent.java | 17 + .../PumpData/records/InsulinMarkerEvent.java | 23 + .../JournalEntryExerciseMarkerPumpEvent.java | 18 + .../JournalEntryPumpLowBatteryPumpEvent.java | 10 + ...JournalEntryPumpLowReservoirPumpEvent.java | 10 + .../Model522ResultTotalsPumpEvent.java | 15 + .../PumpData/records/NewTimeSet.java | 15 + .../PumpData/records/PrimePumpEvent.java | 51 ++ .../PumpData/records/PumpAlarmPumpEvent.java | 42 ++ .../medtronic/PumpData/records/Record.java | 92 +++ .../PumpData/records/RecordTypeEnum.java | 126 ++++ .../records/ResultDailyTotalPumpEvent.java | 34 + .../PumpData/records/ResumePumpEvent.java | 10 + .../PumpData/records/RewindPumpEvent.java | 10 + .../PumpData/records/Sara6EPumpEvent.java | 36 + .../PumpData/records/SuspendPumpEvent.java | 10 + .../records/TempBasalDurationPumpEvent.java | 42 ++ .../records/TempBasalRatePumpEvent.java | 54 ++ .../PumpData/records/TimeStampedRecord.java | 74 ++ .../PumpData/records/UnabsorbedInsulin.java | 94 +++ .../PumpData/records/Unknown7ByteEvent1.java | 13 + .../medtronic/PumpMessage.java | 71 ++ .../RoundtripService/medtronic/PumpModel.java | 55 ++ .../medtronic/PumpTimeStamp.java | 35 + .../medtronic/TempBasalEvent.java | 29 + .../medtronic/TimeFormat.java | 82 +++ .../RoundtripServiceClientConnection.java | 133 ++++ .../roundtrip2/ServiceClientConnection.java | 142 ++++ .../roundtrip2/ServiceData/BasalProfile.java | 47 ++ .../ServiceData/BolusWizardCarbProfile.java | 27 + .../ServiceData/FetchPumpHistoryResult.java | 35 + .../roundtrip2/ServiceData/ISFProfile.java | 29 + .../ServiceData/PumpModelResult.java | 27 + .../ServiceData/PumpStatusResult.java | 137 ++++ .../ServiceData/ReadPumpClockResult.java | 62 ++ .../RetrieveHistoryPageResult.java | 22 + .../ServiceData/ServiceClientActions.java | 138 ++++ .../ServiceData/ServiceCommand.java | 57 ++ .../ServiceData/ServiceMessage.java | 24 + .../ServiceData/ServiceMessageUpdate.java | 12 + .../ServiceData/ServiceNotification.java | 44 ++ .../roundtrip2/ServiceData/ServiceResult.java | 76 +++ .../ServiceData/ServiceTransport.java | 126 ++++ .../ServiceData/TimeValueProfile.java | 126 ++++ .../ServiceMessageView.java | 35 + .../ServiceMessageViewDetailActivity.txt | 83 +++ .../ServiceMessageViewDetailFragment.txt | 69 ++ .../ServiceMessageViewItem.java | 25 + .../ServiceMessageViewListActivity.txt | 203 ++++++ .../gxwtech/roundtrip2/SettingsActivity.txt | 208 ++++++ .../gxwtech/roundtrip2/TreatmentHistory.txt | 320 +++++++++ .../com/gxwtech/roundtrip2/util/ByteUtil.java | 146 ++++ .../java/com/gxwtech/roundtrip2/util/CRC.java | 74 ++ .../com/gxwtech/roundtrip2/util/Check.java | 98 +++ .../com/gxwtech/roundtrip2/util/HexDump.java | 162 +++++ .../roundtrip2/util/LocationHelper.java | 81 +++ .../gxwtech/roundtrip2/util/StringUtil.java | 37 + .../gxwtech/roundtrip2/util/ThreadUtil.java | 17 + .../com/gxwtech/roundtrip2/util/tools.java | 115 ++++ .../info/nightscout/androidaps/Config.java | 1 + .../info/nightscout/androidaps/MainApp.java | 132 ++-- .../androidaps/PreferencesActivity.java | 3 + .../PumpCommon/PumpPluginAbstract.java | 249 +++++++ .../plugins/PumpCommon/data/PumpStatus.java | 45 ++ .../PumpCommon/driver/PumpDriverAbstract.java | 48 ++ .../driver/PumpDriverInterface.java | 15 + .../hw/rileylink/RileyLinkLayer.java | 11 + .../PumpMedtronic/MedtronicFragment.java | 278 ++++++++ .../PumpMedtronic/MedtronicPumpPlugin.java | 174 ++++- .../medtronic/MedtronicPumpDriver.java | 47 ++ .../medtronic/MedtronicPumpStatus.java | 156 +++++ .../medtronic/defs/MedtronicPumpType.java | 144 ++++ .../plugins/PumpPluginAbstract.java | 8 - .../PumpRileyLink/RileyLinkPumpAbstract.java | 8 - .../PumpVirtual/VirtualPumpDriver.java | 394 +++++++++++ .../PumpVirtual/VirtualPumpStatus.java | 25 + .../main/res/layout/medtronic_fragment.xml | 536 +++++++++++++++ app/src/main/res/values/arrays.xml | 22 + app/src/main/res/values/strings.xml | 19 + app/src/main/res/xml/pref_medtronic.xml | 49 ++ build.gradle | 1 + 196 files changed, 15335 insertions(+), 78 deletions(-) create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/AppCompatPreferenceActivity.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/AutoStart.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/CommunicationService.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/Bolus.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/DateDeserializer.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/Integration.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/IntegrationSerializer.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/RealmManager.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/TempBasal.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageDetailActivity.txt create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageDetailFragment.txt create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageListActivity.txt create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageListContent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/MainActivity.txt create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/MainApp.txt create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RT2Const.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RileyLinkScan.txt create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/FrequencyScanResults.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/FrequencyTrial.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/PumpManager.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/PumpManagerStatus.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/RawHistoryPage.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/BLECommOperation.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/BLECommOperationResult.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/CharacteristicReadOperation.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/CharacteristicWriteOperation.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/DescriptorWriteOperation.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/GattAttributes.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RFSpy.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RFSpyReader.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RFSpyResponse.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RFTools.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RadioPacket.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RadioResponse.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RileyLinkBLE.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RoundtripService.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RoundtripServiceIPCConnection.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/DiscoverGattServicesTask.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/FetchPumpHistoryTask.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/InitializePumpManagerTask.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/PumpTask.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ReadBolusWizardCarbProfileTask.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ReadISFProfileTask.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ReadPumpClockTask.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/RetrieveHistoryPageTask.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ServiceTask.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ServiceTaskExecutor.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/UpdatePumpStatusTask.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/WakeAndTuneTask.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/ButtonPressCarelinkMessageBody.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/CarelinkLongMessageBody.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/CarelinkShortMessageBody.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/GetHistoryPageCarelinkMessageBody.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/GetPumpModelCarelinkMessageBody.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/MessageBody.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/MessageType.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/PumpAckMessageBody.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/UnknownMessageBody.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PacketType.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/BasalProfile.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/BasalProfileEntry.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/BasalProfileTypeEnum.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HistoryReport.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlByte.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlCodeTagEnd.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlCodeTagStart.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlElement.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlElementGeneric.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlHistoryPageStart.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/ISFTable.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/Page.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpHistoryDatabaseEntry.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpHistoryDatabaseHandler.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpHistoryManager.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpHistoryPage.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpHistoryParser.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpSettings.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/TempBasalPair.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/AlarmClockReminderPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/AlarmSensorPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BGReceivedPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BasalProfileStart.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BatteryPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BolusNormalPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BolusWizardBolusEstimatePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/CalBgForPhPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeAlarmClockEnablePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeAlarmNotifyModePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeAudioBolusPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBGReminderEnablePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBasalProfilePatternPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBasalProfilePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusReminderEnablePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusReminderTimePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusScrollStepSizePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusWizardSetupPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeCaptureEventEnablePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeCarbUnitsPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeChildBlockEnablePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeMaxBolusPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeOtherDeviceIDPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeReservoirWarningTimePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeSensorRateOfChangeAlertSetupPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeSensorSetup2PumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeTempBasalTypePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeTimeFormatPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeTimePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeVariableBolusPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeWatchdogEnablePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeWatchdogMarriageProfilePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ClearAlarmPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/DeleteAlarmClockTimePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/DeleteBolusReminderTimePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/DeleteOtherDeviceIDPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/EnableDisableRemotePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/InsulinMarkerEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/JournalEntryExerciseMarkerPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/JournalEntryPumpLowBatteryPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/JournalEntryPumpLowReservoirPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/Model522ResultTotalsPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/NewTimeSet.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/PrimePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/PumpAlarmPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/Record.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/RecordTypeEnum.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ResultDailyTotalPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ResumePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/RewindPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/Sara6EPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/SuspendPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/TempBasalDurationPumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/TempBasalRatePumpEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/TimeStampedRecord.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/UnabsorbedInsulin.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/Unknown7ByteEvent1.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpMessage.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpModel.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpTimeStamp.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/TempBasalEvent.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/TimeFormat.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/RoundtripServiceClientConnection.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceClientConnection.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/BasalProfile.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/BolusWizardCarbProfile.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/FetchPumpHistoryResult.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ISFProfile.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/PumpModelResult.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/PumpStatusResult.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ReadPumpClockResult.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/RetrieveHistoryPageResult.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceClientActions.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceCommand.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceMessage.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceMessageUpdate.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceNotification.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceResult.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceTransport.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceData/TimeValueProfile.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageView.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewDetailActivity.txt create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewDetailFragment.txt create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewItem.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewListActivity.txt create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/SettingsActivity.txt create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/TreatmentHistory.txt create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/util/ByteUtil.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/util/CRC.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/util/Check.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/util/HexDump.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/util/LocationHelper.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/util/StringUtil.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/util/ThreadUtil.java create mode 100644 app/src/main/java/com/gxwtech/roundtrip2/util/tools.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/PumpPluginAbstract.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/data/PumpStatus.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/driver/PumpDriverAbstract.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/driver/PumpDriverInterface.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/hw/rileylink/RileyLinkLayer.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/MedtronicFragment.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/medtronic/MedtronicPumpDriver.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/medtronic/MedtronicPumpStatus.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/medtronic/defs/MedtronicPumpType.java delete mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpPluginAbstract.java delete mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpRileyLink/RileyLinkPumpAbstract.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpDriver.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpStatus.java create mode 100644 app/src/main/res/layout/medtronic_fragment.xml create mode 100644 app/src/main/res/xml/pref_medtronic.xml diff --git a/app/build.gradle b/app/build.gradle index 2f22df9728..0a2b868168 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,17 +2,25 @@ buildscript { repositories { maven { url 'https://maven.fabric.io/public' } jcenter() + + google() + maven { + url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' + } } dependencies { classpath 'io.fabric.tools:gradle:1.+' classpath 'com.dicedmelon.gradle:jacoco-android:0.1.2' + //classpath 'io.realm:realm-gradle-plugin:1.1.1' + classpath 'io.realm:realm-gradle-plugin:4.0.0' } } apply plugin: "com.android.application" apply plugin: "io.fabric" apply plugin: "jacoco-android" apply plugin: 'com.jakewharton.butterknife' +apply plugin: 'realm-android' ext { supportLibraryVersion = "27.0.2" diff --git a/app/src/main/java/com/gxwtech/roundtrip2/AppCompatPreferenceActivity.java b/app/src/main/java/com/gxwtech/roundtrip2/AppCompatPreferenceActivity.java new file mode 100644 index 0000000000..a2b4bd050d --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/AppCompatPreferenceActivity.java @@ -0,0 +1,109 @@ +package com.gxwtech.roundtrip2; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.support.annotation.LayoutRes; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatDelegate; +import android.support.v7.widget.Toolbar; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls + * to be used with AppCompat. + */ +public abstract class AppCompatPreferenceActivity extends PreferenceActivity { + + private AppCompatDelegate mDelegate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + super.onCreate(savedInstanceState); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + } + + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + public void setSupportActionBar(@Nullable Toolbar toolbar) { + getDelegate().setSupportActionBar(toolbar); + } + + @Override + public MenuInflater getMenuInflater() { + return getDelegate().getMenuInflater(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + private AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, null); + } + return mDelegate; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/AutoStart.java b/app/src/main/java/com/gxwtech/roundtrip2/AutoStart.java new file mode 100644 index 0000000000..cc0df609be --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/AutoStart.java @@ -0,0 +1,21 @@ +package com.gxwtech.roundtrip2; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.gxwtech.roundtrip2.CommunicationService.CommunicationService; + +/** + * Created by Tim on 07/06/2016. + * Receives BOOT_COMPLETED Intent and starts service + */ +public class AutoStart extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + + context.startService(new Intent(context, CommunicationService.class)); + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/CommunicationService.java b/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/CommunicationService.java new file mode 100644 index 0000000000..79ceb678f5 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/CommunicationService.java @@ -0,0 +1,279 @@ +package com.gxwtech.roundtrip2.CommunicationService; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; +import android.widget.Toast; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import com.gxwtech.roundtrip2.CommunicationService.Objects.Bolus; +import com.gxwtech.roundtrip2.CommunicationService.Objects.DateDeserializer; +import com.gxwtech.roundtrip2.CommunicationService.Objects.Integration; +import com.gxwtech.roundtrip2.CommunicationService.Objects.IntegrationSerializer; +import com.gxwtech.roundtrip2.CommunicationService.Objects.RealmManager; +import com.gxwtech.roundtrip2.CommunicationService.Objects.TempBasal; + +import com.gxwtech.roundtrip2.RT2Const; +import com.gxwtech.roundtrip2.util.Check; + + +import java.util.Date; +import java.util.List; + +import info.nightscout.androidaps.MainApp; + +/** + * Created by Tim on 07/06/2016. + * This service listens out for requests from HAPP and processes them + */ +public class CommunicationService extends android.app.Service { + + public CommunicationService(){} + final static String TAG = "CommunicationService"; + + class IncomingHandler extends Handler { + @Override + public void handleMessage(Message msg) { + String action = ""; + Long requested = 0L; + String safteyCheck = ""; + String pump = ""; + List remoteIntegrations; + List remoteTreatments; + Bundle data = new Bundle(); + RealmManager realmManager = new RealmManager(); + + Log.d(TAG, "START"); + try { + + /* + Expected Bundle data... + "ACTION" - What is this incoming request? Example: "NEW_TREATMENTS" + "DATE_REQUESTED" - When was this requested? So we can ignore old requests + "PUMP" - Name of the pump the APS expects this app to support + "INTEGRATION_OBJECTS" - Array of Integration Objects, details of the objects being synced. *OPTIONAL for NEW_TREATMENTS only* + "TREATMENT_OBJECTS" - Array of Objects themselves being synced, TempBasal or Bolus *OPTIONAL for NEW_TREATMENTS only* + */ + + data = msg.getData(); + action = data.getString(RT2Const.commService.ACTION); + requested = data.getLong(RT2Const.commService.DATE_REQUESTED, 0); + pump = data.getString(RT2Const.commService.PUMP); + Log.d("RECEIVED: ACTION", action); + Log.d("RECEIVED: DATE", requested.toString()); + Log.d("RECEIVED: PUMP", pump); + + } catch (Exception e) { + e.printStackTrace(); + // TODO: 16/01/2016 Issue getting treatment details from APS app msg for user + } + + + switch (action) { + case RT2Const.commService.INCOMING_TEST_MSG: + Resources appR = MainApp.instance().getResources(); + CharSequence txt = appR.getText(appR.getIdentifier("app_name", "string", MainApp.instance().getPackageName())); + Toast.makeText(MainApp.instance(), txt + ": Pump Driver App has connected successfully. ", Toast.LENGTH_LONG).show(); + Log.d(TAG, txt + ": APS app has connected successfully."); + + break; + case RT2Const.commService.INCOMING_NEW_TREATMENTS: + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter(Date.class, new DateDeserializer()); + Gson gson = gsonBuilder.create(); + + remoteIntegrations = gson.fromJson(data.getString(RT2Const.commService.INTEGRATION_OBJECTS), new TypeToken>() {}.getType()); + remoteTreatments = gson.fromJson(data.getString(RT2Const.commService.TREATMENT_OBJECTS), new TypeToken>() {}.getType()); + Log.d("RECEIVED: INTEGRATIONS", remoteIntegrations.toString()); + Log.d("RECEIVED: TREATMENTS", remoteTreatments.toString()); + + for (int i = 0; i < remoteIntegrations.size(); i++) { + + realmManager.getRealm().beginTransaction(); + + Integration integrationForAPS = remoteIntegrations.get(i); + integrationForAPS.setType ("aps_app"); + integrationForAPS.setState ("received"); + integrationForAPS.setToSync (true); + integrationForAPS.setDate_updated (new Date()); + integrationForAPS.setRemote_id(remoteIntegrations.get(i).getLocal_object_id()); + + Integration integrationForPump = new Integration(); + integrationForPump.setType ("pump"); + integrationForPump.setDate_updated (new Date()); + integrationForPump.setLocal_object(remoteIntegrations.get(i).getLocal_object()); + + String localObjectlID = "", localObjectState = "", localObjectDetails = "", rejectRequest = ""; + if (!Check.isPumpSupported(pump)) + rejectRequest += "Pump requested not supported. "; + if (Check.isRequestTooOld(requested)) rejectRequest += "Request too old. "; + + switch (remoteIntegrations.get(i).getLocal_object()) { + case "temp_basal": + TempBasal tempBasal = gson.fromJson(remoteTreatments.get(i), TempBasal.class); + realmManager.getRealm().copyToRealm(tempBasal); + localObjectlID = tempBasal.getId(); + + switch (remoteIntegrations.get(i).getAction()) { + case "new": + rejectRequest += Check.isNewTempBasalSafe(tempBasal); + if (rejectRequest.equals("")) // TODO: 12/08/2016 command to send TempBasal to pump + break; + case "cancel": + rejectRequest += Check.isCancelTempBasalSafe(tempBasal, integrationForAPS, realmManager.getRealm()); + if (rejectRequest.equals("")) // TODO: 12/08/2016 command to send TempBasal to pump + break; + } + break; + + case "bolus_delivery": + Bolus bolus = gson.fromJson(remoteTreatments.get(i), Bolus.class); + realmManager.getRealm().copyToRealm(bolus); + localObjectlID = bolus.getId(); + rejectRequest += Check.isBolusSafeToAction(bolus); + + if (rejectRequest.equals("")) //TODO: 12/08/2016 command to action Bolus + + break; + } + + if (rejectRequest.equals("")) { + //all ok + localObjectState = "received"; + localObjectDetails = "Request sent to pump"; + } else { + //reject + localObjectState = "error"; + localObjectDetails = rejectRequest; + } + + integrationForAPS.setLocal_object_id(localObjectlID); + integrationForAPS.setState(localObjectState); + integrationForAPS.setDetails(localObjectDetails); + realmManager.getRealm().copyToRealm(integrationForAPS); + integrationForPump.setLocal_object_id(localObjectlID); + integrationForPump.setState(localObjectState); + integrationForPump.setDetails(localObjectDetails); + realmManager.getRealm().copyToRealm(integrationForPump); + realmManager.getRealm().commitTransaction(); + } + break; + + default: + Log.e(TAG, "handleMessage: Unknown Action: " + action); + } + + connect_to_aps_app(); + realmManager.closeRealm(); + } + } + + + + final Messenger myMessenger = new Messenger(new IncomingHandler()); + + @Override + public IBinder onBind(Intent intent) { + return myMessenger.getBinder(); + } + + + public void updateAPSApp(){ + + RealmManager realmManager = new RealmManager(); + List integrations = Integration.getIntegrationsToSync("aps_app", null, realmManager.getRealm()); + + if (integrations.size() > 0) { + /* + Bundle data... + "ACTION" - What is this incoming request? Example: "TREATMENT_UPDATES" + "INTEGRATION_OBJECTS" - Array of Integration Objects, details of the objects being synced. *OPTIONAL for UPDATE_TREATMENTS only* + */ + + Log.d(TAG, "UPDATE APS App:" + integrations.size() + " treatments to update"); + Log.d(TAG, "INTEGRATIONS:" + integrations.toString()); + Message msg = Message.obtain(); + boolean updateOK = true; + try { + Gson gson = new GsonBuilder() + .registerTypeAdapter(Class.forName("io.realm.IntegrationRealmProxy"), new IntegrationSerializer()) + .create(); + + Bundle bundle = new Bundle(); + bundle.putString(RT2Const.commService.ACTION, RT2Const.commService.OUTGOING_TREATMENT_UPDATES); + bundle.putString(RT2Const.commService.INTEGRATION_OBJECTS, gson.toJson(integrations)); + msg.setData(bundle); + + } catch (ClassNotFoundException e){ + updateOK = false; + Log.e(TAG, "Error creating gson object: " + e.getLocalizedMessage()); + } + + try { + myService.send(msg); + Log.d(TAG, integrations.size() + " updates sent"); + } catch (RemoteException e) { + updateOK = false; + Log.e(TAG, integrations.size() + " updates failed. " + e.getLocalizedMessage()); + } + + for (Integration integration : integrations){ + realmManager.getRealm().beginTransaction(); + if (updateOK) { + integration.setState("sent"); + } else { + integration.setState("error"); + integration.setDetails("Update to APS failed. Will not be resent."); + } + integration.setToSync(false); + realmManager.getRealm().commitTransaction(); + } + } + + try { + if (isBound) CommunicationService.this.unbindService(myConnection); + } catch (IllegalArgumentException e) { + //catch if service was killed in a unclean way + } + + realmManager.closeRealm(); + } + + //Connect to the APS App Treatments Service + private void connect_to_aps_app(){ + // TODO: 16/06/2016 should be able to pick the APS app from UI not hardcoded + Intent intent = new Intent("com.hypodiabetic.happ.services.TreatmentService"); + intent.setPackage("com.hypodiabetic.happ"); + CommunicationService.this.bindService(intent, myConnection, Context.BIND_AUTO_CREATE); + } + //Our Service that APS App will connect to + private Messenger myService = null; + private Boolean isBound = false; + private ServiceConnection myConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + myService = new Messenger(service); + isBound = true; + + updateAPSApp(); + } + + public void onServiceDisconnected(ComponentName className) { + myService = null; + isBound = false; + } + }; + + +} + diff --git a/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/Bolus.java b/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/Bolus.java new file mode 100644 index 0000000000..d7e4829d2f --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/Bolus.java @@ -0,0 +1,91 @@ +package com.gxwtech.roundtrip2.CommunicationService.Objects; + +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import io.realm.Realm; +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.Sort; + + +/** + * Created by Tim on 05/08/2016. + */ +public class Bolus extends RealmObject { + + public Double getValue() { + return value; + } + public void setValue(Double value) { + this.value = value; + } + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + public Date getTimestamp() { + return timestamp; + } + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public String getId() { + return id; + } + + private String id = UUID.randomUUID().toString(); + private Date timestamp = new Date(); + private String type; + private Double value; + + public static Bolus getBolus(String uuid, Realm realm) { + RealmResults results = realm.where(Bolus.class) + .equalTo("id", uuid) + .findAllSorted("timestamp", Sort.DESCENDING); + + if (results.isEmpty()) { + return null; + } else { + return results.first(); + } + } + + public static List getBolusList(Realm realm){ + RealmResults results = realm.where(Bolus.class) + .findAllSorted("timestamp", Sort.DESCENDING); + if (results.isEmpty()) { + return null; + } else { + return results; + } + } + + public static List getBolusesBetween(Date dateFrom, Date dateTo, Realm realm) { + RealmResults results = realm.where(Bolus.class) + .greaterThanOrEqualTo("timestamp", dateFrom) + .lessThanOrEqualTo("timestamp", dateTo) + .findAllSorted("timestamp", Sort.DESCENDING); + return results; + } + + public static Double getBolusCountBetween(Date dateFrom, Date dateTo, Realm realm) { + Number result = realm.where(Bolus.class) + .greaterThanOrEqualTo("timestamp", dateFrom) + .lessThanOrEqualTo("timestamp", dateTo) + .sum("value"); + return result.doubleValue(); + } + + + public static class sortByDateTimeOld2YoungOLD implements Comparator { + @Override + public int compare(Bolus o1, Bolus o2) { + return o2.getTimestamp().compareTo(o1.getTimestamp()); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/DateDeserializer.java b/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/DateDeserializer.java new file mode 100644 index 0000000000..76c25a6e4c --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/DateDeserializer.java @@ -0,0 +1,20 @@ +package com.gxwtech.roundtrip2.CommunicationService.Objects; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import java.lang.reflect.Type; + +import java.util.Date; + +/** + * Created by Tim on 16/08/2016. + * Used by GSON to Deserializer Dates + */ +public class DateDeserializer implements JsonDeserializer { + public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { + Date date = null; + date = new Date(json.getAsJsonPrimitive().getAsLong()); + return date; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/Integration.java b/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/Integration.java new file mode 100644 index 0000000000..a62a22f3be --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/Integration.java @@ -0,0 +1,216 @@ +package com.gxwtech.roundtrip2.CommunicationService.Objects; + +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import io.realm.Realm; +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.Sort; + + +/** + * Created by Tim on 16/01/2016. + * This table holds Integration details of an object + * one object may have multiple Integrations + */ + +public class Integration extends RealmObject { + + public String getId() { + return id; + } + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public String getState() { + return state; + } + public void setState(String state) { + this.state = state; + } + public String getAction() { + return action; + } + public void setAction(String action) { + this.action = action; + } + public Date getTimestamp() { + return timestamp; + } + public Date getDate_updated() { + return date_updated; + } + public void setDate_updated(Date date_updated) { + this.date_updated = date_updated; + } + public String getLocal_object() { + return local_object; + } + public void setLocal_object(String local_object) { + this.local_object = local_object; + } + public String getLocal_object_id() { + return local_object_id; + } + public void setLocal_object_id(String local_object_id) { + this.local_object_id = local_object_id; + } + public String getRemote_id() { + return remote_id; + } + public void setRemote_id(String remote_id) { + this.remote_id = remote_id; + } + public String getDetails() { + return details; + } + public void setDetails(String details) { + this.details = details; + } + public String getRemote_var1() { + return remote_var1; + } + public void setRemote_var1(String remote_var1) { + this.remote_var1 = remote_var1; + } + public String getAuth_code() { + return auth_code; + } + public void setAuth_code(String auth_code) { + this.auth_code = auth_code; + } + public Boolean getToSync() { + return toSync; + } + public void setToSync(Boolean toSync) { + this.toSync = toSync; + } + + + private String id; + private String type; //What Integration is this? + private String state; //Current state this Integration is in + private String action; //Requested action for this object + private Date timestamp; //Date created + private Date date_updated; //Last time the Integration for this object was updated + private String local_object; //What rt2 object is this? Bolus, TempBasal etc + private String local_object_id; //HAPP ID for this object + private String remote_id; //ID provided by the remote system + private String details; //The details of this Integration attempt + private String remote_var1; //Misc information about this Integration + private String auth_code; //auth_code if required + private Boolean toSync; //Do we need to sync this object? + + public Integration(){ + id = UUID.randomUUID().toString(); + timestamp = new Date(); + date_updated = new Date(); + remote_var1 = ""; + state = ""; + toSync = true; + } + + public Integration(String type, String local_object, String local_object_id){ + id = UUID.randomUUID().toString(); + timestamp = new Date(); + date_updated = new Date(); + remote_var1 = ""; + state = ""; + this.type = type; + this.local_object = local_object; + this.local_object_id = local_object_id; + toSync = true; + } + + public static Integration getIntegration(String type, String local_object, String rt2_id, Realm realm){ + RealmResults results = realm.where(Integration.class) + .equalTo("type", type) + .equalTo("local_object", local_object) + .equalTo("local_object_id", rt2_id) + .findAllSorted("date_updated", Sort.DESCENDING); + + if (results.isEmpty()) { //We dont have an Integration for this item + return null; + + } else { //Found an Integration, return it + return results.first(); + } + } + + public static List getIntegrationsFor(String local_object, String local_object_id, Realm realm) { + RealmResults results = realm.where(Integration.class) + .equalTo("local_object", local_object) + .equalTo("local_object_id", local_object_id) + .findAllSorted("date_updated", Sort.DESCENDING); + return results; + } + + public static List getIntegrationsHoursOld(String type, String local_object, int inLastHours, Realm realm) { + Date now = new Date(); + Date hoursAgo = new Date(now.getTime() - (inLastHours * 60 * 60 * 1000)); + + RealmResults results = realm.where(Integration.class) + .equalTo("local_object", local_object) + .equalTo("type", type) + .greaterThanOrEqualTo("date_updated", hoursAgo) + .lessThanOrEqualTo("date_updated", now) + .findAllSorted("date_updated", Sort.DESCENDING); + return results; + } + + public static List getIntegrationsToSync(String type, String local_object, Realm realm) { + if (local_object != null) { + RealmResults results = realm.where(Integration.class) + .equalTo("local_object", local_object) + .equalTo("type", type) + .equalTo("toSync", Boolean.TRUE) + .findAllSorted("date_updated", Sort.DESCENDING); + return results; + } else { + RealmResults results = realm.where(Integration.class) + .equalTo("type", type) + .equalTo("toSync", Boolean.TRUE) + .findAllSorted("date_updated", Sort.DESCENDING); + return results; + } + } + + public static Integration getIntegrationByID(String uuid, Realm realm) { + RealmResults results = realm.where(Integration.class) + .equalTo("id", uuid) + .findAllSorted("timestamp", Sort.DESCENDING); + if (results.isEmpty()) { + return null; + } else { + return results.first(); + } + } + + public static List getUpdatedInLastMins(Integer inLastMins, String type, Realm realm) { + Date now = new Date(); + Date minsAgo = new Date(now.getTime() - (inLastMins * 60 * 1000)); + + RealmResults results = realm.where(Integration.class) + .equalTo("type", type) + .greaterThanOrEqualTo("date_updated", minsAgo) + .lessThanOrEqualTo("date_updated", now) + .findAllSorted("date_updated", Sort.DESCENDING); + return results; + } + + public static List getIntegrationsWithErrors(String type, Realm realm) { + RealmResults results = realm.where(Integration.class) + .equalTo("type", type) + .equalTo("state", "error") + .findAllSorted("date_updated", Sort.DESCENDING); + return results; + } + + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/IntegrationSerializer.java b/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/IntegrationSerializer.java new file mode 100644 index 0000000000..380e6b3e10 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/IntegrationSerializer.java @@ -0,0 +1,35 @@ +package com.gxwtech.roundtrip2.CommunicationService.Objects; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import java.lang.reflect.Type; + +/** + * Created by Tim on 12/08/2016. + * Required by Realm for converting to gson https://realm.io/docs/java/latest/#gson + */ +public class IntegrationSerializer implements JsonSerializer { + + @Override + public JsonElement serialize(Integration src, Type typeOfSrc, JsonSerializationContext context) { + final JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("id", src.getId()); + jsonObject.addProperty("type", src.getType()); + jsonObject.addProperty("state", src.getState()); + jsonObject.addProperty("action", src.getAction()); + jsonObject.addProperty("timestamp", src.getTimestamp().getTime()); + jsonObject.addProperty("date_updated", src.getDate_updated().getTime()); + jsonObject.addProperty("local_object", src.getLocal_object()); + jsonObject.addProperty("local_object_id", src.getLocal_object_id()); + jsonObject.addProperty("remote_id", src.getRemote_id()); + jsonObject.addProperty("details", src.getDetails()); + jsonObject.addProperty("remote_var1", src.getRemote_var1()); + jsonObject.addProperty("auth_code", src.getAuth_code()); + jsonObject.addProperty("toSync", src.getToSync()); + + return jsonObject; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/RealmManager.java b/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/RealmManager.java new file mode 100644 index 0000000000..fff16ed4ac --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/RealmManager.java @@ -0,0 +1,23 @@ +package com.gxwtech.roundtrip2.CommunicationService.Objects; + +import io.realm.Realm; + +/** + * Created by Tim on 11/08/2016. + */ +public class RealmManager { + private Realm realm; + + public RealmManager(){ + realm = Realm.getDefaultInstance(); + } + + public void closeRealm(){ + realm.close(); + } + + public Realm getRealm(){ + if (realm.isClosed() || realm.isEmpty()) realm = Realm.getDefaultInstance(); + return realm; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/TempBasal.java b/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/TempBasal.java new file mode 100644 index 0000000000..ae021cf967 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/CommunicationService/Objects/TempBasal.java @@ -0,0 +1,179 @@ +package com.gxwtech.roundtrip2.CommunicationService.Objects; + +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import io.realm.Realm; +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.Sort; +import io.realm.annotations.Ignore; + +/** + * Created by Tim on 03/09/2015. + */ +public class TempBasal extends RealmObject { + + + public Double getRate() { + return rate; + } + public void setRate(Double rate) { + this.rate = rate; + } + public Integer getDuration() { + return duration; + } + public void setDuration(Integer duration) { + this.duration = duration; + } + public Date getStart_time() { + return start_time; + } + public void setStart_time(Date start_time) { + this.start_time = start_time; + } + public String getBasal_adjustemnt() { + return basal_adjustemnt; + } + public void setBasal_adjustemnt(String basal_adjustemnt) { + this.basal_adjustemnt = basal_adjustemnt; + } + public String getAps_mode() { + return aps_mode; + } + public void setAps_mode(String aps_mode) { + this.aps_mode = aps_mode; + } + public String getId() { + return id; + } + public Date getTimestamp() { + return timestamp; + } + + private String id = UUID.randomUUID().toString(); + private Double rate = 0D; //Temp Basal Rate for (U/hr) mode + private Integer duration = 0; //Duration of Temp + private Date start_time; //When the Temp Basal started + private String basal_adjustemnt = ""; //High or Low temp + private String aps_mode; + + @Ignore + public Date timestamp = new Date(); + + public static TempBasal getTempBasalByID(String uuid, Realm realm) { + RealmResults results = realm.where(TempBasal.class) + .equalTo("id", uuid) + .findAllSorted("start_time", Sort.DESCENDING); + + if (results.isEmpty()) { + return null; + } else { + return results.first(); + } + } + + public static TempBasal last(Realm realm) { + RealmResults results = realm.where(TempBasal.class) + .findAllSorted("start_time", Sort.DESCENDING); + + if (results.isEmpty()) { + return new TempBasal(); //returns an empty TempBasal, other than null + } else { + return results.first(); + } + } + + public static TempBasal lastActive(Realm realm) { + RealmResults results = realm.where(TempBasal.class) + .findAllSorted("start_time", Sort.DESCENDING); + + if (results.isEmpty()) { + return null; + } else { + TempBasal tempBasal = results.first(); + Integration integration = Integration.getIntegration("pump","temp_Basal",tempBasal.getId(),realm); + + if (integration.getState().equals("set")){ + return tempBasal; + } else { + return null; + } + } + } + + public static TempBasal getCurrentActive(Date atThisDate, Realm realm) { + RealmResults results = realm.where(TempBasal.class) + .findAllSorted("start_time", Sort.DESCENDING); + + TempBasal last = null; + if (!results.isEmpty()) last = results.first(); + if (last != null && last.isactive(atThisDate)){ + return last; + } else { + return new TempBasal(); //returns an empty TempBasal, other than null or inactive basal + } + } + + public boolean isactive(Date atThisDate){ + if (atThisDate == null) atThisDate = new Date(); + + if (start_time == null){ return false;} + + Date fur = new Date(start_time.getTime() + duration * 60000); + if (fur.after(atThisDate)){ + return true; + } else { + return false; + } + } + + public String ageFormattted(){ + Integer minsOld = age(); + if (minsOld > 1){ + return minsOld + " mins ago"; + } else { + return minsOld + " min ago"; + } + } + + public int age(){ + Date timeNow = new Date(); + return (int)(timeNow.getTime() - timestamp.getTime()) /1000/60; //Age in Mins the Temp Basal was suggested + } + + public Date endDate(){ + Date endedAt = new Date(start_time.getTime() + (duration * 1000 * 60)); //The date this Temp Basal ended + return endedAt; + } + + public Long durationLeft(){ + if (start_time != null) { + Date timeNow = new Date(); + Long min_left = ((start_time.getTime() + duration * 60000) - timeNow.getTime()) / 60000; //Time left to run in Mins + return min_left; + } else { + return duration.longValue(); + } + } + + public static List getTempBasalsDated(Date dateFrom, Date dateTo, Realm realm) { + RealmResults results = realm.where(TempBasal.class) + .greaterThanOrEqualTo("start_time", dateFrom) + .lessThanOrEqualTo("start_time", dateTo) + .findAllSorted("start_time", Sort.DESCENDING); + return results; + } + + public boolean checkIsCancelRequest() { + if (rate.equals(0D) && duration.equals(0)){ + return true; + } else { + return false; + } + } + +} + diff --git a/app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageDetailActivity.txt b/app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageDetailActivity.txt new file mode 100644 index 0000000000..a6e06d0fe7 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageDetailActivity.txt @@ -0,0 +1,83 @@ +package com.gxwtech.roundtrip2.HistoryActivity; + +import android.content.Intent; +import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.Snackbar; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; +import android.view.View; + +import com.gxwtech.roundtrip2.R; + +/** + * An activity representing a single HistoryPage detail screen. This + * activity is only used narrow width devices. On tablet-size devices, + * item details are presented side-by-side with a list of items + * in a {@link HistoryPageListActivity}. + */ +public class HistoryPageDetailActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_historypage_detail); + Toolbar toolbar = (Toolbar) findViewById(R.id.detail_toolbar); + setSupportActionBar(toolbar); + + FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Snackbar.make(view, "Replace with your own detail action", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + } + }); + + // Show the Up button in the action bar. + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + + // savedInstanceState is non-null when there is fragment state + // saved from previous configurations of this activity + // (e.g. when rotating the screen from portrait to landscape). + // In this case, the fragment will automatically be re-added + // to its container so we don't need to manually add it. + // For more information, see the Fragments API guide at: + // + // http://developer.android.com/guide/components/fragments.html + // + if (savedInstanceState == null) { + // Create the detail fragment and add it to the activity + // using a fragment transaction. + Bundle arguments = new Bundle(); + arguments.putString(HistoryPageDetailFragment.ARG_ITEM_ID, + getIntent().getStringExtra(HistoryPageDetailFragment.ARG_ITEM_ID)); + HistoryPageDetailFragment fragment = new HistoryPageDetailFragment(); + fragment.setArguments(arguments); + getSupportFragmentManager().beginTransaction() + .add(R.id.historypage_detail_container, fragment) + .commit(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + // This ID represents the Home or Up button. In the case of this + // activity, the Up button is shown. For + // more details, see the Navigation pattern on Android Design: + // + // http://developer.android.com/design/patterns/navigation.html#up-vs-back + // + navigateUpTo(new Intent(this, HistoryPageListActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageDetailFragment.txt b/app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageDetailFragment.txt new file mode 100644 index 0000000000..20ffed149e --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageDetailFragment.txt @@ -0,0 +1,70 @@ +package com.gxwtech.roundtrip2.HistoryActivity; + +import android.app.Activity; +import android.os.Bundle; +import android.support.design.widget.CollapsingToolbarLayout; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.gxwtech.roundtrip2.R; + + +/** + * A fragment representing a single HistoryPage detail screen. + * This fragment is either contained in a {@link HistoryPageListActivity} + * in two-pane mode (on tablets) or a {@link HistoryPageDetailActivity} + * on handsets. + */ +public class HistoryPageDetailFragment extends Fragment { + /** + * The fragment argument representing the item ID that this fragment + * represents. + */ + public static final String ARG_ITEM_ID = "item_id"; + + /** + * The dummy content this fragment is presenting. + */ + private HistoryPageListContent.RecordHolder mItem; + + /** + * Mandatory empty constructor for the fragment manager to instantiate the + * fragment (e.g. upon screen orientation changes). + */ + public HistoryPageDetailFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments().containsKey(ARG_ITEM_ID)) { + // Load the dummy content specified by the fragment + // arguments. In a real-world scenario, use a Loader + // to load content from a content provider. + mItem = HistoryPageListContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID)); + + Activity activity = this.getActivity(); + CollapsingToolbarLayout appBarLayout = (CollapsingToolbarLayout) activity.findViewById(R.id.toolbar_layout); + if (appBarLayout != null) { + appBarLayout.setTitle("Details"/*mItem.content*/); + } + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.historypage_detail, container, false); + + // Show the dummy content as text in a TextView. + if (mItem != null) { + ((TextView) rootView.findViewById(R.id.historypage_detail)).setText(mItem.details); + } + + return rootView; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageListActivity.txt b/app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageListActivity.txt new file mode 100644 index 0000000000..a0517d3a00 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageListActivity.txt @@ -0,0 +1,230 @@ +package com.gxwtech.roundtrip2.HistoryActivity; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.Snackbar; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.gxwtech.roundtrip2.R; +import com.gxwtech.roundtrip2.RT2Const; +import com.gxwtech.roundtrip2.ServiceData.RetrieveHistoryPageResult; +import com.gxwtech.roundtrip2.ServiceData.ServiceResult; +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * An activity representing a list of HistoryPages. This activity + * has different presentations for handset and tablet-size devices. On + * handsets, the activity presents a list of items, which when touched, + * lead to a {@link HistoryPageDetailActivity} representing + * item details. On tablets, the activity presents the list of items and + * item details side-by-side using two vertical panes. + */ +public class HistoryPageListActivity extends AppCompatActivity { + private static final String TAG = "HistoryPageListActivity"; + + + /** + * Whether or not the activity is in two-pane mode, i.e. running on a tablet + * device. + */ + private boolean mTwoPane; + private BroadcastReceiver mBroadcastRecevier; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_historypage_list); + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + toolbar.setTitle(R.string.title_pump_history); + + // Show the Up button in the action bar. + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + + View recyclerView = findViewById(R.id.historypage_list); + assert recyclerView != null; + setupRecyclerView((RecyclerView) recyclerView); + + if (findViewById(R.id.historypage_detail_container) != null) { + // The detail container view will be present only in the + // large-screen layouts (res/values-w900dp). + // If this view is present, then the + // activity should be in two-pane mode. + mTwoPane = true; + } + + mBroadcastRecevier = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent receivedIntent) { + if (receivedIntent == null) { + Log.e(TAG,"onReceive: received null intent"); + } else { + String action = receivedIntent.getAction(); + if (action == null) { + Log.e(TAG, "onReceive: null action"); + } else { + if (RT2Const.local.INTENT_historyPageBundleIncoming.equals(action)) { + Bundle incomingBundle = receivedIntent.getExtras().getBundle(RT2Const.IPC.MSG_PUMP_history_key); + ServiceTransport transport = new ServiceTransport(incomingBundle); + ServiceResult result = transport.getServiceResult(); + if ("RetrieveHistoryPageResult".equals(result.getServiceResultType())) { + RetrieveHistoryPageResult pageResult = (RetrieveHistoryPageResult) result; + Bundle page = pageResult.getPageBundle(); + ArrayList recordBundleList = page.getParcelableArrayList("mRecordList"); + try { + for (Bundle record : recordBundleList) { + HistoryPageListContent.addItem(record); + } + } catch (java.lang.NullPointerException e) { + e.printStackTrace(); + } + } + } else { + Log.e(TAG,"Unrecognized intent action: "+action); + } + } + } + } + }; + IntentFilter filter = new IntentFilter(); + filter.addAction(RT2Const.local.INTENT_historyPageBundleIncoming); + LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(mBroadcastRecevier,filter); + + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_bluetooth_scan, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.miScan: + getHistory(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + public void getHistory(){ + // tell them we're ready for data + Intent intent = new Intent(RT2Const.local.INTENT_historyPageViewerReady); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent); + } + + private void setupRecyclerView(@NonNull RecyclerView recyclerView) { + recyclerView.setAdapter(new SimpleItemRecyclerViewAdapter(HistoryPageListContent.ITEMS)); + } + + public class SimpleItemRecyclerViewAdapter + extends RecyclerView.Adapter { + + private final List mValues; + + public SimpleItemRecyclerViewAdapter(List items) { + mValues = items; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.historypage_list_content, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(final ViewHolder holder, int position) { + holder.mItem = mValues.get(position); + holder.mIdView.setText(mValues.get(position).dateAndName); + String keytext = ""; + Set keys = holder.mItem.content.keySet(); + int n = 0; + for (String key : keys) { + if (!key.equals("_type") && !key.equals("_stype") && !key.equals("timestamp") && !key.equals("_opcode")) { + try { + keytext += key + ":" + holder.mItem.content.get(key).toString(); + n++; + if (n < keys.size() - 1) { + keytext += "\n"; + } + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + } + holder.mContentView.setText(keytext); + + holder.mView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mTwoPane) { + Bundle arguments = new Bundle(); + arguments.putString(HistoryPageDetailFragment.ARG_ITEM_ID, holder.mItem.id); + HistoryPageDetailFragment fragment = new HistoryPageDetailFragment(); + fragment.setArguments(arguments); + getSupportFragmentManager().beginTransaction() + .replace(R.id.historypage_detail_container, fragment) + .commit(); + } else { + Context context = v.getContext(); + Intent intent = new Intent(context, HistoryPageDetailActivity.class); + intent.putExtra(HistoryPageDetailFragment.ARG_ITEM_ID, holder.mItem.id); + + context.startActivity(intent); + } + } + }); + } + + @Override + public int getItemCount() { + return mValues.size(); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + public final View mView; + public final TextView mIdView; + public final TextView mContentView; + public HistoryPageListContent.RecordHolder mItem; + + public ViewHolder(View view) { + super(view); + mView = view; + mIdView = (TextView) view.findViewById(R.id.id); + mContentView = (TextView) view.findViewById(R.id.content); + } + + @Override + public String toString() { + return super.toString() + " '" + mContentView.getText() + "'"; + } + } + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageListContent.java b/app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageListContent.java new file mode 100644 index 0000000000..a61bc86f21 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/HistoryActivity/HistoryPageListContent.java @@ -0,0 +1,94 @@ +package com.gxwtech.roundtrip2.HistoryActivity; + +import android.os.Bundle; +import android.util.ArraySet; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Created by geoff on 6/12/16. + */ +public class HistoryPageListContent { + public static final List ITEMS = new ArrayList<>(); + + /** + * A map of items, by ID. + */ + public static final Map ITEM_MAP = new HashMap<>(); + + static void addItem(Bundle recordBundle) { + addItem(new RecordHolder(recordBundle)); + } + + private static void addItem(RecordHolder item) { + ITEMS.add(item); + ITEM_MAP.put(item.id, item); + } + + private static String makeDetails(int position) { + RecordHolder rh = ITEMS.get(position); + if (rh == null) { + return "(null)"; + } + + return makeDetails(rh.content); + } + + private static String makeDetails(Bundle historyEntry) { + Set ignoredSet = new HashSet<>(); + ignoredSet.add("_type"); + ignoredSet.add("_stype"); + ignoredSet.add("_opcode"); + ignoredSet.add("timestamp"); + StringBuilder builder = new StringBuilder(); + int n = 0; + for (String key : historyEntry.keySet()) { + if (!ignoredSet.contains(key)) { + builder.append(key); + n++; + if (n= 12) { + veryShortName = veryShortName.substring(0,12); + } + dateAndNameBuilder.append(veryShortName); + this.dateAndName = dateAndNameBuilder.toString(); + this.content = content; + details = makeDetails(content); + } + + @Override + public String toString() { + return content.getString("_stype", "(unk)"); + } + } + +} + diff --git a/app/src/main/java/com/gxwtech/roundtrip2/MainActivity.txt b/app/src/main/java/com/gxwtech/roundtrip2/MainActivity.txt new file mode 100644 index 0000000000..41efc5fddf --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/MainActivity.txt @@ -0,0 +1,614 @@ +package com.gxwtech.roundtrip2; + +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.res.Resources; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.os.SystemClock; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.app.AppCompatActivity; +import android.support.design.widget.Snackbar; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.preference.PreferenceManager; +import android.content.SharedPreferences; + +import com.gxwtech.roundtrip2.HistoryActivity.HistoryPageListActivity; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.ListView; + + +import com.gxwtech.roundtrip2.RoundtripService.RoundtripService; +import com.gxwtech.roundtrip2.ServiceData.BasalProfile; +import com.gxwtech.roundtrip2.ServiceData.BolusWizardCarbProfile; +import com.gxwtech.roundtrip2.ServiceData.ISFProfile; +import com.gxwtech.roundtrip2.ServiceData.PumpModelResult; +import com.gxwtech.roundtrip2.ServiceData.ReadPumpClockResult; +import com.gxwtech.roundtrip2.ServiceData.ServiceClientActions; +import com.gxwtech.roundtrip2.ServiceData.ServiceCommand; +import com.gxwtech.roundtrip2.ServiceData.ServiceNotification; +import com.gxwtech.roundtrip2.ServiceData.ServiceResult; +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; +import com.gxwtech.roundtrip2.ServiceMessageViewActivity.ServiceMessageViewListActivity; +import com.gxwtech.roundtrip2.util.tools; + +import java.util.ArrayList; + +public class MainActivity extends AppCompatActivity { + + private static final String TAG = "MainActivity"; + private static final int REQUEST_ENABLE_BT = 2177; // just something unique. + private RoundtripServiceClientConnection roundtripServiceClientConnection; + private BroadcastReceiver mBroadcastReceiver; + + BroadcastReceiver apsAppConnected; + Bundle storeForHistoryViewer; + + //UI items + private DrawerLayout mDrawerLayout; + private LinearLayout mDrawerLinear; + private Toolbar toolbar; + + public static Context mContext; // TODO: 09/07/2016 @TIM this should not be needed + + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.w(TAG,"onCreate"); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + setupMenuAndToolbar(); + + mContext = this; // TODO: 09/07/2016 @TIM this should not be needed + + //Sets default Preferences + PreferenceManager.setDefaultValues(this, R.xml.pref_pump, false); + PreferenceManager.setDefaultValues(this, R.xml.pref_rileylink, false); + + setBroadcastReceiver(); + + + + + /* start the RoundtripService */ + /* using startService() will keep the service running until it is explicitly stopped + * with stopService() or by RoundtripService calling stopSelf(). + * Note that calling startService repeatedly has no ill effects on RoundtripService + */ + // explicitly call startService to keep it running even when the GUI goes away. + Intent bindIntent = new Intent(this,RoundtripService.class); + startService(bindIntent); + + linearProgressBar = (ProgressBar)findViewById(R.id.progressBarCommandActivity); + spinnyProgressBar = (ProgressBar)findViewById(R.id.progressBarSpinny); + } + + + @Override + protected void onResume(){ + super.onResume(); + + setBroadcastReceiver(); + } + + @Override + public void onPause() { + super.onPause(); + if (apsAppConnected != null){ + LocalBroadcastManager.getInstance(MainApp.instance()).unregisterReceiver(apsAppConnected); + } + if (mBroadcastReceiver != null){ + LocalBroadcastManager.getInstance(MainApp.instance()).unregisterReceiver(mBroadcastReceiver); + } + } + + public void setBroadcastReceiver() { + //Register this receiver for UI Updates + mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent receivedIntent) { + + if (receivedIntent == null) { + Log.e(TAG, "onReceive: received null intent"); + } else { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MainApp.instance()); + ServiceTransport transport; + + switch (receivedIntent.getAction()) { + case RT2Const.local.INTENT_serviceConnected: + case RT2Const.local.INTENT_NEW_rileylinkAddressKey: + showIdle(); + /** + * Client MUST send a "UseThisRileylink" message because it asserts that + * the user has given explicit permission to use bluetooth. + * + * We can change the format so that it is a simple "bluetooth OK" message, + * rather than an explicit address of a Rileylink, and the Service can + * use the last known good value. But the kick-off of bluetooth ops must + * come from an Activity. + */ + String RileylinkBLEAddress = prefs.getString(RT2Const.serviceLocal.rileylinkAddressKey, ""); + if (RileylinkBLEAddress.equals("")){ + // TODO: 11/07/2016 @TIM UI message for user + Log.e(TAG, "No Rileylink BLE Address saved in app"); + } else { + showBusy("Configuring Service", 50); + MainApp.getServiceClientConnection().setThisRileylink(RileylinkBLEAddress); + } + break; + case RT2Const.local.INTENT_NEW_pumpIDKey: + MainApp.getServiceClientConnection().sendPUMP_useThisDevice(prefs.getString(RT2Const.serviceLocal.pumpIDKey, "")); + break; + case RT2Const.local.INTENT_historyPageViewerReady: + Intent sendHistoryIntent = new Intent(RT2Const.local.INTENT_historyPageBundleIncoming); + sendHistoryIntent.putExtra(RT2Const.IPC.MSG_PUMP_history_key, storeForHistoryViewer); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(sendHistoryIntent); + break; + case RT2Const.IPC.MSG_ServiceResult: + Log.i(TAG, "Received ServiceResult"); + + Bundle bundle = receivedIntent.getBundleExtra(RT2Const.IPC.bundleKey); + transport = new ServiceTransport(bundle); + if (transport.commandDidCompleteOK()) { + String originalCommandName = transport.getOriginalCommandName(); + switch (originalCommandName) { + case "ReadPumpModel": + PumpModelResult modelResult = new PumpModelResult(); + modelResult.initFromServiceResult(transport.getServiceResult()); + String pumpModelString = modelResult.getPumpModel(); + // GGW Tue Jul 12 02:29:54 UTC 2016: ok, now what do we do with the pump model? + showIdle(); + break; + case "ReadPumpClock": + ReadPumpClockResult clockResult = new ReadPumpClockResult(); + clockResult.initFromServiceResult(transport.getServiceResult()); + TextView pumpTimeTextView = (TextView) findViewById(R.id.textViewPumpClockTime); + pumpTimeTextView.setText(clockResult.getTimeString()); + showIdle(); + break; + case "FetchPumpHistory": + storeForHistoryViewer = receivedIntent.getExtras().getBundle(RT2Const.IPC.bundleKey); + startActivity(new Intent(context, HistoryPageListActivity.class)); + // wait for history viewer to announce "ready" + showIdle(); + break; + case "RetrieveHistoryPage": + storeForHistoryViewer = receivedIntent.getExtras().getBundle(RT2Const.IPC.bundleKey); + startActivity(new Intent(context, HistoryPageListActivity.class)); + // wait for history viewer to announce "ready" + showIdle(); + break; + case "ISFProfile": + ISFProfile isfProfile = new ISFProfile(); + isfProfile.initFromServiceResult(transport.getServiceResult()); + // TODO: do something with isfProfile + showIdle(); + break; + case "BasalProfile": + BasalProfile basalProfile = new BasalProfile(); + basalProfile.initFromServiceResult(transport.getServiceResult()); + // TODO: do something with basal profile + showIdle(); + break; + case "BolusWizardCarbProfile": + BolusWizardCarbProfile carbProfile = new BolusWizardCarbProfile(); + carbProfile.initFromServiceResult(transport.getServiceResult()); + // TODO: do something with carb profile + showIdle(); + break; + case "UpdatePumpStatus": + // rebroadcast for HAPP + + break; + default: + Log.e(TAG, "Dunno what to do with this command completion: " + transport.getOriginalCommandName()); + } + } else { + Log.e(TAG,"Command failed? " + transport.getOriginalCommandName()); + } + break; + case RT2Const.IPC.MSG_ServiceNotification: + transport = new ServiceTransport(receivedIntent.getBundleExtra(RT2Const.IPC.bundleKey)); + ServiceNotification notification = transport.getServiceNotification(); + String note = notification.getNotificationType(); + switch (note) { + case RT2Const.IPC.MSG_BLE_RileyLinkReady: + setRileylinkStatusMessage("OK"); + break; + case RT2Const.IPC.MSG_PUMP_pumpFound: + setPumpStatusMessage("OK"); + break; + case RT2Const.IPC.MSG_PUMP_pumpLost: + setPumpStatusMessage("Lost"); + break; + case RT2Const.IPC.MSG_note_WakingPump: + showBusy("Waking Pump", 99); + break; + case RT2Const.IPC.MSG_note_FindingRileyLink: + showBusy("Finding RileyLink", 99); + break; + case RT2Const.IPC.MSG_note_Idle: + showIdle(); + break; + default: + Log.e(TAG, "Unrecognized Notification: '" + note + "'"); + } + break; + default: + Log.e(TAG, "Unrecognized intent action: " + receivedIntent.getAction()); + } + } + } + }; + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(RT2Const.local.INTENT_serviceConnected); + intentFilter.addAction(RT2Const.IPC.MSG_ServiceResult); + intentFilter.addAction(RT2Const.IPC.MSG_ServiceNotification); + intentFilter.addAction(RT2Const.local.INTENT_historyPageViewerReady); + + + linearProgressBar = (ProgressBar)findViewById(R.id.progressBarCommandActivity); + spinnyProgressBar = (ProgressBar)findViewById(R.id.progressBarSpinny); + LocalBroadcastManager.getInstance(MainApp.instance()).registerReceiver(mBroadcastReceiver, intentFilter); + } + + + + + + + /** + * + * GUI element functions + * + */ + + + private int mProgress = 0; + private int mSpinnyProgress = 0; + private ProgressBar linearProgressBar; + private ProgressBar spinnyProgressBar; + private static final int spinnyFPS = 10; + private Thread spinnyThread; + void showBusy(String activityString, int progress) { + mProgress = progress; + TextView tv = (TextView)findViewById(R.id.textViewActivity); + tv.setText(activityString); + linearProgressBar.setProgress(progress); + if (progress > 0) { + spinnyProgressBar.setVisibility(View.VISIBLE); + if (spinnyThread == null) { + spinnyThread = new Thread(new Runnable() { + @Override + public void run() { + while ((mProgress > 0) && (mProgress < 100)) { + mSpinnyProgress += 100 / spinnyFPS; + spinnyProgressBar.setProgress(mSpinnyProgress); + SystemClock.sleep(1000 / spinnyFPS); + } + spinnyThread = null; + } + }); + spinnyThread.start(); + } + } else { + spinnyProgressBar.setVisibility(View.INVISIBLE); + } + } + + void showIdle() { + showBusy("Idle",0); + } + + void setRileylinkStatusMessage(String statusMessage) { + TextView field = (TextView)findViewById(R.id.textViewFieldRileyLink); + field.setText(statusMessage); + } + + void setPumpStatusMessage(String statusMessage) { + TextView field = (TextView)findViewById(R.id.textViewFieldPump); + field.setText(statusMessage); + } + + public void onTunePumpButtonClicked(View view) { + MainApp.getServiceClientConnection().doTunePump(); + } + + public void onFetchHistoryButtonClicked(View view) { + /* does not work. Crashes sig 11 */ + showBusy("Fetch history page 0",50); + MainApp.getServiceClientConnection().doFetchPumpHistory(); + } + + public void onFetchSavedHistoryButtonClicked(View view) { + showBusy("Fetching history (not saved)",50); + MainApp.getServiceClientConnection().doFetchSavedHistory(); + } + + public void onReadPumpClockButtonClicked(View view) { + showBusy("Reading Pump Clock",50); + MainApp.getServiceClientConnection().readPumpClock(); + } + + public void onGetISFProfileButtonClicked(View view) { + //ServiceCommand getISFProfileCommand = ServiceClientActions.makeReadISFProfileCommand(); + //roundtripServiceClientConnection.sendServiceCommand(getISFProfileCommand); + MainApp.getServiceClientConnection().readISFProfile(); + } + + public void onViewEventLogButtonClicked(View view) { + startActivity(new Intent(getApplicationContext(),ServiceMessageViewListActivity.class)); + } + + public void onUpdateAllStatusButtonClicked(View view) { + MainApp.getServiceClientConnection().updateAllStatus(); + } + + public void onGetCarbProfileButtonClicked(View view) { + showBusy("Getting Carb Profile",1); + roundtripServiceClientConnection.sendServiceCommand(ServiceClientActions.makeReadBolusWizardCarbProfileCommand()); + } + + /* UI Setup */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_MENU: + if (mDrawerLayout.isDrawerOpen(GravityCompat.START)){ + mDrawerLayout.closeDrawers(); + } else { + mDrawerLayout.openDrawer(GravityCompat.START); + } + return true; + } + return super.onKeyUp(keyCode, event); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle item selection + switch (item.getItemId()) { + case android.R.id.home: + mDrawerLayout.openDrawer(mDrawerLinear); + return true; + + default: + return true; + } + } + + public void setupMenuAndToolbar() { + //Setup menu + mDrawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout); + mDrawerLinear = (LinearLayout) findViewById(R.id.left_drawer); + toolbar = (Toolbar) findViewById(R.id.mainActivityToolbar); + Drawable logsIcon = getDrawable(R.drawable.file_chart); + Drawable historyIcon = getDrawable(R.drawable.history); + Drawable settingsIcon = getDrawable(R.drawable.settings); + Drawable catIcon = getDrawable(R.drawable.cat); + Drawable apsIcon = getDrawable(R.drawable.refresh); + + logsIcon.setColorFilter(getResources().getColor(R.color.primary_dark), PorterDuff.Mode.SRC_ATOP); + historyIcon.setColorFilter(getResources().getColor(R.color.primary_dark), PorterDuff.Mode.SRC_ATOP); + settingsIcon.setColorFilter(getResources().getColor(R.color.primary_dark), PorterDuff.Mode.SRC_ATOP); + catIcon.setColorFilter(getResources().getColor(R.color.primary_dark), PorterDuff.Mode.SRC_ATOP); + apsIcon.setColorFilter(getResources().getColor(R.color.primary_dark), PorterDuff.Mode.SRC_ATOP); + + ListView mDrawerList = (ListView)findViewById(R.id.navList); + ArrayList menuItems = new ArrayList<>(); + menuItems.add(new NavItem("APS Integration", apsIcon)); + menuItems.add(new NavItem("Pump History", historyIcon)); + menuItems.add(new NavItem("Treatment Logs", logsIcon)); + menuItems.add(new NavItem("Settings", settingsIcon)); + menuItems.add(new NavItem("View LogCat", catIcon)); + DrawerListAdapter adapterMenu = new DrawerListAdapter(this, menuItems); + mDrawerList.setAdapter(adapterMenu); + mDrawerList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + switch (position) { + case 0: + //Check APS App Connectivity + sendAPSAppMessage(view); + break; + case 1: + //Pump History + startActivity(new Intent(getApplicationContext(), HistoryPageListActivity.class)); + break; + case 2: + //Treatment Logs + startActivity(new Intent(getApplicationContext(), TreatmentHistory.class)); + break; + case 3: + //Settings + startActivity(new Intent(getApplicationContext(), SettingsActivity.class)); + break; + case 4: + //View LogCat + tools.showLogging(); + break; + } + mDrawerLayout.closeDrawers(); + } + }); + + ActionBarDrawerToggle mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,R.string.drawer_open, R.string.drawer_close) { + /** Called when a drawer has settled in a completely open state. */ + public void onDrawerOpened(View drawerView) { + super.onDrawerOpened(drawerView); + invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() + + //Insulin Integration App, try and connect + //checkInsulinAppIntegration(false); + } + /** Called when a drawer has settled in a completely closed state. */ + public void onDrawerClosed(View view) { + super.onDrawerClosed(view); + invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() + } + }; + + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeButtonEnabled(true); + mDrawerToggle.syncState(); + mDrawerToggle.setDrawerIndicatorEnabled(true); + mDrawerLayout.addDrawerListener(mDrawerToggle); + } + + + /* Functions for APS App Service */ + + //Our Service that APS App will connect to + private Messenger myService = null; + private ServiceConnection myConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + myService = new Messenger(service); + + //Broadcast there has been a connection + Intent intent = new Intent("APS_CONNECTED"); + LocalBroadcastManager.getInstance(MainApp.instance()).sendBroadcast(intent); + } + + public void onServiceDisconnected(ComponentName className) { + myService = null; + //FYI, only called if Service crashed or was killed, not on unbind + } + }; + + public void sendAPSAppMessage(final View view) + { + //listen out for a successful connection + apsAppConnected = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + + Resources appR = view.getContext().getResources(); + CharSequence txt = appR.getText(appR.getIdentifier("app_name", "string", view.getContext().getPackageName())); + + Message msg = Message.obtain(); + Bundle bundle = new Bundle(); + bundle.putString(RT2Const.commService.ACTION,RT2Const.commService.OUTGOING_TEST_MSG); + bundle.putString(RT2Const.commService.REMOTE_APP_NAME, txt.toString()); + msg.setData(bundle); + + try { + myService.send(msg); + } catch (RemoteException e) { + e.printStackTrace(); + //cannot Bind to service + Snackbar snackbar = Snackbar + .make(view, "error sending msg: " + e.getMessage(), Snackbar.LENGTH_INDEFINITE); + snackbar.show(); + } + + if (apsAppConnected != null) LocalBroadcastManager.getInstance(MainApp.instance()).unregisterReceiver(apsAppConnected); //Stop listening for new connections + MainApp.instance().unbindService(myConnection); + } + }; + LocalBroadcastManager.getInstance(MainApp.instance()).registerReceiver(apsAppConnected, new IntentFilter("APS_CONNECTED")); + + connect_to_aps_app(MainApp.instance()); + } + + //Connect to the APS App Treatments Service + private void connect_to_aps_app(Context c){ + // TODO: 16/06/2016 add user selected aps app + Intent intent = new Intent("com.hypodiabetic.happ.services.TreatmentService"); + intent.setPackage("com.hypodiabetic.happ"); + c.bindService(intent, myConnection, Context.BIND_AUTO_CREATE); + } + + + +} + +class NavItem { + String mTitle; + Drawable mIcon; + + public NavItem(String title, Drawable icon) { + mTitle = title; + mIcon = icon; + } +} + +class DrawerListAdapter extends BaseAdapter { + + Context mContext; + ArrayList mNavItems; + + public DrawerListAdapter(Context context, ArrayList navItems) { + mContext = context; + mNavItems = navItems; + } + + @Override + public int getCount() { + return mNavItems.size(); + } + + @Override + public Object getItem(int position) { + return mNavItems.get(position); + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view; + + if (convertView == null) { + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = inflater.inflate(R.layout.menu_item, null); + } + else { + view = convertView; + } + + TextView titleView = (TextView) view.findViewById(R.id.menuText); + ImageView iconView = (ImageView) view.findViewById(R.id.menuIcon); + + titleView.setText( mNavItems.get(position).mTitle); + iconView.setBackground(mNavItems.get(position).mIcon); + return view; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/MainApp.txt b/app/src/main/java/com/gxwtech/roundtrip2/MainApp.txt new file mode 100644 index 0000000000..ad553c2056 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/MainApp.txt @@ -0,0 +1,48 @@ +package com.gxwtech.roundtrip2; + +import android.app.Application; + +import io.realm.Realm; +import io.realm.RealmConfiguration; + +/** + * Created by Tim on 15/06/2016. + */ +public class MainApp extends Application { + + private static MainApp sInstance; + private static ServiceClientConnection serviceClientConnection; + + @Override + public void onCreate() { + super.onCreate(); + + sInstance = this; + serviceClientConnection = new ServiceClientConnection(); + + //initialize Realm + RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(instance()) + .name("rt2.realm") + .schemaVersion(0) + .deleteRealmIfMigrationNeeded() // TODO: 03/08/2016 @TIM remove + .build(); + Realm.setDefaultConfiguration(realmConfiguration); + } + + + + + public static MainApp instance() { + return sInstance; + } + + public static ServiceClientConnection getServiceClientConnection(){ + if (serviceClientConnection == null) { + serviceClientConnection = new ServiceClientConnection(); + } + return serviceClientConnection; + } + + // TODO: 09/07/2016 @TIM uncomment ServiceClientConnection once class is added + +} \ No newline at end of file diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RT2Const.java b/app/src/main/java/com/gxwtech/roundtrip2/RT2Const.java new file mode 100644 index 0000000000..1b5e68ff76 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RT2Const.java @@ -0,0 +1,135 @@ +package com.gxwtech.roundtrip2; + +/** + * Created by geoff on 6/5/16. + */ +public class RT2Const { + public static final String RT2Prefix = "com.gxwtech.roundtrip2."; + public class IPC { + public static final String Prefix = RT2Prefix + "IPC."; + + // This message is used to bind the "replyTo" field + public static final int MSG_clientRegistered = 63; // arbitrary + public static final int MSG_unregisterClient = 64; + public static final int MSG_registerClient = 65; + + // used in IPC to mark a message as a generic IPC message + public static final int MSG_IPC = 66; + // used as the key to find the message in the bundle. + public static final String messageKey = Prefix + "messageKey"; + // used as a key for the bundle, when the bundle is packed into an Intent + public static final String bundleKey = Prefix + "bundleKey"; + // a field to hold the instant (milliseconds since 1970) the message + // was sent or received by the CLIENT + public static final String instantKey = Prefix + "instantKey"; + // key for the "command" string in a bundle + public static final String commandKey = Prefix + "commandKey"; + // key for the command-response bundle in a result bundle + public static final String serviceResultKey = Prefix + "commandResponse"; + + // used by gui to pass the address of the Rileylink to the Service + // has an 'address' field with the string address. + public static final String MSG_BLE_useThisDevice = Prefix + "MSG_BLE_useThisDevice"; + public static final String MSG_BLE_useThisDevice_addressKey = Prefix + "MSG_BLE_useThisDevice_addressKey"; + + // used by gui to tell service that BLE access is denied by user. + public static final String MSG_BLE_accessDenied = Prefix + "MSG_BLE_accessDenied"; + public static final String MSG_BLE_accessGranted = Prefix + "MSG_BLE_accessGranted"; + // used by service to ask user for Bluetooth permission + public static final String MSG_BLE_requestAccess = Prefix + "MSG_BLE_requestAccess"; + public static final String MSG_BLE_RileyLinkReady = Prefix + "MSG_BLE_RileyLinkReady"; + + // used to pass the pump ID from GUI to service + // has a 'pumpID' field containing a six digit String + public static final String MSG_PUMP_useThisAddress = Prefix + "MSG_PUMP_useThisAddress"; + public static final String MSG_PUMP_useThisAddress_pumpIDKey = Prefix + "MSG_PUMP_useThisAddress_pumpIDKey"; + + // These are used to pass information about the pump from the service to the GUI. + // has a 'model' field + //public static final String MSG_PUMP_reportedPumpModel = Prefix + "MSG_PUMP_reportedPumpModel"; + public static final String MSG_PUMP_pumpFound = Prefix + "MSG_PUMP_pumpFound"; + public static final String MSG_PUMP_pumpLost = Prefix + "MSG_PUMP_pumpLost"; + + public static final String MSG_PUMP_tunePump = Prefix + "MSG_PUMP_tunePump"; + public static final String MSG_PUMP_quickTune = Prefix + "MSG_PUMP_quickTune"; + public static final String MSG_PUMP_fetchHistory = Prefix + "MSG_PUMP_fetchHistory"; + + public static final String MSG_PUMP_history = Prefix + "MSG_PUMP_history"; + public static final String MSG_PUMP_history_key = Prefix + "MSG_PUMP_history_key"; + public static final String MSG_PUMP_fetchSavedHistory = Prefix + "MSG_PUMP_fetchSavedHistory"; + + // interface for ServiceCommand/ServiceResult + public static final String MSG_ServiceCommand = Prefix + "MSG_ServiceCommand"; + public static final String MSG_ServiceResult = Prefix + "MSG_ServiceResult"; + public static final String MSG_ServiceNotification = Prefix + "MSG_ServiceNotification"; + + // These are notifications to GUIs to let them know what's happening + public static final String MSG_note_Idle = Prefix + "MSG_note_Idle"; + public static final String MSG_note_FindingRileyLink = Prefix + "MSG_note_FindingRileyLink"; + public static final String MSG_note_WakingPump = Prefix + "MSG_note_WakingPump"; + public static final String MSG_note_TaskProgress = Prefix + "MSG_note_TaskProgress"; + + } + public class local { + // These are local to the GUI activities + public static final String Prefix = RT2Prefix + "local."; + public static final String INTENT_serviceConnected = Prefix + "INTENT_serviceConnected"; + public static final String INTENT_NEW_rileylinkAddressKey = Prefix + "INTENT_NEW_rileylinkAddressKey"; + public static final String INTENT_NEW_pumpIDKey = Prefix + "INTENT_NEW_pumpIDKey"; + + + public static final String INTENT_historyPageViewerReady = Prefix + "I'm ready, hit me up"; + public static final String INTENT_historyPageBundleIncoming = Prefix + "Here's the kitchen sink"; + } + + public class serviceLocal { + public static final String Prefix = RT2Prefix + "serviceLocal."; + // for local broadcasts annoucing connectivity events + public static final String INTENT_seekRileylink = Prefix + "INTENT_seekRileylink"; + public static final String bluetooth_connected = Prefix + "bluetooth_connected"; + public static final String bluetooth_disconnected = Prefix + "bluetooth_disconnected"; + public static final String BLE_services_discovered = Prefix + "BLE_services_discovered"; + + public static final String ipcBound = Prefix + "ipcBound"; + + // primary shared preferences file identifier + public static final String sharedPreferencesKey = Prefix + "sharedPreferencesKey"; + + // These are used to identify shared preference items + public static final String pumpIDKey = Prefix + "PumpIDKey"; + public static final String rileylinkAddressKey = Prefix + "rileylinkAddressKey"; + public static final String prefsLastGoodPumpFrequency = Prefix + "prefsLastGoodPumpFrequency"; + + // The the key to identify the hashCode() of the msg.replyTo, when the bundle is moved to an intent. + public static final String IPCReplyTo_hashCodeKey = Prefix + "IPCReplyTo_hashCodeKey"; + + // This is sent from the PumpManager to RoundtripService at the completion of a pump command session. + public static final String INTENT_sessionCompleted = Prefix + "INTENT_sessionCompleted"; + + } + + public class commService { + //Data + public static final String ACTION = "ACTION"; + public static final String DATE_REQUESTED = "DATE_REQUESTED"; + public static final String INTEGRATION_OBJECTS = "INTEGRATION_OBJECTS"; + public static final String TREATMENT_OBJECTS = "TREATMENT_OBJECTS"; + public static final String PUMP = "PUMP"; + public static final String REMOTE_APP_NAME = "REMOTE_APP_NAME"; + + //Incoming actions + public static final String INCOMING_NEW_TREATMENTS = "NEW_TREATMENTS"; + public static final String INCOMING_TEST_MSG = "TEST_MSG"; + + //Outgoing actions + public static final String OUTGOING_TREATMENT_UPDATES = "TREATMENT_UPDATES"; + public static final String OUTGOING_TEST_MSG = "TEST_MSG"; + + } + + public class safety { + public static final int INCOMING_REQUEST_MAX_AGE = 10; //mins + public static final int TREATMENT_MAX_AGE = 8; //mins + + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RileyLinkScan.txt b/app/src/main/java/com/gxwtech/roundtrip2/RileyLinkScan.txt new file mode 100644 index 0000000000..49b37f5489 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RileyLinkScan.txt @@ -0,0 +1,343 @@ +package com.gxwtech.roundtrip2; + +import android.Manifest; +import android.app.AlertDialog; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanFilter; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.support.design.widget.Snackbar; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Toast; +import android.util.Log; + +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; + +import com.gxwtech.roundtrip2.util.LocationHelper; + +import java.util.ArrayList; +import java.util.List; + +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.view.LayoutInflater; + +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; + + +public class RileyLinkScan extends AppCompatActivity { + private final static String TAG = "RileyLinkScan"; + private BluetoothAdapter mBluetoothAdapter; + private BluetoothLeScanner mLEScanner; + private LeDeviceListAdapter mLeDeviceListAdapter; + public boolean mScanning; + private Handler mHandler; + public Snackbar snackbar; + public ScanSettings settings; + public List filters; + public ListView listBTScan; + public Toolbar toolbarBTScan; + public Context mContext = this; + + private static final int PERMISSION_REQUEST_COARSE_LOCATION = 30241; // arbitrary. + private static final int REQUEST_ENABLE_BT = 30242; // arbitrary + // Stops scanning after 10 seconds. + private static final long SCAN_PERIOD = 10000; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_riley_link_scan); + + // Initializes Bluetooth adapter. + final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); + mBluetoothAdapter = bluetoothManager.getAdapter(); + mHandler = new Handler(); + + mLeDeviceListAdapter = new LeDeviceListAdapter(); + listBTScan = (ListView) findViewById(R.id.listBTScan); + listBTScan.setAdapter(mLeDeviceListAdapter); + listBTScan.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + TextView textview = (TextView) view.findViewById(R.id.device_address); + String bleAddress = textview.getText().toString(); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + prefs.edit().putString(RT2Const.serviceLocal.rileylinkAddressKey, bleAddress).apply(); + + //Notify that we have a new rileylinkAddressKey + LocalBroadcastManager.getInstance(MainApp.instance()).sendBroadcast(new Intent(RT2Const.local.INTENT_NEW_rileylinkAddressKey)); + + Log.d(TAG, "New rileylinkAddressKey: " + bleAddress); + + //Notify that we have a new pumpIDKey + LocalBroadcastManager.getInstance(MainApp.instance()).sendBroadcast(new Intent(RT2Const.local.INTENT_NEW_pumpIDKey)); + finish(); + } + }); + + toolbarBTScan = (Toolbar) findViewById(R.id.toolbarBTScan); + toolbarBTScan.setTitle(R.string.title_activity_riley_link_scan); + setSupportActionBar(toolbarBTScan); + + snackbar = Snackbar.make(findViewById(R.id.RileyLinkScan), "Scanning...",Snackbar.LENGTH_INDEFINITE); + snackbar.setAction("STOP", new View.OnClickListener() { + @Override + public void onClick(View view) { + scanLeDevice(false); + } + }); + + startScanBLE(); + } + + @Override + protected void onPause() { + super.onPause(); + scanLeDevice(false); + mLeDeviceListAdapter.clear(); + mLeDeviceListAdapter.notifyDataSetChanged(); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_bluetooth_scan, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.miScan: + startScanBLE(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + public void startScanBLE(){ + // https://developer.android.com/training/permissions/requesting.html + // http://developer.radiusnetworks.com/2015/09/29/is-your-beacon-app-ready-for-android-6.html + if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { + Toast.makeText(this, "R.string.ble_not_supported", Toast.LENGTH_SHORT).show(); + } else { + // Use this check to determine whether BLE is supported on the device. Then + // you can selectively disable BLE-related features. + if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) + != PackageManager.PERMISSION_GRANTED) { + //your code that requires permission + ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ACCESS_COARSE_LOCATION }, + PERMISSION_REQUEST_COARSE_LOCATION); + } + + // Ensures Bluetooth is available on the device and it is enabled. If not, + // displays a dialog requesting user permission to enable Bluetooth. + if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { + Toast.makeText(this, "R.string.ble_not_enabled", Toast.LENGTH_SHORT).show(); + } else { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + // Will request that GPS be enabled for devices running Marshmallow or newer. + LocationHelper.requestLocationForBluetooth(this); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); + } + + mLEScanner = mBluetoothAdapter.getBluetoothLeScanner(); + settings = new ScanSettings.Builder() + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .build(); + filters = new ArrayList(); + + scanLeDevice(true); + } + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_ENABLE_BT) { + if (resultCode == RESULT_OK) { + // User allowed Bluetooth to turn on + } else if (resultCode == RESULT_CANCELED) { + // Error, or user said "NO" + finish(); + } + } + } + + private void scanLeDevice(final boolean enable) { + if (enable) { + // Stops scanning after a pre-defined scan period. + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + mScanning = false; + mLEScanner.stopScan(mScanCallback); + Log.d(TAG, "scanLeDevice: Scanning Stop"); + //Toast.makeText(mContext, "Scanning finished", Toast.LENGTH_SHORT).show(); + snackbar.dismiss(); + } + }, SCAN_PERIOD); + + mScanning = true; + mLEScanner.startScan(mScanCallback); + Log.d(TAG, "scanLeDevice: Scanning Start"); + //Toast.makeText(this, "Scanning", Toast.LENGTH_SHORT).show(); + snackbar.show(); + } else { + mScanning = false; + mLEScanner.stopScan(mScanCallback); + Log.d(TAG, "scanLeDevice: Scanning Stop"); + //Toast.makeText(this, "Scanning finished", Toast.LENGTH_SHORT).show(); + snackbar.dismiss(); + + } + } + private ScanCallback mScanCallback = new ScanCallback() { + @Override + public void onScanResult(int callbackType, ScanResult result) { + final BluetoothDevice device = result.getDevice(); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (device.getName() != null && device.getName().length() > 0) { + mLeDeviceListAdapter.addDevice(device); + mLeDeviceListAdapter.notifyDataSetChanged(); + Log.d(TAG, "Found BLE" + device.getName()); + } + } + }); + } + + @Override + public void onBatchScanResults(final List results) { + runOnUiThread(new Runnable() { + @Override + public void run() { + for (ScanResult result: results) { + BluetoothDevice device = result.getDevice(); + if (device.getName() != null && device.getName().length() > 0) { + mLeDeviceListAdapter.addDevice(device); + Log.d(TAG, "Found BLE" + result.toString()); + } else { + Log.e(TAG, "Found BLE, but name appears to be missing. Ignoring. " + device.getAddress()); + } + } + mLeDeviceListAdapter.notifyDataSetChanged(); + } + }); + } + + @Override + public void onScanFailed(int errorCode) { + Log.e("Scan Failed", "Error Code: " + errorCode); + Toast.makeText(mContext, "Scan Failed " + errorCode, Toast.LENGTH_LONG).show(); + } + }; + + + + private class LeDeviceListAdapter extends BaseAdapter { + private ArrayList mLeDevices; + private LayoutInflater mInflator; + + public LeDeviceListAdapter() { + super(); + mLeDevices = new ArrayList<>(); + mInflator = RileyLinkScan.this.getLayoutInflater(); + } + + public void addDevice(BluetoothDevice device) { + if(!mLeDevices.contains(device)) { + mLeDevices.add(device); + notifyDataSetChanged(); + } + } + + public BluetoothDevice getDevice(int position) { + return mLeDevices.get(position); + } + + public void clear() { + mLeDevices.clear(); + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return mLeDevices.size(); + } + + @Override + public Object getItem(int i) { + return mLeDevices.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + ViewHolder viewHolder; + // General ListView optimization code. + if (view == null) { + view = mInflator.inflate(R.layout.listitem_device, null); + viewHolder = new ViewHolder(); + viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address); + viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name); + view.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) view.getTag(); + } + + BluetoothDevice device = mLeDevices.get(i); + String deviceName = device.getName(); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + if(prefs.getString(RT2Const.serviceLocal.rileylinkAddressKey, "").compareTo(device.getAddress()) == 0) { + viewHolder.deviceName.setTextColor(getColor(R.color.secondary_text_light)); + viewHolder.deviceAddress.setTextColor(getColor(R.color.secondary_text_light)); + deviceName += " (" + getResources().getString(R.string.selected_device) + ")"; + } + viewHolder.deviceName.setText(deviceName); + viewHolder.deviceAddress.setText(device.getAddress()); + + return view; + } + } + + + static class ViewHolder { + TextView deviceName; + TextView deviceAddress; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/FrequencyScanResults.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/FrequencyScanResults.java new file mode 100644 index 0000000000..4dcb91bab8 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/FrequencyScanResults.java @@ -0,0 +1,21 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLink; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * Created by geoff on 5/30/16. + */ +public class FrequencyScanResults { + public ArrayList trials = new ArrayList<>(); + public double bestFrequencyMHz = 0.0; + public void sort() { + Collections.sort(trials, new Comparator() { + @Override + public int compare(FrequencyTrial trial1, FrequencyTrial trial2) { + return trial1.averageRSSI.compareTo(trial2.averageRSSI); + } + }); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/FrequencyTrial.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/FrequencyTrial.java new file mode 100644 index 0000000000..aa2a2eb418 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/FrequencyTrial.java @@ -0,0 +1,11 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLink; + +/** + * Created by geoff on 5/30/16. + */ +public class FrequencyTrial { + public int tries = 0; + public int successes = 0; + public Double averageRSSI = 0.0; + public double frequencyMHz = 0.0; +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/PumpManager.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/PumpManager.java new file mode 100644 index 0000000000..6ca87d7c33 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/PumpManager.java @@ -0,0 +1,598 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLink; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.SystemClock; +import android.util.Log; + +import com.gxwtech.roundtrip2.RT2Const; +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RFSpy; +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RFSpyResponse; +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RadioPacket; +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RadioResponse; +import com.gxwtech.roundtrip2.RoundtripService.RoundtripService; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages.ButtonPressCarelinkMessageBody; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages.CarelinkShortMessageBody; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages.GetHistoryPageCarelinkMessageBody; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages.GetPumpModelCarelinkMessageBody; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages.MessageBody; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages.MessageType; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages.PumpAckMessageBody; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PacketType; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.ISFTable; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.Page; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records.Record; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpMessage; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; +import com.gxwtech.roundtrip2.ServiceData.PumpStatusResult; +import com.gxwtech.roundtrip2.ServiceData.ReadPumpClockResult; +import com.gxwtech.roundtrip2.ServiceData.ServiceResult; +import com.gxwtech.roundtrip2.util.ByteUtil; +import com.gxwtech.roundtrip2.util.StringUtil; + +import org.joda.time.Duration; +import org.joda.time.IllegalFieldValueException; +import org.joda.time.Instant; +import org.joda.time.LocalDateTime; + +import java.util.ArrayList; + + +/** + * Created by geoff on 5/30/16. + */ +public class PumpManager { + private static final String TAG = "PumpManager"; + public double[] scanFrequencies = {916.45, 916.50, 916.55, 916.60, 916.65, 916.70, 916.75, 916.80}; + public static final int startSession_signal = 6656; // arbitrary. + //private long pumpAwakeUntil = 0; + private int pumpAwakeForMinutes = 6; + private final RFSpy rfspy; + private byte[] pumpID; + public boolean DEBUG_PUMPMANAGER = true; + private final Context context; + private SharedPreferences prefs; + private Instant lastGoodPumpCommunicationTime = new Instant(0); + public PumpManager(Context context, RFSpy rfspy, byte[] pumpID) { + this.context = context; + this.rfspy = rfspy; + this.pumpID = pumpID; + prefs = context.getSharedPreferences(RT2Const.serviceLocal.sharedPreferencesKey, Context.MODE_PRIVATE); + } + + private PumpMessage runCommandWithArgs(PumpMessage msg) { + PumpMessage rval; + PumpMessage shortMessage = makePumpMessage(msg.messageType,new CarelinkShortMessageBody(new byte[]{0})); + // look for ack from short message + PumpMessage shortResponse = sendAndListen(shortMessage); + if (shortResponse.messageType.mtype == MessageType.PumpAck) { + rval = sendAndListen(msg); + return rval; + } else { + Log.e(TAG,"runCommandWithArgs: Pump did not ack Attention packet"); + } + return new PumpMessage(); + } + + protected PumpMessage sendAndListen(PumpMessage msg) { + return sendAndListen(msg,2000); + } + + // All pump communications go through this function. + protected PumpMessage sendAndListen(PumpMessage msg, int timeout_ms) { + boolean showPumpMessages = true; + if (showPumpMessages) { + Log.i(TAG,"Sent:"+ByteUtil.shortHexString(msg.getTxData())); + } + RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(msg.getTxData()),timeout_ms); + PumpMessage rval = new PumpMessage(resp.getRadioResponse().getPayload()); + if (rval.isValid()) { + // Mark this as the last time we heard from the pump. + rememberLastGoodPumpCommunicationTime(); + } + if (showPumpMessages) { + Log.i(TAG,"Received:"+ByteUtil.shortHexString(resp.getRadioResponse().getPayload())); + } + return rval; + } + + public Page getPumpHistoryPage(int pageNumber) { + RawHistoryPage rval = new RawHistoryPage(); + wakeup(pumpAwakeForMinutes); + PumpMessage getHistoryMsg = makePumpMessage(new MessageType(MessageType.CMD_M_READ_HISTORY), new GetHistoryPageCarelinkMessageBody(pageNumber)); + //Log.i(TAG,"getPumpHistoryPage("+pageNumber+"): "+ByteUtil.shortHexString(getHistoryMsg.getTxData())); + // Ask the pump to transfer history (we get first frame?) + PumpMessage firstResponse = runCommandWithArgs(getHistoryMsg); + //Log.i(TAG,"getPumpHistoryPage("+pageNumber+"): " + ByteUtil.shortHexString(firstResponse.getContents())); + + PumpMessage ackMsg = makePumpMessage(MessageType.PumpAck,new PumpAckMessageBody()); + GetHistoryPageCarelinkMessageBody currentResponse = new GetHistoryPageCarelinkMessageBody(firstResponse.getMessageBody().getTxData()); + int expectedFrameNum = 1; + boolean done = false; + //while (expectedFrameNum == currentResponse.getFrameNumber()) { + int failures = 0; + while (!done) { + // examine current response for problems. + byte[] frameData = currentResponse.getFrameData(); + if ((frameData != null) && (frameData.length > 0) && currentResponse.getFrameNumber() == expectedFrameNum) { + // success! got a frame. + if (frameData.length != 64) { + Log.w(TAG,"Expected frame of length 64, got frame of length " + frameData.length); + // but append it anyway? + } + // handle successful frame data + rval.appendData(currentResponse.getFrameData()); + RoundtripService.getInstance().announceProgress(((100/16) * currentResponse.getFrameNumber()+1)); + Log.i(TAG,"getPumpHistoryPage: Got frame "+currentResponse.getFrameNumber()); + // Do we need to ask for the next frame? + if (expectedFrameNum < 16) { // This number may not be correct for pumps other than 522/722 + expectedFrameNum++; + } else { + done = true; // successful completion + } + } else { + if (frameData == null) { + Log.e(TAG,"null frame data, retrying"); + } else if (currentResponse.getFrameNumber() != expectedFrameNum) { + Log.w(TAG, String.format("Expected frame number %d, received %d (retrying)", expectedFrameNum, currentResponse.getFrameNumber())); + } else if (frameData.length == 0) { + Log.w(TAG, "Frame has zero length, retrying"); + } + failures++; + if (failures == 6) { + Log.e(TAG,String.format("6 failures in attempting to download frame %d of page %d, giving up.",expectedFrameNum,pageNumber)); + done = true; // failure completion. + } + } + if (!done) { + // ask for next frame + PumpMessage nextMsg = sendAndListen(ackMsg); + currentResponse = new GetHistoryPageCarelinkMessageBody(nextMsg.getMessageBody().getTxData()); + } + } + if (rval.getLength() != 1024) { + Log.w(TAG,"getPumpHistoryPage: short page. Expected length of 1024, found length of "+rval.getLength()); + } + if (rval.isChecksumOK() == false) { + Log.e(TAG,"getPumpHistoryPage: checksum is wrong"); + } + + rval.dumpToDebug(); + + Page page = new Page(); + //page.parseFrom(rval.getData(),PumpModel.MM522); + page.parseFrom(rval.getData(), PumpModel.MM522); + + return page; + } + + public ArrayList getAllHistoryPages() { + ArrayList pages = new ArrayList<>(); + + for (int pageNum = 0; pageNum < 16; pageNum++) { + pages.add(getPumpHistoryPage(pageNum)); + } + + return pages; + } + + public ArrayList getHistoryEventsSinceDate(Instant when) { + ArrayList pages = new ArrayList<>(); + for (int pageNum = 0; pageNum < 16; pageNum++) { + pages.add(getPumpHistoryPage(pageNum)); + for (Page page : pages) { + for (Record r : page.mRecordList) { + LocalDateTime timestamp = r.getTimestamp().getLocalDateTime(); + Log.i(TAG, "Found record: (" + r.getClass().getSimpleName() + ") " + timestamp.toString()); + } + } + } + return pages; + } + + private LocalDateTime parsePumpRTCBytes(byte[] bytes) { + if (bytes == null) return null; + if (bytes.length < 7) return null; + int hours = ByteUtil.asUINT8(bytes[0]); + int minutes = ByteUtil.asUINT8(bytes[1]); + int seconds = ByteUtil.asUINT8(bytes[2]); + int year = (ByteUtil.asUINT8(bytes[4]) & 0x3f) + 1984; + int month = ByteUtil.asUINT8(bytes[5]); + int day = ByteUtil.asUINT8(bytes[6]); + try { + LocalDateTime pumpTime = new LocalDateTime(year, month, day, hours, minutes, seconds); + return pumpTime; + } catch (IllegalFieldValueException e) { + Log.e(TAG,String.format("parsePumpRTCBytes: Failed to parse pump time value: year=%d, month=%d, hours=%d, minutes=%d, seconds=%d",year,month,day,hours,minutes,seconds)); + return null; + } + } + + public ReadPumpClockResult getPumpRTC() { + ReadPumpClockResult rval = new ReadPumpClockResult(); + wakeup(pumpAwakeForMinutes); + PumpMessage getRTCMsg = makePumpMessage(new MessageType(MessageType.CMD_M_READ_RTC), new CarelinkShortMessageBody(new byte[]{0})); + Log.i(TAG,"getPumpRTC: " + ByteUtil.shortHexString(getRTCMsg.getTxData())); + PumpMessage response = sendAndListen(getRTCMsg); + if (response.isValid()) { + byte[] receivedData = response.getContents(); + if (receivedData != null) { + if (receivedData.length >= 9) { + LocalDateTime pumpTime = parsePumpRTCBytes(ByteUtil.substring(receivedData, 2, 7)); + if (pumpTime != null) { + rval.setTime(pumpTime); + rval.setResultOK(); + } else { + rval.setResultError(ServiceResult.ERROR_MALFORMED_PUMP_RESPONSE); + } + } else { + rval.setResultError(ServiceResult.ERROR_MALFORMED_PUMP_RESPONSE); + } + } else { + rval.setResultError(ServiceResult.ERROR_MALFORMED_PUMP_RESPONSE); + } + } else { + rval.setResultError(ServiceResult.ERROR_INVALID_PUMP_RESPONSE); + } + return rval; + } + + public PumpModel getPumpModel() { + wakeup(pumpAwakeForMinutes); + PumpMessage msg = makePumpMessage(new MessageType(MessageType.GetPumpModel), new GetPumpModelCarelinkMessageBody()); + Log.i(TAG,"getPumpModel: " + ByteUtil.shortHexString(msg.getTxData())); + PumpMessage response = sendAndListen(msg); + Log.i(TAG,"getPumpModel response: " + ByteUtil.shortHexString(response.getContents())); + byte[] contents = response.getContents(); + PumpModel rval = PumpModel.UNSET; + if (contents != null) { + if (contents.length >= 7) { + rval = PumpModel.fromString(StringUtil.fromBytes(ByteUtil.substring(contents,3,3))); + } else { + Log.w(TAG,"getPumpModel: Cannot return pump model number: data is too short."); + } + } else { + Log.w(TAG,"getPumpModel: Cannot return pump model number: null response"); + } + + return rval; + } + + + public ISFTable getPumpISFProfile() { + wakeup(pumpAwakeForMinutes); + PumpMessage getISFProfileMessage = makePumpMessage(new MessageType(MessageType.GetISFProfile),new CarelinkShortMessageBody()); + PumpMessage resp = sendAndListen(getISFProfileMessage); + ISFTable table = new ISFTable(); + table.parseFrom(resp.getContents()); + return table; + } + + public PumpMessage getBolusWizardCarbProfile() { + wakeup(pumpAwakeForMinutes); + PumpMessage getCarbProfileMessage = makePumpMessage(new MessageType(MessageType.CMD_M_READ_CARB_RATIOS),new CarelinkShortMessageBody()); + PumpMessage resp = sendAndListen(getCarbProfileMessage); + return resp; + } + + public void tryoutPacket(byte[] pkt) { + sendAndListen(makePumpMessage(pkt)); + } + + public void hunt() { + tryoutPacket(new byte[] {MessageType.CMD_M_READ_PUMP_STATUS,0}); + tryoutPacket(new byte[] {MessageType.CMD_M_READ_FIRMWARE_VER,0}); + tryoutPacket(new byte[] {MessageType.CMD_M_READ_INSULIN_REMAINING,0}); + + } + + // See ButtonPressCarelinkMessageBody + public void pressButton(int which) { + wakeup(pumpAwakeForMinutes); + PumpMessage pressButtonMessage = makePumpMessage(new MessageType(MessageType.CMD_M_KEYPAD_PUSH),new ButtonPressCarelinkMessageBody(which)); + PumpMessage resp = sendAndListen(pressButtonMessage); + if (resp.messageType.mtype != MessageType.PumpAck) { + Log.e(TAG,"Pump did not ack button press."); + } + } + + public void wakeup(int duration_minutes) { + // If it has been longer than n minutes, do wakeup. Otherwise assume pump is still awake. + // **** FIXME: this wakeup doesn't seem to work well... must revisit + pumpAwakeForMinutes = duration_minutes; + Instant lastGood = getLastGoodPumpCommunicationTime(); +// Instant lastGoodPlus = lastGood.plus(new Duration(pumpAwakeForMinutes * 60 * 1000)); + Instant lastGoodPlus = lastGood.plus(new Duration(1 * 60 * 1000)); + Instant now = Instant.now(); + if (now.compareTo(lastGoodPlus) > 0) { + Log.i(TAG,"Waking pump..."); + PumpMessage msg = makePumpMessage(new MessageType(MessageType.PowerOn), new CarelinkShortMessageBody(new byte[]{(byte) duration_minutes})); + RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(msg.getTxData()), (byte) 0, (byte) 200, (byte) 0, (byte) 0, 15000, (byte) 0); + Log.i(TAG, "wakeup: raw response is " + ByteUtil.shortHexString(resp.getRaw())); + } else { + Log.v(TAG,"Last pump communication was recent, not waking pump."); + } + } + + public void setRadioFrequencyForPump(double freqMHz) { + rfspy.setBaseFrequency(freqMHz); + } + + public double tuneForPump() { + return scanForPump(scanFrequencies); + } + + private int tune_tryFrequency(double freqMHz) { + rfspy.setBaseFrequency(freqMHz); + PumpMessage msg = makePumpMessage(new MessageType(MessageType.GetPumpModel),new GetPumpModelCarelinkMessageBody()); + RadioPacket pkt = new RadioPacket(msg.getTxData()); + RFSpyResponse resp = rfspy.transmitThenReceive(pkt,(byte)0,(byte)0,(byte)0,(byte)0,rfspy.EXPECTED_MAX_BLUETOOTH_LATENCY_MS,(byte)0); + if (resp.wasTimeout()) { + Log.w(TAG,String.format("tune_tryFrequency: no pump response at frequency %.2f",freqMHz)); + } else if (resp.looksLikeRadioPacket()) { + RadioResponse radioResponse = new RadioResponse(resp.getRaw()); + if (radioResponse.isValid()) { + Log.w(TAG,String.format("tune_tryFrequency: saw response level %d at frequency %.2f",radioResponse.rssi,freqMHz)); + return radioResponse.rssi; + } else { + Log.w(TAG,"tune_tryFrequency: invalid radio response:"+ByteUtil.shortHexString(radioResponse.getPayload())); + } + } + return 0; + } + + public double quickTuneForPump(double startFrequencyMHz) { + double betterFrequency = startFrequencyMHz; + double stepsize = 0.05; + for (int tries = 0; tries < 4; tries++) { + double evenBetterFrequency = quickTunePumpStep(betterFrequency, stepsize); + if (evenBetterFrequency == 0.0) { + // could not see the pump at all. + // Try again at larger step size + stepsize += 0.05; + } else { + if ((int)(evenBetterFrequency * 100) == (int)(betterFrequency * 100)) { + // value did not change, so we're done. + break; + } + betterFrequency = evenBetterFrequency; // and go again. + } + } + if (betterFrequency == 0.0) { + // we've failed... caller should try a full scan for pump + Log.e(TAG,"quickTuneForPump: failed to find pump"); + } else { + rfspy.setBaseFrequency(betterFrequency); + if (betterFrequency != startFrequencyMHz) { + Log.i(TAG, String.format("quickTuneForPump: new frequency is %.2fMHz", betterFrequency)); + } else { + Log.i(TAG, String.format("quickTuneForPump: pump frequency is the same: %.2fMHz", startFrequencyMHz)); + } + } + return betterFrequency; + } + + private double quickTunePumpStep(double startFrequencyMHz, double stepSizeMHz) { + Log.i(TAG,"Doing quick radio tune for pump ID " + pumpID); + wakeup(pumpAwakeForMinutes); + int startRssi = tune_tryFrequency(startFrequencyMHz); + double lowerFrequency = startFrequencyMHz - stepSizeMHz; + int lowerRssi = tune_tryFrequency(lowerFrequency); + double higherFrequency = startFrequencyMHz + stepSizeMHz; + int higherRssi = tune_tryFrequency(higherFrequency); + if ((higherRssi == 0.0) && (lowerRssi == 0.0) && (startRssi == 0.0)) { + // we can't see the pump at all... + return 0.0; + } + if (higherRssi > startRssi) { + // need to move higher + return higherFrequency; + } else if (lowerRssi > startRssi) { + // need to move lower. + return lowerFrequency; + } + return startFrequencyMHz; + } + + private double scanForPump(double[] frequencies) { + Log.i(TAG,"Scanning for pump ID " + pumpID); + wakeup(pumpAwakeForMinutes); + FrequencyScanResults results = new FrequencyScanResults(); + + for (int i=0; i 0) { + rfspy.setBaseFrequency(results.bestFrequencyMHz); + return results.bestFrequencyMHz; + } else { + Log.e(TAG,"No pump response during scan."); + return 0.0; + } + } + + private PumpMessage makePumpMessage(MessageType messageType, MessageBody messageBody) { + PumpMessage msg = new PumpMessage(); + msg.init(new PacketType(PacketType.Carelink),pumpID,messageType,messageBody); + return msg; + } + + private PumpMessage makePumpMessage(byte msgType, MessageBody body) { + return makePumpMessage(new MessageType(msgType),body); + } + + private PumpMessage makePumpMessage(byte[] typeAndBody) { + PumpMessage msg = new PumpMessage(); + msg.init(ByteUtil.concat(ByteUtil.concat(new byte[]{(byte)0xa7},pumpID),typeAndBody)); + return msg; + } + + private void rememberLastGoodPumpCommunicationTime() { + lastGoodPumpCommunicationTime = Instant.now(); + SharedPreferences.Editor ed = prefs.edit(); + ed.putLong("lastGoodPumpCommunicationTime",lastGoodPumpCommunicationTime.getMillis()); + ed.commit(); + } + + private Instant getLastGoodPumpCommunicationTime() { + // If we have a value of zero, we need to load from prefs. + if (lastGoodPumpCommunicationTime.getMillis() == new Instant(0).getMillis()) { + lastGoodPumpCommunicationTime = new Instant(prefs.getLong("lastGoodPumpCommunicationTime",0)); + // Might still be zero, but that's fine. + } + double minutesAgo = (Instant.now().getMillis() - lastGoodPumpCommunicationTime.getMillis()) / (1000.0 * 60.0); + Log.v(TAG,"Last good pump communication was " + minutesAgo + " minutes ago."); + return lastGoodPumpCommunicationTime; + } + + public PumpMessage getRemainingBattery() { + PumpMessage getBatteryMsg = makePumpMessage(new MessageType(MessageType.GetBattery),new CarelinkShortMessageBody()); + PumpMessage resp = sendAndListen(getBatteryMsg); + return resp; + } + + public PumpMessage getRemainingInsulin() { + PumpMessage msg = makePumpMessage(new MessageType(MessageType.CMD_M_READ_INSULIN_REMAINING),new CarelinkShortMessageBody()); + PumpMessage resp = sendAndListen(msg); + return resp; + } + + public PumpMessage getCurrentBasalRate() { + PumpMessage msg = makePumpMessage(new MessageType(MessageType.ReadTempBasal), new CarelinkShortMessageBody()); + PumpMessage resp = sendAndListen(msg); + return resp; + } + + PumpManagerStatus pumpManagerStatus = new PumpManagerStatus(); + + public PumpManagerStatus getPumpManagerStatus() { return pumpManagerStatus; } + + public void updatePumpManagerStatus() { + PumpMessage resp = getRemainingBattery(); + if (resp.isValid()) { + byte[] remainingBatteryBytes = resp.getContents(); + if (remainingBatteryBytes != null) { + if (remainingBatteryBytes.length == 5) { + /** + * 0x72 0x03, 0x00, 0x00, 0x82 + * meaning what ???? + */ + pumpManagerStatus.remainBattery = ByteUtil.asUINT8(remainingBatteryBytes[5]); + } + } + } + resp = getRemainingInsulin(); + byte[] insulinRemainingBytes = resp.getContents(); + if (insulinRemainingBytes != null) { + if (insulinRemainingBytes.length == 4) { + /* 0x73 0x02 0x05 0xd2 + * the 0xd2 (210) represents 21 units remaining. + */ + double insulinUnitsRemaining = ByteUtil.asUINT8(insulinRemainingBytes[3]) / 10.0; + pumpManagerStatus.remainUnits = insulinUnitsRemaining; + } + } + /* current basal */ + resp = getCurrentBasalRate(); + byte[] basalRateBytes = resp.getContents(); + if (basalRateBytes != null) { + if (basalRateBytes.length == 2) { + /** + * 0x98 0x06 + * 0x98 is "basal rate" + * 0x06 is what? Not currently running a temp basal, current basal is "standard" at 0 + */ + double basalRate = ByteUtil.asUINT8(basalRateBytes[1]); + pumpManagerStatus.currentBasal = basalRate; + } + } + // get last bolus amount + // get last bolus time + // get tempBasalInProgress + // get tempBasalRatio + // get tempBasalRemainMin + // get tempBasalStart + // get pump time + ReadPumpClockResult clockResult = getPumpRTC(); + if (clockResult.resultIsOK()) { + pumpManagerStatus.time = clockResult.getTime().toDate(); + } + // get last sync time + + } + + public void testPageDecode() { + byte[] raw = new byte[] {(byte)0x6D, (byte)0x62, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x63, (byte)0x10, (byte)0x6D, (byte)0x63, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, + (byte)0x01, (byte)0x01, (byte)0x00, (byte)0x5A, (byte)0xA5, (byte)0x49, (byte)0x04, (byte)0x10, (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x00, (byte)0x6D, (byte)0xA5, (byte)0x49, (byte)0x04, (byte)0x10, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x64, (byte)0x10, (byte)0x6D, (byte)0x64, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x64, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x64, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x64, (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x02, (byte)0x0C, (byte)0x00, + (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x64, (byte)0x01, (byte)0x75, (byte)0x94, (byte)0x0D, (byte)0x05, (byte)0x10, (byte)0x64, (byte)0x01, (byte)0x44, (byte)0x95, (byte)0x0D, (byte)0x05, (byte)0x10, (byte)0x17, (byte)0x00, (byte)0x4E, (byte)0x95, (byte)0x0D, (byte)0x05, (byte)0x10, (byte)0x18, (byte)0x00, (byte)0x40, (byte)0x95, (byte)0x0D, (byte)0x05, (byte)0x10, + (byte)0x19, (byte)0x00, (byte)0x40, (byte)0x81, (byte)0x15, (byte)0x05, (byte)0x10, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x65, (byte)0x10, (byte)0x6D, (byte)0x65, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x1A, (byte)0x00, (byte)0x47, (byte)0x82, (byte)0x09, (byte)0x06, + (byte)0x10, (byte)0x1A, (byte)0x01, (byte)0x5C, (byte)0x82, (byte)0x09, (byte)0x06, (byte)0x10, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x66, (byte)0x10, (byte)0x6D, (byte)0x66, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x67, (byte)0x10, (byte)0x6D, (byte)0x67, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x68, (byte)0x10, (byte)0x6D, (byte)0x68, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x69, (byte)0x10, (byte)0x6D, (byte)0x69, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x6A, (byte)0x10, (byte)0x6D, (byte)0x6A, (byte)0x10, (byte)0x05, (byte)0x0C, + (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x6B, (byte)0x10, (byte)0x6D, (byte)0x6B, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x6C, + (byte)0x10, (byte)0x6D, (byte)0x6C, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x6D, (byte)0x10, (byte)0x6D, (byte)0x6D, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x6E, (byte)0x10, (byte)0x6D, (byte)0x6E, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x6F, (byte)0x10, (byte)0x6D, (byte)0x6F, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, + (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x19, (byte)0x00, (byte)0x40, (byte)0x81, (byte)0x03, (byte)0x10, (byte)0x10, (byte)0x1A, (byte)0x00, (byte)0x68, (byte)0x96, (byte)0x0A, (byte)0x10, (byte)0x10, (byte)0x1A, (byte)0x01, (byte)0x40, (byte)0x97, (byte)0x0A, (byte)0x10, (byte)0x10, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x70, (byte)0x10, (byte)0x6D, (byte)0x70, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x71, (byte)0x10, (byte)0x6D, (byte)0x71, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x72, (byte)0x10, (byte)0x6D, (byte)0x72, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x73, (byte)0x10, (byte)0x6D, (byte)0x73, (byte)0x10, (byte)0x05, (byte)0x0C, + (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x74, (byte)0x10, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x2C, (byte)0x79, + }; + Page page = new Page(); + page.parseFrom(raw,PumpModel.MM522); + page.parseByDates(raw,PumpModel.MM522); + page.parsePicky(raw,PumpModel.MM522); + Log.i(TAG,"testPageDecode: done"); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/PumpManagerStatus.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/PumpManagerStatus.java new file mode 100644 index 0000000000..d3896e10c4 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/PumpManagerStatus.java @@ -0,0 +1,61 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLink; + +import java.util.Date; + +/** + * Created by geoff on 7/16/16. + * + * This class is intended to provide compatibility with HAPP, and was + * modeled after the PumpStatus class from happdanardriver + * + * happdanardriver/app/src/main/java/info/nightscout/danar/db/PumpStatus.java + */ +public class PumpManagerStatus { + public long getTimeIndex() { + return (long) Math.ceil(time.getTime() / 60000d ); + } + + public void setTimeIndex(long timeIndex) { + this.timeIndex = timeIndex; + } + + public long timeIndex; + + public Date time; + + public double remainUnits = 0; + + public int remainBattery = 0; + + public double currentBasal = 0; + + public int tempBasalInProgress = 0; + + public int tempBasalRatio = 0; + + public int tempBasalRemainMin = 0 ; + + public Date last_bolus_time ; + + public double last_bolus_amount = 0; + + public Date tempBasalStart; + + @Override + public String toString() { + return "PumpStatus{" + + "timeIndex=" + timeIndex + + ", time=" + time + + ", remainUnits=" + remainUnits + + ", remainBattery=" + remainBattery + + ", currentBasal=" + currentBasal + + ", tempBasalInProgress=" + tempBasalInProgress + + ", tempBasalRatio=" + tempBasalRatio + + ", tempBasalRemainMin=" + tempBasalRemainMin + + ", last_bolus_time=" + last_bolus_time + + ", last_bolus_amount=" + last_bolus_amount + + ", tempBasalStart=" + tempBasalStart + + '}'; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/RawHistoryPage.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/RawHistoryPage.java new file mode 100644 index 0000000000..2fb01053ab --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLink/RawHistoryPage.java @@ -0,0 +1,45 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLink; + +import android.util.Log; + +import com.gxwtech.roundtrip2.util.ByteUtil; +import com.gxwtech.roundtrip2.util.CRC; + +/** + * Created by geoff on 6/4/16. + */ +public class RawHistoryPage { + private static final String TAG = "RawHistoryPage"; + byte[] data = new byte[0]; + public RawHistoryPage() { + } + public void appendData(byte[] newdata) { + data = ByteUtil.concat(data,newdata); + } + public byte[] getData() { + return data; + } + public int getLength() { + return data.length; + } + public boolean isChecksumOK() { + if (getLength() != 1024) { + return false; + } + byte[] computedCRC = CRC.calculate16CCITT(ByteUtil.substring(data,0,1022)); + return ((computedCRC[0] == data[1022]) && (computedCRC[1] == data[1023])); + } + + public void dumpToDebug() { + int linesize = 80; + int offset = 0; + while (offset < data.length) { + int bytesToLog = linesize; + if (offset + linesize > data.length) { + bytesToLog = data.length - offset; + } + Log.d(TAG,ByteUtil.shortHexString(ByteUtil.substring(data,offset,bytesToLog))); + offset += linesize; + } + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/BLECommOperation.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/BLECommOperation.java new file mode 100644 index 0000000000..4c77fe2278 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/BLECommOperation.java @@ -0,0 +1,20 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations; + +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RileyLinkBLE; + +import java.util.UUID; + +/** + * Created by geoff on 5/26/16. + */ +public abstract class BLECommOperation { + public boolean timedOut = false; + public boolean interrupted = false; + + // This is to be run on the main thread + public abstract void execute(RileyLinkBLE comm); + public void gattOperationCompletionCallback(UUID uuid, byte[] value) {} + public int getGattOperationTimeout_ms() { return 22000;} + + public byte[] getValue() { return null; } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/BLECommOperationResult.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/BLECommOperationResult.java new file mode 100644 index 0000000000..4570480310 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/BLECommOperationResult.java @@ -0,0 +1,16 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations; + +/** + * Created by geoff on 5/26/16. + */ +public class BLECommOperationResult { + public byte[] value; + public int resultCode; + + public static final int RESULT_NONE = 0; + public static final int RESULT_SUCCESS = 1; + public static final int RESULT_TIMEOUT = 2; + public static final int RESULT_BUSY = 3; + public static final int RESULT_INTERRUPTED = 4; + public static final int RESULT_NOT_CONFIGURED = 5; +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/CharacteristicReadOperation.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/CharacteristicReadOperation.java new file mode 100644 index 0000000000..e6d647b28d --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/CharacteristicReadOperation.java @@ -0,0 +1,62 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.os.SystemClock; +import android.util.Log; + +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.GattAttributes; +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RileyLinkBLE; + +import java.util.UUID; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +/** + * Created by geoff on 5/26/16. + */ +public class CharacteristicReadOperation extends BLECommOperation { + private static final String TAG = "CharacteristicReadOp"; + private BluetoothGatt gatt; + private BluetoothGattCharacteristic characteristic; + private byte[] value; + private Semaphore operationComplete = new Semaphore(0,true); + public CharacteristicReadOperation(BluetoothGatt gatt, BluetoothGattCharacteristic chara) { + this.gatt = gatt; + this.characteristic = chara; + } + @Override + public void execute(RileyLinkBLE comm) { + gatt.readCharacteristic(characteristic); + // wait here for callback to notify us that value was read. + try { + boolean didAcquire = operationComplete.tryAcquire(getGattOperationTimeout_ms(), TimeUnit.MILLISECONDS); + if (didAcquire) { + SystemClock.sleep(1); // This is to allow the IBinder thread to exit before we continue, allowing easier understanding of the sequence of events. + // success + } else { + Log.e(TAG,"Timeout waiting for gatt write operation to complete"); + timedOut = true; + } + } catch (InterruptedException e) { + Log.e(TAG,"Interrupted while waiting for gatt write operation to complete"); + interrupted = true; + } + value = characteristic.getValue(); + } + + @Override + public void gattOperationCompletionCallback(UUID uuid, byte[] value) { + super.gattOperationCompletionCallback(uuid, value); + if (!characteristic.getUuid().equals(uuid)) { + Log.e(TAG,String.format("Completion callback: UUID does not match! out of sequence? Found: %s, should be %s", + GattAttributes.lookup(characteristic.getUuid()),GattAttributes.lookup(uuid))); + } + operationComplete.release(); + } + + @Override + public byte[] getValue() { + return value; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/CharacteristicWriteOperation.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/CharacteristicWriteOperation.java new file mode 100644 index 0000000000..a08264edf6 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/CharacteristicWriteOperation.java @@ -0,0 +1,66 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.os.SystemClock; +import android.util.Log; + +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.GattAttributes; +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RileyLinkBLE; + +import java.util.UUID; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +/** + * Created by geoff on 5/26/16. + */ +public class CharacteristicWriteOperation extends BLECommOperation { + private static final String TAG = "CharacteristicWriteOp"; + private BluetoothGatt gatt; + private BluetoothGattCharacteristic characteristic; + private byte[] value; + private Semaphore operationComplete = new Semaphore(0,true); + + public CharacteristicWriteOperation(BluetoothGatt gatt, BluetoothGattCharacteristic chara, byte[] value) { + this.gatt = gatt; + this.characteristic = chara; + this.value = value; + } + @Override + public void execute(RileyLinkBLE comm) { + + characteristic.setValue(value); + gatt.writeCharacteristic(characteristic); + // wait here for callback to notify us that value was written. + try { + boolean didAcquire = operationComplete.tryAcquire(getGattOperationTimeout_ms(), TimeUnit.MILLISECONDS); + if (didAcquire) { + SystemClock.sleep(1); // This is to allow the IBinder thread to exit before we continue, allowing easier understanding of the sequence of events. + // success + } else { + Log.e(TAG,"Timeout waiting for gatt write operation to complete"); + timedOut = true; + } + } catch (InterruptedException e) { + Log.e(TAG,"Interrupted while waiting for gatt write operation to complete"); + interrupted = true; + } + + } + + // This will be run on the IBinder thread + @Override + public void gattOperationCompletionCallback(UUID uuid, byte[] value) { + if (!characteristic.getUuid().equals(uuid)) { + Log.e(TAG,String.format("Completion callback: UUID does not match! out of sequence? Found: %s, should be %s", + GattAttributes.lookup(characteristic.getUuid()),GattAttributes.lookup(uuid))); + } + operationComplete.release(); + } + + @Override + public byte[] getValue() { + return value; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/DescriptorWriteOperation.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/DescriptorWriteOperation.java new file mode 100644 index 0000000000..a3519228bc --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/BLECommOperations/DescriptorWriteOperation.java @@ -0,0 +1,56 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattDescriptor; +import android.os.SystemClock; +import android.util.Log; + +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RileyLinkBLE; + +import java.util.UUID; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +/** + * Created by geoff on 5/26/16. + */ +public class DescriptorWriteOperation extends BLECommOperation { + + private static final String TAG = "DescrWriteOp"; + private BluetoothGatt gatt; + private BluetoothGattDescriptor descr; + private byte[] value; + private Semaphore operationComplete = new Semaphore(0,true); + + public DescriptorWriteOperation(BluetoothGatt gatt, BluetoothGattDescriptor descr, byte[] value) { + this.gatt = gatt; + this.descr = descr; + this.value = value; + } + + @Override + public void gattOperationCompletionCallback(UUID uuid, byte[] value) { + super.gattOperationCompletionCallback(uuid, value); + operationComplete.release(); + } + + @Override + public void execute(RileyLinkBLE comm) { + descr.setValue(value); + gatt.writeDescriptor(descr); + // wait here for callback to notify us that value was read. + try { + boolean didAcquire = operationComplete.tryAcquire(getGattOperationTimeout_ms(), TimeUnit.MILLISECONDS); + if (didAcquire) { + SystemClock.sleep(1); // This is to allow the IBinder thread to exit before we continue, allowing easier understanding of the sequence of events. + // success + } else { + Log.e(TAG,"Timeout waiting for descriptor write operation to complete"); + timedOut = true; + } + } catch (InterruptedException e) { + Log.e(TAG,"Interrupted while waiting for descriptor write operation to complete"); + interrupted = true; + } + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/GattAttributes.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/GattAttributes.java new file mode 100644 index 0000000000..a64d19f620 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/GattAttributes.java @@ -0,0 +1,52 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE; + +import java.util.HashMap; +import java.util.UUID; + +/** + * Created by geoff on 5/21/16. + */ +public class GattAttributes { + // NOTE: these uuid strings must be lower case! + private static HashMap attributes = new HashMap(); + public static String PREFIX = "0000"; + public static String SUFFIX = "-0000-1000-8000-00805f9b34fb"; + public static String SERVICE_GAP = PREFIX + "1800" + SUFFIX; + public static String CHARA_GAP_NAME = PREFIX + "2a00" + SUFFIX; + public static String CHARA_GAP_NUM = PREFIX + "2a01" + SUFFIX; + + public static String SERVICE_BATTERY = PREFIX + "180f" + SUFFIX; + public static String CHARA_BATTERY_UNK = PREFIX + "2a19" + SUFFIX; + + public static String SERVICE_RADIO = "0235733b-99c5-4197-b856-69219c2a3845"; + public static String CHARA_RADIO_DATA = "c842e849-5028-42e2-867c-016adada9155"; + public static String CHARA_RADIO_RESPONSE_COUNT = "6e6c7910-b89e-43a5-a0fe-50c5e2b81f4a"; + public static String CHARA_RADIO_TIMER_TICK = "6e6c7910-b89e-43a5-78af-50c5e2b86f7e"; + public static String CHARA_RADIO_CUSTOM_NAME = "d93b2af0-1e28-11e4-8c21-0800200c9a66"; + public static String CHARA_RADIO_VERSION = "30d99dc9-7c91-4295-a051-0a104d238cf2"; + + // table of names for uuids + static { + attributes.put(SERVICE_GAP, "Device Information Service"); + attributes.put(SERVICE_BATTERY,"Battery Service"); + attributes.put(CHARA_RADIO_CUSTOM_NAME,"customName"); + attributes.put(CHARA_RADIO_DATA,"data"); + attributes.put(CHARA_RADIO_RESPONSE_COUNT,"responseCount"); + attributes.put(CHARA_RADIO_TIMER_TICK,"timerTick"); + attributes.put(CHARA_RADIO_VERSION,"radioVersion"); + } + + public static String lookup(UUID uuid) { + return lookup(uuid.toString()); + } + + public static String lookup(String uuid) { + return lookup(uuid, uuid); + } + + public static String lookup(String uuid, String defaultName) { + String name = attributes.get(uuid); + return name == null ? defaultName : name; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RFSpy.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RFSpy.java new file mode 100644 index 0000000000..aec53007d0 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RFSpy.java @@ -0,0 +1,203 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE; + +import android.content.Context; +import android.os.SystemClock; +import android.util.Log; + +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.BLECommOperation; +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.BLECommOperationResult; +import com.gxwtech.roundtrip2.util.ByteUtil; +import com.gxwtech.roundtrip2.util.StringUtil; +import com.gxwtech.roundtrip2.util.ThreadUtil; + +import java.util.UUID; + +/** + * Created by geoff on 5/26/16. + */ +public class RFSpy { + public static final byte RFSPY_GET_STATE = 1; + public static final byte RFSPY_GET_VERSION = 2; + public static final byte RFSPY_GET_PACKET = 3; // aka Listen, receive + public static final byte RFSPY_SEND = 4; + public static final byte RFSPY_SEND_AND_LISTEN = 5; + public static final byte RFSPY_UPDATE_REGISTER = 6; + public static final byte RFSPY_RESET = 7; + + public static final long RILEYLINK_FREQ_XTAL = 24000000; + + public static final byte CC111X_REG_FREQ2 = 0x09; + public static final byte CC111X_REG_FREQ1 = 0x0A; + public static final byte CC111X_REG_FREQ0 = 0x0B; + public static final byte CC111X_MDMCFG4 = 0x0C; + public static final byte CC111X_MDMCFG3 = 0x0D; + public static final byte CC111X_MDMCFG2 = 0x0E; + public static final byte CC111X_MDMCFG1 = 0x0F; + public static final byte CC111X_MDMCFG0 = 0x10; + public static final byte CC111X_AGCCTRL2 = 0x17; + public static final byte CC111X_AGCCTRL1 = 0x18; + public static final byte CC111X_AGCCTRL0 = 0x19; + public static final byte CC111X_FREND1 = 0x1A; + public static final byte CC111X_FREND0 = 0x1B; + + public static final int EXPECTED_MAX_BLUETOOTH_LATENCY_MS = 1500; + + private static final String TAG = "RFSpy"; + private RileyLinkBLE rileyLinkBle; + private RFSpyReader reader; + private Context context; + UUID radioServiceUUID = UUID.fromString(GattAttributes.SERVICE_RADIO); + UUID radioDataUUID = UUID.fromString(GattAttributes.CHARA_RADIO_DATA); + UUID radioVersionUUID = UUID.fromString(GattAttributes.CHARA_RADIO_VERSION); + UUID responseCountUUID = UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT); + + private static final byte[] pumpID = {0x51, (byte)0x81, 0x63}; + + public RFSpy(Context context, RileyLinkBLE rileyLinkBle) { + this.context = context; + this.rileyLinkBle = rileyLinkBle; + reader = new RFSpyReader(context, rileyLinkBle); + } + + // Call this after the RL services are discovered. + // Starts an async task to read when data is available + public void startReader() { + rileyLinkBle.registerRadioResponseCountNotification(new Runnable() { + @Override + public void run() { + newDataIsAvailable(); + } + }); + reader.start(); + } + + // Call this from the "response count" notification handler. + public void newDataIsAvailable() { + // pass the message to the reader (which should be internal to RFSpy) + reader.newDataIsAvailable(); + } + + // This gets the version from the BLE113, not from the CC1110. + // I.e., this gets the version from the BLE interface, not from the radio. + public String getVersion() { + BLECommOperationResult result = rileyLinkBle.readCharacteristic_blocking(radioServiceUUID,radioVersionUUID); + if (result.resultCode == BLECommOperationResult.RESULT_SUCCESS) { + return StringUtil.fromBytes(result.value); + } else { + Log.e(TAG,"getVersion failed with code: "+result.resultCode); + return "(null)"; + } + } + + // The caller has to know how long the RFSpy will be busy with what was sent to it. + private RFSpyResponse writeToData(byte[] bytes, int responseTimeout_ms) { + SystemClock.sleep(100); + // FIXME drain read queue? + byte[] junkInBuffer = reader.poll(0); + + while (junkInBuffer != null) { + Log.w(TAG, ThreadUtil.sig()+"writeToData: draining read queue, found this: " + ByteUtil.shortHexString(junkInBuffer)); + junkInBuffer = reader.poll(0); + } + + // prepend length, and send it. + byte[] prepended = ByteUtil.concat(new byte[] {(byte)(bytes.length)},bytes); + BLECommOperationResult writeCheck = rileyLinkBle.writeCharacteristic_blocking(radioServiceUUID,radioDataUUID,prepended); + if (writeCheck.resultCode != BLECommOperationResult.RESULT_SUCCESS) { + Log.e(TAG,"BLE Write operation failed, code=" + writeCheck.resultCode); + return new RFSpyResponse(); // will be a null (invalid) response + } + SystemClock.sleep(100); + //Log.i(TAG,ThreadUtil.sig()+String.format(" writeToData:(timeout %d) %s",(responseTimeout_ms),ByteUtil.shortHexString(prepended))); + byte[] rawResponse = reader.poll(responseTimeout_ms); + RFSpyResponse resp = new RFSpyResponse(rawResponse); + if (rawResponse == null) { + Log.e(TAG,"writeToData: No response from RileyLink"); + } else { + if (resp.wasInterrupted()) { + Log.e(TAG, "writeToData: RileyLink was interrupted"); + } else if (resp.wasTimeout()) { + Log.e(TAG, "writeToData: RileyLink reports timeout"); + } else if (resp.isOK()) { + Log.w(TAG, "writeToData: RileyLink reports OK"); + } else { + if (resp.looksLikeRadioPacket()) { + RadioResponse radioResp = resp.getRadioResponse(); + byte[] responsePayload = radioResp.getPayload(); + Log.i(TAG,"writeToData: decoded radio response is "+ByteUtil.shortHexString(responsePayload)); + } + //Log.i(TAG, "writeToData: raw response is " + ByteUtil.shortHexString(rawResponse)); + } + } + return resp; + } + + public RFSpyResponse getRadioVersion() { + RFSpyResponse resp = writeToData(new byte[] {RFSPY_GET_VERSION},1000); + if (resp == null) { + Log.e(TAG,"getRadioVersion returned null"); + } + /* + Log.d(TAG,"checking response count"); + BLECommOperationResult checkRC = rileyLinkBle.readCharacteristic_blocking(radioServiceUUID,responseCountUUID); + if (checkRC.resultCode == BLECommOperationResult.RESULT_SUCCESS) { + Log.d(TAG,"Response count is: " + ByteUtil.shortHexString(checkRC.value)); + } else { + Log.e(TAG,"Error getting response count, code is " + checkRC.resultCode); + } + */ + return resp; + } + + public RFSpyResponse transmit(RadioPacket radioPacket, byte sendChannel, byte repeatCount, byte delay_ms) { + // append checksum, encode data, send it. + byte[] fullPacket = ByteUtil.concat(new byte[] {RFSPY_SEND,sendChannel,repeatCount, delay_ms},radioPacket.getEncoded()); + RFSpyResponse response = writeToData(fullPacket,repeatCount * delay_ms); + return response; + } + + public RFSpyResponse receive(byte listenChannel, int timeout_ms, byte retryCount) { + int receiveDelay = timeout_ms * (retryCount+1); + byte[] listen = {RFSPY_GET_PACKET,listenChannel, + (byte)((timeout_ms >> 24)&0x0FF), + (byte)((timeout_ms >> 16)&0x0FF), + (byte)((timeout_ms >> 8)&0x0FF), + (byte)(timeout_ms & 0x0FF), + retryCount}; + return writeToData(listen,receiveDelay); + } + + public RFSpyResponse transmitThenReceive(RadioPacket pkt, int timeout_ms) { + return transmitThenReceive(pkt,(byte)0,(byte)0,(byte)0,(byte)0,timeout_ms,(byte)0); + } + + public RFSpyResponse transmitThenReceive(RadioPacket pkt, byte sendChannel, byte repeatCount, byte delay_ms, byte listenChannel, int timeout_ms, byte retryCount) { + + int sendDelay = repeatCount * delay_ms; + int receiveDelay = timeout_ms * (retryCount + 1); + byte[] sendAndListen = {RFSPY_SEND_AND_LISTEN,sendChannel,repeatCount,delay_ms,listenChannel, + (byte)((timeout_ms >> 24)&0x0FF), + (byte)((timeout_ms >> 16)&0x0FF), + (byte)((timeout_ms >> 8)&0x0FF), + (byte)(timeout_ms & 0x0FF), + retryCount}; + byte[] fullPacket = ByteUtil.concat(sendAndListen,pkt.getEncoded()); + return writeToData(fullPacket, sendDelay + receiveDelay + EXPECTED_MAX_BLUETOOTH_LATENCY_MS); + } + + public RFSpyResponse updateRegister(byte addr, byte val) { + byte[] updateRegisterPkt = new byte[] {RFSPY_UPDATE_REGISTER,addr,val}; + RFSpyResponse resp = writeToData(updateRegisterPkt,EXPECTED_MAX_BLUETOOTH_LATENCY_MS); + return resp; + } + + public void setBaseFrequency(double freqMHz) { + int value = (int)(freqMHz * 1000000/((double)(RILEYLINK_FREQ_XTAL)/Math.pow(2.0,16.0))); + updateRegister(CC111X_REG_FREQ0, (byte)(value & 0xff)); + updateRegister(CC111X_REG_FREQ1, (byte)((value >> 8) & 0xff)); + updateRegister(CC111X_REG_FREQ2, (byte)((value >> 16) & 0xff)); + Log.w(TAG,String.format("Set frequency to %.2f",freqMHz)); + } + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RFSpyReader.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RFSpyReader.java new file mode 100644 index 0000000000..938aab42cc --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RFSpyReader.java @@ -0,0 +1,117 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE; + +import android.content.Context; +import android.os.AsyncTask; +import android.os.SystemClock; +import android.util.Log; + +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.BLECommOperationResult; +import com.gxwtech.roundtrip2.util.ByteUtil; +import com.gxwtech.roundtrip2.util.ThreadUtil; + +import java.util.UUID; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +/** + * Created by geoff on 5/26/16. + */ +public class RFSpyReader { + private static final String TAG = "RFSpyReader"; + private Context context; + private RileyLinkBLE rileyLinkBle; + private Semaphore waitForRadioData = new Semaphore(0,true); + AsyncTask readerTask; + private LinkedBlockingQueue mDataQueue = new LinkedBlockingQueue<>(); + private int acquireCount = 0; + private int releaseCount = 0; + + public RFSpyReader(Context context, RileyLinkBLE rileyLinkBle) { + this.context = context; + this.rileyLinkBle = rileyLinkBle; + } + + public void init(Context context, RileyLinkBLE rileyLinkBLE) { + this.context = context; + this.rileyLinkBle = rileyLinkBLE; + } + + public void setRileyLinkBle(RileyLinkBLE rileyLinkBle) { + if (readerTask != null) { + readerTask.cancel(true); + } + this.rileyLinkBle = rileyLinkBle; + } + + // This timeout must be coordinated with the length of the RFSpy radio operation or Bad Things Happen. + public byte[] poll(int timeout_ms) { + Log.v(TAG, ThreadUtil.sig()+"Entering poll at t=="+SystemClock.uptimeMillis()+", timeout is "+timeout_ms+" mDataQueue size is "+mDataQueue.size()); + if (mDataQueue.isEmpty()) + try { + // block until timeout or data available. + // returns null if timeout. + byte[] dataFromQueue = mDataQueue.poll(timeout_ms, TimeUnit.MILLISECONDS); + if (dataFromQueue != null) { + Log.d(TAG, "Got data [" + ByteUtil.shortHexString(dataFromQueue) + "] at t==" + SystemClock.uptimeMillis()); + } else { + Log.d(TAG, "Got data [null] at t==" + SystemClock.uptimeMillis()); + } + return dataFromQueue; + } catch (InterruptedException e) { + Log.e(TAG,"poll: Interrupted waiting for data"); + } + return null; + } + + // Call this from the "response count" notification handler. + public void newDataIsAvailable() { + releaseCount++; + Log.v(TAG,ThreadUtil.sig()+"waitForRadioData released(count="+releaseCount+") at t="+SystemClock.uptimeMillis()); + waitForRadioData.release(); + } + + public void start() { + readerTask = new AsyncTask() { + @Override + protected Void doInBackground(Void... voids) { + UUID serviceUUID = UUID.fromString(GattAttributes.SERVICE_RADIO); + UUID radioDataUUID = UUID.fromString(GattAttributes.CHARA_RADIO_DATA); + BLECommOperationResult result; + while (true) { + try { + acquireCount++; + waitForRadioData.acquire(); + Log.v(TAG,ThreadUtil.sig()+"waitForRadioData acquired (count="+acquireCount+") at t="+SystemClock.uptimeMillis()); + SystemClock.sleep(100); + SystemClock.sleep(1); + result = rileyLinkBle.readCharacteristic_blocking(serviceUUID, radioDataUUID); + SystemClock.sleep(100); + + if (result.resultCode == BLECommOperationResult.RESULT_SUCCESS) { + // only data up to the first null is valid + for (int i=0; i 2) { + return true; + } + return false; + } + + public byte[] getRaw() { + return raw; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RFTools.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RFTools.java new file mode 100644 index 0000000000..8b62ee29b2 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RFTools.java @@ -0,0 +1,295 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE; + +import android.util.Log; + +import com.gxwtech.roundtrip2.util.ByteUtil; +import com.gxwtech.roundtrip2.util.CRC; + +import java.util.ArrayList; + +/** + * Created by geoff on 7/31/15. + */ +public class RFTools { + private static final String TAG = "RFTools"; + /* + CodeSymbols is an ordered list of translations + 6bits -> 4 bits, in order from 0x0 to 0xF + The 6 bit codes are what is used on the RF side of the RileyLink + to communicate with a Medtronic pump. + + */ + public static byte[] CodeSymbols = { + 0x15, + 0x31, + 0x32, + 0x23, + 0x34, + 0x25, + 0x26, + 0x16, + 0x1a, + 0x19, + 0x2a, + 0x0b, + 0x2c, + 0x0d, + 0x0e, + 0x1c + }; + + public static byte[] appendChecksum(final byte[] input) { + if (input == null) { + return null; + } + if (input.length == 0) { + return null; + } + byte[] rval = new byte[input.length+1]; + System.arraycopy(input, 0, rval, 0, input.length); + byte mycrc = CRC.crc8(input); + Log.d(TAG,String.format("Adding checksum 0x%02X to %d byte array from 0x%02X to 0x%02X",mycrc,input.length,input[0],input[input.length-1])); + rval[input.length] = mycrc; + return rval; + } + + public static ArrayList fromBytes(byte[] data) { + ArrayList rval = new ArrayList<>(); + for (int i=0; i data) { + byte[] rval = new byte[data.size()]; + for (int i = 0; i < data.size(); i++) { + rval[i] = data.get(i); + } + return rval; + } + +/* + + (NSData*)encode4b6b:(NSData*)data { + NSMutableData *outData = [NSMutableData data]; + NSMutableData *dataPlusCrc = [data mutableCopy]; + unsigned char crc = [MinimedPacket crcForData:data]; + [dataPlusCrc appendBytes:&crc length:1]; + char codes[16] = {21,49,50,35,52,37,38,22,26,25,42,11,44,13,14,28}; + const unsigned char *inBytes = [dataPlusCrc bytes]; + unsigned int acc = 0x0; + int bitcount = 0; + for (int i=0; i < dataPlusCrc.length; i++) { + acc <<= 6; + acc |= codes[inBytes[i] >> 4]; + bitcount += 6; + + acc <<= 6; + acc |= codes[inBytes[i] & 0x0f]; + bitcount += 6; + + while (bitcount >= 8) { + unsigned char outByte = acc >> (bitcount-8) & 0xff; + [outData appendBytes:&outByte length:1]; + bitcount -= 8; + acc &= (0xffff >> (16-bitcount)); + } + } + if (bitcount > 0) { + acc <<= (8-bitcount); + unsigned char outByte = acc & 0xff; + [outData appendBytes:&outByte length:1]; + } + return outData; + } +*/ + + public static final byte[] codes = new byte[] {21,49,50,35,52,37,38,22,26,25,42,11,44,13,14,28 }; + + /* O(n) lookup. Run on an O(n) translation of a byte-stream, gives O(n**2) performance. Sigh. */ + public static int codeIndex(byte b) { + for (int i=0; i< codes.length; i++) { + if (b == codes[i]) { + return i; + } + } + return -1; + } + + public static byte[] encode4b6b(byte[] data) { + if ((data.length % 2)!=0) { + // Log.e(TAG,"Warning: data is odd number of bytes"); + } + // use arraylists because byte[] is annoying. + ArrayList inData = fromBytes(data); + ArrayList outData = new ArrayList<>(); + + int acc = 0; + int bitcount = 0; + int i; + for (i=0; i> 4) & 0x0f]; + bitcount += 6; + + acc <<= 6; + acc |= codes[inData.get(i) & 0x0f]; + bitcount += 6; + + while (bitcount >= 8) { + byte outByte = (byte)(acc >> (bitcount-8) & 0xff); + outData.add(outByte); + bitcount -= 8; + acc &= (0xffff >> (16 - bitcount)); + } + } + if (bitcount > 0) { + acc <<= 6; + acc |= 0x14; // marks uneven packet boundary. + bitcount += 6; + if (bitcount >= 8) { + byte outByte = (byte)((acc >> (bitcount - 8)) & 0xff); + outData.add(outByte); + bitcount -=8; + // acc &= (0xffff >> (16 - bitcount)); + } + while(bitcount >=8) { + outData.add((byte)0); + bitcount -= 8; + } + } + + + // convert back to byte[] + byte[] rval = toBytes(outData); + + return rval; + + } + + public static void test() { + /* + {0xa7} -> {0xa9, 0x60} + {0xa7, 0x12} -> {0xa9, 0x6c, 0x72} + {0xa7, 0x12, 0xa7} -> {0xa9, 0x6c, 0x72, 0xa9, 0x60} + */ + /* test compare */ + byte[] s1 = {0,1,2}; + byte[] s2 = {2,1,0,3}; + byte[] s3 = {0,1,2,3}; + if (ByteUtil.compare(s1,s1)!=0) { + Log.e(TAG,"test: compare failed."); + } + if (ByteUtil.compare(s1,s2)>=0) { + Log.e(TAG,"test: compare failed."); + } + if (ByteUtil.compare(s2,s1)<=0) { + Log.e(TAG,"test: compare failed."); + } + if (ByteUtil.compare(s1,s3)>=0) { + Log.e(TAG,"test: compare failed."); + } + //testCompose(new byte[] {(byte)0xa7, (byte)0xa7}); + byte[] bs = encode4b6b(new byte[]{(byte) 0xa7}); + byte[] out = new byte[] {(byte)(0xa9),0x65}; + if (ByteUtil.compare(bs,out)!=0) { + Log.e(TAG,"encode Data failed: expected "+ByteUtil.shortHexString(out)+" but got "+ByteUtil.shortHexString(bs)); + } + bs = encode4b6b(new byte[]{(byte) 0xa7, 0x12}); + out = new byte[] {(byte)(0xa9),0x6c,0x72}; + if (ByteUtil.compare(bs,out)!=0) { + Log.e(TAG,"encode Data failed: expected "+ByteUtil.shortHexString(out)+" but got "+ByteUtil.shortHexString(bs)); + } + bs = encode4b6b(new byte[]{(byte) 0xa7, 0x12, (byte) 0xa7}); + out = new byte[] {(byte)(0xa9),0x6c,0x72,(byte)0xa9,0x65}; + if (ByteUtil.compare(bs,out)!=0) { + Log.e(TAG,"encode Data failed: expected "+ByteUtil.shortHexString(out)+" but got "+ByteUtil.shortHexString(bs)); + } + return; + } + + public static byte[] decode4b6b(byte[] raw) throws NumberFormatException { + /* + if ((raw.length % 2) != 0) { + Log.e(TAG,"Warning: data is odd number of bytes"); + } + */ + byte[] rval = new byte[]{}; + int availableBits = 0; + int codingErrors = 0; + int x = 0; + //Log.w(TAG,"decode4b6b: untested code"); + //Log.w(TAG,String.format("Decoding %d bytes: %s",raw.length,ByteUtil.shortHexString(raw))); + for (int i=0; i= 12) { + // take top six + int highcode = (x >> (availableBits - 6)) & 0x3F; + int highIndex = codeIndex((byte) (highcode)); + // take bottom six + int lowcode = (x >> (availableBits - 12)) & 0x3F; + int lowIndex = codeIndex((byte) (lowcode)); + // special case at end of transmission on uneven boundaries: + if ((highIndex >= 0) && (lowIndex >= 0)) { + byte decoded = (byte) ((highIndex << 4) + lowIndex); + rval = ByteUtil.concat(rval, decoded); + /* + Log.d(TAG,String.format("i=%d,x=0x%08X,0x%02X->0x%02X, 0x%02X->0x%02X, result: 0x%02X, %d bits remaining, errors %d, bytes remaining: %s", + i,x,highcode,highIndex, lowcode, lowIndex,decoded,availableBits,codingErrors,ByteUtil.shortHexString(ByteUtil.substring(raw,i+1,raw.length-i-1)))); + */ + } else { + //Log.d(TAG,String.format("i=%d,x=%08X, coding error: highcode=0x%02X, lowcode=0x%02X, %d bits remaining",i,x,highcode,lowcode,availableBits)); + codingErrors++; + } + + availableBits -= 12; + x = x & (0x0000ffff >> (16 - availableBits)); + } else { + //Log.d(TAG,String.format("i=%d, skip: x=0x%08X, available bits %d",i,x,availableBits)); + } + } + if (availableBits !=0) { + if ((availableBits == 4) && (x == 0x05)) { + // normal end + } else { + Log.e(TAG, "decode4b6b: failed clean decode -- extra bits available (not marker)(" + availableBits + ")"); + codingErrors++; + } + } else { + // also normal end. + } + if (codingErrors>0) { + Log.e(TAG, "decode4b6b: "+codingErrors+" coding errors encountered."); + throw new NumberFormatException(); + } + return rval; + } + + public static String toHexString(byte[] array) { + return toHexString(array, 0, array.length); + } + + private final static char[] HEX_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + public static String toHexString(byte[] array, int offset, int length) { + char[] buf = new char[length * 2]; + + int bufIndex = 0; + for (int i = offset; i < offset + length; i++) { + byte b = array[i]; + buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F]; + buf[bufIndex++] = HEX_DIGITS[b & 0x0F]; + } + + return new String(buf); + } + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RadioPacket.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RadioPacket.java new file mode 100644 index 0000000000..b6916e73e9 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RadioPacket.java @@ -0,0 +1,24 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE; + +import com.gxwtech.roundtrip2.util.ByteUtil; +import com.gxwtech.roundtrip2.util.CRC; + +/** + * Created by geoff on 5/22/16. + */ + +public class RadioPacket { + protected byte[] pkt; + public RadioPacket(byte[] pkt) { + this.pkt = pkt; + } + public byte[] getRaw() { + return pkt; + } + public byte[] getEncoded() { + byte[] withCRC = ByteUtil.concat(pkt, CRC.crc8(pkt)); + byte[] encoded = RFTools.encode4b6b(withCRC); + byte[] withNullTerm = ByteUtil.concat(encoded,(byte)0); + return withNullTerm; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RadioResponse.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RadioResponse.java new file mode 100644 index 0000000000..8d27ab9210 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RadioResponse.java @@ -0,0 +1,67 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE; + +import android.util.Log; + +import com.gxwtech.roundtrip2.util.ByteUtil; +import com.gxwtech.roundtrip2.util.CRC; + +/** + * Created by geoff on 5/30/16. + */ +public class RadioResponse { + private static final String TAG = "RadioResponse"; + public boolean decodedOK = false; + public int rssi; + public int responseNumber; + public byte[] decodedPayload = new byte[0]; + public byte receivedCRC; + + public RadioResponse() { + } + + public RadioResponse(byte[] rxData) { + init(rxData); + } + + public boolean isValid() { + if (!decodedOK) { + return false; + } + if (decodedPayload != null) { + if (receivedCRC == CRC.crc8(decodedPayload)) { + return true; + } + } + return false; + } + + public void init(byte[] rxData) { + if (rxData == null) { + return; + } + if (rxData.length < 3) { + // This does not look like something valid heard from a RileyLink device + return; + } + rssi = rxData[0]; + responseNumber = rxData[1]; + byte[] encodedPayload = ByteUtil.substring(rxData, 2, rxData.length - 2); + try { + byte[] decodeThis = RFTools.decode4b6b(encodedPayload); + decodedOK = true; + decodedPayload = ByteUtil.substring(decodeThis, 0, decodeThis.length - 1); + byte calculatedCRC = CRC.crc8(decodedPayload); + receivedCRC = decodeThis[decodeThis.length - 1]; + if (receivedCRC != calculatedCRC) { + Log.e(TAG, String.format("RadioResponse: CRC mismatch, calculated 0x%02x, received 0x%02x", calculatedCRC, receivedCRC)); + } + } catch (NumberFormatException e) { + decodedOK = false; + Log.e(TAG, "Failed to decode radio data: " + ByteUtil.shortHexString(encodedPayload)); + } + } + + public byte[] getPayload() { + return decodedPayload; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RileyLinkBLE.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RileyLinkBLE.java new file mode 100644 index 0000000000..55562fc6a1 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RileyLinkBLE/RileyLinkBLE.java @@ -0,0 +1,428 @@ +package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.content.Intent; +import android.os.SystemClock; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; +import android.widget.Toast; + +import com.gxwtech.roundtrip2.RT2Const; +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.BLECommOperation; +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.BLECommOperationResult; +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.CharacteristicReadOperation; +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.CharacteristicWriteOperation; +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.DescriptorWriteOperation; +import com.gxwtech.roundtrip2.util.HexDump; +import com.gxwtech.roundtrip2.util.ThreadUtil; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.Semaphore; + +/** + * Created by geoff on 5/26/16. + */ +public class RileyLinkBLE { + private static final String TAG = "RileyLinkBLE"; + public boolean gattDebugEnabled = true; + + private BluetoothAdapter bluetoothAdapter; + private BluetoothGattCallback bluetoothGattCallback; + + private final Context context; + private BluetoothManager bluetoothManager; + + private BluetoothDevice rileyLinkDevice; + private BluetoothGatt bluetoothConnectionGatt = null; + + private BLECommOperation mCurrentOperation; + private Semaphore gattOperationSema = new Semaphore(1,true); + + private Runnable radioResponseCountNotified; + + private boolean mIsConnected = false; + + public RileyLinkBLE(final Context context) { + this.context = context; + this.bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); + this.bluetoothAdapter = bluetoothManager.getAdapter(); + + bluetoothGattCallback = new BluetoothGattCallback() { + + @Override + public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + super.onCharacteristicChanged(gatt, characteristic); + if (gattDebugEnabled) { + Log.v(TAG, ThreadUtil.sig() + "onCharacteristicChanged " + GattAttributes.lookup(characteristic.getUuid()) + " " + HexDump.toHexString(characteristic.getValue())); + if (characteristic.getUuid().equals(UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT))) { + Log.d(TAG, "Response Count is " + HexDump.toHexString(characteristic.getValue())); + } + } + if (radioResponseCountNotified != null) { + radioResponseCountNotified.run(); + } + } + + @Override + public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) { + super.onCharacteristicRead(gatt, characteristic, status); + + + final String statusMessage = getGattStatusMessage(status); + if (gattDebugEnabled) { + Log.v(TAG, ThreadUtil.sig() + "onCharacteristicRead (" + GattAttributes.lookup(characteristic.getUuid()) + ") " + + statusMessage + ":" + HexDump.toHexString(characteristic.getValue())); + } + mCurrentOperation.gattOperationCompletionCallback(characteristic.getUuid(),characteristic.getValue()); + } + + @Override + public void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) { + super.onCharacteristicWrite(gatt, characteristic, status); + + final String uuidString = GattAttributes.lookup(characteristic.getUuid()); + if (gattDebugEnabled) { + Log.v(TAG, ThreadUtil.sig() + "onCharacteristicWrite " + getGattStatusMessage(status) + " " + uuidString + " " + HexDump.toHexString(characteristic.getValue())); + } + mCurrentOperation.gattOperationCompletionCallback(characteristic.getUuid(),characteristic.getValue()); + } + + @Override + public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) { + super.onConnectionStateChange(gatt, status, newState); + + // https://github.com/NordicSemiconductor/puck-central-android/blob/master/PuckCentral/app/src/main/java/no/nordicsemi/puckcentral/bluetooth/gatt/GattManager.java#L117 + if (status == 133) { + Log.e(TAG, "Got the status 133 bug, closing gatt"); + disconnect(); + return; + } + + if (gattDebugEnabled) { + final String stateMessage; + if (newState == BluetoothProfile.STATE_CONNECTED) { + stateMessage = "CONNECTED"; + } else if (newState == BluetoothProfile.STATE_CONNECTING) { + stateMessage = "CONNECTING"; + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + stateMessage = "DISCONNECTED"; + } else if (newState == BluetoothProfile.STATE_DISCONNECTING) { + stateMessage = "DISCONNECTING"; + } else { + stateMessage = "UNKNOWN (" + newState + ")"; + } + + Log.w(TAG, "onConnectionStateChange " + getGattStatusMessage(status) + " " + stateMessage); + } + + if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) { + LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(RT2Const.serviceLocal.bluetooth_connected)); + } else { + LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(RT2Const.serviceLocal.bluetooth_disconnected)); + disconnect(); + Log.w(TAG, "Cannot establish Bluetooth connection."); + } + } + + + @Override + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + super.onDescriptorWrite(gatt, descriptor, status); + if (gattDebugEnabled) { + Log.w(TAG, "onDescriptorWrite " + + GattAttributes.lookup(descriptor.getUuid()) + " " + + getGattStatusMessage(status) + + " written: " + HexDump.toHexString(descriptor.getValue())); + } + mCurrentOperation.gattOperationCompletionCallback(descriptor.getUuid(),descriptor.getValue()); + } + + @Override + public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + super.onDescriptorRead(gatt, descriptor, status); + mCurrentOperation.gattOperationCompletionCallback(descriptor.getUuid(),descriptor.getValue()); + if (gattDebugEnabled) { + Log.w(TAG, "onDescriptorRead " + getGattStatusMessage(status) + " status " + descriptor); + } + } + + @Override + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { + super.onMtuChanged(gatt, mtu, status); + if (gattDebugEnabled) { + Log.w(TAG, "onMtuChanged " + mtu + " status " + status); + } + } + + @Override + public void onReadRemoteRssi(final BluetoothGatt gatt, int rssi, int status) { + super.onReadRemoteRssi(gatt, rssi, status); + if (gattDebugEnabled) { + Log.w(TAG, "onReadRemoteRssi " + getGattStatusMessage(status) + ": " + rssi); + } + } + + @Override + public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { + super.onReliableWriteCompleted(gatt, status); + if (gattDebugEnabled) { + Log.w(TAG, "onReliableWriteCompleted status " + status); + } + } + + @Override + public void onServicesDiscovered(final BluetoothGatt gatt, int status) { + super.onServicesDiscovered(gatt, status); + + if (status == BluetoothGatt.GATT_SUCCESS) { + final List services = gatt.getServices(); + // TODO: in here, we need to determine if this Bluetooth device is a RileyLink with appropriate software (subg_rfspy) and set mIsConnected if the GATT is ok. + for (BluetoothGattService service : services) { + final List characteristics = service.getCharacteristics(); + + final UUID uuidService = service.getUuid(); + final String uuidServiceString = uuidService.toString(); + if (gattDebugEnabled) { + String debugString = "Found service: " + GattAttributes.lookup(uuidServiceString, "Unknown device") + " (" + uuidServiceString + ")"; + + for (BluetoothGattCharacteristic character : characteristics) { + final String uuidCharacteristicString = character.getUuid().toString(); + debugString += " - " + GattAttributes.lookup(uuidCharacteristicString); + } + Log.w(TAG, debugString); + } + } + if (gattDebugEnabled) { + Log.w(TAG, "onServicesDiscovered " + getGattStatusMessage(status)); + } + mIsConnected = true; + Intent intent = new Intent(RT2Const.serviceLocal.BLE_services_discovered); + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + } else { + Log.e(TAG, "onServicesDiscovered " + getGattStatusMessage(status)); + } + } + }; + } + + public void registerRadioResponseCountNotification(Runnable notifier) { + radioResponseCountNotified = notifier; + } + + public boolean isConnected() { + return mIsConnected; + } + + public void discoverServices() { + if (bluetoothConnectionGatt.discoverServices()) { + Log.w(TAG, "Starting to discover GATT Services."); + + } else { + Log.e(TAG, "Cannot discover GATT Services."); + } + } + + public boolean enableNotifications() { + BLECommOperationResult result = setNotification_blocking( + UUID.fromString(GattAttributes.SERVICE_RADIO), + UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT)); + if (result.resultCode != BLECommOperationResult.RESULT_SUCCESS) { + Log.e(TAG, "Error setting response count notification"); + return false; + } + return true; + } + + public void findRileyLink(String RileyLinkAddress) { + Log.d(TAG,"Rileylink address: " + RileyLinkAddress); + // Must verify that this is a valid MAC, or crash. + + rileyLinkDevice = bluetoothAdapter.getRemoteDevice(RileyLinkAddress); + // if this succeeds, we get a connection state change callback? + connectGatt(); + } + + // This function must be run on UI thread. + public void connectGatt() { + bluetoothConnectionGatt = rileyLinkDevice.connectGatt(context, true, bluetoothGattCallback); + if (bluetoothConnectionGatt == null) { + Log.e(TAG,"Failed to connect to Bluetooth Low Energy device at "+bluetoothAdapter.getAddress()); + Toast.makeText(context, "No Rileylink at " + bluetoothAdapter.getAddress(), Toast.LENGTH_SHORT).show(); + } else { + if (gattDebugEnabled) { + Log.d(TAG, "Gatt Connected?"); + } + } + } + + public void disconnect() { + mIsConnected = false; + Log.w(TAG, "Closing GATT connection"); + // Close old conenction + if (bluetoothConnectionGatt != null) { + // Not sure if to disconnect or to close first.. + bluetoothConnectionGatt.disconnect(); + bluetoothConnectionGatt.close(); + bluetoothConnectionGatt = null; + } + } + + public BLECommOperationResult setNotification_blocking(UUID serviceUUID, UUID charaUUID) { + BLECommOperationResult rval = new BLECommOperationResult(); + if (bluetoothConnectionGatt != null) { + + try { + gattOperationSema.acquire(); + SystemClock.sleep(1); // attempting to yield thread, to make sequence of events easier to follow + } catch (InterruptedException e) { + Log.e(TAG, "setNotification_blocking: interrupted waiting for gattOperationSema"); + return rval; + } + if (mCurrentOperation != null) { + rval.resultCode = BLECommOperationResult.RESULT_BUSY; + } else { + if (bluetoothConnectionGatt.getService(serviceUUID) == null) { + // Catch if the service is not supported by the BLE device + rval.resultCode = BLECommOperationResult.RESULT_NONE; + Log.e(TAG, "BT Device not supported"); + // TODO: 11/07/2016 UI update for user + } else { + BluetoothGattCharacteristic chara = bluetoothConnectionGatt.getService(serviceUUID).getCharacteristic(charaUUID); + // Tell Android that we want the notifications + bluetoothConnectionGatt.setCharacteristicNotification(chara, true); + List list = chara.getDescriptors(); + if (gattDebugEnabled) { + for (int i = 0; i < list.size(); i++) { + Log.d(TAG, "Found descriptor: " + list.get(i).toString()); + } + } + BluetoothGattDescriptor descr = list.get(0); + // Tell the remote device to send the notifications + mCurrentOperation = new DescriptorWriteOperation(bluetoothConnectionGatt, descr, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + mCurrentOperation.execute(this); + if (mCurrentOperation.timedOut) { + rval.resultCode = BLECommOperationResult.RESULT_TIMEOUT; + } else if (mCurrentOperation.interrupted) { + rval.resultCode = BLECommOperationResult.RESULT_INTERRUPTED; + } else { + rval.resultCode = BLECommOperationResult.RESULT_SUCCESS; + } + } + mCurrentOperation = null; + gattOperationSema.release(); + } + } else { + Log.e(TAG,"setNotification_blocking: not configured!"); + rval.resultCode = BLECommOperationResult.RESULT_NOT_CONFIGURED; + } + return rval; + } + + // call from main + public BLECommOperationResult writeCharacteristic_blocking(UUID serviceUUID, UUID charaUUID, byte[] value) { + BLECommOperationResult rval = new BLECommOperationResult(); + if (bluetoothConnectionGatt != null) { + rval.value = value; + try { + gattOperationSema.acquire(); + SystemClock.sleep(1); // attempting to yield thread, to make sequence of events easier to follow + } catch (InterruptedException e) { + Log.e(TAG,"writeCharacteristic_blocking: interrupted waiting for gattOperationSema"); + return rval; + } + if (mCurrentOperation != null) { + rval.resultCode = BLECommOperationResult.RESULT_BUSY; + } else { + if (bluetoothConnectionGatt.getService(serviceUUID) == null) { + // Catch if the service is not supported by the BLE device + // GGW: Tue Jul 12 01:14:01 UTC 2016: This can also happen if the + // app that created the bluetoothConnectionGatt has been destroyed/created, + // e.g. when the user switches from portrait to landscape. + rval.resultCode = BLECommOperationResult.RESULT_NONE; + Log.e(TAG, "BT Device not supported"); + // TODO: 11/07/2016 UI update for user + } else { + BluetoothGattCharacteristic chara = bluetoothConnectionGatt.getService(serviceUUID).getCharacteristic(charaUUID); + mCurrentOperation = new CharacteristicWriteOperation(bluetoothConnectionGatt, chara, value); + mCurrentOperation.execute(this); + if (mCurrentOperation.timedOut) { + rval.resultCode = BLECommOperationResult.RESULT_TIMEOUT; + } else if (mCurrentOperation.interrupted) { + rval.resultCode = BLECommOperationResult.RESULT_INTERRUPTED; + } else { + rval.resultCode = BLECommOperationResult.RESULT_SUCCESS; + } + } + mCurrentOperation = null; + gattOperationSema.release(); + } + } else { + Log.e(TAG,"writeCharacteristic_blocking: not configured!"); + rval.resultCode = BLECommOperationResult.RESULT_NOT_CONFIGURED; + } + return rval; + } + + public BLECommOperationResult readCharacteristic_blocking(UUID serviceUUID, UUID charaUUID) { + BLECommOperationResult rval = new BLECommOperationResult(); + if (bluetoothConnectionGatt != null) { + try { + gattOperationSema.acquire(); + SystemClock.sleep(1); // attempting to yield thread, to make sequence of events easier to follow + } catch (InterruptedException e) { + Log.e(TAG, "readCharacteristic_blocking: Interrupted waiting for gattOperationSema"); + return rval; + } + if (mCurrentOperation != null) { + rval.resultCode = BLECommOperationResult.RESULT_BUSY; + } else { + BluetoothGattCharacteristic chara = bluetoothConnectionGatt.getService(serviceUUID).getCharacteristic(charaUUID); + mCurrentOperation = new CharacteristicReadOperation(bluetoothConnectionGatt, chara); + mCurrentOperation.execute(this); + if (mCurrentOperation.timedOut) { + rval.resultCode = BLECommOperationResult.RESULT_TIMEOUT; + } else if (mCurrentOperation.interrupted) { + rval.resultCode = BLECommOperationResult.RESULT_INTERRUPTED; + } else { + rval.resultCode = BLECommOperationResult.RESULT_SUCCESS; + rval.value = mCurrentOperation.getValue(); + } + } + mCurrentOperation = null; + gattOperationSema.release(); + } else { + Log.e(TAG,"readCharacteristic_blocking: not configured!"); + rval.resultCode = BLECommOperationResult.RESULT_NOT_CONFIGURED; + } + return rval; + } + + private String getGattStatusMessage(final int status) { + final String statusMessage; + if (status == BluetoothGatt.GATT_SUCCESS) { + statusMessage = "SUCCESS"; + } else if (status == BluetoothGatt.GATT_FAILURE) { + statusMessage = "FAILED"; + } else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) { + statusMessage = "NOT PERMITTED"; + } else if (status == 133) { + statusMessage = "Found the strange 133 bug"; + } else { + statusMessage = "UNKNOWN (" + status + ")"; + } + + return statusMessage; + } + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RoundtripService.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RoundtripService.java new file mode 100644 index 0000000000..91ca9bc825 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RoundtripService.java @@ -0,0 +1,638 @@ +package com.gxwtech.roundtrip2.RoundtripService; + +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Message; +import android.os.Parcel; +import android.os.PowerManager; +import android.preference.PreferenceManager; +import android.support.annotation.Nullable; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; +import android.widget.Toast; + +import com.gxwtech.roundtrip2.RT2Const; +import com.gxwtech.roundtrip2.RoundtripService.RileyLink.PumpManager; +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RFSpy; +import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RileyLinkBLE; +import com.gxwtech.roundtrip2.RoundtripService.Tasks.DiscoverGattServicesTask; +import com.gxwtech.roundtrip2.RoundtripService.Tasks.FetchPumpHistoryTask; +import com.gxwtech.roundtrip2.RoundtripService.Tasks.InitializePumpManagerTask; +import com.gxwtech.roundtrip2.RoundtripService.Tasks.ReadBolusWizardCarbProfileTask; +import com.gxwtech.roundtrip2.RoundtripService.Tasks.ReadISFProfileTask; +import com.gxwtech.roundtrip2.RoundtripService.Tasks.ReadPumpClockTask; +import com.gxwtech.roundtrip2.RoundtripService.Tasks.RetrieveHistoryPageTask; +import com.gxwtech.roundtrip2.RoundtripService.Tasks.ServiceTask; +import com.gxwtech.roundtrip2.RoundtripService.Tasks.ServiceTaskExecutor; +import com.gxwtech.roundtrip2.RoundtripService.Tasks.UpdatePumpStatusTask; +import com.gxwtech.roundtrip2.RoundtripService.Tasks.WakeAndTuneTask; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.ISFTable; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.Page; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.PumpHistoryManager; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.TimeFormat; +import com.gxwtech.roundtrip2.ServiceData.ServiceNotification; +import com.gxwtech.roundtrip2.ServiceData.ServiceResult; +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; +import com.gxwtech.roundtrip2.util.ByteUtil; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; + +/** + * RoundtripService is intended to stay running when the gui-app is closed. + */ +public class RoundtripService extends Service { + private static final String TAG="RoundtripService"; + private static RoundtripService instance; + private static final String WAKELOCKNAME = "com.gxwtech.roundtrip2.RoundtripServiceWakeLock"; + private static volatile PowerManager.WakeLock lockStatic = null; + + private boolean needBluetoothPermission = true; + + private BroadcastReceiver mBroadcastReceiver; + private Context mContext; + private RoundtripServiceIPCConnection serviceConnection; + private BluetoothManager mBluetoothManager; + private BluetoothAdapter mBluetoothAdapter; + + + // saved settings + + public SharedPreferences sharedPref; + private String pumpIDString; + private byte[] pumpIDBytes; + private String mRileylinkAddress; + + // cache of most recently received set of pump history pages. Probably shouldn't be here. + ArrayList mHistoryPages; + PumpHistoryManager pumpHistoryManager; + + + // Our hardware/software connection + public RileyLinkBLE rileyLinkBLE; // android-bluetooth management + private RFSpy rfspy; // interface for 916MHz radio. + public PumpManager pumpManager; // interface to Minimed + private static ServiceTask currentTask = null; + + public RoundtripService() { + super(); + instance = this; + Log.d(TAG, "RoundtripService newly constructed"); + } + + public static RoundtripService getInstance() { + return instance; + } + + public void onCreate() { + super.onCreate(); + Log.d(TAG, "onCreate"); + mContext = getApplicationContext(); + serviceConnection = new RoundtripServiceIPCConnection(mContext); + + //sharedPref = mContext.getSharedPreferences(RT2Const.serviceLocal.sharedPreferencesKey, Context.MODE_PRIVATE); + sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + + // get most recently used pumpID + pumpIDString = sharedPref.getString(RT2Const.serviceLocal.pumpIDKey,"000000"); + pumpIDBytes = ByteUtil.fromHexString(pumpIDString); + if (pumpIDBytes == null) { + Log.e(TAG,"Invalid pump ID? " + ByteUtil.shortHexString(pumpIDBytes)); + pumpIDBytes = new byte[] {0,0,0}; + pumpIDString = "000000"; + } + if (pumpIDBytes.length != 3) { + Log.e(TAG,"Invalid pump ID? " + ByteUtil.shortHexString(pumpIDBytes)); + pumpIDBytes = new byte[] {0,0,0}; + pumpIDString = "000000"; + } + if (pumpIDString.equals("000000")) { + Log.e(TAG,"Using pump ID "+pumpIDString); + } else { + Log.i(TAG,"Using pump ID "+pumpIDString); + } + + // get most recently used RileyLink address + mRileylinkAddress = sharedPref.getString(RT2Const.serviceLocal.rileylinkAddressKey,""); + + pumpHistoryManager = new PumpHistoryManager(getApplicationContext()); + rileyLinkBLE = new RileyLinkBLE(this); + rfspy = new RFSpy(mContext,rileyLinkBLE); + rfspy.startReader(); + pumpManager = new PumpManager(mContext,rfspy,pumpIDBytes); + + mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + /* here we can listen for local broadcasts, then send ourselves + * a specific intent to deal with them, if we wish + */ + if (intent == null) { + Log.e(TAG,"onReceive: received null intent"); + } else { + String action = intent.getAction(); + if (action == null) { + Log.e(TAG,"onReceive: null action"); + } else { + if (action.equals(RT2Const.serviceLocal.bluetooth_connected)) { + Log.w(TAG,"serviceLocal.bluetooth_connected"); + serviceConnection.sendNotification(new ServiceNotification(RT2Const.IPC.MSG_note_FindingRileyLink),null); + ServiceTaskExecutor.startTask(new DiscoverGattServicesTask()); + // If this is successful, + // We will get a broadcast of RT2Const.serviceLocal.BLE_services_discovered + } else if (action.equals(RT2Const.serviceLocal.BLE_services_discovered)) { + Log.w(TAG,"serviceLocal.BLE_services_discovered"); + serviceConnection.sendNotification(new ServiceNotification(RT2Const.IPC.MSG_note_WakingPump),null); + rileyLinkBLE.enableNotifications(); + rfspy.startReader(); // call startReader from outside? + ServiceTask task = new InitializePumpManagerTask(); + ServiceTaskExecutor.startTask(task); + Log.i(TAG, "Announcing RileyLink open For business"); + } else if (action.equals(RT2Const.serviceLocal.ipcBound)) { + // If we still need permission for bluetooth, ask now. + if (needBluetoothPermission) { + sendBLERequestForAccess(); + } + + } else if (RT2Const.IPC.MSG_BLE_accessGranted.equals(action)) { + //initializeLeAdapter(); + //BluetoothInit(); + } else if (RT2Const.IPC.MSG_BLE_accessDenied.equals(action)) { + stopSelf(); // This will stop the service. + } else if (action.equals(RT2Const.IPC.MSG_PUMP_tunePump)) { + doTunePump(); + } else if (action.equals(RT2Const.IPC.MSG_PUMP_quickTune)) { + doTunePump(); + } else if (action.equals(RT2Const.IPC.MSG_PUMP_fetchHistory)) { + mHistoryPages = pumpManager.getAllHistoryPages(); + final boolean savePages = true; + if (savePages) { + for (int i = 0; i < mHistoryPages.size(); i++) { + String filename = "PumpHistoryPage-" + i; + Log.w(TAG, "Saving history page to file " + filename); + FileOutputStream outputStream; + try { + outputStream = openFileOutput(filename, 0); + byte[] rawData= mHistoryPages.get(i).getRawData(); + if (rawData != null) { + outputStream.write(rawData); + } + outputStream.close(); + } catch (FileNotFoundException fnf) { + fnf.printStackTrace(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + + } + } + + Message msg = Message.obtain(null, RT2Const.IPC.MSG_IPC, 0, 0); + // Create a bundle with the data + Bundle bundle = new Bundle(); + bundle.putString(RT2Const.IPC.messageKey, RT2Const.IPC.MSG_PUMP_history); + ArrayList packedPages = new ArrayList<>(); + for (Page page : mHistoryPages) { + packedPages.add(page.pack()); + } + bundle.putParcelableArrayList(RT2Const.IPC.MSG_PUMP_history_key, packedPages); + + // save it to SQL. + pumpHistoryManager.clearDatabase(); + pumpHistoryManager.initFromPages(bundle); + // write html page to documents folder + pumpHistoryManager.writeHtmlPage(); + + // Set payload + msg.setData(bundle); + serviceConnection.sendMessage(msg,null/*broadcast*/); + Log.d(TAG, "sendMessage: sent Full history report"); + } else if (RT2Const.IPC.MSG_PUMP_fetchSavedHistory.equals(action)) { + Log.i(TAG,"Fetching saved history"); + FileInputStream inputStream; + ArrayList storedHistoryPages = new ArrayList<>(); + for (int i = 0; i < 16; i++) { + + String filename = "PumpHistoryPage-" + i; + try { + inputStream = openFileInput(filename); + byte[] buffer = new byte[1024]; + int numRead = inputStream.read(buffer, 0, 1024); + if (numRead == 1024) { + Page p = new Page(); + //p.parseFrom(buffer, PumpModel.MM522); + p.parseFrom(buffer, PumpModel.MM522); + storedHistoryPages.add(p); + } else { + Log.e(TAG, filename + " error: short file"); + } + } catch (FileNotFoundException fnf) { + Log.e(TAG, "Failed to open " + filename + " for reading."); + } catch (IOException e) { + Log.e(TAG, "Failed to read " + filename); + } catch (Exception e) { + e.printStackTrace(); + } + } + mHistoryPages = storedHistoryPages; + if (storedHistoryPages.isEmpty()) { + Log.e(TAG, "No stored history pages loaded"); + } else { + Message msg = Message.obtain(null, RT2Const.IPC.MSG_IPC, 0, 0); + // Create a bundle with the data + Bundle bundle = new Bundle(); + bundle.putString(RT2Const.IPC.messageKey, RT2Const.IPC.MSG_PUMP_history); + ArrayList packedPages = new ArrayList<>(); + for (Page page : mHistoryPages) { + packedPages.add(page.pack()); + } + bundle.putParcelableArrayList(RT2Const.IPC.MSG_PUMP_history_key, packedPages); + + // save it to SQL. + pumpHistoryManager.clearDatabase(); + pumpHistoryManager.initFromPages(bundle); + // write html page to documents folder + pumpHistoryManager.writeHtmlPage(); + + // Set payload + msg.setData(bundle); + serviceConnection.sendMessage(msg,null/*broadcast*/); + + } + } else if (RT2Const.IPC.MSG_ServiceCommand.equals(action)) { + Bundle bundle = intent.getBundleExtra(RT2Const.IPC.bundleKey); + + handleIncomingServiceTransport(new ServiceTransport(bundle)); + } else if (RT2Const.serviceLocal.INTENT_sessionCompleted.equals(action)) { + Bundle bundle = intent.getBundleExtra(RT2Const.IPC.bundleKey); + if (bundle != null) { + ServiceTransport transport = new ServiceTransport(bundle); + serviceConnection.sendTransport(transport, transport.getSenderHashcode()); + } else { + Log.e(TAG,"sessionCompleted: no bundle!"); + } + } else { + Log.e(TAG, "Unhandled broadcast: action=" + action); + } + } + } + } + }; + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(RT2Const.serviceLocal.bluetooth_connected); + intentFilter.addAction(RT2Const.serviceLocal.bluetooth_disconnected); + intentFilter.addAction(RT2Const.serviceLocal.BLE_services_discovered); + intentFilter.addAction(RT2Const.serviceLocal.ipcBound); + intentFilter.addAction(RT2Const.IPC.MSG_BLE_accessGranted); + intentFilter.addAction(RT2Const.IPC.MSG_BLE_accessDenied); + intentFilter.addAction(RT2Const.IPC.MSG_BLE_useThisDevice); + intentFilter.addAction(RT2Const.IPC.MSG_PUMP_tunePump); + intentFilter.addAction(RT2Const.IPC.MSG_PUMP_fetchHistory); + intentFilter.addAction(RT2Const.IPC.MSG_PUMP_useThisAddress); + intentFilter.addAction(RT2Const.IPC.MSG_PUMP_fetchSavedHistory); + intentFilter.addAction(RT2Const.IPC.MSG_ServiceCommand); + intentFilter.addAction(RT2Const.serviceLocal.INTENT_sessionCompleted); + + LocalBroadcastManager.getInstance(mContext).registerReceiver(mBroadcastReceiver, intentFilter); + + Log.d(TAG, "onCreate(): It's ALIVE!"); + } + + @Override + public boolean onUnbind(Intent intent) { + Log.w(TAG,"onUnbind"); + return super.onUnbind(intent); + } + + @Override + public void onRebind(Intent intent) { + Log.w(TAG,"onRebind"); + super.onRebind(intent); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + Log.w(TAG,"onConfigurationChanged"); + super.onConfigurationChanged(newConfig); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return serviceConnection.doOnBind(intent); + } + + // Here is where the wake-lock begins: + // We've received a service startCommand, we grab the lock. + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.d(TAG, "onStartCommand"); + if (intent != null) { + PowerManager.WakeLock lock = getLock(this.getApplicationContext()); + + if (!lock.isHeld() || (flags & START_FLAG_REDELIVERY) != 0) { + lock.acquire(); + } + + // This will end up running onHandleIntent + super.onStartCommand(intent, flags, startId); + } else { + Log.e(TAG, "Received null intent?"); + } + BluetoothInit(); // this kicks off our process of device discovery. + return (START_REDELIVER_INTENT | START_STICKY); + } + + synchronized private static PowerManager.WakeLock getLock(Context context) { + if (lockStatic == null) { + PowerManager mgr = + (PowerManager) context.getSystemService(Context.POWER_SERVICE); + + lockStatic = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCKNAME); + lockStatic.setReferenceCounted(true); + } + + return lockStatic; + } + + // FIXME: This needs to be run in a session so that is interruptable, has a separate thread, etc. + private void doTunePump() { + double lastGoodFrequency = sharedPref.getFloat(RT2Const.serviceLocal.prefsLastGoodPumpFrequency,(float)0.0); + double newFrequency; + if (lastGoodFrequency != 0.0) { + Log.i(TAG,String.format("Checking for pump near last saved frequency of %.2fMHz",lastGoodFrequency)); + // we have an old frequency, so let's start there. + newFrequency = pumpManager.quickTuneForPump(lastGoodFrequency); + if (newFrequency == 0.0) { + // quick scan failed to find pump. Try full scan + Log.w(TAG,String.format("Failed to find pump near last saved frequency, doing full scan")); + newFrequency = pumpManager.tuneForPump(); + } + } else { + Log.w(TAG,"No saved frequency for pump, doing full scan."); + // we don't have a saved frequency, so do the full scan. + newFrequency = pumpManager.tuneForPump(); + + } + if ((newFrequency!=0.0) && (newFrequency != lastGoodFrequency)) { + Log.i(TAG,String.format("Saving new pump frequency of %.2fMHz",newFrequency)); + SharedPreferences.Editor ed = sharedPref.edit(); + ed.putFloat(RT2Const.serviceLocal.prefsLastGoodPumpFrequency, (float)newFrequency); + ed.apply(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.e(TAG, "I die! I die!"); + } + + /* private functions */ + + void BluetoothInit() { + if (mBluetoothManager == null) { + mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); + if (mBluetoothManager == null) { + Log.e(TAG, "Unable to initialize BluetoothManager."); + } + } + // Ensures Bluetooth is available on the device and it is enabled. If not, + // displays a dialog requesting user permission to enable Bluetooth. + if ((mBluetoothAdapter==null) || (!mBluetoothAdapter.isEnabled())) { + sendBLERequestForAccess(); + } else { + needBluetoothPermission = false; + initializeLeAdapter(); + } + } + + public boolean initializeLeAdapter() { + Log.d(TAG,"initializeLeAdapter: attempting to get an adapter"); + mBluetoothAdapter = mBluetoothManager.getAdapter(); + if (mBluetoothAdapter == null) { + Log.e(TAG, "Unable to obtain a BluetoothAdapter."); + return false; + } else if (!mBluetoothAdapter.isEnabled()) { + // NOTE: This does not work! + Log.e(TAG, "Bluetooth is not enabled."); + } + return true; + } + + private void setPumpIDString(String idString) { + if (idString.length() != 6) { + Log.e(TAG,"setPumpIDString: invalid pump id string: " + idString); + } + pumpIDString = idString; + pumpIDBytes = ByteUtil.fromHexString(pumpIDString); + SharedPreferences prefs = mContext.getSharedPreferences(RT2Const.serviceLocal.sharedPreferencesKey, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(RT2Const.serviceLocal.pumpIDKey,pumpIDString); + editor.apply(); + Log.i(TAG,"setPumpIDString: saved pumpID "+pumpIDString); + } + + private void sendBLERequestForAccess() { + //serviceConnection.sendMessage(RT2Const.IPC.MSG_BLE_requestAccess); + } + + private void reportPumpFound() { + //serviceConnection.sendMessage(RT2Const.IPC.MSG_PUMP_pumpFound); + } + + public void setCurrentTask(ServiceTask task) { + if (currentTask == null) { + currentTask = task; + } else { + Log.e(TAG,"setCurrentTask: Cannot replace current task"); + } + } + + public void finishCurrentTask(ServiceTask task) { + if (task != currentTask) { + Log.e(TAG,"finishCurrentTask: task does not match"); + } + // hack to force deep copy of transport contents + ServiceTransport transport = task.getServiceTransport().clone(); + + if (transport.hasServiceResult()) { + sendServiceTransportResponse(transport,transport.getServiceResult()); + } + currentTask = null; + } + + private void handleIncomingServiceTransport(ServiceTransport serviceTransport) { + if (serviceTransport.getServiceCommand().isPumpCommand()) { + switch (serviceTransport.getOriginalCommandName()) { + case "ReadPumpClock": + ServiceTaskExecutor.startTask(new ReadPumpClockTask(serviceTransport)); + break; + case "FetchPumpHistory": + ServiceTaskExecutor.startTask(new FetchPumpHistoryTask(serviceTransport)); + break; + case "RetrieveHistoryPage": + ServiceTask task = new RetrieveHistoryPageTask(serviceTransport); + ServiceTaskExecutor.startTask(task); + break; + case "ReadISFProfile": + ServiceTaskExecutor.startTask(new ReadISFProfileTask(serviceTransport)); + /* + ISFTable table = pumpManager.getPumpISFProfile(); + ServiceResult result = new ServiceResult(); + if (table.isValid()) { + // convert from ISFTable to ISFProfile + Bundle map = result.getMap(); + map.putIntArray("times", table.getTimes()); + map.putFloatArray("rates", table.getRates()); + map.putString("ValidDate", TimeFormat.standardFormatter().print(table.getValidDate())); + result.setMap(map); + result.setResultOK(); + } + sendServiceTransportResponse(serviceTransport,result); + */ + break; + case "ReadBolusWizardCarbProfile": + ServiceTaskExecutor.startTask(new ReadBolusWizardCarbProfileTask()); + break; + case "UpdatePumpStatus": + ServiceTaskExecutor.startTask(new UpdatePumpStatusTask()); + break; + case "WakeAndTune": + ServiceTaskExecutor.startTask(new WakeAndTuneTask()); + default: + Log.e(TAG,"Failed to handle pump command: " + serviceTransport.getOriginalCommandName()); + break; + } + } else { + switch (serviceTransport.getOriginalCommandName()) { + case "SetPumpID": + // This one is a command to RoundtripService, not to the PumpManager + String pumpID = serviceTransport.getServiceCommand().getMap().getString("pumpID", ""); + ServiceResult result = new ServiceResult(); + if ((pumpID != null) && (pumpID.length() == 6)) { + setPumpIDString(pumpID); + result.setResultOK(); + } else { + Log.e(TAG, "handleIncomingServiceTransport: SetPumpID bundle missing 'pumpID' value"); + result.setResultError(-1, "Invalid parameter (missing pumpID)"); + } + sendServiceTransportResponse(serviceTransport, result); + break; + case "UseThisRileylink": + // If we are not connected, connect using the given address. + // If we are connected and the addresses differ, disconnect, connect to new. + // If we are connected and the addresses are the same, ignore. + String deviceAddress = serviceTransport.getServiceCommand().getMap().getString("rlAddress", ""); + if ("".equals(deviceAddress)) { + Log.e(TAG, "handleIPCMessage: null RL address passed"); + } else { + reconfigureRileylink(deviceAddress); + } + break; + default: + Log.e(TAG, "handleIncomingServiceTransport: Failed to handle service command '" + serviceTransport.getOriginalCommandName() + "'"); + break; + } + } + } + + // returns true if our Rileylink configuration changed + public boolean reconfigureRileylink(String deviceAddress) { + if (rileyLinkBLE.isConnected()) { + if (deviceAddress.equals(mRileylinkAddress)) { + Log.i(TAG, "No change to RL address. Not reconnecting."); + return false; + } else { + Log.w(TAG, "Disconnecting from old RL (" + mRileylinkAddress + "), reconnecting to new: " + deviceAddress); + rileyLinkBLE.disconnect(); + // prolly need to shut down listening thread too? + SharedPreferences.Editor ed = sharedPref.edit(); + ed.putString("rlAddress", deviceAddress); + ed.apply(); + mRileylinkAddress = deviceAddress; + rileyLinkBLE.findRileyLink(mRileylinkAddress); + return true; + } + } else { + Toast.makeText(mContext, "Using RL " + deviceAddress, Toast.LENGTH_SHORT).show(); + Log.d(TAG, "handleIPCMessage: Using RL " + deviceAddress); + if (mBluetoothAdapter == null) { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + } + if (mBluetoothAdapter != null) { + if (mBluetoothAdapter.isEnabled()) { + // FIXME: this may be a long running function: + rileyLinkBLE.findRileyLink(deviceAddress); + // If successful, we will get a broadcast from RileyLinkBLE: RT2Const.serviceLocal.bluetooth_connected + return true; + } else { + Log.e(TAG, "Bluetooth is not enabled."); + return false; + } + } else { + Log.e(TAG, "Failed to get adapter"); + return false; + } + } + } + + public void announceProgress(int progressPercent) { + if (currentTask != null) { + ServiceNotification note = new ServiceNotification(RT2Const.IPC.MSG_note_TaskProgress); + note.getMap().putInt("progress",progressPercent); + note.getMap().putString("task",currentTask.getServiceTransport().getOriginalCommandName()); + Integer senderHashcode = currentTask.getServiceTransport().getSenderHashcode(); + serviceConnection.sendNotification(note, senderHashcode); + } else { + Log.e(TAG,"announceProgress: No current task"); + } + } + + public void sendServiceTransportResponse(ServiceTransport transport, ServiceResult serviceResult) { + // get the key (hashcode) of the client who requested this + Integer clientHashcode = transport.getSenderHashcode(); + // make a new bundle to send as the message data + transport.setServiceResult(serviceResult); + transport.setTransportType(RT2Const.IPC.MSG_ServiceResult); + serviceConnection.sendTransport(transport,clientHashcode); + } + + public boolean sendNotification(ServiceNotification notification, Integer clientHashcode) { + return serviceConnection.sendNotification(notification, clientHashcode); + } + + public void saveHistoryPage(int pagenumber, Page page) { + if ((page == null) || (page.getRawData() == null)) { + return; + } + String filename = "history-" + pagenumber; + FileOutputStream os; + try { + os = openFileOutput(filename, Context.MODE_PRIVATE); + os.write(page.getRawData()); + os.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} + diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RoundtripServiceIPCConnection.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RoundtripServiceIPCConnection.java new file mode 100644 index 0000000000..73d2ac508e --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/RoundtripServiceIPCConnection.java @@ -0,0 +1,165 @@ +package com.gxwtech.roundtrip2.RoundtripService; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; + +import com.gxwtech.roundtrip2.RT2Const; +import com.gxwtech.roundtrip2.ServiceData.ServiceNotification; +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; + +import java.util.HashMap; + +/** + * Created by geoff on 6/11/16. + */ +public class RoundtripServiceIPCConnection { + private static final String TAG = "RTServiceIPC"; + private Context context; + //private ArrayList mClients = new ArrayList<>(); + private HashMap mClients = new HashMap<>(); + + private final Messenger mMessenger = new Messenger(new IncomingHandler()); + + public RoundtripServiceIPCConnection(Context context) { + this.context = context; + } + + class IncomingHandler extends Handler { + @Override + public void handleMessage(Message msg) { + Log.d(TAG, "handleMessage: Received message " + msg); + Bundle bundle = msg.getData(); + switch(msg.what) { + // This just helps sub-divide the message processing + case RT2Const.IPC.MSG_registerClient: + // send a reply, to let them know we're listening. + Message myReply = Message.obtain(null, RT2Const.IPC.MSG_clientRegistered,0,0); + try { + msg.replyTo.send(myReply); + mClients.put(mClients.hashCode(),msg.replyTo); + Log.v(TAG,"handleMessage: Registered client"); + } catch (RemoteException e) { + // I guess they aren't registered after all... + Log.e(TAG,"handleMessage: failed to send acknowledgement of registration"); + } + + break; + case RT2Const.IPC.MSG_unregisterClient: + Log.v(TAG,"Unregistered client"); + mClients.remove(msg.replyTo.hashCode()); + break; + case RT2Const.IPC.MSG_IPC: + // As the current thread is likely a GUI thread from some app, + // rebroadcast the message as a local item. + // Convert from Message to Intent + if (msg.replyTo != null) { + try { + ServiceTransport transport = new ServiceTransport(bundle); + Log.d(TAG, "Service received IPC message" + transport.describeContentsShort()); + transport.setSenderHashcode(msg.replyTo.hashCode()); + Intent intent = new Intent(transport.getTransportType()); + intent.putExtra(RT2Const.IPC.bundleKey, transport.getMap()); + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + } catch (IllegalArgumentException e) { + // This can happen on screen tilts... what else is wrong? + Log.e(TAG,"Malformed service bundle: " + bundle.toString()); + } + } + break; + /* + case RT2Const.MSG_ping: + String hello = (String)bundle.get("key_hello"); + Toast.makeText(mContext,hello, Toast.LENGTH_SHORT).show(); + break; + */ + default: + Log.e(TAG,"handleMessage: unknown 'what' in message: "+msg.what); + super.handleMessage(msg); + } + } + } + + public IBinder doOnBind(Intent intent) { + Log.d(TAG, "onBind"); + LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(RT2Const.serviceLocal.ipcBound)); + return mMessenger.getBinder(); + } + + public boolean sendNotification(ServiceNotification notification, Integer clientHashcode) { + ServiceTransport transport = new ServiceTransport(); + transport.setServiceNotification(notification); + transport.setTransportType(RT2Const.IPC.MSG_ServiceNotification); + return sendTransport(transport,clientHashcode); + } + + // if clientHashcode is null, broadcast to all clients + public boolean sendMessage(Message msg, Integer clientHashcode) { + Messenger clientMessenger = null; + + if (mClients.isEmpty()) { + if (msg.what == RT2Const.IPC.MSG_IPC) { + Log.e(TAG, "sendMessage: no clients, cannot send: " + msg.getData().getString(RT2Const.IPC.messageKey, "(unknown)")); + } else { + Log.e(TAG, "sendMessage: no clients, cannot send: what=" + msg.what); + } + } else { + if (clientHashcode != null) { + clientMessenger = mClients.get(clientHashcode); + } + try { + if (clientMessenger != null) { + // sending to just one client + clientMessenger.send(msg); + } else { + // send to all clients + for (Integer clientHash : mClients.keySet()) { + Message m2 = Message.obtain(msg); + mClients.get(clientHash).send(m2); + } + } + } catch (RemoteException e) { + e.printStackTrace(); + } + /* + catch (IllegalStateException e) { + // This happens every time we are in a bluetooth operation and the screen is turned. + Log.e(TAG,"sendMessage: IllegalStateException"); + } + */ + } + return true; + } + + /* + public void sendMessage(String messageType) { + Message msg = Message.obtain(null, RT2Const.IPC.MSG_IPC,0,0); + // Create a bundle with the data + Bundle bundle = new Bundle(); + bundle.putString(RT2Const.IPC.messageKey, messageType); + + // Set payload + msg.setData(bundle); + sendMessage(msg, null); + Log.d(TAG,"sendMessage: sent "+messageType); + } + */ + + public boolean sendTransport(ServiceTransport transport, Integer clientHashcode) { + Message msg = Message.obtain(null, RT2Const.IPC.MSG_IPC,0,0); + // Set payload + msg.setData(transport.getMap()); + Log.d(TAG,"Service sending message to " + String.valueOf(clientHashcode) + ": " + transport.describeContentsShort()); + if ((clientHashcode != null) && (clientHashcode == 0)) { + return sendMessage(msg, null); + } + return sendMessage(msg, clientHashcode); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/DiscoverGattServicesTask.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/DiscoverGattServicesTask.java new file mode 100644 index 0000000000..8276279fb0 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/DiscoverGattServicesTask.java @@ -0,0 +1,15 @@ +package com.gxwtech.roundtrip2.RoundtripService.Tasks; + +import com.gxwtech.roundtrip2.RoundtripService.RoundtripService; + +/** + * Created by geoff on 7/9/16. + */ +public class DiscoverGattServicesTask extends ServiceTask { + public DiscoverGattServicesTask() {} + + @Override + public void run() { + RoundtripService.getInstance().rileyLinkBLE.discoverServices(); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/FetchPumpHistoryTask.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/FetchPumpHistoryTask.java new file mode 100644 index 0000000000..8d9f529462 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/FetchPumpHistoryTask.java @@ -0,0 +1,40 @@ +package com.gxwtech.roundtrip2.RoundtripService.Tasks; + +import com.gxwtech.roundtrip2.RoundtripService.RoundtripService; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.Page; +import com.gxwtech.roundtrip2.ServiceData.FetchPumpHistoryResult; +import com.gxwtech.roundtrip2.ServiceData.RetrieveHistoryPageResult; +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; + +import java.net.MalformedURLException; +import java.util.ArrayList; + +/** + * Created by geoff on 7/16/16. + */ +public class FetchPumpHistoryTask extends PumpTask { + public FetchPumpHistoryTask() { } + public FetchPumpHistoryTask(ServiceTransport transport) { + super(transport); + } + private FetchPumpHistoryResult result = new FetchPumpHistoryResult(); + + @Override + public void run() { + ArrayList ra = new ArrayList<>(); + for (int i=0; i<16; i++) { + Page page = RoundtripService.getInstance().pumpManager.getPumpHistoryPage(i); + if (page != null) { + ra.add(page); + RoundtripService.getInstance().saveHistoryPage(i,page); + } + } + + result.setMap(getServiceTransport().getServiceResult().getMap()); + result.setResultOK(); + result.setPageArray(ra); + getServiceTransport().setServiceResult(result); + } + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/InitializePumpManagerTask.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/InitializePumpManagerTask.java new file mode 100644 index 0000000000..7b5200259e --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/InitializePumpManagerTask.java @@ -0,0 +1,42 @@ +package com.gxwtech.roundtrip2.RoundtripService.Tasks; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import com.gxwtech.roundtrip2.RT2Const; +import com.gxwtech.roundtrip2.RoundtripService.RileyLink.PumpManager; +import com.gxwtech.roundtrip2.RoundtripService.RoundtripService; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; +import com.gxwtech.roundtrip2.ServiceData.ServiceNotification; +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; + +/** + * Created by geoff on 7/9/16. + * + * This class is intended to be run by the Service, for the Service. + * Not intended for clients to run. + */ +public class InitializePumpManagerTask extends ServiceTask { + private static final String TAG = "InitPumpManagerTask"; + public InitializePumpManagerTask() { super(); } + public InitializePumpManagerTask(ServiceTransport transport) { super(transport); } + + @Override + public void run() { + SharedPreferences sharedPref = RoundtripService.getInstance().getApplicationContext().getSharedPreferences(RT2Const.serviceLocal.sharedPreferencesKey, Context.MODE_PRIVATE); + double lastGoodFrequency = sharedPref.getFloat(RT2Const.serviceLocal.prefsLastGoodPumpFrequency,(float)0.0); + if (lastGoodFrequency != 0) { + Log.i(TAG,String.format("Setting radio frequency to %.2fMHz",lastGoodFrequency)); + RoundtripService.getInstance().pumpManager.setRadioFrequencyForPump(lastGoodFrequency); + } + + PumpModel reportedPumpModel = RoundtripService.getInstance().pumpManager.getPumpModel(); + if (!reportedPumpModel.equals(PumpModel.UNSET)) { + RoundtripService.getInstance().sendNotification(new ServiceNotification(RT2Const.IPC.MSG_PUMP_pumpFound),null); + } else { + RoundtripService.getInstance().sendNotification(new ServiceNotification(RT2Const.IPC.MSG_PUMP_pumpLost),null); + } + RoundtripService.getInstance().sendNotification(new ServiceNotification(RT2Const.IPC.MSG_note_Idle),null); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/PumpTask.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/PumpTask.java new file mode 100644 index 0000000000..5351ad42d7 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/PumpTask.java @@ -0,0 +1,13 @@ +package com.gxwtech.roundtrip2.RoundtripService.Tasks; + +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; + +/** + * Created by geoff on 7/10/16. + */ +public class PumpTask extends ServiceTask { + public PumpTask() {super();} + public PumpTask(ServiceTransport transport) { + super(transport); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ReadBolusWizardCarbProfileTask.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ReadBolusWizardCarbProfileTask.java new file mode 100644 index 0000000000..f887cbfc19 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ReadBolusWizardCarbProfileTask.java @@ -0,0 +1,24 @@ +package com.gxwtech.roundtrip2.RoundtripService.Tasks; + +import com.gxwtech.roundtrip2.RoundtripService.RoundtripService; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpMessage; +import com.gxwtech.roundtrip2.ServiceData.ServiceResult; +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; + +/** + * Created by geoff on 7/10/16. + */ +public class ReadBolusWizardCarbProfileTask extends PumpTask { + public ReadBolusWizardCarbProfileTask() { super(); } + public ReadBolusWizardCarbProfileTask(ServiceTransport transport) { + super(transport); + } + + @Override + public void run() { + PumpMessage msg = RoundtripService.getInstance().pumpManager.getBolusWizardCarbProfile(); + ServiceResult result = getServiceTransport().getServiceResult(); + // interpret msg here. + getServiceTransport().setServiceResult(result); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ReadISFProfileTask.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ReadISFProfileTask.java new file mode 100644 index 0000000000..554e894674 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ReadISFProfileTask.java @@ -0,0 +1,37 @@ +package com.gxwtech.roundtrip2.RoundtripService.Tasks; + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.RoundtripService; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.ISFTable; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.TimeFormat; +import com.gxwtech.roundtrip2.ServiceData.ServiceResult; +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; + +/** + * Created by geoff on 7/10/16. + */ +public class ReadISFProfileTask extends PumpTask { + public ReadISFProfileTask() {} + public ReadISFProfileTask(ServiceTransport transport) { super(transport); } + @Override + public void preOp() { + } + + @Override + public void run() { + ISFTable table = RoundtripService.getInstance().pumpManager.getPumpISFProfile(); + ServiceResult result = getServiceTransport().getServiceResult(); + if (table.isValid()) { + // convert from ISFTable to ISFProfile + Bundle map = result.getMap(); + map.putIntArray("times", table.getTimes()); + map.putFloatArray("rates", table.getRates()); + map.putString("ValidDate", TimeFormat.standardFormatter().print(table.getValidDate())); + result.setMap(map); + result.setResultOK(); + getServiceTransport().setServiceResult(result); + } + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ReadPumpClockTask.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ReadPumpClockTask.java new file mode 100644 index 0000000000..9a45e41509 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ReadPumpClockTask.java @@ -0,0 +1,29 @@ +package com.gxwtech.roundtrip2.RoundtripService.Tasks; + +import android.util.Log; + +import com.gxwtech.roundtrip2.RoundtripService.RoundtripService; +import com.gxwtech.roundtrip2.ServiceData.ReadPumpClockResult; +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; + +/** + * Created by geoff on 7/9/16. + */ +public class ReadPumpClockTask extends PumpTask { + private static final String TAG = "ReadPumpClockTask"; + public ReadPumpClockTask() { } + public ReadPumpClockTask(ServiceTransport transport) { + super(transport); + } + + @Override + public void run() { + ReadPumpClockResult pumpResponse = RoundtripService.getInstance().pumpManager.getPumpRTC(); + if (pumpResponse != null) { + Log.i(TAG, "ReadPumpClock: " + pumpResponse.getTimeString()); + } else { + Log.e(TAG, "handleServiceCommand(" + mTransport.getOriginalCommandName() + ") pumpResponse is null"); + } + getServiceTransport().setServiceResult(pumpResponse); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/RetrieveHistoryPageTask.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/RetrieveHistoryPageTask.java new file mode 100644 index 0000000000..87c8efa485 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/RetrieveHistoryPageTask.java @@ -0,0 +1,37 @@ +package com.gxwtech.roundtrip2.RoundtripService.Tasks; + +import com.gxwtech.roundtrip2.RoundtripService.RoundtripService; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.Page; +import com.gxwtech.roundtrip2.ServiceData.RetrieveHistoryPageResult; +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; + +/** + * Created by geoff on 7/9/16. + */ +public class RetrieveHistoryPageTask extends PumpTask { + public RetrieveHistoryPageTask() { } + public RetrieveHistoryPageTask(ServiceTransport transport) { + super(transport); + } + private Page page; + private RetrieveHistoryPageResult result; + private int pageNumber; + + @Override + public void preOp() { + // This is to avoid allocating any memory from async thread, though I'm not sure it's necessary. + RetrieveHistoryPageResult result = new RetrieveHistoryPageResult(); + getServiceTransport().setServiceResult(result); + } + + @Override + public void run() { + pageNumber = mTransport.getServiceCommand().getMap().getInt("pageNumber"); + page = RoundtripService.getInstance().pumpManager.getPumpHistoryPage(pageNumber); + result = (RetrieveHistoryPageResult) getServiceTransport().getServiceResult(); + result.setResultOK(); + result.setPageNumber(pageNumber); + result.setPageBundle(page.pack()); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ServiceTask.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ServiceTask.java new file mode 100644 index 0000000000..2b7c0ea546 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ServiceTask.java @@ -0,0 +1,50 @@ +package com.gxwtech.roundtrip2.RoundtripService.Tasks; + +import android.os.AsyncTask; +import android.util.Log; + +import com.gxwtech.roundtrip2.RoundtripService.RoundtripService; +import com.gxwtech.roundtrip2.ServiceData.ServiceResult; +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; + +/** + * Created by geoff on 7/9/16. + */ +public class ServiceTask implements Runnable { + private static final String TAG = "ServiceTask(base)"; + protected ServiceTransport mTransport; + public boolean completed = false; + public ServiceTask() { + init(new ServiceTransport()); + } + public ServiceTask(ServiceTransport transport) { + init(transport); + } + + public void init(ServiceTransport transport) { + mTransport = transport; + } + + @Override + public void run() { + } + + public void preOp() { + // This function is called by UI thread before running asynch thread. + } + + public void postOp() { + // This function is called by UI thread after running asynch thread. + } + + public ServiceTransport getServiceTransport() { + return mTransport; + } + + /* + protected void sendResponse(ServiceResult result) { + RoundtripService.getInstance().sendServiceTransportResponse(mTransport,result); + } + */ +} + diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ServiceTaskExecutor.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ServiceTaskExecutor.java new file mode 100644 index 0000000000..2817ba1517 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/ServiceTaskExecutor.java @@ -0,0 +1,42 @@ +package com.gxwtech.roundtrip2.RoundtripService.Tasks; + +import android.util.Log; + +import com.gxwtech.roundtrip2.RoundtripService.RoundtripService; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Created by geoff on 7/9/16. + */ +public class ServiceTaskExecutor extends ThreadPoolExecutor { + private static final String TAG = "ServiceTaskExecutor"; + private static ServiceTaskExecutor instance; + private static LinkedBlockingQueue taskQueue = new LinkedBlockingQueue<>(); + static { + instance = new ServiceTaskExecutor(); + } + private ServiceTaskExecutor() { + super(1,1,10000, TimeUnit.MILLISECONDS,taskQueue); + } + public static ServiceTask startTask(ServiceTask task) { + instance.execute(task); // task will be run on async thread from pool. + return task; + } + protected void beforeExecute(Thread t, Runnable r) { + // This is run on either caller UI thread or Service UI thread. + ServiceTask task = (ServiceTask) r; + Log.v(TAG,"About to run task " + task.getClass().getSimpleName()); + RoundtripService.getInstance().setCurrentTask(task); + task.preOp(); + } + protected void afterExecute(Runnable r, Throwable t) { + // This is run on either caller UI thread or Service UI thread. + ServiceTask task = (ServiceTask) r; + task.postOp(); + Log.v(TAG,"Finishing task " + task.getClass().getSimpleName()); + RoundtripService.getInstance().finishCurrentTask(task); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/UpdatePumpStatusTask.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/UpdatePumpStatusTask.java new file mode 100644 index 0000000000..e4f699722e --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/UpdatePumpStatusTask.java @@ -0,0 +1,58 @@ +package com.gxwtech.roundtrip2.RoundtripService.Tasks; + +import android.util.Log; + +import com.gxwtech.roundtrip2.RoundtripService.RileyLink.PumpManagerStatus; +import com.gxwtech.roundtrip2.RoundtripService.RoundtripService; +import com.gxwtech.roundtrip2.ServiceData.PumpStatusResult; +import com.gxwtech.roundtrip2.ServiceData.ServiceResult; +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; + +/** + * Created by geoff on 7/16/16. + */ +public class UpdatePumpStatusTask extends PumpTask { + private static final String TAG = "UpdatePumpStatusTask"; + public UpdatePumpStatusTask() { } + public UpdatePumpStatusTask(ServiceTransport transport) { + super(transport); + } + + @Override + public void run() { + // force pump to update everything it can + RoundtripService.getInstance().pumpManager.updatePumpManagerStatus(); + // get the newly cached status + PumpManagerStatus status = RoundtripService.getInstance().pumpManager.getPumpManagerStatus(); + // fill a PumpStatusResult message with the goods + PumpStatusResult result = new PumpStatusResult(); + /* + double remainBattery; + double remainUnits; + double currentBasal; + double lastBolusAmount; + String lastBolusTime; + int tempBasalInProgress; + double tempBasalRatio; + double tempBasalRemainMin; + String tempBasalStart; + String time; + String timeLastSync; + */ + + result.setRemainBattery(status.remainBattery); + result.setRemainUnits(status.remainUnits); + result.setCurrentBasal(status.currentBasal); + result.setLastBolusAmount(status.last_bolus_amount); + result.setLastBolusTime(status.last_bolus_time.toString()); + result.setTempBasalInProgress(status.tempBasalInProgress); + result.setTempBasalRatio(status.tempBasalRatio); + result.setTempBasalRemainMin(status.tempBasalRemainMin); + result.setTempBasalStart(status.tempBasalStart.toString()); + result.setTime(status.time.toString()); + result.setTimeLastSync(""); + getServiceTransport().setServiceResult(result); + + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/WakeAndTuneTask.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/WakeAndTuneTask.java new file mode 100644 index 0000000000..6900489682 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/Tasks/WakeAndTuneTask.java @@ -0,0 +1,22 @@ +package com.gxwtech.roundtrip2.RoundtripService.Tasks; + +import com.gxwtech.roundtrip2.RoundtripService.RoundtripService; +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; + +/** + * Created by geoff on 7/16/16. + */ +public class WakeAndTuneTask extends PumpTask { + private static final String TAG = "WakeAndTuneTask"; + public WakeAndTuneTask() { } + public WakeAndTuneTask(ServiceTransport transport) { + super(transport); + } + + @Override + public void run() { + RoundtripService.getInstance().pumpManager.wakeup(6); + RoundtripService.getInstance().pumpManager.tuneForPump(); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/ButtonPressCarelinkMessageBody.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/ButtonPressCarelinkMessageBody.java new file mode 100644 index 0000000000..5564813616 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/ButtonPressCarelinkMessageBody.java @@ -0,0 +1,23 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages; + +/** + * Created by geoff on 6/2/16. + */ +public class ButtonPressCarelinkMessageBody extends CarelinkLongMessageBody { + public static final byte BUTTON_EASY = 0x00; + public static final byte BUTTON_ESC = 0x01; + public static final byte BUTTON_ACT = 0x02; + public static final byte BUTTON_UP = 0x03; + public static final byte BUTTON_DOWN = 0x04; + + public ButtonPressCarelinkMessageBody(int which) { + init(which); + } + + public void init(int buttonType) { + int numArgs = 1; + super.init(new byte[] {(byte)numArgs,(byte)buttonType}); + } + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/CarelinkLongMessageBody.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/CarelinkLongMessageBody.java new file mode 100644 index 0000000000..bc55acd877 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/CarelinkLongMessageBody.java @@ -0,0 +1,36 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages; + +/** + * Created by geoff on 6/2/16. + */ +public class CarelinkLongMessageBody extends MessageBody { + public static final int LONG_MESSAGE_BODY_LENGTH = 65; + protected byte[] data; + + public CarelinkLongMessageBody() { + init(new byte[0]); + } + + public CarelinkLongMessageBody(byte[] payload) { + init(payload); + } + + public void init(byte[] rxData) { + data = new byte[LONG_MESSAGE_BODY_LENGTH]; + if (rxData != null) { + int size = rxData.length < LONG_MESSAGE_BODY_LENGTH ? rxData.length : LONG_MESSAGE_BODY_LENGTH; + for (int i=0; i 0) { + return data[0] & 0x7f; + } + return 255; + } + + public boolean wasLastFrame() { + return (data[0] & 0x80) != 0; + } + + public byte[] getFrameData() { + return ByteUtil.substring(data,1,data.length-1); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/GetPumpModelCarelinkMessageBody.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/GetPumpModelCarelinkMessageBody.java new file mode 100644 index 0000000000..271df7650f --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/GetPumpModelCarelinkMessageBody.java @@ -0,0 +1,31 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages; + +/** + * Created by geoff on 5/29/16. + */ +public class GetPumpModelCarelinkMessageBody extends MessageBody { + + public int getLength() { + return 1; + } + + public void init(byte[] rxData) { + + } + + public byte[] getRxData() { + return new byte[] { 0 }; + } + + public void setRxData(byte[] rxData) { + + } + + public byte[] getTxData() { + return new byte[] { 0 }; + } + + public void setTxData(byte[] txData) { + + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/MessageBody.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/MessageBody.java new file mode 100644 index 0000000000..61488be9fe --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/MessageBody.java @@ -0,0 +1,10 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages; + +/** + * Created by geoff on 5/29/16. + */ +public class MessageBody { + public int getLength() { return 0; } + public void init(byte[] rxData) { } + public byte[] getTxData() { return new byte[] {}; } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/MessageType.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/MessageType.java new file mode 100644 index 0000000000..9c0c4e342e --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/MessageType.java @@ -0,0 +1,105 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages; + +/** + * Created by geoff on 5/29/16. + */ +public class MessageType { + public static final byte Invalid = (byte)0x00; + public static final byte Alert = (byte)0x01; + public static final byte AlertCleared = (byte)0x02; + public static final byte DeviceTest = (byte)0x03; + public static final byte PumpStatus = (byte)0x04; + public static final byte PumpAck = (byte)0x06; + public static final byte PumpBackfill = (byte)0x08; + public static final byte FindDevice = (byte)0x09; + public static final byte DeviceLink = (byte)0x0a; + public static final byte ChangeTime = (byte)0x40; + public static final byte Bolus = (byte)0x42; + public static final byte ChangeTempBasal = (byte)0x4c; + public static final byte ButtonPress = (byte)0x5b; + public static final byte PowerOn = (byte)0x5d; + public static final byte ReadTime = (byte)0x70; + public static final byte GetBattery = (byte)0x72; + public static final byte GetHistoryPage = (byte)0x80; + public static final byte GetISFProfile = (byte)0x8b; + public static final byte GetPumpModel = (byte)0x8d; + public static final byte ReadTempBasal = (byte)0x98; + public static final byte ReadSettings = (byte)0xc0; + + // The above codes include codes that are not 522/722 specific. + + // The codes below here are Medtronic pump specific. + // from Roundtrip.Carelink: + public static final byte CMD_M_PACKET_LENGTH = ((byte)7); // 0x07 + public static final byte CMD_M_BEGIN_PARAMETER_SETTING = ((byte)38); // 0x26 + public static final byte CMD_M_END_PARAMETER_SETTING = ((byte)39); // 0x27 + public static final byte CMD_M_SET_A_PROFILE = ((byte)48); // 0x30 + public static final byte CMD_M_SET_B_PROFILE = ((byte)49); // 0x31 + public static final byte CMD_M_SET_LOGIC_LINK_ID = ((byte)50); // 0x32 + public static final byte CMD_M_SET_LOGIC_LINK_ENABLE = ((byte)51); // 0x33 + public static final byte CMD_M_SET_RTC = ((byte)64); // 0x40 + public static final byte CMD_M_SET_MAX_BOLUS = ((byte)65); // 0x41 + public static final byte CMD_M_BOLUS = ((byte)66); // 0x42 + public static final byte CMD_M_SET_VAR_BOLUS_ENABLE = ((byte)69); // 0x45 + public static final byte CMD_M_SET_CURRENT_PATTERN = ((byte)74); // 0x4a + public static final byte CMD_M_TEMP_BASAL_RATE = ((byte)76); // 0x4c + public static final byte CMD_M_SUSPEND_RESUME = ((byte)77); // 0x4d + public static final byte CMD_M_SET_AUTO_OFF = ((byte)78); // 0x4e + public static final byte CMD_M_SET_EASY_BOLUS_ENABLE = ((byte)79); // 0x4f + public static final byte CMD_M_SET_RF_REMOTE_ID = ((byte)81); // 0x51 + public static final byte CMD_M_SET_BLOCK_ENABLE = ((byte)82); // 0x52 + public static final byte CMD_M_SET_ALERT_TYPE = ((byte)84); // 0x54 + public static final byte CMD_M_SET_PATTERNS_ENABLE = ((byte)85); // 0x55 + public static final byte CMD_M_SET_RF_ENABLE = ((byte)87); // 0x57 + public static final byte CMD_M_SET_INSULIN_ACTION_TYPE = ((byte)88); // 0x58 + public static final byte CMD_M_KEYPAD_PUSH = ((byte)91); // 0x5b + public static final byte CMD_M_SET_TIME_FORMAT = ((byte)92); // 0x5c + public static final byte CMD_M_POWER_CTRL = ((byte)93); // 0x5d + public static final byte CMD_M_SET_BOLUS_WIZARD_SETUP = ((byte)94); // 0x5e + public static final byte CMD_M_SET_BG_ALARM_ENABLE = ((byte)103);// 0x67 + public static final byte CMD_M_SET_TEMP_BASAL_TYPE = ((byte)104);// 0x68 + public static final byte CMD_M_SET_RESERVOIR_WARNING = ((byte)106);// 0x6a + public static final byte CMD_M_SET_BG_ALARM_CLOCKS = ((byte)107);// 0x6b + public static final byte CMD_M_SET_BG_REMINDER_ENABLE = ((byte)108);// 0x6c + public static final byte CMD_M_SET_MAX_BASAL = ((byte)110);// 0x6e + public static final byte CMD_M_SET_STD_PROFILE = ((byte)111);// 0x6f + public static final byte CMD_M_READ_RTC = ((byte)112);// 0x70 + public static final byte CMD_M_READ_PUMP_ID = ((byte)113);// 0x71 + public static final byte CMD_M_READ_INSULIN_REMAINING = ((byte)115);// 0x73 + public static final byte CMD_M_READ_FIRMWARE_VER = ((byte)116);// 0x74 + public static final byte CMD_M_READ_ERROR_STATUS = ((byte)117);// 0x75 + public static final byte CMD_M_READ_REMOTE_CTRL_IDS = ((byte)118);// 0x76 + public static final byte CMD_M_READ_HISTORY = ((byte)128);// 0x80 + public static final byte CMD_M_READ_PUMP_STATE = ((byte)131);// 0x83 + public static final byte CMD_M_READ_BOLUS_WIZARD_SETUP_STATUS = ((byte)135);// 0x87 + public static final byte CMD_M_READ_CARB_UNITS = ((byte)136);// 0x88 + public static final byte CMD_M_READ_BG_UNITS = ((byte)137);// 0x89 + public static final byte CMD_M_READ_CARB_RATIOS = ((byte)138);// 0x8a + public static final byte CMD_M_READ_INSULIN_SENSITIVITIES = ((byte)139);// 0x8b + public static final byte CMD_M_READ_BG_TARGETS = ((byte)140);// 0x8c + public static final byte CMD_M_READ_PUMP_MODEL_NUMBER = ((byte)141);// 0x8d + public static final byte CMD_M_READ_BG_ALARM_CLOCKS = ((byte)142);// 0x8e + public static final byte CMD_M_READ_RESERVOIR_WARNING = ((byte)143);// 0x8f + public static final byte CMD_M_READ_BG_REMINDER_ENABLE = ((byte)144);// 0x90 + public static final byte CMD_M_READ_SETTINGS = ((byte)145);// 0x91 + public static final byte CMD_M_READ_STD_PROFILES = ((byte)146);// 0x92 + public static final byte CMD_M_READ_A_PROFILES = ((byte)147);// 0x93 + public static final byte CMD_M_READ_B_PROFILES = ((byte)148);// 0x94 + public static final byte CMD_M_READ_LOGIC_LINK_IDS = ((byte)149);// 0x95 + public static final byte CMD_M_READ_BG_ALARM_ENABLE = ((byte)151);// 0x97 + public static final byte CMD_M_READ_TEMP_BASAL = ((byte)152);// 0x98 + public static final byte CMD_M_READ_PUMP_SETTINGS = ((byte)192);// 0xc0 + public static final byte CMD_M_READ_PUMP_STATUS = ((byte)206);// 0xce + + public byte mtype; + public MessageType(byte mtype) { + this.mtype = mtype; + } + + public static MessageBody constructMessageBody(MessageType messageType, byte[] bodyData) { + switch (messageType.mtype) { + case PumpAck: return new PumpAckMessageBody(bodyData); + default: return new UnknownMessageBody(bodyData); + } + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/PumpAckMessageBody.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/PumpAckMessageBody.java new file mode 100644 index 0000000000..cb2f3ec307 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/PumpAckMessageBody.java @@ -0,0 +1,11 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages; + +/** + * Created by geoff on 5/29/16. + */ +public class PumpAckMessageBody extends CarelinkShortMessageBody { + public PumpAckMessageBody() { init(new byte[] {0}); } + public PumpAckMessageBody(byte[] bodyData) { + init(bodyData); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/UnknownMessageBody.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/UnknownMessageBody.java new file mode 100644 index 0000000000..086af96d20 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/Messages/UnknownMessageBody.java @@ -0,0 +1,35 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages; + +/** + * Created by geoff on 5/29/16. + */ +public class UnknownMessageBody extends MessageBody { + public byte[] rxData; + + public int getLength() { + return 0; + } + + public UnknownMessageBody(byte[] data) { + this.rxData = data; + } + + public void init(byte[] rxData) { + } + + public byte[] getRxData() { + return rxData; + } + + public void setRxData(byte[] rxData) { + this.rxData = rxData; + } + + public byte[] getTxData() { + return rxData; + } + + public void setTxData(byte[] txData) { + this.rxData = txData; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PacketType.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PacketType.java new file mode 100644 index 0000000000..d9d519c79d --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PacketType.java @@ -0,0 +1,20 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic; + +/** + * Created by geoff on 5/29/16. + */ +public class PacketType { + public static final short Invalid = 0x00; + public static final short MySentry = 0xa2; + public static final short Meter = 0xa5; + public static final short Carelink = 0xa7; + public static final short Sensor = 0xa8; + + public short value = 0; + public PacketType() { + } + public PacketType(short value) { + this.value = value; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/BasalProfile.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/BasalProfile.java new file mode 100644 index 0000000000..cf091b1bb8 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/BasalProfile.java @@ -0,0 +1,175 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData; + +import android.util.Log; + +import org.joda.time.Instant; + +import java.util.ArrayList; + +/** + * Created by geoff on 6/1/15. + * + * There are three basal profiles stored on the pump. (722 only?) + * They are all parsed the same, the user just has 3 to choose from: + * Standard, A, and B + * + * The byte array seems to be 21 three byte entries long, plus a zero? + * If the profile is completely empty, it should have one entry: [0,0,0x3F] (?) + * The first entry of [0,0,0] marks the end of the used entries. + * + * Each entry is assumed to span from the specified start time to the start time of the + * next entry, or to midnight if there are no more entries. + * + * Individual entries are of the form [r,z,m] where + * r is the rate (in 0.025 U increments) + * z is zero (?) + * m is the start time-of-day for the basal rate period (in 30 minute increments?) + */ +public class BasalProfile { + private static final String TAG = "BasalProfile"; + private static final boolean DEBUG_BASALPROFILE = false; + protected static final int MAX_RAW_DATA_SIZE = (21 * 3) + 1; + protected byte[] mRawData; // store as byte array to make transport (via parcel) easier + public BasalProfile() { + init(); + } + public void init() { + mRawData = new byte[MAX_RAW_DATA_SIZE]; + mRawData[0] = 0; + mRawData[1] = 0; + mRawData[2] = 0x3f; + } + // this asUINT8 should be combined with Record.asUINT8, and placed in a new util class. + protected static int readUnsignedByte(byte b) { return (b<0)?b+256:b; } + public boolean setRawData(byte[] data) { + if (data == null) { + Log.e(TAG,"setRawData: buffer is null!"); + return false; + } + int len = Math.min(MAX_RAW_DATA_SIZE, data.length); + System.arraycopy(data, 0, mRawData, 0, len); + if (DEBUG_BASALPROFILE) { + Log.v(TAG, String.format("setRawData: copied raw data buffer of %d bytes.", len)); + } + return true; + } + + public void dumpBasalProfile() { + Log.v(TAG, "Basal Profile entries:"); + ArrayList entries = getEntries(); + for (int i=0; i< entries.size(); i++) { + BasalProfileEntry entry = entries.get(i); + String startString = entry.startTime.toString("HH:mm"); + Log.v(TAG, String.format("Entry %d, rate=%.3f (0x%02X), start=%s (0x%02X)", + i+1, entry.rate, entry.rate_raw, + startString, entry.startTime_raw)); + + } + } + + // TODO: this function must be expanded to include changes in which profile is in use. + // and changes to the profiles themselves. + public BasalProfileEntry getEntryForTime(Instant when) { + BasalProfileEntry rval = new BasalProfileEntry(); + ArrayList entries = getEntries(); + if (entries.size() == 0) { + Log.w(TAG, String.format("getEntryForTime(%s): table is empty", + when.toDateTime().toLocalTime().toString("HH:mm"))); + return rval; + } + //Log.w(TAG,"Assuming first entry"); + rval = entries.get(0); + if (entries.size() == 1) { + Log.v(TAG,"getEntryForTime: Only one entry in profile"); + return rval; + } + + int localMillis = when.toDateTime().toLocalTime().getMillisOfDay(); + boolean done = false; + int i=1; + while (!done) { + BasalProfileEntry entry = entries.get(i); + if (DEBUG_BASALPROFILE) { + Log.v(TAG, String.format("Comparing 'now'=%s to entry 'start time'=%s", + when.toDateTime().toLocalTime().toString("HH:mm"), + entry.startTime.toString("HH:mm"))); + } + if (localMillis >= entry.startTime.getMillisOfDay()) { + rval = entry; + if (DEBUG_BASALPROFILE) Log.v(TAG,"Accepted Entry"); + } else { + // entry at i has later start time, keep older entry + if (DEBUG_BASALPROFILE) Log.v(TAG,"Rejected Entry"); + done = true; + } + i++; + if (i >= entries.size()) { + done = true; + } + } + if (DEBUG_BASALPROFILE) { + Log.v(TAG, String.format("getEntryForTime(%s): Returning entry: rate=%.3f (%d), start=%s (%d)", + when.toDateTime().toLocalTime().toString("HH:mm"), + rval.rate, rval.rate_raw, + rval.startTime.toString("HH:mm"), rval.startTime_raw)); + } + return rval; + } + + public ArrayList getEntries() { + ArrayList entries = new ArrayList<>(); + if (mRawData[2] == 0x3f) { + Log.w(TAG,"Raw Data is empty."); + return entries; // an empty list + } + int i = 0; + boolean done = false; + int r,st; + while (!done) { + r = readUnsignedByte(mRawData[i]); + // What is mRawData[i+1]? Not used in decocare. + st = readUnsignedByte(mRawData[i+2]); + entries.add(new BasalProfileEntry(r,st)); + i=i+3; + if (i>=MAX_RAW_DATA_SIZE) { + done=true; + } else if ((mRawData[i]==0) && (mRawData[i+1]==0) && (mRawData[i+2]==0)) { + done = true; + } + } + return entries; + } + + public static void testParser() { + byte[] testData = new byte[] { + 32, 0, 0, + 38, 0, 13, + 44, 0, 19, + 38, 0, 28, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + /* from decocare: + _test_schedule = {'total': 22.50, 'schedule': [ + { 'start': '12:00A', 'rate': 0.80 }, + { 'start': '6:30A', 'rate': 0.95 }, + { 'start': '9:30A', 'rate': 1.10 }, + { 'start': '2:00P', 'rate': 0.95 }, + ]} + */ + BasalProfile profile = new BasalProfile(); + profile.setRawData(testData); + ArrayList entries = profile.getEntries(); + if (entries.isEmpty()) { + Log.e(TAG,"testParser: failed"); + } else { + for (int i=0; i mBolusWizardEvents; + public ArrayList mBasalEvents; + public HistoryReport() { + mBolusWizardEvents = new ArrayList<>(); + mBasalEvents = new ArrayList<>(); + } + public void addBolusWizardEvent(BolusWizardBolusEstimatePumpEvent event) { + mBolusWizardEvents.add(event); + } + public void addTempBasalEvent(TempBasalEvent event) + { + mBasalEvents.add(event); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlByte.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlByte.java new file mode 100644 index 0000000000..634a480642 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlByte.java @@ -0,0 +1,11 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData; + +/** + * Created by geoff on 6/24/16. + */ +public class HtmlByte extends HtmlElement { + private byte data = 0; + public HtmlByte() {} + public HtmlByte(byte b) { data = b; } + public String toString() {return String.format("%02x",data);} +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlCodeTagEnd.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlCodeTagEnd.java new file mode 100644 index 0000000000..9f6855fa89 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlCodeTagEnd.java @@ -0,0 +1,9 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData; + +/** + * Created by geoff on 6/24/16. + */ +public class HtmlCodeTagEnd extends HtmlElement { + public HtmlCodeTagEnd() {} + public String toString() { return ""; } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlCodeTagStart.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlCodeTagStart.java new file mode 100644 index 0000000000..3a92602f98 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlCodeTagStart.java @@ -0,0 +1,30 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData; + +/** + * Created by geoff on 6/24/16. + */ +public class HtmlCodeTagStart extends HtmlElement { + String title = ""; + String color = ""; + public HtmlCodeTagStart(String title, String bgColor) { + this.title = title; + this.color = bgColor; + } + public String toString() { + StringBuilder b = new StringBuilder(); + b.append(" 0)) { + b.append(" title=\""); + b.append(title); + b.append("\""); + } + if ((color != null) && (color.length() > 0)) { + b.append(" style=\"background-color:"); + b.append(color); + b.append(";\""); + } + b.append(">"); + return b.toString(); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlElement.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlElement.java new file mode 100644 index 0000000000..f1fa7ecfdb --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlElement.java @@ -0,0 +1,9 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData; + +/** + * Created by geoff on 6/24/16. + */ +abstract public class HtmlElement { + public HtmlElement() {} + abstract public String toString(); +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlElementGeneric.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlElementGeneric.java new file mode 100644 index 0000000000..5c49956de5 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlElementGeneric.java @@ -0,0 +1,15 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData; + +/** + * Created by geoff on 6/24/16. + */ +public class HtmlElementGeneric extends HtmlElement { + String s = ""; + public HtmlElementGeneric() {} + public HtmlElementGeneric(String s) { + this.s = s; + } + public String toString() { + return s; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlHistoryPageStart.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlHistoryPageStart.java new file mode 100644 index 0000000000..86f95ca1e1 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/HtmlHistoryPageStart.java @@ -0,0 +1,15 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData; + +/** + * Created by geoff on 6/24/16. + */ +public class HtmlHistoryPageStart extends HtmlElement { + int pagenum = 0; + public HtmlHistoryPageStart() {} + public HtmlHistoryPageStart(int num) { + pagenum = num; + } + public String toString() { + return "

History Page " + pagenum + "

\n"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/ISFTable.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/ISFTable.java new file mode 100644 index 0000000000..3f1eacc060 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/ISFTable.java @@ -0,0 +1,102 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData; + +import com.gxwtech.roundtrip2.util.ByteUtil; + +import org.joda.time.LocalDateTime; + +/** + * Created by geoff on 7/2/16. + * + * This class exists to map the Minimed byte response to "Get sensitivity factors" + * into a more usable form. + */ +public class ISFTable { + boolean mIsValid = false; + + LocalDateTime validDate; + byte[] header; + int[] times; + float[] rates; + public ISFTable() {} + public boolean parseFrom(byte[] responseBytes) { + // example format: { 7, 1, 0, 45, 12, 30, 42, 50, 0, 0 } + // means value pairs: {0, 45}, {12, 30}, {42, 50} + // means: at midnight, the amount is 45, + // at 6am, the about is 30 + // at 9pm, the amount is 50 + if (responseBytes == null) return false; + // minimum of two bytes in header + if (responseBytes.length < 2) { + return false; + } + // Must be an even number of times and rates + if (responseBytes.length % 2 != 0) { + return false; + } + mIsValid = true; + header = ByteUtil.substring(responseBytes,0,2); + // find end of list + int index = 2; + while (true) { + if (index + 1 > responseBytes.length) { + break; + } + if ((responseBytes[index]==0) && (responseBytes[index+1]==0)) { + break; + } + index += 2; + } + if (index == 2) { + // no entries. + times = new int[]{}; + rates = new float[]{}; + } else { + int numEntries = (index - 2) / 2; + times = new int[numEntries]; + rates = new float[numEntries]; + for (int i=0; i mRecordList; + + public Page() { + this.model = PumpModel.UNSET; + mRecordList = new ArrayList<>(); + } + + public byte[] getRawData() { + if (data == null) { + return crc; + } + if (crc == null) { + return data; + } + return ByteUtil.concat(data,crc); + } + + protected PumpTimeStamp collectTimeStamp(byte[] data, int offset) { + try { + PumpTimeStamp timestamp = new PumpTimeStamp(TimeFormat.parse5ByteDate(data, offset)); + return timestamp; + } catch (org.joda.time.IllegalFieldValueException e) { + return null; + } + } + + public boolean parsePicky(byte[] rawPage, PumpModel model) { + mRecordList = new ArrayList<>(); + this.model = model; + int pageOffset = 0; + + if ((rawPage == null) || (rawPage.length == 0)) return false; + this.data = Arrays.copyOfRange(rawPage, 0, rawPage.length-2); + this.crc = Arrays.copyOfRange(rawPage, rawPage.length-2, rawPage.length); + byte[] expectedCrc = CRC.calculate16CCITT(this.data); + if (DEBUG_PAGE) { + Log.i(TAG, String.format("Data length: %d", data.length)); + } + if (!Arrays.equals(crc, expectedCrc)) { + Log.w(TAG, String.format("CRC does not match expected value. Expected: %s Was: %s", HexDump.toHexString(expectedCrc), HexDump.toHexString(crc))); + } else { + if (DEBUG_PAGE) { + Log.i(TAG, "CRC OK"); + } + } + + Record record = null; + while (pageOffset < data.length) { + if (data[pageOffset] == 0) { + if (record != null) { + Log.i(TAG,String.format("End of page or Previous parse fail: prev opcode 0x%02x, curr offset %d, %d bytes remaining", + record.getRecordOp(),pageOffset,data.length - pageOffset + 1)); + break; + } else { + Log.i(TAG, "WTF?"); + } + } + try { + record = attemptParseRecord(data, pageOffset); + } catch (org.joda.time.IllegalFieldValueException e) { + record = null; + } + if (record == null) { + Log.i(TAG, "PARSE FAIL"); + pageOffset++; + } else { + mRecordList.add(record); + pageOffset+=record.getLength(); + } + } + ArrayList pickyRecords = new ArrayList<>(); + pickyRecords.addAll(mRecordList); + parseByDates(rawPage,model); + for (Record r : mRecordList) { + for (Record r2 : pickyRecords) { + if (r.getFoundAtOffset() == r2.getFoundAtOffset()) { + Log.v(TAG,"Found matching record at offset " + r.getFoundAtOffset()); + } + } + } + return true; + } + + public boolean parseByDates(byte[] rawPage, PumpModel model) { + mRecordList = new ArrayList<>(); + if (rawPage.length != 1024) { + Log.e(TAG,"Unexpected page size. Expected: 1024 Was: " + rawPage.length); + //return false; + } + this.model = model; + if (DEBUG_PAGE) { + Log.i(TAG, "Parsing page"); + } + + if (rawPage.length < 4) { + Log.e(TAG,"Page too short, need at least 4 bytes"); + return false; + } + + this.data = Arrays.copyOfRange(rawPage, 0, rawPage.length-2); + this.crc = Arrays.copyOfRange(rawPage, rawPage.length-2, rawPage.length); + byte[] expectedCrc = CRC.calculate16CCITT(this.data); + if (DEBUG_PAGE) { + Log.i(TAG, String.format("Data length: %d", data.length)); + } + if (!Arrays.equals(crc, expectedCrc)) { + Log.w(TAG, String.format("CRC does not match expected value. Expected: %s Was: %s", HexDump.toHexString(expectedCrc), HexDump.toHexString(crc))); + } else { + if (DEBUG_PAGE) { + Log.i(TAG, "CRC OK"); + } + } + + int pageOffset = 0; + PumpTimeStamp lastPumpTimeStamp = new PumpTimeStamp(); + while (pageOffset < this.data.length - 7) { + PumpTimeStamp timestamp = collectTimeStamp(data,pageOffset+2); + if (timestamp!=null) { + String year = timestamp.toString().substring(0,3); + Record record; + if ("201".equals(year)) { + // maybe found a record. + try { + record = attemptParseRecord(data, pageOffset); + } catch (org.joda.time.IllegalFieldValueException e) { + record = null; + } + if (record != null) { + if (timestamp.getLocalDateTime().compareTo(lastPumpTimeStamp.getLocalDateTime()) >= 0) { + Log.i(TAG, "Timestamp is increasing"); + lastPumpTimeStamp = timestamp; + mRecordList.add(record); + } else { + Log.e(TAG, "Timestamp is decreasing"); + } + } + } + } + pageOffset++; + } + + + + return true; + } + + public boolean parseFrom(byte[] rawPage, PumpModel model) { + mRecordList = new ArrayList<>(); // wipe old contents each time when parsing. + if (rawPage.length != 1024) { + Log.e(TAG,"Unexpected page size. Expected: 1024 Was: " + rawPage.length); + //return false; + } + this.model = model; + if (DEBUG_PAGE) { + Log.i(TAG, "Parsing page"); + } + + if (rawPage.length < 4) { + Log.e(TAG,"Page too short, need at least 4 bytes"); + return false; + } + + this.data = Arrays.copyOfRange(rawPage, 0, rawPage.length-2); + this.crc = Arrays.copyOfRange(rawPage, rawPage.length-2, rawPage.length); + byte[] expectedCrc = CRC.calculate16CCITT(this.data); + if (DEBUG_PAGE) { + Log.i(TAG, String.format("Data length: %d", data.length)); + } + if (!Arrays.equals(crc, expectedCrc)) { + Log.w(TAG, String.format("CRC does not match expected value. Expected: %s Was: %s", HexDump.toHexString(expectedCrc), HexDump.toHexString(crc))); + } else { + if (DEBUG_PAGE) { + Log.i(TAG, "CRC OK"); + } + } + + int dataIndex = 0; + boolean done = false; + while (!done) { + Record record = null; + if (data[dataIndex] != 0) { + // If the data byte is zero, assume that means end of page + try { + record = attemptParseRecord(data, dataIndex); + } catch (org.joda.time.IllegalFieldValueException e) { + record = null; + } + } else { + Log.v(TAG,"Zero opcode encountered -- end of page. " + (rawPage.length - dataIndex) + " bytes remaining."); + break; + } + if (record != null) { + Log.v(TAG,"parseFrom: found event "+record.getClass().getSimpleName() + " length=" + record.getLength() + " offset=" + record.getFoundAtOffset()); + mRecordList.add(record); + dataIndex += record.getLength(); + } else { + Log.e(TAG,String.format("parseFrom: Failed to parse opcode 0x%02x, offset=%d",data[dataIndex],dataIndex)); + done = true; + } + if (dataIndex >= data.length - 2) { + done = true; + } + } + if (DEBUG_PAGE) { + Log.i(TAG, String.format("Number of records: %d", mRecordList.size())); + int index = 1; + for (Record r : mRecordList) { + Log.v(TAG, String.format("Record #%d: %s", index,r.getShortTypeName())); + index += 1; + } + } + return true; + } + + /* attemptParseRecord will attempt to create a subclass of Record from the given + * data and offset. It will return NULL if it fails. If it succeeds, the returned + * subclass of Record can be examined for its length, so that the next attempt can be made. + */ + public static T attemptParseRecord(byte[] data, int offsetStart) { + // no data? + if (data == null) { + return null; + } + // invalid offset? + if (data.length < offsetStart) { + return null; + } + //Log.d(TAG,String.format("checking for handler for record type 0x%02X at index %d",data[offsetStart],offsetStart)); + RecordTypeEnum en = RecordTypeEnum.fromByte(data[offsetStart]); + T record = en.getRecordClassInstance(PumpModel.MM522); + if (record != null) { + // have to do this to set the record's opCode + byte[] tmpData = new byte[data.length]; + System.arraycopy(data, offsetStart, tmpData, 0, data.length - offsetStart); + boolean didParse = record.parseWithOffset(tmpData, PumpModel.MM522, offsetStart); + if (!didParse) { + Log.e(TAG,String.format("attemptParseRecord: class %s (opcode 0x%02X) failed to parse at offset %d",record.getShortTypeName(),data[offsetStart],offsetStart)); + } + } + return record; + } + + public static DateTime parseSimpleDate(byte[] data, int offset) { + DateTime timeStamp = null; + int seconds = 0; + int minutes = 0; + int hour = 0; + //int high = data[0] >> 4; + int low = data[0 + offset] & 0x1F; + //int year_high = data[1] >> 4; + int mhigh = (data[0 + offset] & 0xE0) >> 4; + int mlow = (data[1 + offset] & 0x80) >> 7; + int month = mhigh + mlow; + int dayOfMonth = low + 1; + // python code says year is data[1] & 0x0F, but that will cause problem in 2016. + // Hopefully, the remaining bits are part of the year... + int year = data[1 + offset] & 0x3F; + /* + Log.w(TAG, String.format("Attempting to create DateTime from: %04d-%02d-%02d %02d:%02d:%02d", + year + 2000, month, dayOfMonth, hour, minutes, seconds)); + */ + try { + timeStamp = new DateTime(year + 2000, month, dayOfMonth, hour, minutes, seconds); + } catch (org.joda.time.IllegalFieldValueException e) { + //Log.e(TAG,"Illegal DateTime field"); + //e.printStackTrace(); + return null; + } + return timeStamp; + } + + public static void discoverRecords(byte[] data) { + int i = 0; + boolean done = false; + + ArrayList keyLocations= new ArrayList(); + while (!done) { + RecordTypeEnum en = RecordTypeEnum.fromByte(data[i]); + if (en != RecordTypeEnum.RECORD_TYPE_NULL) { + keyLocations.add(i); + Log.v(TAG, String.format("Possible record of type %s found at index %d", en, i)); + } + /* + DateTime ts = parseSimpleDate(data,i); + if (ts != null) { + if (ts.year().get() == 2015) { + Log.w(TAG, String.format("Possible simple date at index %d", i)); + } + } + */ + i = i + 1; + done = (i >= data.length-2); + } + // for each of the discovered key locations, attempt to parse a sequence of records + for(RecordTypeEnum en : RecordTypeEnum.values()) { + + } + for (int ix = 0; ix < keyLocations.size(); ix++) { + + } + } + + /* + * + * For IPC serialization + * + */ + + /* + private byte[] crc; + private byte[] data; + protected PumpModel model; + public List mRecordList; + */ + + public Bundle pack() { + Bundle bundle = new Bundle(); + bundle.putByteArray("crc",crc); + bundle.putByteArray("data",data); + bundle.putString("model",PumpModel.toString(model)); + ArrayList records = new ArrayList<>(); + for (int i=0; i records = in.getParcelableArrayList("mRecordList"); + mRecordList = new ArrayList<>(); + if (records != null) { + for (int i=0; i list) { + SQLiteDatabase db = getWritableDatabase(); + for (ContentValues cvs : list) { + db.insert(DATABASE_TABLE_entries, null, cvs); + } + db.close(); + Log.d(TAG,"Database "+ DATABASE_NAME + " saved"); + } + + public void clearPumpHistoryDatabase() { + SQLiteDatabase db = getWritableDatabase(); + db.execSQL("DROP TABLE IF EXISTS "+DATABASE_TABLE_entries); + onCreate(db); + db.close(); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpHistoryManager.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpHistoryManager.java new file mode 100644 index 0000000000..061776abb6 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpHistoryManager.java @@ -0,0 +1,341 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData; + +import android.content.ContentValues; +import android.content.Context; +import android.os.Bundle; +import android.os.Environment; +import android.util.Log; + +import com.gxwtech.roundtrip2.RT2Const; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records.Record; +import com.gxwtech.roundtrip2.util.StringUtil; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Array; +import java.util.ArrayList; + +/** + * Created by geoff on 6/17/16. + */ +public class PumpHistoryManager { + private static final String TAG = "PumpHistoryManager"; + private Context context; + private PumpHistoryDatabaseHandler phdb; + ArrayList dbContentValues = new ArrayList<>(); + ArrayList packedPages = null; + public PumpHistoryManager(Context context) { + this.context = context; + phdb = new PumpHistoryDatabaseHandler(context); + } + + public void initFromPages(Bundle historyBundle) { + packedPages = historyBundle.getParcelableArrayList(RT2Const.IPC.MSG_PUMP_history_key); + for (int i=0; i recordBundleList = pageBundle.getParcelableArrayList("mRecordList"); + for (Bundle b : recordBundleList) { + try { + PumpHistoryDatabaseEntry entry = new PumpHistoryDatabaseEntry(); + if (entry.initFromRecordBundle(i, b)) { + dbContentValues.add(entry.getContentValues()); + } + } catch (java.lang.NullPointerException e) { + e.printStackTrace(); + } + } + } + } + phdb.addContentValuesList(dbContentValues); + } + + public void clearDatabase() { + phdb.clearPumpHistoryDatabase(); + } + + /* 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 boolean timestampOK(String timestamp) { + if (timestamp == null) { + return false; + } + if (timestamp.length() < 4) { + return false; + } + if ("2015".equals(timestamp.substring(0,4))) { + return true; + } + if ("2016".equals(timestamp.substring(0,4))) { + return true; + } + return false; + } + + public HtmlCodeTagStart renderContexts(ArrayList relevantBundles) { + // search for all contexts that span this byte + // make a start tag that represents them. + StringBuilder titleBuilder = new StringBuilder(); + for (Bundle b : relevantBundles) { + String name = b.getString("_type"); + String timestamp = b.getString("timestamp"); + int length = b.getInt("length"); + int offset = b.getInt("foundAtOffset"); + titleBuilder.append(String.format("[%s%s %s l=%d o=%d]", + timestampOK(timestamp) ? "" : "BAD ", + name==null?"(null)":name, + timestamp == null?"(null)":timestamp, + length, offset)); + } + String colorString = null; + if (relevantBundles.size() == 1) { + if (timestampOK(relevantBundles.get(0).getString("timestamp"))) { + colorString = "#99ff99"; + } else { + colorString = "#F0E68C"; + } + } else if (relevantBundles.size() > 1) { + boolean allOK = relevantBundlesAllOK(relevantBundles); + boolean allBad = relevantBundlesAllBad(relevantBundles); + if (allOK) { + colorString = "#248f24"; + } else if (allBad) { + colorString = "#cc0000"; + } else { + colorString = "#b3b300"; + } + } + return new HtmlCodeTagStart(titleBuilder.toString(),colorString); + } + + public ArrayList findRelevantBundles(int pageNum, int pageOffset) { + ArrayList relevantBundles = new ArrayList<>(); + ArrayList recordBundleList = packedPages.get(pageNum).getParcelableArrayList("mRecordList"); + for (int i=0; i< recordBundleList.size(); i++) { + Bundle recordBundle = recordBundleList.get(i); + int offset = recordBundle.getInt("foundAtOffset"); + int length = recordBundle.getInt("length"); + if ((offset <= pageOffset) && (offset + length > pageOffset)) { + relevantBundles.add(recordBundle); + } + } + return relevantBundles; + } + + public boolean relevantBundlesAllOK(ArrayList bundles) { + for (Bundle b : bundles) { + String ts = b.getString("timestamp"); + if (ts == null) { + return false; + } + if (!timestampOK(ts)) { + return false; + } + + } + return true; + } + + public boolean relevantBundlesAllBad(ArrayList bundles) { + for (Bundle b : bundles) { + String ts = b.getString("timestamp"); + if (ts != null) { + if (timestampOK(ts)) { + return false; + } + } + } + return true; + } + + public boolean relevantBundlesSame(ArrayList bundles1, ArrayList bundles2) { + if (bundles1.size() != bundles2.size()) { + return false; + } + for (Bundle b : bundles1) { + if (!bundles2.contains(b)) { + return false; + } + } + for (Bundle b : bundles2) { + if (!bundles1.contains(b)) { + return false; + } + } + return true; + } + + public boolean relevantBundlesAllDifferent(ArrayList bundles1, ArrayList bundles2) { + for (Bundle b : bundles1) { + if (bundles2.contains(b)) { + return false; + } + } + for (Bundle b : bundles2) { + if (bundles1.contains(b)) { + return false; + } + } + return true; + } + + public ArrayList makeDom() { + ArrayList rval = new ArrayList<>(); + for (int pageNum = 0; pageNum < packedPages.size(); pageNum++) { + Log.i(TAG,"Rendering page " + pageNum); + rval.add(new HtmlHistoryPageStart(pageNum)); + byte[] pageData = packedPages.get(pageNum).getByteArray("data"); + if (pageData == null) { + return rval; + } + int pageSize = pageData.length; + byte[] crc = packedPages.get(pageNum).getByteArray("crc"); + if (pageSize != 1022) { + Log.e(TAG, "Page size is not 1022, it is " + pageSize); + } + int pageOffset = 0; + boolean done = false; + ArrayList currentBundles = findRelevantBundles(pageNum, pageOffset); + ArrayList nextBundles = new ArrayList<>(); + rval.add(renderContexts(currentBundles)); + while (!done) { + rval.add(new HtmlByte(pageData[pageOffset])); + if (pageOffset == pageSize - 1) { + done = true; + } else { + nextBundles = findRelevantBundles(pageNum, pageOffset + 1); + if (relevantBundlesSame(currentBundles, nextBundles)) { + // Not changing context + if (pageOffset % 32 == 31) { + rval.add(new HtmlElementGeneric("
\n")); + } else { + rval.add(new HtmlElementGeneric(" ")); + } + } else { + // Changing context + rval.add(new HtmlCodeTagEnd()); + if (relevantBundlesAllDifferent(currentBundles, nextBundles)) { + rval.add(new HtmlCodeTagStart("", "")); // + if (pageOffset % 32 == 31) { + rval.add(new HtmlElementGeneric("
\n")); + } else { + rval.add(new HtmlElementGeneric(" ")); + } + if (nextBundles.size() != 0) { + rval.add(new HtmlCodeTagEnd()); + rval.add(renderContexts(nextBundles)); + } + } else { + if (pageOffset % 32 == 31) { + rval.add(new HtmlElementGeneric("
\n")); + } else { + rval.add(new HtmlElementGeneric(" ")); + } + rval.add(new HtmlCodeTagEnd()); + rval.add(renderContexts(nextBundles)); + } + } + } + if (!done) { + pageOffset++; + currentBundles = nextBundles; + } + } + } + return rval; + } + + public void writeHtmlPage() { + + /* + final String key_timestamp = "timestamp"; + String timestampString; + final String key_pageNum = "pagenum"; + int pageNum; + final String key_pageOffset = "offset"; + int foundAtOffset; + final String key_recordType = "type"; + String recordType; + final String key_length = "length"; + int length; +*/ + if (!isExternalStorageWritable()) { + Log.e(TAG,"External storage not writable."); + return; + } + + String filename = "PumpHistoryBytes.html"; + + File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC); + File pageFile = new File(path,filename); + + OutputStream os = null; + try { + os = new FileOutputStream(pageFile); + } catch (FileNotFoundException fnf) { + Log.e(TAG,"Failed to open " + filename + " for writing"); + } + if (os == null) { + return; + } + + // write header + try { + os.write("".getBytes()); + os.write("".getBytes()); + os.write("".getBytes()); + os.write("Page Title".getBytes()); + os.write("".getBytes()); + os.write("".getBytes()); + + byte[] pageData = packedPages.get(0).getByteArray("data"); + int pageSize = pageData.length; + //byte[] crc = packedPages.get(0).getByteArray("crc"); + if (pageSize != 1022) { + Log.e(TAG,"Page size is not 1022, it is " + pageSize); + } + + ArrayList dom = makeDom(); + Log.i(TAG,"There are " + dom.size() + " elements to render."); + for (HtmlElement e : dom) { + if (e != null) { + String elementString = e.toString(); + if (elementString != null) { + byte[] bytes = elementString.getBytes(); + if (bytes != null) { + os.write(bytes); + } else { + Log.e(TAG,"WriteHtmlPage: bytes is null"); + } + } else { + Log.e(TAG,"WriteHtmlPage: elementString is null"); + } + } else { + Log.e(TAG,"WriteHtmlPage: element is null"); + } + } + + os.write("".getBytes()); + os.close(); + } catch (FileNotFoundException fnf) { + fnf.printStackTrace(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpHistoryPage.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpHistoryPage.java new file mode 100644 index 0000000000..14dd971eb7 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpHistoryPage.java @@ -0,0 +1,43 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; +import com.gxwtech.roundtrip2.util.ByteUtil; +import com.gxwtech.roundtrip2.util.CRC; + +/** + * Created by geoff on 6/18/16. + */ +public class PumpHistoryPage { + public byte[] data = new byte[0]; + private PumpModel model = PumpModel.MM522; + private int pageNumber; + public PumpHistoryPage() {} + public PumpHistoryPage(byte[] data, PumpModel model, int pageNumber) { + init(data,model,pageNumber); + } + public void init(byte[] data, PumpModel model, int pageNumber) { + this.data = data; + this.model = model; + this.pageNumber = pageNumber; + } + public int getPageNumber() { return pageNumber; } + public boolean isValid() { + return isCRCValid(); + } + public boolean isCRCValid() { + if (data == null) { + return false; + } + if (data.length < 3) { + return false; + } + byte[] crc16 = CRC.calculate16CCITT(ByteUtil.substring(data,0,data.length-2)); + if (crc16 == null) { + return false; + } + if ((crc16[0] == data[data.length-2]) && (crc16[1]==data[data.length-1])) { + return true; + } + return false; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpHistoryParser.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpHistoryParser.java new file mode 100644 index 0000000000..d277bb56df --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpHistoryParser.java @@ -0,0 +1,263 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData; + +import android.os.Bundle; +import android.util.Log; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpTimeStamp; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.TimeFormat; +import com.gxwtech.roundtrip2.util.ByteUtil; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Created by geoff on 6/18/16. + * + * + * + * This class is not in use -- it may become a replacement for the history object heirarchy parser + * + * + * + * + * + * + * + */ +@Deprecated +public class PumpHistoryParser { + private static final String TAG = "PumpHistoryParser"; + public PumpModel model; + public ArrayList pages = new ArrayList<>(); + public ArrayList entries = new ArrayList<>(); + public PumpHistoryParser(PumpModel model) { + initOpcodes(); + } + public void parsePage(byte[] rawData) { + PumpHistoryPage page = new PumpHistoryPage(rawData,model,pages.size()+1); + pages.add(page); + if (page.isCRCValid() == false) { + Log.e(TAG,"CRC16 for page " + page.getPageNumber() + " is invalid."); + } + if (page.data.length != 1024) { + Log.w(TAG,String.format("Page %d has length %d, expected 1024",page.getPageNumber(),page.data.length)); + } + int currentOffset = 0; + boolean done = false; + while (!done) { + Bundle b = attemptParseRecord(page.getPageNumber(),currentOffset); + if (b == null) { + // then the parse failed. + } + } + } + + static int asUINT8(byte b) { + return (b < 0) ? b + 256 : b; + } + + static double insulinDecode(int a, int b) { + return ((a << 8) + b) / 40.0; + } + + public Bundle attemptParseRecord(int pageNum, int offset) { + byte[] dataPage = pages.get(pageNum).data; + int opcode = pages.get(pageNum).data[offset]; + Bundle record = new Bundle(); + String typename = opCodeNames.get(opcode); + if (typename != null) { + record.putInt("opcode",opcode); + record.putString("type",typename); + record.putInt("page",pageNum); + record.putInt("offset",offset); + record.putString("indexer",String.format("%02d%04d",pageNum,offset)); + } else { + return null; + } + int length = 0; + int dateOffset = 0; + PumpTimeStamp timestamp; + byte[] data = ByteUtil.substring(dataPage,offset,dataPage.length - 2 - offset); + switch (opcode) { + case RECORD_TYPE_BolusNormal: { + length = PumpModel.isLargerFormat(model) ? 13 : 9; + if (length + offset > data.length) return null; + record.putInt("length",length); + double programmedAmount; + double deliveredAmount; + double unabsorbedInsulinTotal; + int duration; + String bolusType; + if (PumpModel.isLargerFormat(model)) { + programmedAmount = insulinDecode(asUINT8(data[1]),asUINT8(data[2])); + deliveredAmount = insulinDecode(asUINT8(data[3]),asUINT8(data[4])); + unabsorbedInsulinTotal = insulinDecode(asUINT8(data[5]),asUINT8(data[6])); + duration = asUINT8(data[7]) * 30; + try { + timestamp = new PumpTimeStamp(TimeFormat.parse5ByteDate(data, 8)); + } catch (org.joda.time.IllegalFieldValueException e) { + timestamp = new PumpTimeStamp(); + } + } else { + programmedAmount = asUINT8(data[1]) / 10.0f; + deliveredAmount = asUINT8(data[2]) / 10.0f; + duration = asUINT8(data[3]) * 30; + unabsorbedInsulinTotal = 0; + try { + timestamp = new PumpTimeStamp(TimeFormat.parse5ByteDate(data, 4)); + } catch (org.joda.time.IllegalFieldValueException e) { + timestamp = new PumpTimeStamp(); + } + } + bolusType = (duration > 0) ? "square" : "normal"; + record.putDouble("programmedAmount",programmedAmount); + record.putDouble("deliveredAmount",deliveredAmount); + record.putInt("duration",duration); + record.putDouble("unabsorbedInsulinTotal",unabsorbedInsulinTotal); + record.putString("bolusType",bolusType); + record.putString("timestamp",timestamp.getLocalDateTime().toString()); + + } break; + case RECORD_TYPE_AlarmSensor: + length = 8; + if (length + offset > data.length) return null; + record.putInt("length",length); + case RECORD_TYPE_Resume: + case RECORD_TYPE_AlarmClockReminder: + length = 7; + if (length + offset > data.length) return null; + record.putInt("length",length); + try { + timestamp = new PumpTimeStamp(TimeFormat.parse5ByteDate(data, 2)); + } catch (org.joda.time.IllegalFieldValueException e) { + timestamp = new PumpTimeStamp(); + } + record.putString("timestamp",timestamp.getLocalDateTime().toString()); + default: + record.putInt("length",0); + record.putString("comment","unparsed"); + } + return record; + } + + HashMap opCodeNames = new HashMap<>(); + HashMap opCodeNumbers = new HashMap<>(); + + private void initOpcode(int number, String name) { + opCodeNames.put(number,name); + opCodeNumbers.put(name,number); + } + + public static final int RECORD_TYPE_BolusNormal = 0x01; + public static final int RECORD_TYPE_Prime = 0x03; + public static final int RECORD_TYPE_PumpAlarm = 0x06; + public static final int RECORD_TYPE_ResultDailyTotal = 0x07; + public static final int RECORD_TYPE_ChangeBasalProfilePattern = 0x08; + public static final int RECORD_TYPE_ChangeBasalProfile = 0x09; + public static final int RECORD_TYPE_CalBgForPh = 0x0A; + public static final int RECORD_TYPE_AlarmSensor = 0x0B; + public static final int RECORD_TYPE_ClearAlarm = 0x0C; + public static final int RECORD_TYPE_SelectBasalProfile = 0x14; + public static final int RECORD_TYPE_TempBasalDuration = 0x16; + public static final int RECORD_TYPE_ChangeTime = 0x17; + public static final int RECORD_TYPE_NewTimeSet = 0x18; + public static final int RECORD_TYPE_JournalEntryPumpLowBattery = 0x19; + public static final int RECORD_TYPE_Battery = 0x1A; + public static final int RECORD_TYPE_Suspend = 0x1E; + public static final int RECORD_TYPE_Resume = 0x1F; + public static final int RECORD_TYPE_Rewind = 0x21; + public static final int RECORD_TYPE_ChangeChildBlockEnable = 0x23; + public static final int RECORD_TYPE_ChangeMaxBolus = 0x24; + public static final int RECORD_TYPE_EnableDisableRemote = 0x26; + public static final int RECORD_TYPE_TempBasalRate = 0x33; + public static final int RECORD_TYPE_JournalEntryPumpLowReservoir = 0x34; + public static final int RECORD_TYPE_AlarmClockReminder = 0x35; + public static final int RECORD_TYPE_BGReceived = 0x3F; + public static final int RECORD_TYPE_JournalEntryExerciseMarker = 0x41; + public static final int RECORD_TYPE_ChangeSensorSetup2 = 0x50; + public static final int RECORD_TYPE_ChangeSensorRateOfChangeAlertSetup = 0x56; + public static final int RECORD_TYPE_ChangeBolusScrollStepSize = 0x57; + public static final int RECORD_TYPE_ChangeBolusWizardSetup = 0x5A; + public static final int RECORD_TYPE_BolusWizardBolusEstimate = 0x5B; + public static final int RECORD_TYPE_UnabsorbedInsulin = 0x5C; + public static final int RECORD_TYPE_ChangeVariableBolus = 0x5e; + public static final int RECORD_TYPE_ChangeAudioBolus = 0x5f; + public static final int RECORD_TYPE_ChangeBGReminderEnable = 0x60; + public static final int RECORD_TYPE_ChangeAlarmClockEnable = 0x61; + public static final int RECORD_TYPE_ChangeTempBasalType = 0x62; + public static final int RECORD_TYPE_ChangeAlarmNotifyMode = 0x63; + public static final int RECORD_TYPE_ChangeTimeFormat = 0x64; + public static final int RECORD_TYPE_ChangeReservoirWarningTime = 0x65; + public static final int RECORD_TYPE_ChangeBolusReminderEnable = 0x66; + public static final int RECORD_TYPE_ChangeBolusReminderTime = 0x67; + public static final int RECORD_TYPE_DeleteBolusReminderTime = 0x68; + public static final int RECORD_TYPE_DeleteAlarmClockTime = 0x6a; + public static final int RECORD_TYPE_Model522ResultTotals = 0x6D; + public static final int RECORD_TYPE_Sara6E = 0x6E; + public static final int RECORD_TYPE_ChangeCarbUnits = 0x6f; + public static final int RECORD_TYPE_BasalProfileStart = 0x7B; + public static final int RECORD_TYPE_ChangeWatchdogEnable = 0x7c; + public static final int RECORD_TYPE_ChangeOtherDeviceID = 0x7d; + public static final int RECORD_TYPE_ChangeWatchdogMarriageProfile = 0x81; + public static final int RECORD_TYPE_DeleteOtherDeviceID = 0x82; + public static final int RECORD_TYPE_ChangeCaptureEventEnable = 0x83; + + private void initOpcodes() { + initOpcode(0x01,"BolusNormal"); + initOpcode(0x03,"Prime"); + initOpcode(0x06,"PumpAlarm"); + initOpcode(0x07,"ResultDailyTotal"); + initOpcode(0x08,"ChangeBasalProfilePattern"); + initOpcode(0x09,"ChangeBasalProfile"); + initOpcode(0x0A,"CalBgForPh"); + initOpcode(0x0B,"AlarmSensor"); + initOpcode(0x0C,"ClearAlarm"); + //initOpcode(0x14,"SelectBasalProfile"); + initOpcode(0x16,"TempBasalDuration"); + initOpcode(0x17,"ChangeTime"); + //initOpcode(0x18,"NewTimeSet"); + initOpcode(0x19,"JournalEntryPumpLowBattery"); + initOpcode(0x1A,"Battery"); + initOpcode(0x1E,"Suspend"); + initOpcode(0x1F,"Resume"); + initOpcode(0x21,"Rewind"); + initOpcode(0x23,"ChangeChildBlockEnable"); + initOpcode(0x24,"ChangeMaxBolus"); + initOpcode(0x26,"EnableDisableRemote"); + initOpcode(0x33,"TempBasalRate"); + initOpcode(0x34,"JournalEntryPumpLowReservoir"); + initOpcode(0x35,"AlarmClockReminder"); + initOpcode(0x3F,"BGReceived"); + initOpcode(0x41,"JournalEntryExerciseMarker"); + initOpcode(0x50,"ChangeSensorSetup2"); + initOpcode(0x56,"ChangeSensorRateOfChangeAlertSetup"); + initOpcode(0x57,"ChangeBolusScrollStepSize"); + initOpcode(0x5A,"ChangeBolusWizardSetup"); + initOpcode(0x5B,"BolusWizardBolusEstimate"); + initOpcode(0x5C,"UnabsorbedInsulin"); + initOpcode(0x5e,"ChangeVariableBolus"); + initOpcode(0x5f,"ChangeAudioBolus"); + initOpcode(0x60,"ChangeBGReminderEnable"); + initOpcode(0x61,"ChangeAlarmClockEnable"); + initOpcode(0x62,"ChangeTempBasalType"); + initOpcode(0x63,"ChangeAlarmNotifyMode"); + initOpcode(0x64,"ChangeTimeFormat"); + initOpcode(0x65,"ChangeReservoirWarningTime"); + initOpcode(0x66,"ChangeBolusReminderEnable"); + initOpcode(0x67,"ChangeBolusReminderTime"); + initOpcode(0x68,"DeleteBolusReminderTime"); + initOpcode(0x6a,"DeleteAlarmClockTime"); + initOpcode(0x6D,"Model522ResultTotals"); + initOpcode(0x6E,"Sara6E"); + initOpcode(0x6f,"ChangeCarbUnits"); + initOpcode(0x7B,"BasalProfileStart"); + initOpcode(0x7c,"ChangeWatchdogEnable"); + initOpcode(0x7d,"ChangeOtherDeviceID"); + initOpcode(0x81,"ChangeWatchdogMarriageProfile"); + initOpcode(0x82,"DeleteOtherDeviceID"); + initOpcode(0x83,"ChangeCaptureEventEnable"); + } + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpSettings.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpSettings.java new file mode 100644 index 0000000000..6c8772ec20 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/PumpSettings.java @@ -0,0 +1,168 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData; + +/** + * Created by geoff on 5/7/15. + * + * This class is intended to be very basic, and only hold the simple data. + * That way it will be portable, reusable. + * + * For instance, for the android UI, there is a parcelable version of this class + * (PumpSettingsParcel) but I don't want the pump implementation to be dependent + * on Android.Parcelable. + * + * For marshalling the data into other forms, I'm keeping the original + * byte array (mRawData) around. So if we have to save/reload this data object + * we can just write the raw data array, and reload from it. We already have to + * parse our values from it, so just keep it handy. Makes the Parcelable + * implementation very simple. + * + */ +@Deprecated +public class PumpSettings { + private static final String TAG = "PumpSettings"; + + // these values help with the parcel extension + protected static final int MINIMUM_DATA_LENGTH = 20; // bytes + protected static final int MAXIMUM_DATA_LENGTH = 64; // bytes + protected byte[] mRawData; + + public byte mAutoOffDuration_hours = 0; + public byte mAlarmMode = 0; + public byte mAlarmVolume = 0; + public boolean mAudioBolusEnable = false; + public double mAudioBolusSize = 0.0; + public boolean mVariableBolusEnable = false; + public double mMaxBolus = 0.0; // MM23 is different (?) + // MM512 and up (?) + public double mMaxBasal = 0.0; + public byte mTimeFormat = 0; + public int mInsulinConcentration = 100; // 100 or 50 + public boolean mPatternsEnabled = false; + public BasalProfileTypeEnum mSelectedPattern = BasalProfileTypeEnum.STD; + public boolean mRFEnable = false; + public boolean mBlockEnable = false; + public byte mTempBasalType = 0; // 1 means Percent, 0 means UnitsPerHour + public byte mTempBasalRate = 0; // TODO: scaling? + public byte mParadigmEnable = 0; + // MM12 + // boolean mInsulinActionType (0='Fast' or 1='Regular') + // MM15 + public byte mInsulinActionType = 0; + public byte mLowReservoirWarnType = 0; + public byte mLowReservoirWarnPoint = 0; + public byte mKeypadLockStatus = 0; + + public PumpSettings() { + init(); + } + + public void init() { + // this data buffer has to be sized to maximum even when empty + // as we may be reading from a parcel. See PumpSettingsParcel. + mRawData = new byte[MAXIMUM_DATA_LENGTH]; + /* fill in default values */ + } + + public byte[] getRawData() { return mRawData; } + + public boolean parseFrom(byte[] data) { + if (!setRawData(data)) { return false; } + return parseFromRaw(); + } + + // copy as much as we can from a byte array, but don't parse it. + public boolean setRawData(byte[] data) { + if (data == null) { + return false; + } + int len = Math.min(MAXIMUM_DATA_LENGTH,data.length); + System.arraycopy(data,0,mRawData,0,len); + return true; + } + + public boolean parseFromRaw() { + mAutoOffDuration_hours = mRawData[0]; + mAlarmVolume = mRawData[1]; + mAlarmMode = 2; + if (mRawData[1] == 4) { + mAlarmVolume = -1; + mAlarmMode = 1; + } + mAudioBolusEnable = (mRawData[2] == 1); + mAudioBolusSize = 0; + if (mAudioBolusEnable) { + mAudioBolusSize = mRawData[3] / 10.0; + } + mVariableBolusEnable = (mRawData[4] == 1); + // MM23 is different + int maxBolusByte = mRawData[5]; + if (maxBolusByte < 0) { maxBolusByte += 256; } + mMaxBolus = (maxBolusByte) / 10.0; + // MM512 and up + // TODO: check mMaxBasal calculation + // did I get the bytes in the right order? + int maxBasalHighByte = mRawData[6]; + if (maxBasalHighByte < 0) { + maxBasalHighByte += 256; + } + int maxBasalLowByte = mRawData[7]; + if (maxBasalLowByte < 0) { + maxBasalLowByte += 256; + } + mMaxBasal = ((maxBasalHighByte * 256) + maxBasalLowByte)/40.0; + mTimeFormat = mRawData[8]; + // mInsulinConcentration: 0 is 100%, 1 is 50% + mInsulinConcentration = 100; + if (mRawData[9] == 1) { + mInsulinConcentration = 50; + } + mPatternsEnabled = (mRawData[10] == 1); + mSelectedPattern = BasalProfileTypeEnum.STD; + if (mRawData[11] == 0x01) { + mSelectedPattern = BasalProfileTypeEnum.A; + } else if (mRawData[11] == 0x02) { + mSelectedPattern = BasalProfileTypeEnum.B; + } + mRFEnable = (mRawData[12] == 1); + mBlockEnable = (mRawData[13] == 1); + mTempBasalType = mRawData[14]; // todo: put into proper class of its own + mTempBasalRate = mRawData[15]; + + mParadigmEnable = mRawData[16]; + mInsulinActionType = mRawData[17]; + mLowReservoirWarnType = mRawData[18]; + mLowReservoirWarnPoint = mRawData[19]; + mKeypadLockStatus = mRawData[20]; + return true; + } + public String explain() { + String rval = ""; + // write out a string describing the current contents + rval += String.format("Auto-Off Duration (hours): %d\n",mAutoOffDuration_hours); + rval += String.format("Alarm (Volume: %d, Mode: %d)\n",mAlarmVolume,mAlarmMode); + rval += String.format("Audio BolusNormalPumpEvent (enabled=%s, size=%g)\n",mAudioBolusEnable,mAudioBolusSize); + rval += String.format("Variable BolusNormalPumpEvent Enable: %s\n", mVariableBolusEnable); + rval += String.format("Max BolusNormalPumpEvent: %g\n",mMaxBolus); + rval += String.format("Max Basal: %g\n",mMaxBasal); + rval += String.format("Time Format 0x%02X\n", mTimeFormat); + rval += String.format("Insulin Concentration %d%%\n",mInsulinConcentration); + rval += String.format("Patterns Enabled: %s\n",mPatternsEnabled); + rval += String.format("Selected Pattern: %d\n",mSelectedPattern); + rval += String.format("RF Enabled: %s\n",mRFEnable); + rval += String.format("Block Enabled: %s\n",mBlockEnable); + rval += String.format("Temp Basal Type: %s\n",(mTempBasalType == 1)?"Percent":"Units/Hr"); + if (mTempBasalType == 1) { + rval += String.format("Temp Basal Rate: %d%%\n",mTempBasalRate); + } else { + rval += String.format("Temp Basal Rate: %d U/h\n",mTempBasalRate); + } + rval += String.format("Paradigm Enabled: %s\n", mParadigmEnable); + rval += String.format("Insulin Action Type: 0x%02X\n",mInsulinActionType); + rval += String.format("Low Reservoir Warn Type: 0x%02X\n",mLowReservoirWarnType); + rval += String.format("Low Reservoir Warn Point: 0x%02X\n",mLowReservoirWarnPoint); + rval += String.format("Keypad Lock Status: 0x%02X\n",mKeypadLockStatus); + return rval; + } + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/TempBasalPair.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/TempBasalPair.java new file mode 100644 index 0000000000..553a1f3309 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/TempBasalPair.java @@ -0,0 +1,45 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData; + +/** + * Created by geoff on 5/29/15. + * + * Just need a class to keep the pair together, for parcel transport. + */ +@Deprecated +public class TempBasalPair { + private double mInsulinRate = 0.0; + private int mDurationMinutes = 0; + private boolean mIsPercent = false; + + public double getInsulinRate() { + return mInsulinRate; + } + + public void setInsulinRate(double insulinRate) { + this.mInsulinRate = insulinRate; + } + + public int getDurationMinutes() { + return mDurationMinutes; + } + + public void setDurationMinutes(int durationMinutes) { + this.mDurationMinutes = durationMinutes; + } + + public boolean isPercent() { + return mIsPercent; + } + + public void setIsPercent(boolean yesIsPercent) { + this.mIsPercent = yesIsPercent; + } + + public TempBasalPair() { } + + public TempBasalPair(double insulinRate, boolean isPercent, int durationMinutes) { + mInsulinRate = insulinRate; + mIsPercent = isPercent; + mDurationMinutes = durationMinutes; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/AlarmClockReminderPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/AlarmClockReminderPumpEvent.java new file mode 100644 index 0000000000..fde5a4ea46 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/AlarmClockReminderPumpEvent.java @@ -0,0 +1,13 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +/** + * Created by geoff on 6/11/16. + */ +public class AlarmClockReminderPumpEvent extends TimeStampedRecord { + public AlarmClockReminderPumpEvent() {}; + + @Override + public String getShortTypeName() { + return "Alarm Reminder"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/AlarmSensorPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/AlarmSensorPumpEvent.java new file mode 100644 index 0000000000..2c313b966a --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/AlarmSensorPumpEvent.java @@ -0,0 +1,19 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +/** + * Created by geoff on 6/5/16. + */ +public class AlarmSensorPumpEvent extends TimeStampedRecord { + public AlarmSensorPumpEvent() {} + + @Override + public int getLength() { return 8; } + + @Override + public String getShortTypeName() { + return "Alarm Sensor"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BGReceivedPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BGReceivedPumpEvent.java new file mode 100644 index 0000000000..1e944c2cc4 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BGReceivedPumpEvent.java @@ -0,0 +1,47 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; +import com.gxwtech.roundtrip2.util.ByteUtil; + +public class BGReceivedPumpEvent extends TimeStampedRecord { + private int amount = 0; + private byte[] meter = new byte[3]; + + public BGReceivedPumpEvent() { + } + + @Override + public int getLength() { return 10; } + + @Override + public String getShortTypeName() { + return "BG Received"; + } + + @Override + public boolean parseFrom(byte[] data, PumpModel model) { + if (!super.simpleParse(data,2)) { + return false; + } + amount = (asUINT8(data[1]) << 3) + (asUINT8(data[4])>>5); + meter = ByteUtil.substring(data,7,3); + return true; + } + + @Override + public boolean readFromBundle(Bundle in) { + amount = in.getInt("amount"); + meter = in.getByteArray("meter"); + return super.readFromBundle(in); + } + + @Override + public void writeToBundle(Bundle in) { + super.writeToBundle(in); + in.putInt("amount",amount); + in.putByteArray("meter",meter); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BasalProfileStart.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BasalProfileStart.java new file mode 100644 index 0000000000..9322d259c9 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BasalProfileStart.java @@ -0,0 +1,51 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +public class BasalProfileStart extends TimeStampedRecord { + private static final String TAG = "BasalProfileStart"; + private int offset = 0; + private double rate = 0.0; + private int profileIndex = 0; + + public BasalProfileStart() { } + + @Override + public int getLength() { return 10; } + + @Override + public String getShortTypeName() { + return "Basal Profile Start"; + } + + @Override + public boolean parseFrom(byte[] data, PumpModel model) { + if (!simpleParse(data,2)) { + return false; + } + + profileIndex = asUINT8(data[1]); + offset = asUINT8(data[7]) * 30 * 1000 * 60; + rate = (double)(asUINT8(data[8])) / 40.0; + return true; + } + + @Override + public boolean readFromBundle(Bundle in) { + offset = in.getInt("offset"); + rate = in.getDouble("rate"); + profileIndex = in.getInt("profileIndex"); + return super.readFromBundle(in); + } + + @Override + public void writeToBundle(Bundle in) { + super.writeToBundle(in); + in.putInt("offset",offset); + in.putDouble("rate",rate); + in.putInt("profileIndex",profileIndex); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BatteryPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BatteryPumpEvent.java new file mode 100644 index 0000000000..714f72b059 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BatteryPumpEvent.java @@ -0,0 +1,10 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +public class BatteryPumpEvent extends TimeStampedRecord { + public BatteryPumpEvent() {} + + @Override + public String getShortTypeName() { + return "Battery"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BolusNormalPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BolusNormalPumpEvent.java new file mode 100644 index 0000000000..32768b3ebf --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BolusNormalPumpEvent.java @@ -0,0 +1,86 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpTimeStamp; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.TimeFormat; + +public class BolusNormalPumpEvent extends TimeStampedRecord { + private final static String TAG = "BolusNormalPumpEvent"; + + private double programmedAmount = 0.0; + private double deliveredAmount = 0.0; + private int duration = 0; + private double unabsorbedInsulinTotal = 0.0; + private String bolusType = "Unset"; + + public BolusNormalPumpEvent() { + } + + @Override + public int getLength() { return PumpModel.isLargerFormat(model) ? 13 : 9; } + + @Override + public String getShortTypeName() { + return "Normal Bolus"; + } + + private double insulinDecode(int a, int b) { + return ((a << 8) + b) / 40.0; + } + + @Override + public boolean parseFrom(byte[] data, PumpModel model) { + if (getLength() > data.length) { + return false; + } + if (PumpModel.isLargerFormat(model)) { + programmedAmount = insulinDecode(asUINT8(data[1]),asUINT8(data[2])); + deliveredAmount = insulinDecode(asUINT8(data[3]),asUINT8(data[4])); + unabsorbedInsulinTotal = insulinDecode(asUINT8(data[5]),asUINT8(data[6])); + duration = asUINT8(data[7]) * 30; + try { + timestamp = new PumpTimeStamp(TimeFormat.parse5ByteDate(data, 8)); + } catch (org.joda.time.IllegalFieldValueException e) { + return false; + } + } else { + programmedAmount = asUINT8(data[1]) / 10.0f; + deliveredAmount = asUINT8(data[2]) / 10.0f; + duration = asUINT8(data[3]) * 30; + unabsorbedInsulinTotal = 0; + try { + timestamp = new PumpTimeStamp(TimeFormat.parse5ByteDate(data,4)); + } catch (org.joda.time.IllegalFieldValueException e) { + return false; + } + + } + + bolusType = (duration > 0) ? "square" : "normal"; + return true; + } + + @Override + public boolean readFromBundle(Bundle in) { + programmedAmount = in.getDouble("programmedAmount",0.0); + deliveredAmount = in.getDouble("deliveredAmount",0.0); + duration = in.getInt("duration",0); + unabsorbedInsulinTotal = in.getDouble("unabsorbedInsulinTotal",0.0); + bolusType = in.getString("bolusType","Unset"); + return super.readFromBundle(in); + } + + @Override + public void writeToBundle(Bundle in) { + super.writeToBundle(in); + in.putDouble("programmedAmount",programmedAmount); + in.putDouble("deliveredAmount",deliveredAmount); + in.putInt("duration",duration); + in.putDouble("unabsorbedInsulinTotal",unabsorbedInsulinTotal); + in.putString("bolusType",bolusType); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BolusWizardBolusEstimatePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BolusWizardBolusEstimatePumpEvent.java new file mode 100644 index 0000000000..ffd628f204 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/BolusWizardBolusEstimatePumpEvent.java @@ -0,0 +1,118 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +public class BolusWizardBolusEstimatePumpEvent extends TimeStampedRecord { + private final static String TAG = "BolusWizardBolusEstimatePumpEvent"; + + private int carbohydrates; + private int bloodGlucose; + private double foodEstimate; + private double correctionEstimate; + private double bolusEstimate; + private double unabsorbedInsulinTotal; + private int bgTargetLow; + private int bgTargetHigh; + private int insulinSensitivity; + private double carbRatio; + + public BolusWizardBolusEstimatePumpEvent() { + correctionEstimate = (double)0.0; + bloodGlucose = 0; + carbohydrates = 0; + carbRatio = 0.0; + insulinSensitivity = 0; + bgTargetLow = 0; + bgTargetHigh = 0; + bolusEstimate = 0.0; + foodEstimate = 0.0; + unabsorbedInsulinTotal = 0.0; + } + + @Override + public int getLength() { return PumpModel.isLargerFormat(model) ? 22 : 20;} + + @Override + public String getShortTypeName() { + return "Bolus Wizard Est."; + } + + @Override + public boolean readFromBundle(Bundle in) { + carbohydrates = in.getInt("carbohydrates",0); + bloodGlucose = in.getInt("bloodGlucose",0); + foodEstimate = in.getDouble("foodEstimate",0); + correctionEstimate = in.getDouble("correctionEstimate",0); + bolusEstimate = in.getDouble("bolusEstimate",0); + unabsorbedInsulinTotal = in.getDouble("unabsorbedInsulinTotal",0); + bgTargetLow = in.getInt("bgTargetLow",0); + bgTargetHigh = in.getInt("bgTargetHigh",0); + insulinSensitivity = in.getInt("insulinSensitivity",0); + carbRatio = in.getDouble("carbRatio",0); + return super.readFromBundle(in); + } + + @Override + public void writeToBundle(Bundle in) { + super.writeToBundle(in); + in.putInt("carbohydrates",carbohydrates); + in.putInt("bloodGlucose",bloodGlucose); + in.putDouble("foodEstimate",foodEstimate); + in.putDouble("correctionEstimate",correctionEstimate); + in.putDouble("bolusEstimate",bolusEstimate); + in.putDouble("unabsorbedInsulinTotal",unabsorbedInsulinTotal); + in.putInt("bgTargetLow",bgTargetLow); + in.putInt("bgTargetHigh",bgTargetHigh); + in.putInt("insulinSensitivity",insulinSensitivity); + in.putDouble("carbRatio",carbRatio); + } + + public double getCorrectionEstimate() { return correctionEstimate; } + public long getBG() { return bloodGlucose; } + public int getCarbohydrates() { return carbohydrates; } + public double getICRatio() { return carbRatio; } + public int getInsulinSensitivity() { return insulinSensitivity; } + public int getBgTargetLow() { return bgTargetLow; } + public int getBgTargetHigh() { return bgTargetHigh; } + public double getBolusEstimate() { return bolusEstimate; } + public double getFoodEstimate() { return foodEstimate; } + public double getUnabsorbedInsulinTotal() { return unabsorbedInsulinTotal; } + + private double insulinDecode(int a, int b) { + return ((a << 8) + b) / 40.0; + } + + @Override + public boolean parseFrom(byte[] data, PumpModel model) { + if (!simpleParse(data,2)) { + return false; + } + if (PumpModel.isLargerFormat(model)) { + carbohydrates = (asUINT8(data[8]) & 0x0c << 6) + asUINT8(data[7]); + bloodGlucose = (asUINT8(data[8]) & 0x03 << 8) + asUINT8(data[1]); + foodEstimate = insulinDecode(asUINT8(data[14]), asUINT8(data[15])); + correctionEstimate = (double)((asUINT8(data[16])& 0b111000) << 5 + asUINT8(data[13]))/40.0; + bolusEstimate = insulinDecode(asUINT8(data[19]), asUINT8(data[20])); + unabsorbedInsulinTotal = insulinDecode(asUINT8(data[17]),asUINT8(data[18])); + bgTargetLow = asUINT8(data[12]); + bgTargetHigh = asUINT8(data[21]); + insulinSensitivity = asUINT8(data[11]); + carbRatio = (double)(((asUINT8(data[9]) & 0x07) << 8) + asUINT8(data[10]))/40.0; + } else { + carbohydrates = asUINT8(data[7]); + bloodGlucose = ((asUINT8(data[8]) & 0x03) << 8) + asUINT8(data[1]); + foodEstimate = (double)(asUINT8(data[13]))/10.0; + correctionEstimate = (double)((asUINT8(data[14])<<8) + asUINT8(data[12])) / 10.0; + bolusEstimate = (double)(asUINT8(data[18]))/10.0; + unabsorbedInsulinTotal = (double)(asUINT8(data[16])) / 10.0; + bgTargetLow = asUINT8(data[11]); + bgTargetHigh = asUINT8(data[19]); + insulinSensitivity = asUINT8(data[10]); + carbRatio = (double)asUINT8(data[9]); + } + + return true; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/CalBgForPhPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/CalBgForPhPumpEvent.java new file mode 100644 index 0000000000..1f18c26c6d --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/CalBgForPhPumpEvent.java @@ -0,0 +1,39 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +public class CalBgForPhPumpEvent extends TimeStampedRecord { + private int amount = 0; + + public CalBgForPhPumpEvent() { + } + + @Override + public String getShortTypeName() { + return "Cal Bg For Ph"; + } + + @Override + public boolean parseFrom(byte[] data, PumpModel model) { + if (!simpleParse(data,2)) { + return false; + } + amount = ((asUINT8(data[6]) & 0x80) << 1) + asUINT8(data[1]); + return true; + } + + @Override + public boolean readFromBundle(Bundle in) { + amount = in.getInt("amount",0); + return super.readFromBundle(in); + } + + @Override + public void writeToBundle(Bundle in) { + super.writeToBundle(in); + in.putInt("amount",amount); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeAlarmClockEnablePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeAlarmClockEnablePumpEvent.java new file mode 100644 index 0000000000..e7a20ae425 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeAlarmClockEnablePumpEvent.java @@ -0,0 +1,13 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeAlarmClockEnablePumpEvent extends TimeStampedRecord { + public ChangeAlarmClockEnablePumpEvent() {} + + @Override + public String getShortTypeName() { + return "Alarm Clock Enable"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeAlarmNotifyModePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeAlarmNotifyModePumpEvent.java new file mode 100644 index 0000000000..a1ba91e27e --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeAlarmNotifyModePumpEvent.java @@ -0,0 +1,10 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +public class ChangeAlarmNotifyModePumpEvent extends TimeStampedRecord { + public ChangeAlarmNotifyModePumpEvent() {} + + @Override + public String getShortTypeName() { + return "Ch Alarm Notify Mode"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeAudioBolusPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeAudioBolusPumpEvent.java new file mode 100644 index 0000000000..271dc3ef15 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeAudioBolusPumpEvent.java @@ -0,0 +1,13 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeAudioBolusPumpEvent extends TimeStampedRecord { + public ChangeAudioBolusPumpEvent() {} + + @Override + public String getShortTypeName() { + return "Ch Audio Bolus"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBGReminderEnablePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBGReminderEnablePumpEvent.java new file mode 100644 index 0000000000..35fad6322c --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBGReminderEnablePumpEvent.java @@ -0,0 +1,13 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeBGReminderEnablePumpEvent extends TimeStampedRecord { + public ChangeBGReminderEnablePumpEvent() {} + + @Override + public String getShortTypeName() { + return "Ch BG Rmndr Enable"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBasalProfilePatternPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBasalProfilePatternPumpEvent.java new file mode 100644 index 0000000000..a145d27d1c --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBasalProfilePatternPumpEvent.java @@ -0,0 +1,21 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeBasalProfilePatternPumpEvent extends TimeStampedRecord { + public ChangeBasalProfilePatternPumpEvent() {} + + @Override + public int getLength() { + return 152; + } + + @Override + public String getShortTypeName() { + return "Ch Basal Prof Pat"; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBasalProfilePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBasalProfilePumpEvent.java new file mode 100644 index 0000000000..c653ac0cfd --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBasalProfilePumpEvent.java @@ -0,0 +1,15 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +public class ChangeBasalProfilePumpEvent extends TimeStampedRecord { + public ChangeBasalProfilePumpEvent() { + } + + @Override + public int getLength() { return 152; } + + @Override + public String getShortTypeName() { + return "Ch Basal Profile"; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusReminderEnablePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusReminderEnablePumpEvent.java new file mode 100644 index 0000000000..45bc0b2327 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusReminderEnablePumpEvent.java @@ -0,0 +1,19 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeBolusReminderEnablePumpEvent extends TimeStampedRecord { + public ChangeBolusReminderEnablePumpEvent() {} + + @Override + public int getLength() { return 9; } + + @Override + public String getShortTypeName() { + return "Ch Bolus Rmndr Enable"; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusReminderTimePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusReminderTimePumpEvent.java new file mode 100644 index 0000000000..1f9a8d6d57 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusReminderTimePumpEvent.java @@ -0,0 +1,19 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeBolusReminderTimePumpEvent extends TimeStampedRecord { + public ChangeBolusReminderTimePumpEvent(){} + + @Override + public String getShortTypeName() { + return "Ch Bolus Rmndr Time"; + } + + @Override + public int getLength() { return 9; } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusScrollStepSizePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusScrollStepSizePumpEvent.java new file mode 100644 index 0000000000..90ac2477cf --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusScrollStepSizePumpEvent.java @@ -0,0 +1,13 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeBolusScrollStepSizePumpEvent extends TimeStampedRecord { + public ChangeBolusScrollStepSizePumpEvent() {} + + @Override + public String getShortTypeName() { + return "Ch Bolus Scroll SS"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusWizardSetupPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusWizardSetupPumpEvent.java new file mode 100644 index 0000000000..e602421c0f --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeBolusWizardSetupPumpEvent.java @@ -0,0 +1,21 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +public class ChangeBolusWizardSetupPumpEvent extends TimeStampedRecord { + + public ChangeBolusWizardSetupPumpEvent() { + + } + + @Override + public int getLength() { + return 144; + } + + @Override + public String getShortTypeName() { + return "Ch Bolus Wizard Setup"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeCaptureEventEnablePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeCaptureEventEnablePumpEvent.java new file mode 100644 index 0000000000..32cf7094df --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeCaptureEventEnablePumpEvent.java @@ -0,0 +1,13 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeCaptureEventEnablePumpEvent extends TimeStampedRecord { + public ChangeCaptureEventEnablePumpEvent() {} + + @Override + public String getShortTypeName() { + return "Ch Capture Event Ena"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeCarbUnitsPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeCarbUnitsPumpEvent.java new file mode 100644 index 0000000000..b4c64c25f6 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeCarbUnitsPumpEvent.java @@ -0,0 +1,13 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeCarbUnitsPumpEvent extends TimeStampedRecord { + public ChangeCarbUnitsPumpEvent() {} + + @Override + public String getShortTypeName() { + return "Ch Carb Units"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeChildBlockEnablePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeChildBlockEnablePumpEvent.java new file mode 100644 index 0000000000..21da1c37c1 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeChildBlockEnablePumpEvent.java @@ -0,0 +1,13 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeChildBlockEnablePumpEvent extends TimeStampedRecord { + public ChangeChildBlockEnablePumpEvent(){} + + @Override + public String getShortTypeName() { + return "Ch Child Block Ena"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeMaxBolusPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeMaxBolusPumpEvent.java new file mode 100644 index 0000000000..9c9904f187 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeMaxBolusPumpEvent.java @@ -0,0 +1,13 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeMaxBolusPumpEvent extends TimeStampedRecord { + public ChangeMaxBolusPumpEvent() {} + + @Override + public String getShortTypeName() { + return "Ch Max Bolux"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeOtherDeviceIDPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeOtherDeviceIDPumpEvent.java new file mode 100644 index 0000000000..36d3230711 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeOtherDeviceIDPumpEvent.java @@ -0,0 +1,19 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +public class ChangeOtherDeviceIDPumpEvent extends TimeStampedRecord { + + public ChangeOtherDeviceIDPumpEvent() { + } + + @Override + public int getLength() { return 37; } + + @Override + public String getShortTypeName() { + return "Ch Other Dev ID"; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeReservoirWarningTimePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeReservoirWarningTimePumpEvent.java new file mode 100644 index 0000000000..67632c2e32 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeReservoirWarningTimePumpEvent.java @@ -0,0 +1,13 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeReservoirWarningTimePumpEvent extends TimeStampedRecord { + public ChangeReservoirWarningTimePumpEvent(){} + + @Override + public String getShortTypeName() { + return "Ch Res Warn Time"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeSensorRateOfChangeAlertSetupPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeSensorRateOfChangeAlertSetupPumpEvent.java new file mode 100644 index 0000000000..da6cf942b9 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeSensorRateOfChangeAlertSetupPumpEvent.java @@ -0,0 +1,19 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeSensorRateOfChangeAlertSetupPumpEvent extends TimeStampedRecord { + public ChangeSensorRateOfChangeAlertSetupPumpEvent() {} + + @Override + public int getLength() { return 12; } + + @Override + public String getShortTypeName() { + return "Ch Sensor ROC Alert"; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeSensorSetup2PumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeSensorSetup2PumpEvent.java new file mode 100644 index 0000000000..817f15be54 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeSensorSetup2PumpEvent.java @@ -0,0 +1,19 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeSensorSetup2PumpEvent extends TimeStampedRecord { + public ChangeSensorSetup2PumpEvent() {} + + @Override + public int getLength() { return 37; } + + @Override + public String getShortTypeName() { + return "Ch Sensor Setup2"; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeTempBasalTypePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeTempBasalTypePumpEvent.java new file mode 100644 index 0000000000..0b932b5df5 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeTempBasalTypePumpEvent.java @@ -0,0 +1,44 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeTempBasalTypePumpEvent extends TimeStampedRecord { + private boolean isPercent=false; // either absolute or percent + public ChangeTempBasalTypePumpEvent() {} + + @Override + public String getShortTypeName() { + return "Ch Temp Basal Type"; + } + + @Override + public boolean parseFrom(byte[] data, PumpModel model) { + if (!simpleParse(data,2)) { + return false; + } + if (asUINT8(data[1])==1) { + isPercent = true; + } else { + isPercent = false; + } + return true; + } + + @Override + public boolean readFromBundle(Bundle in) { + isPercent = in.getBoolean("isPercent",false); + return super.readFromBundle(in); + } + + @Override + public void writeToBundle(Bundle in) { + in.putBoolean("isPercent",isPercent); + super.writeToBundle(in); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeTimeFormatPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeTimeFormatPumpEvent.java new file mode 100644 index 0000000000..199793761b --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeTimeFormatPumpEvent.java @@ -0,0 +1,11 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +public class ChangeTimeFormatPumpEvent extends TimeStampedRecord { + public ChangeTimeFormatPumpEvent() { + } + + @Override + public String getShortTypeName() { + return "Ch Time Format"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeTimePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeTimePumpEvent.java new file mode 100644 index 0000000000..ca65f566f1 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeTimePumpEvent.java @@ -0,0 +1,18 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +public class ChangeTimePumpEvent extends TimeStampedRecord { + public ChangeTimePumpEvent() { + + } + + @Override + public int getLength() { return 14; } + + @Override + public String getShortTypeName() { + return "Change Time"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeVariableBolusPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeVariableBolusPumpEvent.java new file mode 100644 index 0000000000..d778e87dbb --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeVariableBolusPumpEvent.java @@ -0,0 +1,13 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeVariableBolusPumpEvent extends TimeStampedRecord { + public ChangeVariableBolusPumpEvent(){} + + @Override + public String getShortTypeName() { + return "Ch Var. Bolus"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeWatchdogEnablePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeWatchdogEnablePumpEvent.java new file mode 100644 index 0000000000..70bc7a2ca2 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeWatchdogEnablePumpEvent.java @@ -0,0 +1,13 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeWatchdogEnablePumpEvent extends TimeStampedRecord { + public ChangeWatchdogEnablePumpEvent(){} + + @Override + public String getShortTypeName() { + return "Ch Watchdog Enable"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeWatchdogMarriageProfilePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeWatchdogMarriageProfilePumpEvent.java new file mode 100644 index 0000000000..f9f0f00b90 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ChangeWatchdogMarriageProfilePumpEvent.java @@ -0,0 +1,19 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +/** + * Created by geoff on 6/5/16. + */ +public class ChangeWatchdogMarriageProfilePumpEvent extends TimeStampedRecord { + public ChangeWatchdogMarriageProfilePumpEvent() {} + + @Override + public int getLength() { return 12; } + + @Override + public String getShortTypeName() { + return "Ch WD Marriage"; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ClearAlarmPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ClearAlarmPumpEvent.java new file mode 100644 index 0000000000..3bf663ac44 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ClearAlarmPumpEvent.java @@ -0,0 +1,10 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +public class ClearAlarmPumpEvent extends TimeStampedRecord { + public ClearAlarmPumpEvent() {} + + @Override + public String getShortTypeName() { + return "Clear Alarm"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/DeleteAlarmClockTimePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/DeleteAlarmClockTimePumpEvent.java new file mode 100644 index 0000000000..0f726986b4 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/DeleteAlarmClockTimePumpEvent.java @@ -0,0 +1,19 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +/** + * Created by geoff on 6/5/16. + */ +public class DeleteAlarmClockTimePumpEvent extends TimeStampedRecord { + public DeleteAlarmClockTimePumpEvent() {} + + @Override + public int getLength() { return 14; } + + @Override + public String getShortTypeName() { + return "Del Alarm Clock Time"; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/DeleteBolusReminderTimePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/DeleteBolusReminderTimePumpEvent.java new file mode 100644 index 0000000000..787923f870 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/DeleteBolusReminderTimePumpEvent.java @@ -0,0 +1,19 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +/** + * Created by geoff on 6/5/16. + */ +public class DeleteBolusReminderTimePumpEvent extends TimeStampedRecord { + public DeleteBolusReminderTimePumpEvent() {} + + @Override + public int getLength() { return 9; } + + @Override + public String getShortTypeName() { + return "Del Bolus Rmndr Time"; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/DeleteOtherDeviceIDPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/DeleteOtherDeviceIDPumpEvent.java new file mode 100644 index 0000000000..98a069c139 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/DeleteOtherDeviceIDPumpEvent.java @@ -0,0 +1,19 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +/** + * Created by geoff on 6/5/16. + */ +public class DeleteOtherDeviceIDPumpEvent extends TimeStampedRecord { + public DeleteOtherDeviceIDPumpEvent(){} + + @Override + public int getLength() { return 12; } + + @Override + public String getShortTypeName() { + return "Del Other Dev ID"; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/EnableDisableRemotePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/EnableDisableRemotePumpEvent.java new file mode 100644 index 0000000000..65ef42a33e --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/EnableDisableRemotePumpEvent.java @@ -0,0 +1,17 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +public class EnableDisableRemotePumpEvent extends TimeStampedRecord { + public EnableDisableRemotePumpEvent() { + } + + @Override + public int getLength() { return 21; } + + @Override + public String getShortTypeName() { + return "Toggle Remote"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/InsulinMarkerEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/InsulinMarkerEvent.java new file mode 100644 index 0000000000..41af3b4b4e --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/InsulinMarkerEvent.java @@ -0,0 +1,23 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +/** + * Created by geoff on 7/16/16. + */ +public class InsulinMarkerEvent extends TimeStampedRecord { + public InsulinMarkerEvent() {} + + @Override + public int getLength() { + return 8; + } + + /* + Darrell Wright: + it is a manual entry of a bolus that the pump didn't deliver, so opcode, timestamp and at least a number to represent the units of insulin + */ + + @Override + public String getShortTypeName() { + return "UnknownInsulin"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/JournalEntryExerciseMarkerPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/JournalEntryExerciseMarkerPumpEvent.java new file mode 100644 index 0000000000..1e2994fbe6 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/JournalEntryExerciseMarkerPumpEvent.java @@ -0,0 +1,18 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +/** + * Created by geoff on 6/5/16. + */ +public class JournalEntryExerciseMarkerPumpEvent extends TimeStampedRecord { + public JournalEntryExerciseMarkerPumpEvent(){} + + @Override + public int getLength() { return 8; } + + @Override + public String getShortTypeName() { + return "Exercise Marker"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/JournalEntryPumpLowBatteryPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/JournalEntryPumpLowBatteryPumpEvent.java new file mode 100644 index 0000000000..85355d203a --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/JournalEntryPumpLowBatteryPumpEvent.java @@ -0,0 +1,10 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +public class JournalEntryPumpLowBatteryPumpEvent extends TimeStampedRecord { + public JournalEntryPumpLowBatteryPumpEvent() {} + + @Override + public String getShortTypeName() { + return "Low Battery"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/JournalEntryPumpLowReservoirPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/JournalEntryPumpLowReservoirPumpEvent.java new file mode 100644 index 0000000000..bc7c5f28c4 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/JournalEntryPumpLowReservoirPumpEvent.java @@ -0,0 +1,10 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +public class JournalEntryPumpLowReservoirPumpEvent extends TimeStampedRecord { + public JournalEntryPumpLowReservoirPumpEvent() {} + + @Override + public String getShortTypeName() { + return "Low Reservoir"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/Model522ResultTotalsPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/Model522ResultTotalsPumpEvent.java new file mode 100644 index 0000000000..c0cf926a22 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/Model522ResultTotalsPumpEvent.java @@ -0,0 +1,15 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +public class Model522ResultTotalsPumpEvent extends TimeStampedRecord { + public Model522ResultTotalsPumpEvent() {} + + public int getDatestampOffset() { return 1; } + + @Override + public int getLength() { return 44; } + + @Override + public String getShortTypeName() { + return "M522 Result Totals"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/NewTimeSet.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/NewTimeSet.java new file mode 100644 index 0000000000..8a5832973d --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/NewTimeSet.java @@ -0,0 +1,15 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +// This event existed as 0x18 in Roundtrip and early Decocare, +// but I don't see a corresponding event in RileyLink_ios. +public class NewTimeSet extends TimeStampedRecord { + public NewTimeSet() { + } + + @Override + public boolean parseFrom(byte[] data, PumpModel model) { + return false; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/PrimePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/PrimePumpEvent.java new file mode 100644 index 0000000000..b0c26a10a7 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/PrimePumpEvent.java @@ -0,0 +1,51 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +public class PrimePumpEvent extends TimeStampedRecord { + private double amount=0.0; + private double programmedAmount=0.0; + private String primeType = "unknown"; + + public PrimePumpEvent() { + } + + @Override + public int getLength() { return 10; } + + @Override + public String getShortTypeName() { + return "Prime Pump"; + } + + @Override + public boolean parseFrom(byte[] data, PumpModel model) { + if (!simpleParse(data,5)) { + return false; + } + amount = (double)(asUINT8(data[4])<<2) / 40.0; + programmedAmount = (double)(asUINT8(data[2])<<2) / 40.0; + primeType = programmedAmount == 0 ? "manual" : "fixed"; + return true; + } + + @Override + public boolean readFromBundle(Bundle in) { + amount = in.getDouble("amount",0.0); + programmedAmount = in.getDouble("programmedAmount",0); + primeType = in.getString("primeType","unknown"); + return super.readFromBundle(in); + } + + @Override + public void writeToBundle(Bundle in) { + in.putDouble("amount",amount); + in.putDouble("programmedAmount",programmedAmount); + in.putString("primeType",primeType); + super.writeToBundle(in); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/PumpAlarmPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/PumpAlarmPumpEvent.java new file mode 100644 index 0000000000..7081dec177 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/PumpAlarmPumpEvent.java @@ -0,0 +1,42 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +public class PumpAlarmPumpEvent extends TimeStampedRecord { + private int rawtype = 0; + public PumpAlarmPumpEvent() { + } + + @Override + public int getLength() { return 9; } + + @Override + public String getShortTypeName() { + return "Pump Alarm"; + } + + @Override + public boolean parseFrom(byte[] data, PumpModel model) { + if (!simpleParse(data,4)) { + return false; + } + rawtype = asUINT8(data[1]); + return true; + } + + @Override + public boolean readFromBundle(Bundle in) { + rawtype = in.getInt("rawtype",0); + return super.readFromBundle(in); + } + + @Override + public void writeToBundle(Bundle in) { + in.putInt("rawtype",rawtype); + super.writeToBundle(in); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/Record.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/Record.java new file mode 100644 index 0000000000..6992ddfae8 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/Record.java @@ -0,0 +1,92 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpTimeStamp; + +abstract public class Record { + private static final String TAG = "Record"; + protected PumpModel model; + protected byte recordOp; + //protected int length; + protected int foundAtOffset; + protected byte[] rawbytes = new byte[0]; + //protected String recordTypeName = this.getClass().getSimpleName(); + + public String getRecordTypeName() { return this.getClass().getSimpleName(); } + public String getShortTypeName() { + return this.getClass().getSimpleName(); + } + public void setPumpModel(PumpModel model) { this.model = model; } + public int getFoundAtOffset() { return foundAtOffset; } + + public Record() { + + } + + public boolean parseWithOffset(byte[] data, PumpModel model, int foundAtOffset) { + // keep track of where the record was found for later analysis + this.foundAtOffset = foundAtOffset; + if (data == null) { + return false; + } + if (data.length < 1) { + return false; + } + recordOp = data[0]; + boolean didParse = parseFrom(data,model); + if (didParse) { + captureRawBytes(data); + } + return didParse; + } + + public void captureRawBytes(byte[] data) { + this.rawbytes = new byte[getLength()]; + System.arraycopy(data, 0, this.rawbytes,0,getLength()-1); + } + + public boolean parseFrom(byte[] data, PumpModel model) { + return true; + } + + public PumpTimeStamp getTimestamp() { + return new PumpTimeStamp(); + } + + public int getLength() { + return 1; + } + + public byte getRecordOp() { + return recordOp; + } + + protected static int asUINT8(byte b) { + return (b < 0) ? b + 256 : b; + } + + public Bundle dictionaryRepresentation() { + Bundle rval = new Bundle(); + writeToBundle(rval); + return rval; + } + + public boolean readFromBundle(Bundle in) { + // length is determined at instantiation + // record type name is "static" + // opcode has already been read. + return true; + } + + public void writeToBundle(Bundle in) { + in.putInt("length",getLength()); + in.putInt("foundAtOffset",foundAtOffset); + in.putInt("_opcode",recordOp); + in.putString("_type", getRecordTypeName()); + in.putString("_stype", getShortTypeName()); + in.putByteArray("rawbytes",rawbytes); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/RecordTypeEnum.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/RecordTypeEnum.java new file mode 100644 index 0000000000..12a031eda4 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/RecordTypeEnum.java @@ -0,0 +1,126 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * Created by geoff on 5/28/15. + */ +public enum RecordTypeEnum { + RECORD_TYPE_NULL((byte)0x00, null), + RECORD_TYPE_BOLUSNORMAL((byte)0x01,BolusNormalPumpEvent.class), + RECORD_TYPE_PRIME((byte)0x03,PrimePumpEvent.class), + RECORD_TYPE_ALARMPUMP((byte)0x06,PumpAlarmPumpEvent.class), + RECORD_TYPE_RESULTDAILYTOTAL((byte)0x07,ResultDailyTotalPumpEvent.class), + RECORD_TYPE_CHANGEBASALPROFILEPATTERN((byte)0x08,ChangeBasalProfilePatternPumpEvent.class), + RECORD_TYPE_CHANGEBASALPROFILE((byte)0x09,ChangeBasalProfilePumpEvent.class), + RECORD_TYPE_CALBGFORPH((byte)0x0A,CalBgForPhPumpEvent.class), + RECORD_TYPE_ALARMSENSOR((byte)0x0B,AlarmSensorPumpEvent.class), + RECORD_TYPE_CLEARALARM((byte)0x0C,ClearAlarmPumpEvent.class), + //RECORD_TYPE_SELECTBASALPROFILE((byte)0x14,SelectBasalProfile.class), + RECORD_TYPE_TEMPBASALDURATION((byte)0x16,TempBasalDurationPumpEvent.class), + RECORD_TYPE_CHANGETIME((byte)0x17,ChangeTimePumpEvent.class), + RECORD_TYPE_NEWTIMESET((byte)0x18,NewTimeSet.class), + RECORD_TYPE_JournalEntryPumpLowBattery((byte)0x19,JournalEntryPumpLowBatteryPumpEvent.class), + RECORD_TYPE_BATTERY((byte)0x1A,BatteryPumpEvent.class), + RECORD_TYPE_PUMPSUSPENDED((byte)0x1E,SuspendPumpEvent.class), + RECORD_TYPE_PUMPRESUMED((byte)0x1F,ResumePumpEvent.class), + RECORD_TYPE_REWIND((byte)0x21,RewindPumpEvent.class), + RECORD_TYPE_CHANGECHILDBLOCKENABLE((byte)0x23,ChangeChildBlockEnablePumpEvent.class), + RECORD_TYPE_CHANGEMAXBOLUS((byte)0x24,ChangeMaxBolusPumpEvent.class), + RECORD_TYPE_ENABLEDISABLEREMOTE((byte)0x26,EnableDisableRemotePumpEvent.class), + RECORD_TYPE_TEMPBASALRATE((byte)0x33,TempBasalRatePumpEvent.class), + RECORD_TYPE_LOWRESERVOIR((byte)0x34,JournalEntryPumpLowReservoirPumpEvent.class), + RECORD_TYPE_AlarmClockReminder((byte)0x35,AlarmClockReminderPumpEvent.class), + RECORD_TYPE_BGRECEIVED((byte)0x3F,BGReceivedPumpEvent.class), + RECORD_TYPE_JournalEntryExerciseMarker((byte)0x41,JournalEntryExerciseMarkerPumpEvent.class), + RECORD_TYPE_Unknown7Byte_1((byte)0x42,Unknown7ByteEvent1.class), + RECORD_TYPE_InsulinMarker((byte)0x43,InsulinMarkerEvent.class), + RECORD_TYPE_CHANGESENSORSETUP2((byte)0x50,ChangeSensorSetup2PumpEvent.class), + RECORD_TYPE_ChangeSensorRateOfChangeAlertSetup((byte)0x56,ChangeSensorRateOfChangeAlertSetupPumpEvent.class), + RECORD_TYPE_ChangeBolusScrollStepSize((byte)0x57,ChangeBolusScrollStepSizePumpEvent.class), + RECORD_TYPE_ChangeBolusWizardSetup((byte)0x5A,ChangeBolusWizardSetupPumpEvent.class), + RECORD_TYPE_BolusWizardBolusEstimate((byte)0x5B,BolusWizardBolusEstimatePumpEvent.class), + RECORD_TYPE_UNABSORBEDINSULIN((byte)0x5C,UnabsorbedInsulin.class), + RECORD_TYPE_CHANGEVARIABLEBOLUS((byte)0x5e,ChangeVariableBolusPumpEvent.class), + RECORD_TYPE_CHANGEAUDIOBOLUS((byte)0x5f,ChangeAudioBolusPumpEvent.class), + RECORD_TYPE_ChangeBGReminderEnable((byte)0x60,ChangeBGReminderEnablePumpEvent.class), + RECORD_TYPE_ChangeAlarmClockEnable((byte)0x61,ChangeAlarmClockEnablePumpEvent.class), + RECORD_TYPE_ChangeTempBasalType((byte)0x62,ChangeTempBasalTypePumpEvent.class), + RECORD_TYPE_ChangeAlarmNotifyMode((byte)0x63,ChangeAlarmNotifyModePumpEvent.class), + RECORD_TYPE_ChangeTimeFormat((byte)0x64,ChangeTimeFormatPumpEvent.class), + RECORD_TYPE_ChangeReservoirWarningTime((byte)0x65,ChangeReservoirWarningTimePumpEvent.class), + RECORD_TYPE_ChangeBolusReminderEnable((byte)0x66,ChangeBolusReminderEnablePumpEvent.class), + RECORD_TYPE_ChangeBolusReminderTime((byte)0x67,ChangeBolusReminderTimePumpEvent.class), + RECORD_TYPE_DeleteBolusReminderTime((byte)0x68,DeleteBolusReminderTimePumpEvent.class), + RECORD_TYPE_DeleteAlarmClockTime((byte)0x6a,DeleteAlarmClockTimePumpEvent.class), + RECORD_TYPE_MODEL522RESULTTOTALS((byte)0x6D,Model522ResultTotalsPumpEvent.class), + RECORD_TYPE_SARA6E((byte)0x6E,Sara6EPumpEvent.class), + RECORD_TYPE_ChangeCarbUnits((byte)0x6f,ChangeCarbUnitsPumpEvent.class), + RECORD_TYPE_BASALPROFILESTART((byte)0x7B,BasalProfileStart.class), + RECORD_TYPE_ChangeWatchdogEnable((byte)0x7c,ChangeWatchdogEnablePumpEvent.class), + RECORD_TYPE_CHANGEOTHERDEVICEID((byte)0x7d,ChangeOtherDeviceIDPumpEvent.class), + RECORD_TYPE_ChangeWatchdogMarriageProfile((byte)0x81,ChangeWatchdogMarriageProfilePumpEvent.class), + RECORD_TYPE_DeleteOtherDeviceID((byte)0x82,DeleteOtherDeviceIDPumpEvent.class), + RECORD_TYPE_ChangeCaptureEventEnable((byte)0x83,ChangeCaptureEventEnablePumpEvent.class); + + + private byte opcode; + private Class mRecordClass; + + public byte opcode() { + return opcode; + } + public Class recordClass() { + return mRecordClass; + } + RecordTypeEnum(byte b,Class c) { + opcode = b; + mRecordClass = c; + } + public static RecordTypeEnum fromByte(byte b) { + for(RecordTypeEnum en : RecordTypeEnum.values()) { + if (en.opcode() == b) { + return en; + } + } + return RECORD_TYPE_NULL; + } + + private static final String TAG = "RecordTypeEnum"; + public T getRecordClassInstance(PumpModel model) { + Constructor ctor; + T record = null; + try { + Class c = recordClass(); + if (c!=null) { + ctor = recordClass().getConstructor(); + if (ctor != null) { + record = ctor.newInstance(); + record.setPumpModel(model); + } + } + } catch (NoSuchMethodException e) { + // NOTE: these were all OR'd together, but android requires us to separate them. + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return record; + } + + public static T getRecordClassInstance(Bundle bundle, PumpModel model) { + byte opcode = bundle.getByte("_opcode"); + RecordTypeEnum e = RecordTypeEnum.fromByte(opcode); + return e.getRecordClassInstance(model); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ResultDailyTotalPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ResultDailyTotalPumpEvent.java new file mode 100644 index 0000000000..09c06f5f8f --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ResultDailyTotalPumpEvent.java @@ -0,0 +1,34 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpTimeStamp; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.TimeFormat; + +public class ResultDailyTotalPumpEvent extends TimeStampedRecord { + private static final String TAG = "ResultDailyTotalPumpEvent"; + public ResultDailyTotalPumpEvent() { + } + + @Override + public int getDatestampOffset() { return 5; } + + @Override + public int getLength() { return PumpModel.isLargerFormat(model) ? 10 : 7; } + + @Override + protected boolean collectTimeStamp(byte[] data, int offset) { + try { + // This might be a 5 byte date on largerFormat + timestamp = new PumpTimeStamp(TimeFormat.parse2ByteDate(data, offset)); + } catch (org.joda.time.IllegalFieldValueException e) { + return false; + } + return true; + } + + @Override + public String getShortTypeName() { + return "Result Daily Total"; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ResumePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ResumePumpEvent.java new file mode 100644 index 0000000000..df514fdf4a --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/ResumePumpEvent.java @@ -0,0 +1,10 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +public class ResumePumpEvent extends TimeStampedRecord { + public ResumePumpEvent() {} + + @Override + public String getShortTypeName() { + return "Resume"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/RewindPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/RewindPumpEvent.java new file mode 100644 index 0000000000..2ffacbafd5 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/RewindPumpEvent.java @@ -0,0 +1,10 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +public class RewindPumpEvent extends TimeStampedRecord { + public RewindPumpEvent() {} + + @Override + public String getShortTypeName() { + return "Rewind"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/Sara6EPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/Sara6EPumpEvent.java new file mode 100644 index 0000000000..712b13437a --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/Sara6EPumpEvent.java @@ -0,0 +1,36 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpTimeStamp; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.TimeFormat; + +public class Sara6EPumpEvent extends TimeStampedRecord { + public Sara6EPumpEvent() { + } + + @Override + public int getLength() { + return 52; + } + + @Override + public String getShortTypeName() { + return "Sara6E"; + } + + @Override + public boolean parseFrom(byte[] data, PumpModel model) { + // We don't understand this event... + // Minimum 16 characters? date components? + if (16 > data.length) { + return false; + } + try { + timestamp = new PumpTimeStamp(TimeFormat.parse2ByteDate(data,1)); + } catch (org.joda.time.IllegalFieldValueException e) { + return false; + } + return true; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/SuspendPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/SuspendPumpEvent.java new file mode 100644 index 0000000000..355a1c2381 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/SuspendPumpEvent.java @@ -0,0 +1,10 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +public class SuspendPumpEvent extends TimeStampedRecord { + public SuspendPumpEvent() {} + + @Override + public String getShortTypeName() { + return "Suspend"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/TempBasalDurationPumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/TempBasalDurationPumpEvent.java new file mode 100644 index 0000000000..5356cd7815 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/TempBasalDurationPumpEvent.java @@ -0,0 +1,42 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +public class TempBasalDurationPumpEvent extends TimeStampedRecord { + private int durationMinutes = 0; + public TempBasalDurationPumpEvent() { } + + @Override + public String getShortTypeName() { + return "Temp Basal Duration"; + } + + public int getDurationMinutes() { + return durationMinutes; + } + + @Override + public boolean parseFrom(byte[] data, PumpModel model) { + if (!simpleParse(data,2)) { + return false; + } + durationMinutes = asUINT8(data[1]) * 30; + return true; + } + + @Override + public boolean readFromBundle(Bundle in) { + durationMinutes = in.getInt("durationMinutes",0); + return super.readFromBundle(in); + } + + @Override + public void writeToBundle(Bundle in) { + super.writeToBundle(in); + in.putInt("durationMinutes",durationMinutes); + } + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/TempBasalRatePumpEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/TempBasalRatePumpEvent.java new file mode 100644 index 0000000000..d29a849588 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/TempBasalRatePumpEvent.java @@ -0,0 +1,54 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; + +public class TempBasalRatePumpEvent extends TimeStampedRecord { + private double basalRate = 0.0; // rate in Units/hr + private boolean mIsPercent = false; // The value is either an absolute number or a percentage + + public TempBasalRatePumpEvent() { } + + @Override + public int getLength() { return 8; } + + @Override + public String getShortTypeName() { + return "Temp Basal Rate"; + } + + public double getBasalRate() { return basalRate; } + public boolean isPercent() { return mIsPercent; } + + @Override + public boolean parseFrom(byte[] data, PumpModel model) { + if (!simpleParse(data,2)) { + return false; + } + if ((asUINT8(data[7])>>3)==0) { + mIsPercent = false; + basalRate = (double)(asUINT8(data[1])) / 40.0; + } else { + mIsPercent = true; + basalRate = asUINT8(data[1]); + } + return true; + } + + @Override + public boolean readFromBundle(Bundle in) { + basalRate = in.getDouble("basalRate",0); + mIsPercent = in.getBoolean("mIsPercent",false); + return super.readFromBundle(in); + } + + @Override + public void writeToBundle(Bundle in) { + in.putDouble("basalRate",basalRate); + in.putBoolean("mIsPercent",mIsPercent); + super.writeToBundle(in); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/TimeStampedRecord.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/TimeStampedRecord.java new file mode 100644 index 0000000000..ea20e18e93 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/TimeStampedRecord.java @@ -0,0 +1,74 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpTimeStamp; +import com.gxwtech.roundtrip2.RoundtripService.medtronic.TimeFormat; +import com.gxwtech.roundtrip2.util.ByteUtil; + +/* + * Many events in the history only consist of a single opcode and a datestamp. + * This serves to record that a particular event happened at a particular date. + * Many of the subclasses of this class only override the opcode. + */ +abstract public class TimeStampedRecord extends Record { + private final static String TAG = "TimeStampedRecord"; + private final static boolean DEBUG_TIMESTAMPEDRECORD = false; + + @Override + public int getLength() { return 7; } + + public int getDatestampOffset() { return 2; } + + protected PumpTimeStamp timestamp; + + public TimeStampedRecord() { + timestamp = new PumpTimeStamp(); + } + + @Override + public PumpTimeStamp getTimestamp() { + return timestamp; + } + + @Override + public boolean parseFrom(byte[] data, PumpModel model) { + return simpleParse(data,getDatestampOffset()); + } + + // This is useful if there is no data inside, or we don't care about the data. + public boolean simpleParse(byte[] data, int fiveByteDateOffset) { + if (getLength() > data.length) { + return false; + } + if (!collectTimeStamp(data,fiveByteDateOffset)) { + return false; + } + rawbytes = ByteUtil.substring(data,0,getLength()); + return true; + } + + protected boolean collectTimeStamp(byte[] data, int offset) { + try { + timestamp = new PumpTimeStamp(TimeFormat.parse5ByteDate(data, offset)); + } catch (org.joda.time.IllegalFieldValueException e) { + return false; + } + return true; + } + + @Override + public boolean readFromBundle(Bundle in) { + String timestampString = in.getString("timestamp"); + timestamp = new PumpTimeStamp(timestampString); + return super.readFromBundle(in); + } + + @Override + public void writeToBundle(Bundle in) { + super.writeToBundle(in); + in.putString("timestamp",timestamp.toString()); + + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/UnabsorbedInsulin.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/UnabsorbedInsulin.java new file mode 100644 index 0000000000..0d90b78449 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpData/records/UnabsorbedInsulin.java @@ -0,0 +1,94 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records; + + +import android.os.Bundle; +import android.util.Log; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel; +import com.gxwtech.roundtrip2.util.ByteUtil; + +import java.util.ArrayList; + +public class UnabsorbedInsulin extends Record { + private static final String TAG = "UnabsorbedInsulin"; + private int length = 2; + + @Override + public int getLength() { return length; /* this is a variable sized record */} + + @Override + public String getShortTypeName() { + return "Unabsorbed Insulin"; + } + + class UnabsorbedInsulinRecord { + public double amount = 0.0; + public int age = 0; + public UnabsorbedInsulinRecord(double amount, int age) { + this.amount = amount; + this.age = age; + } + } + + ArrayList records = new ArrayList<>(); + + public UnabsorbedInsulin() { + } + + @Override + public boolean parseFrom(byte[] data, PumpModel model) { + if (data.length < 2) { + return false; + } + length = asUINT8(data[1]); + if (length < 2) { + length = 2; + } + if (length > data.length) { + return false; + } + + int numRecords = (asUINT8(data[1]) - 2) / 3; + for (int i=0; i 0) { + this.packetType = new PacketType(rxData[0]); + } + if (rxData.length > 3) { + this.address = ByteUtil.substring(rxData, 1, 3); + } + if (rxData.length > 4) { + this.messageType = new MessageType(rxData[4]); + } + if (rxData.length > 5) { + this.messageBody = MessageType.constructMessageBody(messageType, ByteUtil.substring(rxData, 5, rxData.length - 5)); + } + } + + public byte[] getTxData() { + byte[] rval = ByteUtil.concat(new byte[] {(byte)packetType.value},address); + rval = ByteUtil.concat(rval,(byte)messageType.mtype); + rval = ByteUtil.concat(rval,messageBody.getTxData()); + return rval; + } + + public byte[] getContents() { + return ByteUtil.concat(new byte[] {messageType.mtype}, messageBody.getTxData()); + } + + public boolean isValid() { + if (packetType == null) return false; + if (address == null) return false; + if (messageType == null) return false; + if (messageBody == null) return false; + return true; + } + + public MessageBody getMessageBody() { + return messageBody; + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpModel.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpModel.java new file mode 100644 index 0000000000..cbdcc8f7c5 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpModel.java @@ -0,0 +1,55 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic; +// cribbed from: +//package com.nightscout.core.drivers.Medtronic; + +/** + * Created by geoff on 5/13/15. + */ + +public enum PumpModel { + UNSET, + MM508, + MM515, + MM522, + MM523; + public static boolean isLargerFormat(PumpModel model) { + if (model == MM523) { + return true; + } + return false; + } + public static String toString(PumpModel model) { + switch(model) { + case UNSET: + return "UNSET"; + case MM508: + return "508"; + case MM515: + return "515"; + case MM522: + return "522"; + case MM523: + return "523"; + default: + return "(error)"; + } + } + public static PumpModel fromString(String s) { + if ("UNSET".equals(s)) { + return UNSET; + } + if (("508".equals(s)) || ("MM508".equals(s))) { + return MM508; + } + if (("515".equals(s)) || ("MM515".equals(s))) { + return MM515; + } + if (("522".equals(s)) || ("MM522".equals(s))) { + return MM522; + } + if (("523".equals(s)) || ("MM523".equals(s))) { + return MM523; + } + return UNSET; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpTimeStamp.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpTimeStamp.java new file mode 100644 index 0000000000..f02fd2b816 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/PumpTimeStamp.java @@ -0,0 +1,35 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic; + +import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; + +/** + * Created by geoff on 6/4/16. + * Exists to easily merge 2 byte timestamps and 5 byte timestamps. + */ +public class PumpTimeStamp { + private LocalDateTime localDateTime; + public PumpTimeStamp() { + localDateTime = new LocalDateTime(1973,1,1,1,1); + } + public PumpTimeStamp(String stringRepresentation) { localDateTime.parse(stringRepresentation); } + public PumpTimeStamp(LocalDate localDate) { + try { + localDateTime = new LocalDateTime(localDate); + } catch (IllegalArgumentException e) { + // This should be caught earlier + localDateTime = new LocalDateTime(1973,1,1,1,1); + } + } + public PumpTimeStamp(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + } + public LocalDateTime getLocalDateTime() { + return localDateTime; + } + @Override + public String toString() { + return getLocalDateTime().toString(); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/TempBasalEvent.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/TempBasalEvent.java new file mode 100644 index 0000000000..1a1ae3b7c5 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/TempBasalEvent.java @@ -0,0 +1,29 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic; + + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.TempBasalPair; + +import org.joda.time.LocalDateTime; + +/** + * Created by geoff on 6/8/15. + * + * In the pump's history, temp basals are recorded as 1) a TempBasalRatePumpEvent event, + * with a timestamp and a rate, and 2) as a separate TempBasalDurationPumpEvent event, with a timestamp + * and a duration. This is inconvenient for the rest of the software, so this class puts the two + * together as a timestamp, duration, and rate in one package. + * + */ +@Deprecated +public class TempBasalEvent { + public LocalDateTime mTimestamp; + public TempBasalPair mBasalPair; + public TempBasalEvent() { + mTimestamp = new LocalDateTime(); + mBasalPair = new TempBasalPair(); + } + public TempBasalEvent(LocalDateTime timestamp, TempBasalPair pair) { + mTimestamp = timestamp; + mBasalPair = pair; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/TimeFormat.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/TimeFormat.java new file mode 100644 index 0000000000..5ce44890d0 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripService/medtronic/TimeFormat.java @@ -0,0 +1,82 @@ +package com.gxwtech.roundtrip2.RoundtripService.medtronic; + +import android.util.Log; + +import com.gxwtech.roundtrip2.util.ByteUtil; + +import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +/** + * Created by geoff on 6/4/16. + */ +public class TimeFormat { + private static final boolean DEBUG_TIMEFORMAT = false; + private static final String TAG = "TimeFormat"; + public TimeFormat() { } + public static final String standardFormatString = "YYYY-MM-dd HH:mm:ss"; + public static DateTimeFormatter standardFormatter() { + return DateTimeFormat.forPattern(standardFormatString); + } + + public static LocalDate parse2ByteDate(byte[] data, int offset) throws org.joda.time.IllegalFieldValueException { + int low = ByteUtil.asUINT8(data[0 + offset]) & 0x1F; + int mhigh = (ByteUtil.asUINT8(data[0 + offset]) & 0xE0) >> 4; + int mlow = (ByteUtil.asUINT8(data[1 + offset]) & 0x80) >> 7; + int month = mhigh + mlow; + int dayOfMonth = low + 1; + int year = 2000 + (ByteUtil.asUINT8(data[offset + 1]) & 0x7F); + /* + Log.w(TAG, String.format("Attempting to create DateTime from: %04d-%02d-%02d %02d:%02d:%02d", + year + 2000, month, dayOfMonth, hour, minutes, seconds)); + */ +// try { + LocalDate rval = new LocalDate(year, month, dayOfMonth); + return rval; + /* + } catch (org.joda.time.IllegalFieldValueException e) { + Log.e(TAG,"Illegal DateTime field"); + //e.printStackTrace(); + return new LocalDate(1973,3,3); + } + */ + } + + // for relation to old code, replace offset with headerSize + + + public static LocalDateTime parse5ByteDate(byte[] data, int offset) throws org.joda.time.IllegalFieldValueException { + //offset = headerSize; + if (DEBUG_TIMEFORMAT) { + Log.w(TAG, String.format("bytes to parse: 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X", + data[offset], data[offset + 1], data[offset + 2], data[offset + 3], data[offset + 4])); + } + int seconds = data[offset] & 0x3F; + int minutes = data[offset + 1] & 0x3F; + int hour = data[offset + 2] & 0x1F; + int dayOfMonth = data[offset + 3] & 0x1F; + // Yes, the month bits are stored in the high bits above seconds and minutes!! + int month = ((data[offset] >> 4) & 0x0c) + ((data[offset + 1] >> 6) & 0x03); + int year = data[offset + 4] & 0x3F; // Assuming this is correct, need to verify. Otherwise this will be a problem in 2016. + /* + Log.w(TAG,String.format("Attempting to create DateTime from: %04d-%02d-%02d %02d:%02d:%02d", + year+2000,month,dayOfMonth,hour,minutes,seconds)); + */ +// try { + LocalDateTime timeStamp = new LocalDateTime(year + 2000, month, dayOfMonth, hour, minutes, seconds); + return timeStamp; + /* + } catch (org.joda.time.IllegalFieldValueException e) { + Log.e(TAG, "Illegal DateTime field"); + //e.printStackTrace(); + return new LocalDateTime(1973,2,2,2,2); + } + */ + } + + + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/RoundtripServiceClientConnection.java b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripServiceClientConnection.java new file mode 100644 index 0000000000..08cd395160 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/RoundtripServiceClientConnection.java @@ -0,0 +1,133 @@ +package com.gxwtech.roundtrip2; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; + +import com.gxwtech.roundtrip2.ServiceData.ServiceCommand; +import com.gxwtech.roundtrip2.ServiceData.ServiceTransport; + +import org.joda.time.DateTime; +import org.joda.time.Instant; + +/** + * Created by geoff on 6/11/16. + */ +public class RoundtripServiceClientConnection { + private static final String TAG = "RTServiceClient"; + private Context context; + private Messenger mService = null; + private boolean mBound = false; + + public RoundtripServiceClientConnection(Context context) { + this.context = context; + } + + class IncomingHandler extends Handler { + @Override + public void handleMessage(Message msg) { + Bundle bundle = msg.getData(); + Intent intent; + switch (msg.what) { + case RT2Const.IPC.MSG_clientRegistered: + // Service has registered us. Communication lines are open. + mBound = true; + intent = new Intent(RT2Const.local.INTENT_serviceConnected); + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + break; + case RT2Const.IPC.MSG_IPC: + // broadcast contents of message as an intent + ServiceTransport transport = new ServiceTransport(msg.getData()); + Log.d(TAG,"Client received IPC message, bouncing to local: " + transport.describeContentsShort()); + intent = new Intent(transport.getTransportType()); + intent.putExtra(RT2Const.IPC.bundleKey,transport.getMap()); + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + break; + default: + Log.e(TAG,"handleMessage: unknown 'what' in message: "+msg.what); + super.handleMessage(msg); + } + } + } + + final Messenger mMessenger = new Messenger(new IncomingHandler()); + + private ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + mService = new Messenger(service); + try { + Message msg = Message.obtain(null,RT2Const.IPC.MSG_registerClient); + msg.replyTo = mMessenger; + mService.send(msg); + } catch (RemoteException e) { + // In this case the service has crashed before we could even + // do anything with it; we can count on soon being + // disconnected (and then reconnected if it can be restarted) + // so there is no need to do anything here. + } + Log.d(TAG,"Sent registration message to service"); + } + + @Override + public void onServiceDisconnected(ComponentName className) { + mService = null; + Log.d(TAG,"Disconnected from service."); + } + }; + + public ServiceConnection getServiceConnection() { + return mConnection; + } + + public void unbind() { + if (mBound) { + if (mService!=null) { + try { + Message msg = Message.obtain(null,RT2Const.IPC.MSG_unregisterClient); + msg.replyTo = mMessenger; + mService.send(msg); + } catch (RemoteException e) { + // Nothing to do if the connection has already crashed. + } + } + mBound = false; + } + } + + public boolean sendServiceCommand(ServiceCommand command) { + if (!mBound) { + Log.e(TAG,"sendServiceCommand: cannot send command -- not yet bound to service"); + return false; + } + + ServiceTransport transport = new ServiceTransport(); + Log.d(TAG,"client sending message: " + transport.describeContentsShort()); + + // can't set sender hashcode -- Service will do that. + transport.setServiceCommand(command); + transport.setTransportType(RT2Const.IPC.MSG_ServiceCommand); + + Message msg = Message.obtain(null, RT2Const.IPC.MSG_IPC, 0, 0); + + msg.setData(transport.getMap()); + msg.replyTo = mMessenger; + try { + mService.send(msg); + } catch (RemoteException e) { + Log.e(TAG,"sendServiceCommand: failed to send message"); + e.printStackTrace(); + return false; + } + return true; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceClientConnection.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceClientConnection.java new file mode 100644 index 0000000000..d7281b1ac3 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceClientConnection.java @@ -0,0 +1,142 @@ +package com.gxwtech.roundtrip2; + +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.util.Log; + + +import com.gxwtech.roundtrip2.RT2Const; +import com.gxwtech.roundtrip2.RoundtripService.RoundtripService; +import com.gxwtech.roundtrip2.RoundtripServiceClientConnection; +import com.gxwtech.roundtrip2.ServiceData.ServiceClientActions; +import com.gxwtech.roundtrip2.ServiceData.ServiceCommand; + +import info.nightscout.androidaps.MainApp; + +/** + * Created by Tim on 27/06/2016. + * Object that interfaces with the RT2 Service and Client Actions + */ +public class ServiceClientConnection { + + private static String TAG = "ServiceClientConnection"; + private RoundtripServiceClientConnection roundtripServiceClientConnection; + private Context context = MainApp.instance(); + + public ServiceClientConnection() { + roundtripServiceClientConnection = new RoundtripServiceClientConnection(context); + + //Connect to the RT service + doBindService(); + } + + /* + * + * Functions to work with the RT2 Service + * + */ + private void doBindService() { +// context.bindService(new Intent(context,RoundtripService.class), +// roundtripServiceClientConnection.getServiceConnection(), +// Context.BIND_AUTO_CREATE); + Log.d(TAG,"doBindService: binding. N/A"); + } + private void doUnbindService() { +// ServiceConnection conn = roundtripServiceClientConnection.getServiceConnection(); +// roundtripServiceClientConnection.unbind(); +// context.unbindService(conn); + Log.d(TAG,"doUnbindService: unbinding. N/A"); + } + + // send one-liner message to RoundtripService + //private void sendIPCMessage(String ipcMsgType) { + // Create a bundle with the data + // Bundle bundle = new Bundle(); + // bundle.putString(RT2Const.IPC.messageKey, ipcMsgType); + // if (sendMessage(bundle)) { + // Log.d(TAG,"sendIPCMessage: sent "+ipcMsgType); + // } else { + // Log.e(TAG,"sendIPCMessage: send failed"); + // } + //} + + //private boolean sendMessage(Bundle bundle) { + //return roundtripServiceClientConnection.sendMessage(bundle); + //} + + /* + * + * functions the client can call + * + */ + //public void sendBLEaccessGranted() { sendIPCMessage(RT2Const.IPC.MSG_BLE_accessGranted); } + + //public void sendBLEaccessDenied() { sendIPCMessage(RT2Const.IPC.MSG_BLE_accessDenied); } + + public void setThisRileylink(String address) { + //Bundle bundle = new Bundle(); + //bundle.putString(RT2Const.IPC.messageKey, RT2Const.IPC.MSG_BLE_useThisDevice); + //bundle.putString(RT2Const.IPC.MSG_BLE_useThisDevice_addressKey,address); + //sendMessage(bundle); + ServiceCommand command = ServiceClientActions.makeUseThisRileylinkCommand(address); + roundtripServiceClientConnection.sendServiceCommand(command); + Log.d(TAG,"sendIPCMessage: (use this address) "+address); + } + + public void sendPUMP_useThisDevice(String pumpIDString) { + //Bundle bundle = new Bundle(); + //bundle.putString(RT2Const.IPC.messageKey, RT2Const.IPC.MSG_PUMP_useThisAddress); + //bundle.putString(RT2Const.IPC.MSG_PUMP_useThisAddress_pumpIDKey,pumpIDString); + //sendMessage(bundle); + ServiceCommand command = ServiceClientActions.makeSetPumpIDCommand(pumpIDString); + roundtripServiceClientConnection.sendServiceCommand(command); + Log.d(TAG,"sendPUMP_useThisDevice: " + pumpIDString); + } + + public void doTunePump() { + ServiceCommand command = ServiceClientActions.makeTunePumpCommand(); + roundtripServiceClientConnection.sendServiceCommand(command); + } + + public void getHistory() { + //sendIPCMessage(RT2Const.IPC.MSG_PUMP_fetchHistory); + } + + public void getSavedHistory(){ + //sendIPCMessage(RT2Const.IPC.MSG_PUMP_fetchSavedHistory); + } + + public void setTempBasal(double amountUnitsPerHour, int durationMinutes, int uid) { + ServiceCommand command = ServiceClientActions.makeSetTempBasalCommand(amountUnitsPerHour,durationMinutes); + roundtripServiceClientConnection.sendServiceCommand(command); + } + + public void readPumpClock() { + ServiceCommand command = ServiceClientActions.makeReadPumpClockCommand(); + roundtripServiceClientConnection.sendServiceCommand(command); + } + + public void readISFProfile() { + ServiceCommand getISFProfileCommand = ServiceClientActions.makeReadISFProfileCommand(); + roundtripServiceClientConnection.sendServiceCommand(getISFProfileCommand); + } + + public void updateAllStatus() { + ServiceCommand command = ServiceClientActions.makeUpdateAllStatusCommand(); + roundtripServiceClientConnection.sendServiceCommand(command); + } + + public void doFetchPumpHistory() { + ServiceCommand retrievePageCommand = ServiceClientActions.makeFetchPumpHistoryCommand(); + roundtripServiceClientConnection.sendServiceCommand(retrievePageCommand); + } + + public void doFetchSavedHistory() { + // Does not (at the moment) fetch saved history :( + ServiceCommand cmd = ServiceClientActions.makeFetchPumpHistoryCommand(); + roundtripServiceClientConnection.sendServiceCommand(cmd); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/BasalProfile.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/BasalProfile.java new file mode 100644 index 0000000000..c34a413cda --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/BasalProfile.java @@ -0,0 +1,47 @@ +package com.gxwtech.roundtrip2.ServiceData; + +import android.os.Bundle; +import org.joda.time.LocalTime; + +/** + * Created by geoff on 6/25/16. + */ +public class BasalProfile extends TimeValueProfile { + String profileType = "unknown"; + public BasalProfile() { + } + + public double getRateForTime(LocalTime atTime) { + Double rval = -999999999.0; // clearly invalid + Object o = getObjectForTime(atTime); + if (o != null) { + try { + rval = (Double)o; + } catch (ClassCastException e) { + } + } + return rval; + } + + public boolean initFromServiceResult(ServiceResult serviceResult) { + boolean initValid = initFromServiceResult(serviceResult,"BasalProfile"); + // now get our specific values from the BasalProfile + Bundle resultMap = serviceResult.getMap(); + if (resultMap != null) { + Bundle profile = resultMap.getBundle("BasalProfile"); + if (profile == null) { + return false; + } + String which = profile.getString("ProfileType"); + if (which != null) { + this.profileType = which; + } else { + mIsValid = false; + } + } else { + mIsValid = false; + } + mIsValid = initValid; + return mIsValid; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/BolusWizardCarbProfile.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/BolusWizardCarbProfile.java new file mode 100644 index 0000000000..443cb9b4a4 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/BolusWizardCarbProfile.java @@ -0,0 +1,27 @@ +package com.gxwtech.roundtrip2.ServiceData; + +import org.joda.time.LocalTime; + +/** + * Created by geoff on 6/25/16. + */ +public class BolusWizardCarbProfile extends TimeValueProfile { + public BolusWizardCarbProfile() { + } + + public double getCarbRatioForTime(LocalTime atTime) { + Double rval = -999999999.0; // clearly invalid + Object o = getObjectForTime(atTime); + if (o != null) { + try { + rval = (Double) o; + } catch (ClassCastException e) { + } + } + return rval; + } + + public boolean initFromServiceResult(ServiceResult serviceResult) { + return initFromServiceResult(serviceResult, "BolusWizardCarbProfile"); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/FetchPumpHistoryResult.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/FetchPumpHistoryResult.java new file mode 100644 index 0000000000..d2c13b2e86 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/FetchPumpHistoryResult.java @@ -0,0 +1,35 @@ +package com.gxwtech.roundtrip2.ServiceData; + +import android.os.Bundle; + +import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.Page; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by geoff on 7/16/16. + */ +public class FetchPumpHistoryResult extends ServiceResult { + public FetchPumpHistoryResult() {} + + public ArrayList getPageArray() { + ArrayList pagebundles = map.getParcelableArrayList("pageArrayList"); + ArrayList pages = new ArrayList<>(); + for (Bundle b : pagebundles) { + Page p = new Page(); + p.unpack(b); + pages.add(p); + } + return pages; + } + + public void setPageArray(List pageList) { + ArrayList pageBundles = new ArrayList<>(); + for (Page p : pageList) { + pageBundles.add(p.pack()); + } + map.putParcelableArrayList("pageArrayList",pageBundles); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ISFProfile.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ISFProfile.java new file mode 100644 index 0000000000..9e6264ca8a --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ISFProfile.java @@ -0,0 +1,29 @@ +package com.gxwtech.roundtrip2.ServiceData; + +import org.joda.time.LocalTime; + +/** + * Created by geoff on 6/25/16. + */ +public class ISFProfile extends TimeValueProfile { + public ISFProfile() { + + } + + public double getISFForTime(LocalTime atTime) { + Double rval = -99999999.0; // clearly invalid + Object o = getObjectForTime(atTime); + if (o != null) { + try { + rval = (Double)o; + } catch (ClassCastException e) { + } + } + return rval; + } + + public boolean initFromServiceResult(ServiceResult serviceResult) { + return initFromServiceResult(serviceResult,"ISFProfile"); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/PumpModelResult.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/PumpModelResult.java new file mode 100644 index 0000000000..1da971b71f --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/PumpModelResult.java @@ -0,0 +1,27 @@ +package com.gxwtech.roundtrip2.ServiceData; + +/** + * Created by geoff on 7/11/16. + */ +public class PumpModelResult extends ServiceResult { + private static final String TAG = "PumpModelResult"; + public PumpModelResult() { } + + @Override + public void init() { + map.putString("ServiceMessageType","PumpModelResult"); + } + + public void setPumpModel(String model) { + map.putString("model", model); + } + + public String getPumpModel() { + return map.getString("model"); + } + + public void initFromServiceResult(ServiceResult serviceResult) { + setMap(serviceResult.getMap()); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/PumpStatusResult.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/PumpStatusResult.java new file mode 100644 index 0000000000..3a9821e0fb --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/PumpStatusResult.java @@ -0,0 +1,137 @@ +package com.gxwtech.roundtrip2.ServiceData; + +import com.gxwtech.roundtrip2.RoundtripService.RileyLink.PumpManagerStatus; + +/** + * Created by geoff on 7/16/16. + */ +public class PumpStatusResult extends ServiceResult { + private static final String TAG = "PumpStatusResult"; + public PumpStatusResult() { } + + @Override + public void init() { + map.putString("ServiceMessageType","PumpStatusResult"); + } + + /** + * pumpStatus.remainBattery = statusEvent.remainBattery; + pumpStatus.remainUnits = statusEvent.remainUnits; + pumpStatus.currentBasal = statusEvent.currentBasal; + pumpStatus.last_bolus_amount = statusEvent.last_bolus_amount; + pumpStatus.last_bolus_time = statusEvent.last_bolus_time; + pumpStatus.tempBasalInProgress = statusEvent.tempBasalInProgress; + pumpStatus.tempBasalRatio = statusEvent.tempBasalRatio; + pumpStatus.tempBasalRemainMin = statusEvent.tempBasalRemainMin; + pumpStatus.tempBasalStart = statusEvent.tempBasalStart; + pumpStatus.time = statusEvent.time;//Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTime(); + statusEvent.timeLastSync = statusEvent.time; + */ + + /* + double remainBattery; + double remainUnits; + double currentBasal; + double lastBolusAmount; + String lastBolusTime; + int tempBasalInProgress; + double tempBasalRatio; + double tempBasalRemainMin; + String tempBasalStart; + String time; + String timeLastSync; + */ + + public double getRemainBattery() { + return map.getDouble("remainBattery"); + } + + public void setRemainBattery(double remainBattery) { + map.putDouble("remainBattery",remainBattery); + } + + public double getRemainUnits() { + return map.getDouble("remainUnits"); + } + + public void setRemainUnits(double remainUnits) { + map.putDouble("remainUnits",remainUnits); + } + + public double getCurrentBasal() { + return map.getDouble("currentBasal"); + } + + public void setCurrentBasal(double currentBasal) { + map.putDouble("currentBasal",currentBasal); + } + + public double getLastBolusAmount() { + return map.getDouble("lastBolusAmount"); + } + + public void setLastBolusAmount(double lastBolusAmount) { + map.putDouble("lastBolusAmount",lastBolusAmount); + } + + public String getLastBolusTime() { + return map.getString("lastBolusTime",""); + } + + public void setLastBolusTime(String lastBolusTime) { + map.putString("lastBolusTime",lastBolusTime); + } + + public int getTempBasalInProgress() { + return map.getInt("tempBasalInProgress"); + } + + public void setTempBasalInProgress(int tempBasalInProgress) { + map.putInt("tempBasalInProgress",tempBasalInProgress); + } + + public double getTempBasalRatio() { + return map.getDouble("tempBasalRatio"); + } + + public void setTempBasalRatio(double tempBasalRatio) { + map.putDouble("tempBasalRatio",tempBasalRatio); + } + + public double getTempBasalRemainMin() { + return map.getDouble("tempBasalRemainMin"); + } + + public void setTempBasalRemainMin(double tempBasalRemainMin) { + map.putDouble("tempBasalRemainMin",tempBasalRemainMin); + } + + public String getTempBasalStart() { + return map.getString("tempBasalStart",""); + } + + public void setTempBasalStart(String tempBasalStart) { + map.putString("tempBasalStart",tempBasalStart); + } + + public String getTime() { + return map.getString("time",""); + } + + public void setTime(String time) { + map.putString("time",time); + } + + public String getTimeLastSync() { + return map.getString("timeLastSync",""); + } + + public void setTimeLastSync(String timeLastSync) { + map.putString("timeLastSync",timeLastSync); + } + + public void initFromServiceResult(ServiceResult serviceResult) { + setMap(serviceResult.getMap()); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ReadPumpClockResult.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ReadPumpClockResult.java new file mode 100644 index 0000000000..2ce81d15de --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ReadPumpClockResult.java @@ -0,0 +1,62 @@ +package com.gxwtech.roundtrip2.ServiceData; + +import android.os.Bundle; +import android.util.Log; + +import org.joda.time.LocalDateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +/** + * Created by geoff on 6/25/16. + */ +public class ReadPumpClockResult extends ServiceResult { + private static final String TAG="ReadPumpClockResult"; + public ReadPumpClockResult() {} + + @Override + public void init() { + map.putString("ServiceMessageType","ReadPumpClockResult"); + } + + public void setTime(LocalDateTime pumpTime) { + Bundle map = getMap(); + DateTimeFormatter fmt = DateTimeFormat.forStyle("FF"); + map.putString("PumpTime",fmt.print(pumpTime)); + setMap(map); + } + + public LocalDateTime getTime() { + LocalDateTime rval = new LocalDateTime(1900,1,1,1,1); + Bundle map = getMap(); + if (map != null) { + String timeString = map.getString("PumpTime"); + if (timeString != null) { + DateTimeFormatter fmt = DateTimeFormat.forStyle("FF"); + try { + rval = fmt.parseLocalDateTime(timeString); + } catch (IllegalArgumentException e) { + Log.e(TAG,"getTime: failed to parse time from '"+timeString+"'"); + } + } + } + return rval; + } + + public String getTimeString() { + Bundle map = getMap(); + if (map != null) { + String rval = map.getString("PumpTime"); + if (rval != null) { + return rval; + } + } + return ""; + } + + // This can be overridden by subclasses -- essentially it allows + // casting from the base class to the subclass. + public void initFromServiceResult(ServiceResult serviceResult) { + setMap(serviceResult.getMap()); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/RetrieveHistoryPageResult.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/RetrieveHistoryPageResult.java new file mode 100644 index 0000000000..83616501fd --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/RetrieveHistoryPageResult.java @@ -0,0 +1,22 @@ +package com.gxwtech.roundtrip2.ServiceData; + +import android.os.Bundle; + +/** + * Created by geoff on 7/2/16. + */ +public class RetrieveHistoryPageResult extends ServiceResult { + public RetrieveHistoryPageResult() {} + public void setPageNumber(int pageNumber) { + map.putInt("pageNumber",pageNumber); + } + public int getPageNumber() { + return map.getInt("pageNumber", -1); + } + public void setPageBundle(Bundle pageBundle) { + map.putBundle("pageBundle",pageBundle); + } + public Bundle getPageBundle() { + return map.getBundle("pageBundle"); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceClientActions.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceClientActions.java new file mode 100644 index 0000000000..ebdaeb0070 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceClientActions.java @@ -0,0 +1,138 @@ +package com.gxwtech.roundtrip2.ServiceData; + +import android.os.Bundle; + +import org.joda.time.LocalDateTime; + +import java.util.UUID; + +/** + * Created by geoff on 6/25/16. + */ +public class ServiceClientActions { + public ServiceClientActions() {} + + /* + * Set Temp Basal + * + * inputs: + * amountUnitsPerHour - temp basal amount, in Units per hour + * durationMinutes - temp basal duration, in minutes + * + * result: standard ok/error result + */ + + public static String makeRandomID() { + UUID uuid = UUID.randomUUID(); + return uuid.toString(); + } + + public static ServiceCommand makeSetTempBasalCommand(double amountUnitsPerHour, int durationMinutes) { + ServiceCommand command = new ServiceCommand("SetTempBasal",makeRandomID()); + Bundle b = command.getMap(); + b.putDouble("amountUnitsPerHour",amountUnitsPerHour); + b.putInt("durationMinutes",durationMinutes); + command.setMap(b); + return command; + } + + /* + * Read Basal Profile + * + * inputs: + * which - "STD", "A", or "B" + * + * result: an ok/error result with a basal profile Bundle. + * Get the profile using BasalProfile.initFromServiceResult() + */ + + // 'which' is "STD", "A", or "B" + public static ServiceCommand makeReadBasalProfileCommand(String which) { + ServiceCommand command = new ServiceCommand("ReadBasalProfile",makeRandomID()); + Bundle b = command.getMap(); + b.putString("which",which); + command.setMap(b); + return command; + } + + public static ServiceCommand makeReadPumpClockCommand() { + return new ServiceCommand("ReadPumpClock",makeRandomID()); + } + + public static ServiceCommand makeSendBolusCommand(double amountUnits) { + ServiceCommand command = new ServiceCommand("SendBolus",makeRandomID()); + Bundle b = command.getMap(); + b.putDouble("amountInUnits",amountUnits); + command.setMap(b); + return command; + } + + public static ServiceCommand makeSetPumpClockCommand(LocalDateTime localDateTime) { + ServiceCommand command = new ServiceCommand("SetPumpClock",makeRandomID()); + Bundle b = command.getMap(); + b.putString("localDateTime",localDateTime.toString()); + command.setMap(b); + return command; + } + + public static ServiceCommand makeReadISFProfileCommand() { + return new ServiceCommand("ReadISFProfile",makeRandomID()); + } + + public static ServiceCommand makeReadBolusWizardCarbProfileCommand() { + return new ServiceCommand("ReadBolusWizardCarbProfile",makeRandomID()); + } + + public static ServiceCommand makeReadDIASettingCommand() { + return new ServiceCommand("ReadDIASetting",makeRandomID()); + } + + public static ServiceCommand makeReadBatteryLevelCommand() { + return new ServiceCommand("ReadBatteryLevel",makeRandomID()); + } + + public static ServiceCommand makeReadReservoirLevelCommand() { + return new ServiceCommand("ReadReservoirLevel",makeRandomID()); + } + + public static ServiceCommand makeSetPumpIDCommand(String pumpID) { + ServiceCommand cmd = new ServiceCommand("SetPumpID",makeRandomID()); + Bundle b = cmd.getMap(); + b.putString("pumpID",pumpID); + cmd.setMap(b); + return cmd; + } + + public static ServiceCommand makeUseThisRileylinkCommand(String rlAddress) { + ServiceCommand cmd = new ServiceCommand("UseThisRileylink",makeRandomID()); + Bundle b = cmd.getMap(); + b.putString("rlAddress",rlAddress); + cmd.setMap(b); + return cmd; + } + + public static ServiceCommand makeRetrieveHistoryPageCommand(int pageNumber) { + ServiceCommand cmd = new ServiceCommand("RetrieveHistoryPage",makeRandomID()); + Bundle b = cmd.getMap(); + b.putInt("pageNumber",pageNumber); + cmd.setMap(b); + return cmd; + } + + public static ServiceCommand getPumpModel() { + return new ServiceCommand("ReadPumpModel",makeRandomID()); + } + + public static ServiceCommand makeUpdateAllStatusCommand() { + return new ServiceCommand("UpdatePumpStatus",makeRandomID()); + } + + public static ServiceCommand makeTunePumpCommand() { + return new ServiceCommand("WakeAndTune",makeRandomID()); + } + + public static ServiceCommand makeFetchPumpHistoryCommand() { + return new ServiceCommand("FetchPumpHistory",makeRandomID()); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceCommand.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceCommand.java new file mode 100644 index 0000000000..6878f7b503 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceCommand.java @@ -0,0 +1,57 @@ +package com.gxwtech.roundtrip2.ServiceData; + +import android.os.Bundle; + +/** + * Created by geoff on 6/25/16. + */ +public class ServiceCommand extends ServiceMessage { + public ServiceCommand() { + map = new Bundle(); + } + // commandID is a string that the client can set on the message. + // The service does not use this value, but passes it back with the result + // so that the client can identify it. + public ServiceCommand(String commandName, String commandID) { + init(); + map.putString("command",commandName); + map.putString("commandID",commandID); + } + public ServiceCommand(Bundle commandBundle) { + if (commandBundle != null) { + map = commandBundle; + } else { + map = new Bundle(); + init(); + map.putString("command","(null)"); + map.putString("commandID", "(null"); + } + } + + @Override + public void init() { + map.putString("ServiceMessageType","ServiceCommand"); + } + + public String getCommandID() { + return map.getString("commandID"); + } + public String getCommandName() { + return map.getString("command"); + } + + public boolean isPumpCommand() { + switch (getCommandName()) { + case "FetchPumpHistory": + case "ReadPumpClock": + case "RetrieveHistoryPage": + case "ReadISFProfile": + case "ReadBolusWizardCarbProfile": + case "UpdatePumpStatus": + case "WakeAndTune": + return true; + default: + return false; + } + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceMessage.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceMessage.java new file mode 100644 index 0000000000..8e587d722b --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceMessage.java @@ -0,0 +1,24 @@ +package com.gxwtech.roundtrip2.ServiceData; + +import android.os.Bundle; + +/** + * Created by geoff on 7/4/16. + * + * Base class for all messages passed between service and client + */ +public class ServiceMessage { + protected Bundle map = new Bundle(); + public ServiceMessage() { init(); } + public void init() { + map.putString("ServiceMessageClass",this.getClass().getCanonicalName()); + map.putString("ServiceMessageType",this.getClass().getSimpleName()); + } + public Bundle getMap() { return map; } + public void setMap(Bundle map) { this.map = map; } + public String getServiceMessageType() { + return map.getString("ServiceMessageType"); + } + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceMessageUpdate.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceMessageUpdate.java new file mode 100644 index 0000000000..f85366ecaa --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceMessageUpdate.java @@ -0,0 +1,12 @@ +package com.gxwtech.roundtrip2.ServiceData; + +/** + * Created by geoff on 7/4/16. + */ +@Deprecated +public class ServiceMessageUpdate extends ServiceMessage { + public ServiceMessageUpdate() {} + public void init() { + map.putString("ServiceMessageType","ServiceUpdateMessage"); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceNotification.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceNotification.java new file mode 100644 index 0000000000..72a1e81418 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceNotification.java @@ -0,0 +1,44 @@ +package com.gxwtech.roundtrip2.ServiceData; + +import android.os.Bundle; + +/** + * Created by geoff on 7/6/16. + * + * These are "one liner" messages between client and service. + * Must still be contained within ServiceTransports + * + */ +public class ServiceNotification extends ServiceMessage { + public ServiceNotification() {} + + public ServiceNotification(Bundle b) { + if (b != null) { + if ("ServiceNotification".equals(b.getString("ServiceMessageType"))) { + setMap(b); + } else { + throw new IllegalArgumentException(); + } + } + } + + public ServiceNotification(String notificationType) { + setNotificationType(notificationType); + } + + @Override + public void init() { + super.init(); + map.putString("ServiceMessageType","ServiceNotification"); + } + + public void setNotificationType(String notificationType) { + map.putString("NotificationType", notificationType); + } + + public String getNotificationType() { + return map.getString("NotificationType",""); + } + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceResult.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceResult.java new file mode 100644 index 0000000000..a5b095e537 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceResult.java @@ -0,0 +1,76 @@ +package com.gxwtech.roundtrip2.ServiceData; + +import android.os.Bundle; +import android.support.v4.content.LocalBroadcastManager; + +import java.util.HashMap; + +/** + * Created by geoff on 6/25/16. + */ +public class ServiceResult extends ServiceMessage { + public ServiceResult() { init(); } + public ServiceResult(Bundle resultBundle) { + if (resultBundle != null) { + setMap(resultBundle); + } else { + init(); + } + } + + @Override + public void init() { + super.init(); + map.putString("ServiceMessageType","ServiceResult"); + setServiceResultType(this.getClass().getSimpleName()); + setResultError(0,"Uninitialized ServiceResult"); + } + + public void setServiceResultType(String serviceResultType) { + map.putString("ServiceResultType",serviceResultType); + } + + public String getServiceResultType() { + return map.getString("ServiceResultType","ServiceResult"); + } + + public void setResultOK() { + map.putString("result","OK"); + } + public void setResultError(int errorCode) { + setResultError(errorCode,getErrorDescription(errorCode)); + } + public void setResultError(int errorCode, String errorDescription) { + map.putString("result","error"); + map.putInt("errorCode",errorCode); + map.putString("errorDescription",errorDescription); + } + + public static final int ERROR_MALFORMED_PUMP_RESPONSE = 1; + public static final int ERROR_NULL_PUMP_RESPONSE = 2; + public static final int ERROR_INVALID_PUMP_RESPONSE = 3; + public static final int ERROR_PUMP_BUSY = 4; + + public static final String getErrorDescription(int errorCode) { + switch(errorCode) { + case ERROR_MALFORMED_PUMP_RESPONSE: return "Malformed Pump Response"; + case ERROR_NULL_PUMP_RESPONSE: return "Null pump response"; + case ERROR_INVALID_PUMP_RESPONSE: return "Invalid pump response"; + case ERROR_PUMP_BUSY: return "A pump command session is already in progress"; + default: return "Unknown error code (" + errorCode + ")"; + } + } + + public boolean resultIsOK() { + return ("OK".equals(map.getString("result",""))); + } + public String getErrorDescription() { + return map.getString("errorDescription",""); + } + + public String getResult() { + return map.getString("result",""); + } + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceTransport.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceTransport.java new file mode 100644 index 0000000000..f67cf9a032 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/ServiceTransport.java @@ -0,0 +1,126 @@ +package com.gxwtech.roundtrip2.ServiceData; + +import android.os.Bundle; +import android.os.Parcel; + +import com.gxwtech.roundtrip2.RT2Const; + +/** + * Created by geoff on 7/6/16. + * + * This class exists to hold a ServiceCommand along with transport variables + * such as time sent, time received, sender. + * May also contain result, if the command is completed. + */ +public class ServiceTransport extends ServiceMessage { + public ServiceTransport() { + } + + public ServiceTransport(Bundle b) { + if (b != null) { + if ("ServiceTransport".equals(b.getString("ServiceMessageType"))) { + setMap(b); + } else { + throw new IllegalArgumentException(); + } + } + } + + @Override + public void init() { + super.init(); + map.putString("ServiceMessageType","ServiceTransport"); + setTransportType("unknown"); + setSenderHashcode(0); + } + + public void setSenderHashcode(Integer senderHashcode) { + map.putInt("senderHashcode",senderHashcode); + } + + public Integer getSenderHashcode() { + return new Integer(map.getInt("senderHashCode",0)); + } + + public void setServiceCommand(ServiceCommand serviceCommand) { + map.putBundle("ServiceCommand",serviceCommand.getMap()); + } + + public ServiceCommand getServiceCommand() { + return new ServiceCommand(map.getBundle("ServiceCommand")); + } + + public boolean hasServiceCommand() { + return (getMap().containsKey("ServiceCommand")); + } + + // On remote end, this will be converted to the "action" of a local Intent, + // so can be used for separating types of messages to different internal handlers. + public void setTransportType(String transportType) { + map.putString("transportType",transportType); + } + + public String getTransportType() { + return map.getString("transportType","unknown"); + } + + public void setServiceResult(ServiceResult serviceResult) { + map.putBundle("ServiceResult",serviceResult.getMap()); + } + + public ServiceResult getServiceResult() { + return new ServiceResult(map.getBundle("ServiceResult")); + } + + public boolean hasServiceResult() { + return (getMap().containsKey("ServiceResult")); + } + + public void setServiceNotification(ServiceNotification notification) { + map.putBundle("ServiceNotification",notification.getMap()); + } + + public ServiceNotification getServiceNotification() { + return new ServiceNotification(map.getBundle("ServiceNotification")); + } + + public boolean hasServiceNotification() { + return (getMap().containsKey("ServiceNotification")); + } + + public boolean commandDidCompleteOK() { + return getServiceResult().resultIsOK(); + } + + public String getOriginalCommandName() { + return getServiceCommand().getCommandName(); + } + + public String describeContentsShort() { + String rval = ""; + rval += getTransportType(); + if (RT2Const.IPC.MSG_ServiceNotification.equals(getTransportType())) { + rval += "note: " + getServiceNotification().getNotificationType(); + } else if (RT2Const.IPC.MSG_ServiceCommand.equals(getTransportType())) { + rval += ", cmd=" + getOriginalCommandName(); + } else if (RT2Const.IPC.MSG_ServiceResult.equals(getTransportType())) { + rval += ", cmd=" + getOriginalCommandName(); + rval += ", rslt=" + getServiceResult().getResult(); + rval += ", err=" + getServiceResult().getErrorDescription(); + } + return rval; + } + + public ServiceTransport clone() { + Parcel p = Parcel.obtain(); + Parcel p2 = Parcel.obtain(); + getMap().writeToParcel(p,0); + byte[] bytes = p.marshall(); + p2.unmarshall(bytes, 0, bytes.length); + p2.setDataPosition(0); + Bundle b = p2.readBundle(); + ServiceTransport rval = new ServiceTransport(); + rval.setMap(b); + return rval; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/TimeValueProfile.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/TimeValueProfile.java new file mode 100644 index 0000000000..1d14c0e07a --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceData/TimeValueProfile.java @@ -0,0 +1,126 @@ +package com.gxwtech.roundtrip2.ServiceData; + +import android.os.Bundle; +import android.util.Log; + +import org.joda.time.LocalDateTime; +import org.joda.time.LocalTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Created by geoff on 6/25/16. + */ +public class TimeValueProfile { + private static final String TAG = "TimeValueProfile"; + boolean mIsValid = false; + LocalDateTime validAt; + ArrayList times = new ArrayList<>(); + HashMap values = new HashMap<>(); + + public TimeValueProfile() { + // initialize with very wrong timestamp. + validAt = new LocalDateTime(1900,1,1,1,1); + } + + public boolean isValid() { + return mIsValid; + } + + public ArrayList getTimes() { + return times; + } + + // This function will return the "object" at any time of day by finding the correct interval. + public Object getObjectForTime(LocalTime atTime) { + LocalTime prevTime = null; + double rval = -1.0; + for (LocalTime t : times) { + if (atTime.compareTo(t) < 0) { + if (prevTime != null) { + Object o = values.get(prevTime); + if (o != null) { + return o; + } + } + } + prevTime = t; + } + // cover last interval -- last time until next midnight + if (prevTime != null) { + Object o = values.get(prevTime); + if (o!=null) { + return o; + } + } + return rval; + } + + public boolean initFromServiceResult(ServiceResult serviceResult, String timeValueProfileName) { + if (serviceResult == null) { + return false; + } + Bundle resultMap = serviceResult.getMap(); + if (resultMap == null) { + return false; + } + Bundle profile = resultMap.getBundle(timeValueProfileName); + if (profile == null) { + return false; + } + mIsValid = true; + + String validDate = profile.getString("ValidDate"); + if (validDate != null) { + DateTimeFormatter fmt = DateTimeFormat.forPattern("YYYY-MM-ddTHH:mm:ss"); + try { + fmt.parseLocalDateTime(validDate); + } catch (IllegalArgumentException e) { + // invalid format + Log.e(TAG,"initFromServiceResult("+this.getClass().getSimpleName()+"): Failed to parse date from '"+validDate+"'"); + mIsValid = false; + } + } else { + mIsValid = false; + } + + int[] profileTimes = profile.getIntArray("times"); + float[] rates = profile.getFloatArray("rates"); + if ((profileTimes != null) && (rates!=null) && (profileTimes.length > 0) && (rates.length > 0) && (profileTimes.length == rates.length)) { + // first value must be zero (midnight) + if (profileTimes[0] != 0) { + mIsValid = false; + // but still try to load all of them. + } + LocalTime prevTime = null; + boolean timesValid = true; + for (int i=0; i= 0) { + timesValid = false; + } + } + } + if (!timesValid) { + Log.e(TAG,"initFromServiceResult(\"+this.getClass().getSimpleName()+\"): times must be monotonic"); + for (int j=0; j ITEMS = new ArrayList<>(); + + public static Map ITEM_MAP = new HashMap<>(); + + public static void addItem(ServiceMessageViewItem item) { + ITEMS.add(item); + ITEM_MAP.put(item.id, item); + } + + public static ServiceMessageViewItem createItem(int position) { + return new ServiceMessageViewItem(String.valueOf(position), "Item " + position, makeDetails(position)); + } + + public static String makeDetails(int position) { + StringBuilder builder = new StringBuilder(); + builder.append("Details about Item: ").append(position); + for (int i = 0; i < position; i++) { + builder.append("\nMore details information here."); + } + return builder.toString(); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewDetailActivity.txt b/app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewDetailActivity.txt new file mode 100644 index 0000000000..b309b925cf --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewDetailActivity.txt @@ -0,0 +1,83 @@ +package com.gxwtech.roundtrip2.ServiceMessageViewActivity; + +import android.content.Intent; +import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.Snackbar; +import android.support.v7.widget.Toolbar; +import android.view.View; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.app.ActionBar; +import android.view.MenuItem; + +import com.gxwtech.roundtrip2.R; + +/** + * An activity representing a single ServiceMessageView detail screen. This + * activity is only used narrow width devices. On tablet-size devices, + * item details are presented side-by-side with a list of items + * in a {@link ServiceMessageViewListActivity}. + */ +public class ServiceMessageViewDetailActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_servicemessageview_detail); + Toolbar toolbar = (Toolbar) findViewById(R.id.detail_toolbar); + setSupportActionBar(toolbar); + + FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Snackbar.make(view, "Replace with your own detail action", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + } + }); + + // Show the Up button in the action bar. + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + + // savedInstanceState is non-null when there is fragment state + // saved from previous configurations of this activity + // (e.g. when rotating the screen from portrait to landscape). + // In this case, the fragment will automatically be re-added + // to its container so we don't need to manually add it. + // For more information, see the Fragments API guide at: + // + // http://developer.android.com/guide/components/fragments.html + // + if (savedInstanceState == null) { + // Create the detail fragment and add it to the activity + // using a fragment transaction. + Bundle arguments = new Bundle(); + arguments.putString(ServiceMessageViewDetailFragment.ARG_ITEM_ID, + getIntent().getStringExtra(ServiceMessageViewDetailFragment.ARG_ITEM_ID)); + ServiceMessageViewDetailFragment fragment = new ServiceMessageViewDetailFragment(); + fragment.setArguments(arguments); + getSupportFragmentManager().beginTransaction() + .add(R.id.servicemessageview_detail_container, fragment) + .commit(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + // This ID represents the Home or Up button. In the case of this + // activity, the Up button is shown. For + // more details, see the Navigation pattern on Android Design: + // + // http://developer.android.com/design/patterns/navigation.html#up-vs-back + // + navigateUpTo(new Intent(this, ServiceMessageViewListActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewDetailFragment.txt b/app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewDetailFragment.txt new file mode 100644 index 0000000000..27137bc27a --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewDetailFragment.txt @@ -0,0 +1,69 @@ +package com.gxwtech.roundtrip2.ServiceMessageViewActivity; + +import android.app.Activity; +import android.support.design.widget.CollapsingToolbarLayout; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.gxwtech.roundtrip2.R; + +/** + * A fragment representing a single ServiceMessageView detail screen. + * This fragment is either contained in a {@link ServiceMessageViewListActivity} + * in two-pane mode (on tablets) or a {@link ServiceMessageViewDetailActivity} + * on handsets. + */ +public class ServiceMessageViewDetailFragment extends Fragment { + /** + * The fragment argument representing the item ID that this fragment + * represents. + */ + public static final String ARG_ITEM_ID = "item_id"; + + /** + * The dummy content this fragment is presenting. + */ + private ServiceMessageViewItem mItem; + + /** + * Mandatory empty constructor for the fragment manager to instantiate the + * fragment (e.g. upon screen orientation changes). + */ + public ServiceMessageViewDetailFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments().containsKey(ARG_ITEM_ID)) { + // Load the dummy content specified by the fragment + // arguments. In a real-world scenario, use a Loader + // to load content from a content provider. + mItem = ServiceMessageView.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID)); + + Activity activity = this.getActivity(); + CollapsingToolbarLayout appBarLayout = (CollapsingToolbarLayout) activity.findViewById(R.id.toolbar_layout); + if (appBarLayout != null) { + appBarLayout.setTitle(mItem.content); + } + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.servicemessageview_detail, container, false); + + // Show the dummy content as text in a TextView. + if (mItem != null) { + ((TextView) rootView.findViewById(R.id.servicemessageview_detail)).setText(mItem.details); + } + + return rootView; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewItem.java b/app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewItem.java new file mode 100644 index 0000000000..003e7fd7f8 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewItem.java @@ -0,0 +1,25 @@ +package com.gxwtech.roundtrip2.ServiceMessageViewActivity; + +import com.gxwtech.roundtrip2.ServiceData.ServiceMessage; + +/** + * Created by geoff on 7/4/16. + */ + +public class ServiceMessageViewItem { + public final String id; + public final String content; + public final String details; + + public ServiceMessageViewItem(String id, String content, String details) { + this.id = id; + this.content = content; + this.details = details; + } + + @Override + public String toString() { + return content; + } +} + diff --git a/app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewListActivity.txt b/app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewListActivity.txt new file mode 100644 index 0000000000..4ada6dbe2a --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/ServiceMessageViewActivity/ServiceMessageViewListActivity.txt @@ -0,0 +1,203 @@ +package com.gxwtech.roundtrip2.ServiceMessageViewActivity; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.Snackbar; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + + +import com.gxwtech.roundtrip2.R; +import com.gxwtech.roundtrip2.RT2Const; + +import java.util.List; + +/** + * An activity representing a list of ServiceMessageViews. This activity + * has different presentations for handset and tablet-size devices. On + * handsets, the activity presents a list of items, which when touched, + * lead to a {@link ServiceMessageViewDetailActivity} representing + * item details. On tablets, the activity presents the list of items and + * item details side-by-side using two vertical panes. + */ +public class ServiceMessageViewListActivity extends AppCompatActivity { + private static final String TAG = "SvcMsgViewListActivity"; + /** + * Whether or not the activity is in two-pane mode, i.e. running on a tablet + * device. + */ + private boolean mTwoPane; + private BroadcastReceiver mBroadcastReceiver; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_servicemessageview_list); + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + toolbar.setTitle(getTitle()); + + FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + } + }); + + View recyclerView = findViewById(R.id.servicemessageview_list); + assert recyclerView != null; + setupRecyclerView((RecyclerView) recyclerView); + + if (findViewById(R.id.servicemessageview_detail_container) != null) { + // The detail container view will be present only in the + // large-screen layouts (res/values-w900dp). + // If this view is present, then the + // activity should be in two-pane mode. + mTwoPane = true; + } + mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (RT2Const.IPC.MSG_ServiceCommand.equals(action) || + (RT2Const.IPC.MSG_ServiceResult.equals(action))) { + Bundle bundle = intent.getBundleExtra(RT2Const.IPC.bundleKey); + // build ID from the millisecond timestamp and a descriptive part of the ServiceMessage + String command = bundle.getString("command"); + String serviceResultType = bundle.getString("ServiceResultType"); + String idPart = "(null)"; + if (command != null) { + String commandID = bundle.getString("commandID"); + if (commandID != null) { + idPart = command + commandID; + } else { + idPart = command; + } + } + if (serviceResultType != null) { + idPart = serviceResultType; + } + String messageID = bundle.getString(RT2Const.IPC.instantKey,"(null)") + idPart; + // build content from the descriptive string + String content = idPart; + // build details from the KV pairs from the message + StringBuilder detailsBuilder = new StringBuilder(); + for (String key : bundle.keySet()) { + if ("command".equals(key)) continue; + if ("ServiceResultType".equals(key)) continue; + if ("instant".equals(key)) continue; + detailsBuilder.append(key); + detailsBuilder.append("="); + String stringValue = bundle.getString(key); + if (stringValue != null) { + detailsBuilder.append(stringValue); + } else { + Bundle bundleValue = bundle.getBundle(key); + if (bundleValue != null) { + detailsBuilder.append("(bundle)"); + } else { + detailsBuilder.append("(?)"); + } + } + detailsBuilder.append("\n"); + } + ServiceMessageView.addItem(new ServiceMessageViewItem(messageID,content,detailsBuilder.toString())); + } else { + Log.e(TAG,"onReceive: unhandled action: " + action); + } + } + }; + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(RT2Const.IPC.MSG_ServiceCommand); + intentFilter.addAction(RT2Const.IPC.MSG_ServiceResult); + LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter); + } + + private void setupRecyclerView(@NonNull RecyclerView recyclerView) { + recyclerView.setAdapter(new SimpleItemRecyclerViewAdapter(ServiceMessageView.ITEMS)); + } + + public class SimpleItemRecyclerViewAdapter + extends RecyclerView.Adapter { + + private final List mValues; + + public SimpleItemRecyclerViewAdapter(List items) { + mValues = items; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.servicemessageview_list_content, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(final ViewHolder holder, int position) { + holder.mItem = mValues.get(position); + holder.mIdView.setText(mValues.get(position).id); + holder.mContentView.setText(mValues.get(position).content); + + holder.mView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mTwoPane) { + Bundle arguments = new Bundle(); + arguments.putString(ServiceMessageViewDetailFragment.ARG_ITEM_ID, holder.mItem.id); + ServiceMessageViewDetailFragment fragment = new ServiceMessageViewDetailFragment(); + fragment.setArguments(arguments); + getSupportFragmentManager().beginTransaction() + .replace(R.id.servicemessageview_detail_container, fragment) + .commit(); + } else { + Context context = v.getContext(); + Intent intent = new Intent(context, ServiceMessageViewDetailActivity.class); + intent.putExtra(ServiceMessageViewDetailFragment.ARG_ITEM_ID, holder.mItem.id); + + context.startActivity(intent); + } + } + }); + } + + @Override + public int getItemCount() { + return mValues.size(); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + public final View mView; + public final TextView mIdView; + public final TextView mContentView; + public ServiceMessageViewItem mItem; + + public ViewHolder(View view) { + super(view); + mView = view; + mIdView = (TextView) view.findViewById(R.id.id); + mContentView = (TextView) view.findViewById(R.id.content); + } + + @Override + public String toString() { + return super.toString() + " '" + mContentView.getText() + "'"; + } + } + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/SettingsActivity.txt b/app/src/main/java/com/gxwtech/roundtrip2/SettingsActivity.txt new file mode 100644 index 0000000000..c4f8fa118c --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/SettingsActivity.txt @@ -0,0 +1,208 @@ +package com.gxwtech.roundtrip2; + + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Build; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v7.app.ActionBar; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; +import android.view.MenuItem; + +import java.util.List; + +/** + * A {@link PreferenceActivity} that presents a set of application settings. On + * handset devices, settings are presented as a single list. On tablets, + * settings are split by category, with category headers shown to the left of + * the list of settings. + *

+ * See + * Android Design: Settings for design guidelines and the Settings + * API Guide for more information on developing a Settings UI. + */ +public class SettingsActivity extends AppCompatPreferenceActivity { + /** + * A preference value change listener that updates the preference's summary + * to reflect its new value. + */ + private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object value) { + String stringValue = value.toString(); + + switch (preference.getKey()){ + case "pref_user_max_bolus": + preference.setSummary(stringValue + "U"); + break; + case "pref_user_max_basal_rate": + preference.setSummary(stringValue + "U/hr"); + break; + case "pref_user_max_basal_duration": + preference.setSummary(stringValue + "mins"); + break; + case RT2Const.serviceLocal.pumpIDKey: + preference.setSummary(stringValue); + break; + default: + // For all other preferences, set the summary to the value's + // simple string representation. + preference.setSummary(stringValue); + } + + return true; + } + }; + + /** + * Helper method to determine if the device has an extra-large screen. For + * example, 10" tablets are extra-large. + */ + private static boolean isXLargeTablet(Context context) { + return (context.getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE; + } + + /** + * Binds a preference's summary to its value. More specifically, when the + * preference's value is changed, its summary (line of text below the + * preference title) is updated to reflect the value. The summary is also + * immediately updated upon calling this method. The exact display format is + * dependent on the type of preference. + * + * @see #sBindPreferenceSummaryToValueListener + */ + private static void bindPreferenceSummaryToValue(Preference preference) { + // Set the listener to watch for value changes. + preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener); + + // Trigger the listener immediately with the preference's + // current value. + sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, + PreferenceManager + .getDefaultSharedPreferences(preference.getContext()) + .getString(preference.getKey(), "")); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setupActionBar(); + } + + /** + * Set up the {@link android.app.ActionBar}, if the API is available. + */ + private void setupActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + // Show the Up button in the action bar. + actionBar.setDisplayHomeAsUpEnabled(true); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onIsMultiPane() { + return isXLargeTablet(this); + } + + /** + * {@inheritDoc} + */ + @Override + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public void onBuildHeaders(List

target) { + loadHeadersFromResource(R.xml.pref_headers, target); + } + + /** + * This method stops fragment injection in malicious applications. + * Make sure to deny any unknown fragments here. + */ + protected boolean isValidFragment(String fragmentName) { + return PreferenceFragment.class.getName().equals(fragmentName) + || PumpPreferenceFragment.class.getName().equals(fragmentName) + || RileyLinkPreferenceFragment.class.getName().equals(fragmentName); + } + + /** + * This fragment shows general preferences only. It is used when the + * activity is showing a two-pane settings UI. + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class PumpPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_pump); + setHasOptionsMenu(true); + + // Bind the summaries of EditText/List/Dialog/Ringtone preferences + // to their values. When their values change, their summaries are + // updated to reflect the new value, per the Android Design + // guidelines. + bindPreferenceSummaryToValue(findPreference(RT2Const.serviceLocal.pumpIDKey)); + bindPreferenceSummaryToValue(findPreference("pref_user_max_bolus")); + bindPreferenceSummaryToValue(findPreference("pref_user_max_basal_rate")); + bindPreferenceSummaryToValue(findPreference("pref_user_max_basal_duration")); + + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + startActivity(new Intent(getActivity(), SettingsActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class RileyLinkPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_rileylink); + setHasOptionsMenu(true); + + // Bind the summaries of EditText/List/Dialog/Ringtone preferences + // to their values. When their values change, their summaries are + // updated to reflect the new value, per the Android Design + // guidelines. + bindPreferenceSummaryToValue(findPreference(RT2Const.serviceLocal.rileylinkAddressKey)); + Preference rileylink_ble = findPreference(RT2Const.serviceLocal.rileylinkAddressKey); + rileylink_ble.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + startActivity(new Intent(MainApp.instance(), RileyLinkScan.class)); + return true; + } + }); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + startActivity(new Intent(getActivity(), SettingsActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } + } + + + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/TreatmentHistory.txt b/app/src/main/java/com/gxwtech/roundtrip2/TreatmentHistory.txt new file mode 100644 index 0000000000..84bbdd86db --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/TreatmentHistory.txt @@ -0,0 +1,320 @@ +package com.gxwtech.roundtrip2; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.SimpleAdapter; +import android.widget.Spinner; +import android.widget.TextView; + +import com.gxwtech.roundtrip2.CommunicationService.Objects.Bolus; +import com.gxwtech.roundtrip2.CommunicationService.Objects.Integration; +import com.gxwtech.roundtrip2.CommunicationService.Objects.RealmManager; +import com.gxwtech.roundtrip2.CommunicationService.Objects.TempBasal; +import com.gxwtech.roundtrip2.util.tools; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +public class TreatmentHistory extends AppCompatActivity { + private static final String TAG = "TreatmentHistory"; + + SectionsPagerAdapter mSectionsPagerAdapter; //will provide fragments for each of the sections + ViewPager mViewPager; + Fragment bolusFragmentObject; + Fragment basalFragmentObject; + BroadcastReceiver refreshTreatments; + BroadcastReceiver refreshBasal; + static Spinner numHours; + static RealmManager realmManager; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_treatment_history); + + realmManager = new RealmManager(); + + //Num hours spinner setup + numHours = (Spinner) findViewById(R.id.integrationHours); + String[] integrationHours = {"4", "8", "12", "24", "48"}; + ArrayAdapter stringArrayAdapterHours= new ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, integrationHours); + numHours.setAdapter(stringArrayAdapterHours); + numHours.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + bolusFragment.update(); + basalFragment.update(); + } + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + numHours.setSelection(0); + + // Create the adapter that will return a fragment for each of the 4 primary sections of the app. + mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); + // Set up the ViewPager with the sections adapter. + mViewPager = (ViewPager) this.findViewById(R.id.pager); + mViewPager.setAdapter(mSectionsPagerAdapter); + bolusFragmentObject = new bolusFragment(); + basalFragmentObject = new basalFragment(); + } + + @Override + public void onDestroy(){ + super.onDestroy(); + realmManager.closeRealm(); + } + + @Override + public void onPause() { + super.onPause(); + if (refreshTreatments != null){ + unregisterReceiver(refreshTreatments); + } + if (refreshBasal != null){ + unregisterReceiver(refreshBasal); + } + } + + @Override + protected void onResume(){ + super.onResume(); + + //Refresh the treatments list + refreshTreatments = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + bolusFragment.update(); + } + }; + registerReceiver(refreshTreatments, new IntentFilter("UPDATE_TREATMENTS")); + bolusFragment.update(); + + //Refresh the Basal list + refreshBasal = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + basalFragment.update(); + } + }; + registerReceiver(refreshBasal, new IntentFilter("UPDATE_BASAL")); + basalFragment.update(); + } + + /* Page layout Fragments */ + + public class SectionsPagerAdapter extends FragmentPagerAdapter { + + public SectionsPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + // getItem is called to instantiate the fragment for the given page. + switch (position){ + case 0: + return bolusFragmentObject; + case 1: + return basalFragmentObject; + default: + return null; + } + } + + @Override + public int getCount() { + // Show 2 total pages. + return 2; + } + + @Override + public CharSequence getPageTitle(int position) { + switch (position) { + case 0: + return "Bolus Requests"; + case 1: + return "Basal Requests"; + } + return null; + } + } + + public static class bolusFragment extends Fragment { + public bolusFragment(){} + private static ListView list; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_treatments_list, container, false); + list = (ListView) rootView.findViewById(R.id.treatmentsFragmentList); + + update(); + return rootView; + } + + public static void update(){ + + if (list != null) { + ArrayList> bolusList = new ArrayList<>(); + int hoursAgo = Integer.getInteger(numHours.getSelectedItem().toString(),4); + SimpleDateFormat sdfDateTime = new SimpleDateFormat("dd MMM HH:mm", MainApp.instance().getResources().getConfiguration().locale); + List boluses = Bolus.getBolusesBetween(new Date(new Date().getTime() - ((60000 * 60 * hoursAgo))), new Date(), realmManager.getRealm()); + + for (Bolus bolus : boluses){ + HashMap bolusItem = new HashMap(); + List integrations = Integration.getIntegrationsFor("bolus", bolus.getId(), realmManager.getRealm()); + + bolusItem.put("id", bolus.getId()); + bolusItem.put("object", "bolus"); + bolusItem.put("value", bolus.getValue().toString()); + bolusItem.put("timestamp", sdfDateTime.format(bolus.getTimestamp().getTime())); + bolusItem.put("type", bolus.getType()); + for (Integration integration : integrations){ + switch (integration.getType()){ + case "aps_app": + bolusItem.put("integrationAPSState", integration.getState()); + break; + case "pump": + bolusItem.put("integrationPumpState", integration.getState()); + break; + } + } + bolusList.add(bolusItem); + } + + mySimpleAdapter adapter = new mySimpleAdapter(MainApp.instance(), bolusList, R.layout.treatments_list_layout, + new String[]{"id", "object", "value", "timestamp", "type", "aps_app", "pump"}, + new int[]{R.id.treatmentID, R.id.treatmentObject, R.id.treatmentAmount, R.id.treatmentTimestamp, R.id.treatmentType, R.id.treatmentAPS, R.id.treatmentResult}); + list.setAdapter(adapter); + } + } + } + + public static class basalFragment extends Fragment { + public basalFragment() { + } + + private static ListView list; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_treatments_list, container, false); + list = (ListView) rootView.findViewById(R.id.treatmentsFragmentList); + + update(); + return rootView; + } + + public static void update() { + if (list != null) { + ArrayList> basalList = new ArrayList<>(); + Integer hoursAgo = Integer.getInteger(numHours.getSelectedItem().toString(),4); + SimpleDateFormat sdfDateTime = new SimpleDateFormat("dd MMM HH:mm", MainApp.instance().getResources().getConfiguration().locale); + List tempBasals = TempBasal.getTempBasalsDated(new Date(new Date().getTime() - ((60000 * 60 * hoursAgo))), new Date(), realmManager.getRealm()); + + for (TempBasal tempBasal : tempBasals){ + HashMap basalItem = new HashMap(); + List integrations = Integration.getIntegrationsFor("temp_basal", tempBasal.getId(), realmManager.getRealm()); + + basalItem.put("id", tempBasal.getId()); + basalItem.put("object", "temp_basal"); + basalItem.put("rate", tempBasal.getRate().toString()); + basalItem.put("starttime", sdfDateTime.format(tempBasal.getStart_time().getTime())); + for (Integration integration : integrations){ + switch (integration.getType()){ + case "aps_app": + basalItem.put("integrationAPSState", integration.getState()); + break; + case "pump": + basalItem.put("integrationPumpState", integration.getState()); + break; + } + } + basalList.add(basalItem); + } + + mySimpleAdapter adapter = new mySimpleAdapter(MainApp.instance(), basalList, R.layout.treatments_list_layout, + new String[]{"id", "object", "rate", "starttime", "aps_app", "pump"}, + new int[]{R.id.treatmentID, R.id.treatmentObject, R.id.treatmentAmount, R.id.treatmentTimestamp, R.id.treatmentAPS, R.id.treatmentResult}); + list.setAdapter(adapter); + } + } + } + + public static class mySimpleAdapter extends SimpleAdapter { + + public mySimpleAdapter(Context context, List> items, int resource, String[] from, int[] to) { + super(context, items, resource, from, to); + } + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view = super.getView(position, convertView, parent); + + TextView value, type; + type = (TextView) view.findViewById(R.id.treatmentObject); + value = (TextView) view.findViewById(R.id.treatmentAmount); + switch (type.getText().toString()){ + case "bolus": + value.setBackgroundResource(R.drawable.insulin_treatment_round); + value.setText(tools.formatDisplayInsulin(Double.valueOf(value.getText().toString()), 1)); + value.setTextColor(MainApp.instance().getResources().getColor(R.color.primary_light)); + break; + case "temp_basal": + value.setBackgroundResource(R.drawable.insulin_basal_square); + value.setText(tools.formatDisplayBasal(Double.valueOf(value.getText().toString()),false)); + value.setTextColor(MainApp.instance().getResources().getColor(R.color.primary_light)); + break; + } + + //Shows Pump result + TextView textPump = (TextView) view.findViewById(R.id.treatmentResult); + ImageView imagePump = (ImageView) view.findViewById(R.id.treatmentResultIcon); + switch (textPump.getText().toString()) { + case "to sync": + imagePump.setBackgroundResource(R.drawable.autorenew); + break; + case "sent": + imagePump.setBackgroundResource(R.drawable.arrow_right_bold_circle); + break; + case "received": + imagePump.setBackgroundResource(R.drawable.information); + break; + case "delayed": + imagePump.setBackgroundResource(R.drawable.clock); + break; + case "delivered": + imagePump.setBackgroundResource(R.drawable.checkbox_marked_circle); + break; + case "error": + imagePump.setBackgroundResource(R.drawable.alert_circle); + break; + default: + imagePump.setBackgroundResource(0); + break; + } + + return view; + } + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/util/ByteUtil.java b/app/src/main/java/com/gxwtech/roundtrip2/util/ByteUtil.java new file mode 100644 index 0000000000..76bbc05beb --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/util/ByteUtil.java @@ -0,0 +1,146 @@ +package com.gxwtech.roundtrip2.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by geoff on 4/28/15. + */ +public class ByteUtil { + private final static char[] HEX_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + private final static String HEX_DIGITS_STR = "0123456789ABCDEF"; + + public static byte highByte(short s) { + return (byte) (s / 256); + } + + public static byte lowByte(short s) { + return (byte) (s % 256); + } + + public static int asUINT8(byte b) { + return (b < 0) ? b + 256 : b; + } + + /* For Reference: static void System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length) */ + + public static byte[] concat(byte[] a, byte[] b) { + int aLen = a.length; + int bLen = b.length; + byte[] c = new byte[aLen + bLen]; + System.arraycopy(a, 0, c, 0, aLen); + System.arraycopy(b, 0, c, aLen, bLen); + return c; + } + + public static byte[] concat(byte[] a, byte b) { + int aLen = a.length; + byte[] c = new byte[aLen + 1]; + System.arraycopy(a, 0, c, 0, aLen); + c[aLen] = b; + return c; + } + + public static byte[] substring(byte[] a, int start, int len) { + byte[] rval = new byte[len]; + System.arraycopy(a, start, rval, 0, len); + return rval; + } + + public static String shortHexString(byte[] ra) { + String rval = ""; + if (ra == null) { + return rval; + } + if (ra.length == 0) { + return rval; + } + for (int i = 0; i < ra.length; i++) { + rval = rval + HEX_DIGITS[(ra[i] & 0xF0) >> 4]; + rval = rval + HEX_DIGITS[(ra[i] & 0x0F)]; + if (i < ra.length - 1) { + rval = rval + " "; + } + } + return rval; + } + + public static String showPrintable(byte[] ra) { + String s = new String(); + for (int i=0; i='0') && (c<='9')) || + ((c>='A') && (c<='Z')) || + ((c>='a') && (c<='z'))) { + s = s+c; + } + else { + s = s+'.'; + } + } + return s; + } + + public static byte[] fromHexString(String src) { + String s = src.toUpperCase(); + byte[] rval = new byte[]{}; + if ((s.length() % 2)!=0) { + // invalid hex string! + return null; + } + for (int i=0; i byteArray){ + byte[] rval = new byte[byteArray.size()]; + for (int i=0; i toByteArray(byte[] data) { + ArrayList rval = new ArrayList<>(data.length); + for (int i=0; i len2) { + return 1; + } + if (len2 > len1) { + return -1; + } + int acc=0; + for (i=0; i data.length) { + len = data.length; + } + for (int i=0; i 0) { + for (int j=0; j> (7 - i) & 1) == 1); + boolean c15 = ((crc >> 15 & 1) == 1); + crc <<= 1; + if (c15 ^ bit) crc ^= polynomial; + } + } + } + } + crc &= 0xffff; + return new byte[]{(byte) ((crc & 0xFF00) >> 8), (byte) (crc & 0xFF)}; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/util/Check.java b/app/src/main/java/com/gxwtech/roundtrip2/util/Check.java new file mode 100644 index 0000000000..bcce649b11 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/util/Check.java @@ -0,0 +1,98 @@ +package com.gxwtech.roundtrip2.util; + +import com.gxwtech.roundtrip2.CommunicationService.Objects.Bolus; +import com.gxwtech.roundtrip2.CommunicationService.Objects.Integration; +import com.gxwtech.roundtrip2.CommunicationService.Objects.TempBasal; +import com.gxwtech.roundtrip2.RT2Const; + +import java.util.Date; + +import io.realm.Realm; + +/** + * Created by Tim on 16/06/2016. + * Class that provides validation and safety checks + */ +public class Check { + final static boolean DEBUG = true; + + public static String isNewTempBasalSafe(TempBasal tempBasal){ + String reply = ""; + + if (isTreatmentTooOld(tempBasal.getStart_time())) reply += "Treatment is older than " + RT2Const.safety.TREATMENT_MAX_AGE + " mins, rejected."; + //is TBR supported? + // TODO: 21/02/2016 perform checks here, return empty string for OK or text detailing the issue + + return reply; + } + + public static String isCancelTempBasalSafe(TempBasal tempBasal, Integration integrationAPS, Realm realm){ + String reply = ""; + + if (isTreatmentTooOld(tempBasal.getStart_time())) reply += "Treatment is older than " + RT2Const.safety.TREATMENT_MAX_AGE + " mins, rejected. "; + if (!isThisBasalLastActioned(integrationAPS, realm)) reply += "Current Running Temp Basal does not match this Cancel request, Temp Basal has not been canceled. "; + //is TBR supported? + // TODO: 21/02/2016 perform checks here, return empty string for OK or text detailing the issue + + return reply; + } + + public static String isBolusSafeToAction(Bolus bolus){ + String reply = ""; + + if (isTreatmentTooOld(bolus.getTimestamp())) reply += "Treatment is older than " + RT2Const.safety.TREATMENT_MAX_AGE + " mins, rejected. "; + // TODO: 16/06/2016 add additional Bolus safety checks here + + return reply; + } + + public static boolean isPumpSupported(String expectedPump){ + //Do we support the pump requested? + if (DEBUG) return true; + + switch (expectedPump){ + case "medtronic_absolute": + case "medtronic_percent": + return true; + default: + return false; + } + } + + public static boolean isRequestTooOld(Long date){ + //Is this request too old to action? + Long ageInMins = (new Date().getTime() - date) /1000/60; + if (ageInMins > RT2Const.safety.INCOMING_REQUEST_MAX_AGE){ + return true; + } else { + return false; + } + } + + public static boolean isTreatmentTooOld(Date date){ + //Is this treatment too old to action? + Long ageInMins = (new Date().getTime() - date.getTime()) /1000/60; + if (ageInMins > RT2Const.safety.TREATMENT_MAX_AGE){ + return true; + } else { + return false; + } + } + + private static boolean isThisBasalLastActioned(Integration integrationAPS, Realm realm){ + //Is this Basal the most recent one set to the pump? + TempBasal lastActive = TempBasal.lastActive(realm); //Basal that is active or last active + if (lastActive == null) return false; //No basal has ever been active + + Integration integration = Integration.getIntegration("aps_app","temp_basal",lastActive.getId(),realm); + if (integration == null) return false; //We are not aware of any Basal set by this App + + if (integrationAPS.getRemote_id().equals(integration.getRemote_id())) { + return true; + } else { + return false; //Basal to cancel does not match the current active basal + } + } + +} + diff --git a/app/src/main/java/com/gxwtech/roundtrip2/util/HexDump.java b/app/src/main/java/com/gxwtech/roundtrip2/util/HexDump.java new file mode 100644 index 0000000000..ff38fd2118 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/util/HexDump.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.gxwtech.roundtrip2.util; + +/** + * Clone of Android's HexDump class, for use in debugging. Cosmetic changes + * only. + */ +public class HexDump { + private final static char[] HEX_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + public static String dumpHexString(byte[] array) { + return dumpHexString(array, 0, array.length); + } + + public static String dumpHexString(byte[] array, int offset, int length) { + StringBuilder result = new StringBuilder(); + + byte[] line = new byte[16]; + int lineIndex = 0; + + result.append("\n0x"); + result.append(toHexString(offset)); + + for (int i = offset; i < offset + length; i++) { + if (lineIndex == 16) { + result.append(" "); + + for (int j = 0; j < 16; j++) { + if (line[j] > ' ' && line[j] < '~') { + result.append(new String(line, j, 1)); + } else { + result.append("."); + } + } + + result.append("\n0x"); + result.append(toHexString(i)); + lineIndex = 0; + } + + byte b = array[i]; + result.append(" "); + result.append(HEX_DIGITS[(b >>> 4) & 0x0F]); + result.append(HEX_DIGITS[b & 0x0F]); + + line[lineIndex++] = b; + } + + if (lineIndex != 16) { + int count = (16 - lineIndex) * 3; + count++; + for (int i = 0; i < count; i++) { + result.append(" "); + } + + for (int i = 0; i < lineIndex; i++) { + if (line[i] > ' ' && line[i] < '~') { + result.append(new String(line, i, 1)); + } else { + result.append("."); + } + } + } + + return result.toString(); + } + + public static String toHexString(byte b) { + return toHexString(toByteArray(b)); + } + + public static String toHexString(byte[] array) { + return toHexString(array, 0, array.length); + } + + public static String toHexString(byte[] array, int offset, int length) { + char[] buf = new char[length * 2]; + + int bufIndex = 0; + for (int i = offset; i < offset + length; i++) { + byte b = array[i]; + buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F]; + buf[bufIndex++] = HEX_DIGITS[b & 0x0F]; + } + + return new String(buf); + } + + public static String toHexString(int i) { + return toHexString(toByteArray(i)); + } + + public static String toHexString(short i) { + return toHexString(toByteArray(i)); + } + + public static byte[] toByteArray(byte b) { + byte[] array = new byte[1]; + array[0] = b; + return array; + } + + public static byte[] toByteArray(int i) { + byte[] array = new byte[4]; + + array[3] = (byte) (i & 0xFF); + array[2] = (byte) ((i >> 8) & 0xFF); + array[1] = (byte) ((i >> 16) & 0xFF); + array[0] = (byte) ((i >> 24) & 0xFF); + + return array; + } + + public static byte[] toByteArray(short i) { + byte[] array = new byte[2]; + + array[1] = (byte) (i & 0xFF); + array[0] = (byte) ((i >> 8) & 0xFF); + + return array; + } + + private static int toByte(char c) { + if (c >= '0' && c <= '9') + return (c - '0'); + if (c >= 'A' && c <= 'F') + return (c - 'A' + 10); + if (c >= 'a' && c <= 'f') + return (c - 'a' + 10); + + throw new RuntimeException("Invalid hex char '" + c + "'"); + } + + public static byte[] hexStringToByteArray(String hexString) { + int length = hexString.length(); + byte[] buffer = new byte[length / 2]; + + for (int i = 0; i < length; i += 2) { + buffer[i / 2] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString + .charAt(i + 1))); + } + + return buffer; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/util/LocationHelper.java b/app/src/main/java/com/gxwtech/roundtrip2/util/LocationHelper.java new file mode 100644 index 0000000000..29bc09b6bb --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/util/LocationHelper.java @@ -0,0 +1,81 @@ +package com.gxwtech.roundtrip2.util; + +import android.Manifest; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.location.LocationManager; +import android.os.Build; +import android.support.v4.app.ActivityCompat; +import android.util.Log; + + + +/** + * Helper for checking if location services are enabled on the device. + */ +public class LocationHelper { + + final static String TAG = "LocationHelper"; + + /** + * Determine if GPS is currently enabled. + * + * On Android 6 (Marshmallow), location needs to be enabled for Bluetooth discovery to work. + * + * @param context The current app context. + * @return true if location is enabled, false otherwise. + */ + public static boolean isLocationEnabled(Context context) { + LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); + } + + /** + * Prompt the user to enable GPS location if it isn't already on. + * + * @param parent The currently visible activity. + */ + public static void requestLocation(final Activity parent) { + if (LocationHelper.isLocationEnabled(parent)) { + return; + } + + // Shamelessly borrowed from http://stackoverflow.com/a/10311877/868533 + + Log.e(TAG, "requestLocation: N/A"); + +// AlertDialog.Builder builder = new AlertDialog.Builder(parent); +// builder.setTitle(R.string.location_not_found_title); +// builder.setMessage(R.string.location_not_found_message); +// builder.setPositiveButton(R.string.location_yes, new DialogInterface.OnClickListener() { +// public void onClick(DialogInterface dialogInterface, int i) { +// parent.startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); +// } +// }); +// builder.setNegativeButton(R.string.location_no, null); +// builder.create().show(); + } + + /** + * Prompt the user to enable GPS location on devices that need it for Bluetooth discovery. + * + * Android 6 (Marshmallow) needs GPS enabled for Bluetooth discovery to work. + * + * @param activity The currently visible activity. + */ + public static void requestLocationForBluetooth(Activity activity) { + // Location needs to be enabled for Bluetooth discovery on Marshmallow. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + LocationHelper.requestLocation(activity); + } + } + + //public static Boolean locationPermission(ActivityWithMenu act) { + // return ActivityCompat.checkSelfPermission(act, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; + //} + +} \ No newline at end of file diff --git a/app/src/main/java/com/gxwtech/roundtrip2/util/StringUtil.java b/app/src/main/java/com/gxwtech/roundtrip2/util/StringUtil.java new file mode 100644 index 0000000000..8cbe024484 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/util/StringUtil.java @@ -0,0 +1,37 @@ +package com.gxwtech.roundtrip2.util; + +import java.nio.charset.Charset; +import java.util.ArrayList; + +/** + * Created by geoff on 4/28/15. + */ +public class StringUtil { + + public static String fromBytes(byte[] ra) { + return new String(ra, Charset.forName("UTF-8")); + } + + // these should go in some project-wide string utils package + public static String join(ArrayList ra, String joiner) { + int sz = ra.size(); + String rval = ""; + int n; + for (n = 0; n < sz; n++) { + rval = rval + ra.get(n); + if (n < sz - 1) { + rval = rval + joiner; + } + } + return rval; + } + + public static String testJoin() { + ArrayList ra = new ArrayList(); + ra.add("one"); + ra.add("two"); + ra.add("three"); + return join(ra, "+"); + } + +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/util/ThreadUtil.java b/app/src/main/java/com/gxwtech/roundtrip2/util/ThreadUtil.java new file mode 100644 index 0000000000..e966739db2 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/util/ThreadUtil.java @@ -0,0 +1,17 @@ +package com.gxwtech.roundtrip2.util; + +/** + * Created by geoff on 5/27/16. + */ +public class ThreadUtil { + public static long getThreadId() { + return Thread.currentThread().getId(); + } + public static String getThreadName() { + return Thread.currentThread().getName(); + } + public static String sig() { + Thread t = Thread.currentThread(); + return t.getName() + "[" + t.getId() + "]"; + } +} diff --git a/app/src/main/java/com/gxwtech/roundtrip2/util/tools.java b/app/src/main/java/com/gxwtech/roundtrip2/util/tools.java new file mode 100644 index 0000000000..61adc29dc1 --- /dev/null +++ b/app/src/main/java/com/gxwtech/roundtrip2/util/tools.java @@ -0,0 +1,115 @@ +package com.gxwtech.roundtrip2.util; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.text.ClipboardManager; +import android.util.Log; +import android.widget.TextView; +import android.widget.Toast; + + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; + +/** + * Created by Tim on 15/06/2016. + */ +public class tools { + final static String TAG = "Tools"; + + public static void showLogging(){ + String logCat = "no logs"; + final String processId = Integer.toString(android.os.Process.myPid()); + try { + Process process = Runtime.getRuntime().exec("logcat -d"); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); + StringBuilder log = new StringBuilder(); + String line; + + while ((line = bufferedReader.readLine()) != null) { + if(line.contains(processId)) log.append(line + "\n"); + } + logCat = log.toString(); + + } catch (IOException e) { + logCat = e.getLocalizedMessage(); + } finally { + //showAlertText(logCat, MainApp.instance()); + showAlertText(logCat, null); + } + } + + public static void showAlertText(final String msg, final Context context){ +// try { +// AlertDialog alertDialog = new AlertDialog.Builder(MainActivity.mContext) // TODO: 09/07/2016 @TIM this should not be needed, should be context +// .setMessage(msg) +// .setPositiveButton("Copy to Clipboard", new DialogInterface.OnClickListener() { +// public void onClick(DialogInterface dialog, int which) { +// ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); +// clipboard.setText(msg); +// Toast.makeText(MainApp.instance(), "Copied to clipboard", Toast.LENGTH_SHORT).show(); +// } +// }) +// .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { +// public void onClick(DialogInterface dialog, int which) { +// // do nothing +// } +// }) +// .show(); +// +// if (msg.length() > 100) { +// TextView textView = (TextView) alertDialog.findViewById(android.R.id.message); +// textView.setTextSize(10); +// } +// } catch (Exception e){ +// //Crashlytics.logException(e); +// Log.e(TAG, "showAlertText: " + e.getLocalizedMessage()); +// } + + Log.e(TAG, "showAlertText: " + msg); + } + + public static Double round(Double value, int decPoints){ + if (value == null || value.isInfinite() || value.isNaN()) return 0D; + DecimalFormat df; + DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols(Locale.ENGLISH); + otherSymbols.setDecimalSeparator('.'); + otherSymbols.setGroupingSeparator(','); + + switch (decPoints){ + case 1: + //if (precisionRounding()){ + // df = new DecimalFormat("##0.00", otherSymbols); + //} else { + df = new DecimalFormat("##0.0", otherSymbols); + //} + break; + case 2: + df = new DecimalFormat("##0.00", otherSymbols); + break; + case 3: + df = new DecimalFormat("##0.000", otherSymbols); + break; + default: + df = new DecimalFormat("##0.0000", otherSymbols); + } + return Double.parseDouble(df.format(value)); + } + + + public static String formatDisplayInsulin(Double value, int decPoints){ + return round(value,decPoints) + "u"; + } + public static String formatDisplayBasal(Double value, Boolean doubleLine){ + if (doubleLine) { + return round(value, 2) + "\n" + "U/h"; + } else { + return round(value, 2) + "U/h"; + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/Config.java b/app/src/main/java/info/nightscout/androidaps/Config.java index 3c8d6095ed..13df038b21 100644 --- a/app/src/main/java/info/nightscout/androidaps/Config.java +++ b/app/src/main/java/info/nightscout/androidaps/Config.java @@ -14,6 +14,7 @@ public class Config { public static final boolean PUMPCONTROL = BuildConfig.PUMPCONTROL; public static final boolean DANAR = BuildConfig.PUMPDRIVERS; + public static final boolean MEDTRONIC = true && BuildConfig.PUMPDRIVERS; public static final boolean ACTION = !BuildConfig.NSCLIENTOLNY && !BuildConfig.G5UPLOADER; public static final boolean VIRTUALPUMP = !BuildConfig.NSCLIENTOLNY && !BuildConfig.G5UPLOADER; diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index a05f1c6d0c..303304f219 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -55,6 +55,7 @@ import info.nightscout.androidaps.plugins.PumpDanaRKorean.DanaRKoreanPlugin; import info.nightscout.androidaps.plugins.PumpDanaRS.DanaRSPlugin; import info.nightscout.androidaps.plugins.PumpDanaRv2.DanaRv2Plugin; import info.nightscout.androidaps.plugins.PumpMDI.MDIPlugin; +import info.nightscout.androidaps.plugins.PumpMedtronic.MedtronicPumpPlugin; import info.nightscout.androidaps.plugins.PumpVirtual.VirtualPumpPlugin; import info.nightscout.androidaps.plugins.SensitivityAAPS.SensitivityAAPSPlugin; import info.nightscout.androidaps.plugins.SensitivityOref0.SensitivityOref0Plugin; @@ -110,71 +111,80 @@ public class MainApp extends Application { registerLocalBroadcastReceiver(); - if (pluginsList == null) { - pluginsList = new ArrayList<>(); - // Register all tabs in app here - pluginsList.add(OverviewPlugin.getPlugin()); - pluginsList.add(IobCobCalculatorPlugin.getPlugin()); - if (Config.ACTION) pluginsList.add(ActionsFragment.getPlugin()); - pluginsList.add(InsulinFastactingPlugin.getPlugin()); - pluginsList.add(InsulinFastactingProlongedPlugin.getPlugin()); - pluginsList.add(InsulinOrefRapidActingPlugin.getPlugin()); - pluginsList.add(InsulinOrefUltraRapidActingPlugin.getPlugin()); - pluginsList.add(InsulinOrefFreePeakPlugin.getPlugin()); - pluginsList.add(SensitivityOref0Plugin.getPlugin()); - pluginsList.add(SensitivityAAPSPlugin.getPlugin()); - pluginsList.add(SensitivityWeightedAveragePlugin.getPlugin()); - if (Config.DANAR) pluginsList.add(DanaRPlugin.getPlugin()); - if (Config.DANAR) pluginsList.add(DanaRKoreanPlugin.getPlugin()); - if (Config.DANAR) pluginsList.add(DanaRv2Plugin.getPlugin()); - if (Config.DANAR) pluginsList.add(DanaRSPlugin.getPlugin()); - pluginsList.add(CareportalPlugin.getPlugin()); - if (Config.MDI) pluginsList.add(MDIPlugin.getPlugin()); - if (Config.VIRTUALPUMP) pluginsList.add(VirtualPumpPlugin.getPlugin()); - if (Config.APS) pluginsList.add(LoopPlugin.getPlugin()); - if (Config.APS) pluginsList.add(OpenAPSMAPlugin.getPlugin()); - if (Config.APS) pluginsList.add(OpenAPSAMAPlugin.getPlugin()); - pluginsList.add(NSProfilePlugin.getPlugin()); - if (Config.OTHERPROFILES) pluginsList.add(SimpleProfilePlugin.getPlugin()); - if (Config.OTHERPROFILES) pluginsList.add(LocalProfilePlugin.getPlugin()); - if (Config.OTHERPROFILES) - pluginsList.add(CircadianPercentageProfileFragment.getPlugin()); - pluginsList.add(TreatmentsPlugin.getPlugin()); - if (Config.SAFETY) pluginsList.add(SafetyPlugin.getPlugin()); - if (Config.APS) pluginsList.add(ObjectivesPlugin.getPlugin()); - if (!Config.NSCLIENT && !Config.G5UPLOADER) - pluginsList.add(SourceXdripPlugin.getPlugin()); - if (!Config.G5UPLOADER) - pluginsList.add(SourceNSClientPlugin.getPlugin()); - if (!Config.NSCLIENT && !Config.G5UPLOADER) - pluginsList.add(SourceMM640gPlugin.getPlugin()); - if (!Config.NSCLIENT && !Config.G5UPLOADER) - pluginsList.add(SourceGlimpPlugin.getPlugin()); - if (!Config.NSCLIENT) - pluginsList.add(SourceDexcomG5Plugin.getPlugin()); - if (Config.SMSCOMMUNICATORENABLED) pluginsList.add(SmsCommunicatorPlugin.getPlugin()); - pluginsList.add(FoodPlugin.getPlugin()); + try { - pluginsList.add(WearPlugin.initPlugin(this)); - pluginsList.add(StatuslinePlugin.initPlugin(this)); - pluginsList.add(new PersistentNotificationPlugin(this)); - pluginsList.add(NSClientInternalPlugin.getPlugin()); + if (pluginsList == null) { + pluginsList = new ArrayList<>(); + // Register all tabs in app here + pluginsList.add(OverviewPlugin.getPlugin()); + pluginsList.add(IobCobCalculatorPlugin.getPlugin()); + if (Config.ACTION) pluginsList.add(ActionsFragment.getPlugin()); + pluginsList.add(InsulinFastactingPlugin.getPlugin()); + pluginsList.add(InsulinFastactingProlongedPlugin.getPlugin()); + pluginsList.add(InsulinOrefRapidActingPlugin.getPlugin()); + pluginsList.add(InsulinOrefUltraRapidActingPlugin.getPlugin()); + pluginsList.add(InsulinOrefFreePeakPlugin.getPlugin()); + pluginsList.add(SensitivityOref0Plugin.getPlugin()); + pluginsList.add(SensitivityAAPSPlugin.getPlugin()); + pluginsList.add(SensitivityWeightedAveragePlugin.getPlugin()); + if (Config.DANAR) pluginsList.add(DanaRPlugin.getPlugin()); + if (Config.DANAR) pluginsList.add(DanaRKoreanPlugin.getPlugin()); + if (Config.DANAR) pluginsList.add(DanaRv2Plugin.getPlugin()); + if (Config.DANAR) pluginsList.add(DanaRSPlugin.getPlugin()); + if (Config.MEDTRONIC) pluginsList.add(MedtronicPumpPlugin.getPlugin()); + pluginsList.add(CareportalPlugin.getPlugin()); + if (Config.MDI) pluginsList.add(MDIPlugin.getPlugin()); + if (Config.VIRTUALPUMP) pluginsList.add(VirtualPumpPlugin.getPlugin()); + if (Config.APS) pluginsList.add(LoopPlugin.getPlugin()); + if (Config.APS) pluginsList.add(OpenAPSMAPlugin.getPlugin()); + if (Config.APS) pluginsList.add(OpenAPSAMAPlugin.getPlugin()); + pluginsList.add(NSProfilePlugin.getPlugin()); + if (Config.OTHERPROFILES) pluginsList.add(SimpleProfilePlugin.getPlugin()); + if (Config.OTHERPROFILES) pluginsList.add(LocalProfilePlugin.getPlugin()); + if (Config.OTHERPROFILES) + pluginsList.add(CircadianPercentageProfileFragment.getPlugin()); + pluginsList.add(TreatmentsPlugin.getPlugin()); + if (Config.SAFETY) pluginsList.add(SafetyPlugin.getPlugin()); + if (Config.APS) pluginsList.add(ObjectivesPlugin.getPlugin()); + if (!Config.NSCLIENT && !Config.G5UPLOADER) + pluginsList.add(SourceXdripPlugin.getPlugin()); + if (!Config.G5UPLOADER) + pluginsList.add(SourceNSClientPlugin.getPlugin()); + if (!Config.NSCLIENT && !Config.G5UPLOADER) + pluginsList.add(SourceMM640gPlugin.getPlugin()); + if (!Config.NSCLIENT && !Config.G5UPLOADER) + pluginsList.add(SourceGlimpPlugin.getPlugin()); + if (!Config.NSCLIENT) + pluginsList.add(SourceDexcomG5Plugin.getPlugin()); + if (Config.SMSCOMMUNICATORENABLED) + pluginsList.add(SmsCommunicatorPlugin.getPlugin()); + pluginsList.add(FoodPlugin.getPlugin()); - pluginsList.add(sConfigBuilder = ConfigBuilderFragment.getPlugin()); + pluginsList.add(WearPlugin.initPlugin(this)); + pluginsList.add(StatuslinePlugin.initPlugin(this)); + pluginsList.add(new PersistentNotificationPlugin(this)); + pluginsList.add(NSClientInternalPlugin.getPlugin()); - MainApp.getConfigBuilder().initialize(); + pluginsList.add(sConfigBuilder = ConfigBuilderFragment.getPlugin()); + + MainApp.getConfigBuilder().initialize(); + } + NSUpload.uploadAppStart(); + if (Config.NSCLIENT) + Answers.getInstance().logCustom(new CustomEvent("AppStart-NSClient")); + else if (Config.G5UPLOADER) + Answers.getInstance().logCustom(new CustomEvent("AppStart-G5Uploader")); + else if (Config.PUMPCONTROL) + Answers.getInstance().logCustom(new CustomEvent("AppStart-PumpControl")); + else if (MainApp.getConfigBuilder().isClosedModeEnabled()) + Answers.getInstance().logCustom(new CustomEvent("AppStart-ClosedLoop")); + else + Answers.getInstance().logCustom(new CustomEvent("AppStart-OpenLoop")); + } + catch(Exception ex) + { + log.error("Error loading plugins: " + ex.getMessage(), ex); } - NSUpload.uploadAppStart(); - if (Config.NSCLIENT) - Answers.getInstance().logCustom(new CustomEvent("AppStart-NSClient")); - else if (Config.G5UPLOADER) - Answers.getInstance().logCustom(new CustomEvent("AppStart-G5Uploader")); - else if (Config.PUMPCONTROL) - Answers.getInstance().logCustom(new CustomEvent("AppStart-PumpControl")); - else if (MainApp.getConfigBuilder().isClosedModeEnabled()) - Answers.getInstance().logCustom(new CustomEvent("AppStart-ClosedLoop")); - else - Answers.getInstance().logCustom(new CustomEvent("AppStart-OpenLoop")); new Thread(new Runnable() { @Override diff --git a/app/src/main/java/info/nightscout/androidaps/PreferencesActivity.java b/app/src/main/java/info/nightscout/androidaps/PreferencesActivity.java index 5a0d84d1f7..a13c298a2e 100644 --- a/app/src/main/java/info/nightscout/androidaps/PreferencesActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/PreferencesActivity.java @@ -27,6 +27,7 @@ import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin; import info.nightscout.androidaps.plugins.PumpDanaRKorean.DanaRKoreanPlugin; import info.nightscout.androidaps.plugins.PumpDanaRS.DanaRSPlugin; import info.nightscout.androidaps.plugins.PumpDanaRv2.DanaRv2Plugin; +import info.nightscout.androidaps.plugins.PumpMedtronic.MedtronicPumpPlugin; import info.nightscout.androidaps.plugins.PumpVirtual.VirtualPumpPlugin; import info.nightscout.androidaps.plugins.SensitivityAAPS.SensitivityAAPSPlugin; import info.nightscout.androidaps.plugins.SensitivityOref0.SensitivityOref0Plugin; @@ -169,6 +170,8 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre addPreferencesFromResourceIfEnabled(VirtualPumpPlugin.getPlugin(), PluginBase.PUMP); } + addPreferencesFromResourceIfEnabled(MedtronicPumpPlugin.getPlugin(), PluginBase.PUMP); + addPreferencesFromResourceIfEnabled(InsulinOrefFreePeakPlugin.getPlugin(), PluginBase.INSULIN); addPreferencesFromResourceIfEnabled(NSClientInternalPlugin.getPlugin(), PluginBase.GENERAL); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/PumpPluginAbstract.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/PumpPluginAbstract.java new file mode 100644 index 0000000000..2a66ece017 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/PumpPluginAbstract.java @@ -0,0 +1,249 @@ +package info.nightscout.androidaps.plugins.PumpCommon; + +import android.util.Log; + +import org.json.JSONObject; + +import java.util.Date; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PumpDescription; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.PumpCommon.data.PumpStatus; +import info.nightscout.androidaps.plugins.PumpCommon.driver.PumpDriverInterface; +import info.nightscout.androidaps.plugins.PumpMedtronic.MedtronicPumpPlugin; + +/** + * Created by andy on 23.04.18. + */ + +public abstract class PumpPluginAbstract implements PluginBase, PumpInterface { + + protected boolean fragmentVisible = false; + protected boolean fragmentEnabled = false; + protected boolean pumpServiceRunning = false; + private static final String TAG = "PumpPluginAbstract"; + //protected PumpStatus pumpStatus; + + + + protected static PumpPluginAbstract plugin = null; + protected PumpDriverInterface pumpDriver; + + + protected PumpPluginAbstract(PumpDriverInterface pumpDriverInterface) + { + this.pumpDriver = pumpDriverInterface; + } + + + @Override + public int getType() { + return PluginBase.PUMP; + } + + + @Override + public boolean isVisibleInTabs(int type) { + return type == PUMP && fragmentVisible; + } + + + @Override + public boolean canBeHidden(int type) { + return true; + } + + + @Override + public boolean hasFragment() { + return true; + } + + + @Override + public boolean showInList(int type) { + return type == PUMP; + } + + + @Override + public void setFragmentEnabled(int type, boolean fragmentEnabled) { + if (type == PUMP) { + this.fragmentEnabled = fragmentEnabled; + + if (fragmentEnabled) { + if (!pumpServiceRunning) + startPumpService(); + else + Log.d(TAG, "Can't start, Pump service (" + getInternalName() + "is already running."); + } + else { + if (pumpServiceRunning) + stopPumpService(); + else + Log.d(TAG, "Can't stop, Pump service (" + getInternalName() + "is already stopped."); + } + } + } + + + @Override + public void setFragmentVisible(int type, boolean fragmentVisible) { + if (type == PUMP) this.fragmentVisible = fragmentVisible; + } + + protected abstract String getInternalName(); + + protected abstract void startPumpService(); + + protected abstract void stopPumpService(); + + + public PumpStatus getPumpStatusData() + { + return pumpDriver.getPumpStatusData(); + } + + + public boolean isInitialized() + { + return pumpDriver.isInitialized(); + } + + public boolean isSuspended(){ + return pumpDriver.isSuspended(); + } + + public boolean isBusy(){ + return pumpDriver.isBusy(); + } + + + public boolean isConnected(){ + return pumpDriver.isConnected(); + } + + + public boolean isConnecting(){ + return pumpDriver.isConnecting(); + } + + + public void connect(String reason){ + pumpDriver.connect(reason); + } + + + public void disconnect(String reason){ + pumpDriver.disconnect(reason); + } + + + public void stopConnecting(){ + pumpDriver.stopConnecting(); + } + + + public void getPumpStatus(){ + pumpDriver.getPumpStatus(); + } + + + // Upload to pump new basal profile + public PumpEnactResult setNewBasalProfile(Profile profile){ + return pumpDriver.setNewBasalProfile(profile); + } + + + public boolean isThisProfileSet(Profile profile){ + return pumpDriver.isThisProfileSet(profile); + } + + + public Date lastDataTime(){ + return pumpDriver.lastDataTime(); + } + + + public double getBaseBasalRate(){ + return pumpDriver.getBaseBasalRate(); + } // base basal rate, not temp basal + + + + public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo){ + return pumpDriver.deliverTreatment(detailedBolusInfo); + } + + + public void stopBolusDelivering(){ + pumpDriver.stopBolusDelivering(); + } + + + public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew){ + return pumpDriver.setTempBasalAbsolute(absoluteRate, durationInMinutes, enforceNew); + } + + + public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, boolean enforceNew){ + return pumpDriver.setTempBasalPercent(percent, durationInMinutes, enforceNew); + } + + + public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes){ + return pumpDriver.setExtendedBolus(insulin, durationInMinutes); + } + //some pumps might set a very short temp close to 100% as cancelling a temp can be noisy + //when the cancel request is requested by the user (forced), the pump should always do a real cancel + + + public PumpEnactResult cancelTempBasal(boolean enforceNew){ + return pumpDriver.cancelTempBasal(enforceNew); + } + + + public PumpEnactResult cancelExtendedBolus(){ + return pumpDriver.cancelExtendedBolus(); + } + + // Status to be passed to NS + + + public JSONObject getJSONStatus(){ + return pumpDriver.getJSONStatus(); + } + + + public String deviceID(){ + return pumpDriver.deviceID(); + } + + // Pump capabilities + + + public PumpDescription getPumpDescription(){ + return pumpDriver.getPumpDescription(); + } + + // Short info for SMS, Wear etc + + + public String shortStatus(boolean veryShort){ + return pumpDriver.shortStatus(veryShort); + } + + + + public boolean isFakingTempsByExtendedBoluses(){ + return pumpDriver.isInitialized(); + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/data/PumpStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/data/PumpStatus.java new file mode 100644 index 0000000000..518948efea --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/data/PumpStatus.java @@ -0,0 +1,45 @@ +package info.nightscout.androidaps.plugins.PumpCommon.data; + +import java.util.Date; + +import info.nightscout.androidaps.interfaces.PumpDescription; + +/** + * Created by andy on 4/28/18. + */ + +public abstract class PumpStatus { + + public Date lastDataTime; + public long lastConnection = 0L; + public Date lastBolusTime; + public String activeProfile = "0"; + public double reservoirRemainingUnits = 0d; + public String reservoirFullUnits = "???"; + public double batteryRemaining = 0d; + public String iob = "0"; + protected PumpDescription pumpDescription; + + public PumpStatus(PumpDescription pumpDescription) + { + this.pumpDescription = pumpDescription; + + this.initSettings(); + } + + + public abstract void initSettings(); + + + public void setLastDataTimeToNow() { + this.lastDataTime = new Date(); + this.lastConnection = System.currentTimeMillis(); + } + + + public abstract String getErrorInfo(); + + + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/driver/PumpDriverAbstract.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/driver/PumpDriverAbstract.java new file mode 100644 index 0000000000..b730562b46 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/driver/PumpDriverAbstract.java @@ -0,0 +1,48 @@ +package info.nightscout.androidaps.plugins.PumpCommon.driver; + +import java.util.Date; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.interfaces.PumpDescription; +import info.nightscout.androidaps.plugins.PumpCommon.data.PumpStatus; + +/** + * Created by andy on 4/28/18. + */ + +public abstract class PumpDriverAbstract implements PumpDriverInterface { + + protected PumpDescription pumpDescription = new PumpDescription(); + protected PumpStatus pumpStatusData; + + protected static final PumpEnactResult OPERATION_NOT_SUPPORTED = new PumpEnactResult() + .success(false).enacted(false).comment(MainApp.gs(R.string.pump_operation_not_supported_by_pump)); + + protected static final PumpEnactResult OPERATION_NOT_YET_SUPPORTED = new PumpEnactResult() + .success(false).enacted(false).comment(MainApp.gs(R.string.pump_operation_not_yet_supported_by_pump)); + + + @Override + public String deviceID() { + return null; + } + + @Override + public PumpStatus getPumpStatusData() + { + return this.pumpStatusData; + } + + @Override + public PumpDescription getPumpDescription() { + return pumpDescription; + } + + + @Override + public Date lastDataTime() { + return this.pumpStatusData.lastDataTime; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/driver/PumpDriverInterface.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/driver/PumpDriverInterface.java new file mode 100644 index 0000000000..c10f76d1e7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/driver/PumpDriverInterface.java @@ -0,0 +1,15 @@ +package info.nightscout.androidaps.plugins.PumpCommon.driver; + +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.PumpCommon.data.PumpStatus; + +/** + * Created by andy on 4/28/18. + */ + +public interface PumpDriverInterface extends PumpInterface { + + + PumpStatus getPumpStatusData(); + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/hw/rileylink/RileyLinkLayer.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/hw/rileylink/RileyLinkLayer.java new file mode 100644 index 0000000000..7901d0fa03 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCommon/hw/rileylink/RileyLinkLayer.java @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.plugins.PumpCommon.hw.rileylink; + +/** + * Created by andy on 4/28/18. + */ + +public class RileyLinkLayer { + + // this is just placeholder, but it should be start of RileyLink protocol layer, which can be later used for Medtronic and Omnipod, or + // any other solution dependent on Radio Frequency communication (that can use RileyLinkLayer. +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/MedtronicFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/MedtronicFragment.java new file mode 100644 index 0000000000..2bc6c4eb0e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/MedtronicFragment.java @@ -0,0 +1,278 @@ +package info.nightscout.androidaps.plugins.PumpMedtronic; + + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.support.v4.app.FragmentManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.crashlytics.android.Crashlytics; +import com.squareup.otto.Subscribe; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.events.EventExtendedBolusChange; +import info.nightscout.androidaps.events.EventPumpStatusChanged; +import info.nightscout.androidaps.events.EventTempBasalChange; +import info.nightscout.androidaps.plugins.Common.SubscriberFragment; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.PumpCommon.data.PumpStatus; +import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; +import info.nightscout.androidaps.plugins.PumpDanaR.Dialogs.ProfileViewDialog; +import info.nightscout.androidaps.plugins.PumpDanaR.activities.DanaRHistoryActivity; +import info.nightscout.androidaps.plugins.PumpDanaR.activities.DanaRStatsActivity; +import info.nightscout.androidaps.plugins.PumpDanaR.events.EventDanaRNewStatus; +import info.nightscout.androidaps.plugins.PumpMedtronic.medtronic.MedtronicPumpStatus; +import info.nightscout.androidaps.queue.events.EventQueueChanged; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.SetWarnColor; + +public class MedtronicFragment extends SubscriberFragment { + private static Logger log = LoggerFactory.getLogger(MedtronicFragment.class); + + private Handler loopHandler = new Handler(); + private Runnable refreshLoop = new Runnable() { + @Override + public void run() { + updateGUI(); + loopHandler.postDelayed(refreshLoop, 60 * 1000L); + } + }; + + @BindView(R.id.medtronic_lastconnection) + TextView lastConnectionView; + + @BindView(R.id.medtronic_btconnection) + TextView btConnectionView; + + @BindView(R.id.medtronic_lastbolus) + TextView lastBolusView; + + @BindView(R.id.medtronic_basabasalrate) + TextView basaBasalRateView; + + @BindView(R.id.medtronic_tempbasal) + TextView tempBasalView; + + @BindView(R.id.medtronic_battery) + TextView batteryView; + + @BindView(R.id.medtronic_reservoir) + TextView reservoirView; + + @BindView(R.id.medtronic_iob) + TextView iobView; + + @BindView(R.id.medtronic_errors) + TextView errorsView; + + + + @BindView(R.id.medtronic_queue) + TextView queueView; + + @BindView(R.id.overview_pumpstatuslayout) + LinearLayout pumpStatusLayout; + + @BindView(R.id.overview_pumpstatus) TextView pumpStatusView; + + public MedtronicFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + loopHandler.postDelayed(refreshLoop, 60 * 1000L); + } + + @Override + public void onDestroy() { + super.onDestroy(); + loopHandler.removeCallbacks(refreshLoop); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + try { + View view = inflater.inflate(R.layout.medtronic_fragment, container, false); + unbinder = ButterKnife.bind(this, view); + + pumpStatusView.setBackgroundColor(MainApp.sResources.getColor(R.color.colorInitializingBorder)); + + return view; + } catch (Exception e) { + Crashlytics.logException(e); + } + + return null; + } + + @OnClick(R.id.medtronic_history) + void onHistoryClick() { + startActivity(new Intent(getContext(), DanaRHistoryActivity.class)); + } + + @OnClick(R.id.medtronic_viewprofile) + void onViewProfileClick() { + FragmentManager manager = getFragmentManager(); + ProfileViewDialog profileViewDialog = new ProfileViewDialog(); + profileViewDialog.show(manager, "ProfileViewDialog"); + } + + @OnClick(R.id.medtronic_stats) + void onStatsClick() { + startActivity(new Intent(getContext(), DanaRStatsActivity.class)); + } + + @OnClick(R.id.medtronic_btconnection) + void onBtConnectionClick() { + log.debug("Clicked connect to pump"); + DanaRPump.getInstance().lastConnection = 0; + ConfigBuilderPlugin.getCommandQueue().readStatus("Clicked connect to pump", null); + } + + @Subscribe + public void onStatusEvent(final EventPumpStatusChanged c) { + Activity activity = getActivity(); + final String status = c.textStatus(); + if (activity != null) { + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + if (c.sStatus == EventPumpStatusChanged.CONNECTING) + btConnectionView.setText("{fa-bluetooth-b spin} " + c.sSecondsElapsed + "s"); + else if (c.sStatus == EventPumpStatusChanged.CONNECTED) + btConnectionView.setText("{fa-bluetooth}"); + else if (c.sStatus == EventPumpStatusChanged.DISCONNECTED) + btConnectionView.setText("{fa-bluetooth-b}"); + + if (!status.equals("")) { + pumpStatusView.setText(status); + pumpStatusLayout.setVisibility(View.VISIBLE); + } else { + pumpStatusLayout.setVisibility(View.GONE); + } + } + } + ); + } + } + + @Subscribe + public void onStatusEvent(final EventDanaRNewStatus s) { + updateGUI(); + } + + @Subscribe + public void onStatusEvent(final EventTempBasalChange s) { + updateGUI(); + } + + @Subscribe + public void onStatusEvent(final EventExtendedBolusChange s) { + updateGUI(); + } + + @Subscribe + public void onStatusEvent(final EventQueueChanged s) { + updateGUI(); + } + + // GUI functions + @Override + protected void updateGUI() { + Activity activity = getActivity(); + if (activity != null && basaBasalRateView != null) + activity.runOnUiThread(new Runnable() { + @SuppressLint("SetTextI18n") + @Override + public void run() { + + MedtronicPumpPlugin plugin = (MedtronicPumpPlugin)MedtronicPumpPlugin.getPlugin(); + PumpStatus pump = plugin.getPumpStatusData(); + + if (pump.lastConnection != 0) { + Long agoMsec = System.currentTimeMillis() - pump.lastConnection; + int agoMin = (int) (agoMsec / 60d / 1000d); + lastConnectionView.setText(DateUtil.timeString(pump.lastConnection) + " (" + String.format(MainApp.sResources.getString(R.string.minago), agoMin) + ")"); + SetWarnColor.setColor(lastConnectionView, agoMin, 16d, 31d); + } + if (pump.lastBolusTime!=null && pump.lastBolusTime.getTime() != 0) { + Long agoMsec = System.currentTimeMillis() - pump.lastBolusTime.getTime(); + double agoHours = agoMsec / 60d / 60d / 1000d; + if (agoHours < 6) // max 6h back + lastBolusView.setText(DateUtil.timeString(pump.lastBolusTime) + " " + DateUtil.sinceString(pump.lastBolusTime.getTime()) + " " + DecimalFormatter.to2Decimal(DanaRPump.getInstance().lastBolusAmount) + " U"); + else + lastBolusView.setText(""); + } + + //dailyUnitsView.setText(DecimalFormatter.to0Decimal(pump.dailyTotalUnits) + " / " + pump.maxDailyTotalUnits + " U"); + //SetWarnColor.setColor(dailyUnitsView, pump.dailyTotalUnits, pump.maxDailyTotalUnits * 0.75d, pump.maxDailyTotalUnits * 0.9d); + basaBasalRateView.setText("(" + (pump.activeProfile) + ") " + DecimalFormatter.to2Decimal(ConfigBuilderPlugin.getActivePump().getBaseBasalRate()) + " U/h"); + + if (ConfigBuilderPlugin.getActivePump().isFakingTempsByExtendedBoluses()) { + if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { + tempBasalView.setText(MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()).toStringFull()); + } else { + tempBasalView.setText(""); + } + } else { + // v2 plugin + if (MainApp.getConfigBuilder().isTempBasalInProgress()) { + tempBasalView.setText(MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()).toStringFull()); + } else { + tempBasalView.setText(""); + } + } + + reservoirView.setText(DecimalFormatter.to0Decimal(pump.reservoirRemainingUnits) + " / " + pump.reservoirFullUnits + " U"); + SetWarnColor.setColorInverse(reservoirView, pump.reservoirRemainingUnits, 50d, 20d); + batteryView.setText("{fa-battery-" + (pump.batteryRemaining / 25) + "}"); + SetWarnColor.setColorInverse(batteryView, pump.batteryRemaining, 51d, 26d); + iobView.setText(pump.iob + " U"); + + //if (pump.isNewPump) { + // firmwareView.setText(String.format(MainApp.sResources.getString(R.string.danar_model), pump.model, pump.protocol, pump.productCode)); + //} else { + // firmwareView.setText("OLD"); + //} + + + if (queueView != null) { + // FIXME + queueView.setVisibility(View.GONE); + +// Spanned status = ConfigBuilderPlugin.getCommandQueue().spannedStatus(); +// if (status.toString().equals("")) { +// queueView.setVisibility(View.GONE); +// } else { +// queueView.setVisibility(View.VISIBLE); +// queueView.setText(status); +// } + } + + + errorsView.setText(pump.getErrorInfo()); + + + } + }); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/MedtronicPumpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/MedtronicPumpPlugin.java index 9302caff25..9236a3a1d6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/MedtronicPumpPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/MedtronicPumpPlugin.java @@ -1,8 +1,180 @@ package info.nightscout.androidaps.plugins.PumpMedtronic; +import com.gxwtech.roundtrip2.ServiceClientConnection; + +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; + +import info.nightscout.androidaps.BuildConfig; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.db.ExtendedBolus; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.PumpCommon.PumpPluginAbstract; +import info.nightscout.androidaps.plugins.PumpMedtronic.medtronic.MedtronicPumpDriver; +import info.nightscout.androidaps.plugins.PumpVirtual.VirtualPumpDriver; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.SP; + /** * Created by andy on 23.04.18. */ -public class MedtronicPumpPlugin { +public class MedtronicPumpPlugin extends PumpPluginAbstract implements PumpInterface { + + //private static final String TAG = "MedtronicPumpPlugin"; + private static Logger LOG = LoggerFactory.getLogger(MedtronicPumpPlugin.class); + + //private static final PumpEnactResult OPERATION_NOT_SUPPORTED = new PumpEnactResult() + // .success(false).enacted(false).comment(MainApp.gs(R.string.pump_operation_not_supported_by_pump)); + + private ServiceClientConnection serviceClientConnection; + + + //private static MedtronicPumpPlugin plugin = null; + //private MedtronicPumpStatus medtronicPumpStatus = MedtronicPumpStatus.getInstance(); + + + public static PumpPluginAbstract getPlugin() { + + if (plugin == null) + plugin = new MedtronicPumpPlugin(); + return plugin; + } + + // + // private Date lastDataTime; + + + + public MedtronicPumpPlugin() + { + super(new MedtronicPumpDriver()); + } + + + + + + @Override + protected String getInternalName() { + return "MedtronicPump"; + } + + @Override + protected void startPumpService() { + + //serviceClientConnection = new ServiceClientConnection(); + } + + @Override + protected void stopPumpService() { + + } + + + + + + + + + @Override + public JSONObject getJSONStatus() { + //if (!SP.getBoolean("virtualpump_uploadstatus", false)) { + // return null; + //} + JSONObject pump = new JSONObject(); + JSONObject battery = new JSONObject(); + JSONObject status = new JSONObject(); + JSONObject extended = new JSONObject(); + try { + battery.put("percent", 90); + status.put("status", "normal"); + extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); + try { + extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName()); + } catch (Exception e) { + } + TemporaryBasal tb = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + if (tb != null) { + extended.put("TempBasalAbsoluteRate", tb.tempBasalConvertedToAbsolute(System.currentTimeMillis())); + extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date)); + extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes()); + } + ExtendedBolus eb = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); + if (eb != null) { + extended.put("ExtendedBolusAbsoluteRate", eb.absoluteRate()); + extended.put("ExtendedBolusStart", DateUtil.dateAndTimeString(eb.date)); + extended.put("ExtendedBolusRemaining", eb.getPlannedRemainingMinutes()); + } + status.put("timestamp", DateUtil.toISOString(new Date())); + + pump.put("battery", battery); + pump.put("status", status); + pump.put("extended", extended); + pump.put("reservoir", 66); + pump.put("clock", DateUtil.toISOString(new Date())); + } catch (JSONException e) { + LOG.error("Unhandled exception", e); + } + return pump; + } + + @Override + public String deviceID() { + return "Medtronic"; + } + + + + @Override + public String shortStatus(boolean veryShort) { + return "Medtronic Pump"; + } + + @Override + public boolean isFakingTempsByExtendedBoluses() { + return false; + } + + + @Override + public String getFragmentClass() + { + return MedtronicFragment.class.getName(); + } + + @Override + public String getName() { + return "Medtronic"; + } + + @Override + public String getNameShort() { + return "MEDT"; + } + + @Override + public boolean isEnabled(int type) { + // TODO might need tweaking + if (type == PluginBase.PUMP) + return fragmentEnabled; + else if (type == PluginBase.CONSTRAINTS) + return fragmentEnabled; + return false; + } + + + @Override + public int getPreferencesId() { + return R.xml.pref_medtronic; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/medtronic/MedtronicPumpDriver.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/medtronic/MedtronicPumpDriver.java new file mode 100644 index 0000000000..840dc1f03b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/medtronic/MedtronicPumpDriver.java @@ -0,0 +1,47 @@ +package info.nightscout.androidaps.plugins.PumpMedtronic.medtronic; + +import info.nightscout.androidaps.interfaces.PumpDescription; +import info.nightscout.androidaps.plugins.PumpVirtual.VirtualPumpDriver; + +/** + * Created by andy on 4/28/18. + */ + +public class MedtronicPumpDriver extends VirtualPumpDriver /*implements PumpInterface*/ { + + + public MedtronicPumpDriver() + { + // bolus + pumpDescription.isBolusCapable = true; + pumpDescription.bolusStep = 0.1d; // this needs to be reconfigurable + + // TBR + pumpDescription.isTempBasalCapable = true; + pumpDescription.tempBasalStyle = PumpDescription.ABSOLUTE; + pumpDescription.maxTempAbsolute = 35.0d; + //pumpDescription.maxTempPercent = 200; + //pumpDescription.tempPercentStep = 1; + pumpDescription.tempDurationStep = 30; + pumpDescription.tempMaxDuration = 24 * 60; + + // extended bolus + pumpDescription.isExtendedBolusCapable = false; + pumpDescription.extendedBolusStep = 0.1d; // 0 - 25 + pumpDescription.extendedBolusDurationStep = 30; + pumpDescription.extendedBolusMaxDuration = 8 * 60; + + // set basal profile + pumpDescription.isSetBasalProfileCapable = true; + pumpDescription.basalStep = 0.05d; + pumpDescription.basalMinimumRate = 0.05d; + + // ? + pumpDescription.isRefillingCapable = false; + + // ? + pumpDescription.storesCarbInfo = false; + + this.pumpStatusData = new MedtronicPumpStatus(pumpDescription); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/medtronic/MedtronicPumpStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/medtronic/MedtronicPumpStatus.java new file mode 100644 index 0000000000..7955531e59 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/medtronic/MedtronicPumpStatus.java @@ -0,0 +1,156 @@ +package info.nightscout.androidaps.plugins.PumpMedtronic.medtronic; + +import android.service.autofill.RegexValidator; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.PumpDescription; +import info.nightscout.androidaps.plugins.PumpCommon.data.PumpStatus; +import info.nightscout.androidaps.plugins.PumpMedtronic.medtronic.defs.MedtronicPumpType; +import info.nightscout.utils.SP; + +/** + * Created by andy on 4/28/18. + */ + +public class MedtronicPumpStatus extends PumpStatus { + + + //private static MedtronicPumpStatus medtronicPumpStatus = new MedtronicPumpStatus(); + private static Logger LOG = LoggerFactory.getLogger(MedtronicPumpStatus.class); + + //public Date lastDataTime; + //public long lastConnection = 0L; + //public Date lastBolusTime; + //public String activeProfile = "A"; + //public double reservoirRemainingUnits = 50d; + //public double batteryRemaining = 75d; + //public String iob = "0"; + + public String errorDescription = null; + public String serialNumber; + public MedtronicPumpType pumpType = null; + public String pumpFrequency = null; + public String rileyLinkAddress = null; + + String regexMac = "([\\da-fA-F]{1,2}(?:\\:|$)){6}"; + String regexSN = "[0-9]{6}"; + + + + public MedtronicPumpStatus(PumpDescription pumpDescription) + { + super(pumpDescription); + } + + + @Override + public void initSettings() { + this.activeProfile = "A"; + this.reservoirRemainingUnits = 75d; + this.batteryRemaining = 75d; + } + + +// +// //public static MedtronicPumpStatus getInstance() +// { +// return medtronicPumpStatus; +// } + + + + public void verifyConfiguration() + { + try { + this.errorDescription = null; + + String serialNr = SP.getString("pref_medtronic_serial", null); + + if (serialNr == null) { + this.errorDescription = MainApp.sResources.getString(R.string.medtronic_error_serial_not_set); + return; + } else { + if (!serialNr.matches(regexSN)) { + this.errorDescription = MainApp.sResources.getString(R.string.medtronic_error_serial_invalid); + return; + } else { + serialNumber = serialNr; + } + } + + String pumpType = SP.getString("pref_medtronic_pump_type", null); + + if (pumpType == null) { + this.errorDescription = MainApp.sResources.getString(R.string.medtronic_error_pump_type_not_set); + return; + } else { + String pumpTypePart = pumpType.substring(0, 3); + + if (!pumpTypePart.matches("[0-9]{3}")) { + this.errorDescription = MainApp.sResources.getString(R.string.medtronic_error_pump_type_invalid); + return; + } else { + this.pumpType = MedtronicPumpType.getByCode(pumpTypePart); + setDescriptionFromPumpType(); + } + } + + + String pumpFrequency = SP.getString("pref_medtronic_frequency", null); + + if (pumpFrequency == null) { + this.errorDescription = MainApp.sResources.getString(R.string.medtronic_error_pump_frequency_not_set); + return; + } else { + if (!pumpFrequency.equals("US") && !pumpFrequency.equals("EU")) { + this.errorDescription = MainApp.sResources.getString(R.string.medtronic_error_pump_frequency_invalid); + return; + } else { + this.pumpFrequency = pumpFrequency; + } + } + + + String rileyLinkAddress = SP.getString("pref_medtronic_rileylink_mac", null); + + if (rileyLinkAddress == null) { + this.errorDescription = MainApp.sResources.getString(R.string.medtronic_error_rileylink_address_invalid); + return; + } else { + if (!rileyLinkAddress.matches(regexMac)) { + this.errorDescription = MainApp.sResources.getString(R.string.medtronic_error_rileylink_address_invalid); + } else { + this.rileyLinkAddress = rileyLinkAddress; + } + } + } + catch(Exception ex) + { + this.errorDescription = ex.getMessage(); + LOG.error("Error on Verification: " + ex.getMessage(), ex); + } + } + + private void setDescriptionFromPumpType() { + if (this.pumpType==MedtronicPumpType.Unknown) + return; + + this.reservoirFullUnits = "" + this.pumpType.getReservoir(); + } + + + public String getErrorInfo() + { + verifyConfiguration(); + + return (this.errorDescription==null) ? "-" : this.errorDescription; + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/medtronic/defs/MedtronicPumpType.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/medtronic/defs/MedtronicPumpType.java new file mode 100644 index 0000000000..49e3f6dfa7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMedtronic/medtronic/defs/MedtronicPumpType.java @@ -0,0 +1,144 @@ +package info.nightscout.androidaps.plugins.PumpMedtronic.medtronic.defs; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by andy on 4/28/18. + */ + +public enum MedtronicPumpType { + + Unknown("xxx", 0.1f, 0.1f, 200, 35.0d, 0), + Medtronic_512("512", 0.05f, 0.1f, 200, 35.0d, 180), + Medtronic_712("712", 0.05f, 0.1f, 200, 35.0d, 300), + + Medtronic_515("515", 0.05f, 0.1f, 200, 35.0d, 180), + Medtronic_715("715", 0.05f, 0.1f, 200, 35.0d, 300), + Medtronic_522("522", 0.05f, 0.1f, 200, 35.0d, 180), + Medtronic_722("722", 0.05f, 0.1f, 200, 35.0d, 300), + + Medtronic_523("523", 0.1f, 0.1f, 200, 35.0d, 180), + Medtronic_723("723", 0.1f, 0.1f, 200, 35.0d, 300), + Medtronic_554("554", 0.1f, 0.1f, 200, 35.0d, 180), + Medtronic_754("754", 0.1f, 0.1f, 200, 35.0d, 300), + ; + + //0.025 units (for rates between 0.025-0.975 u/h) + //0.05 units (for rates between 1-9.95 u/h) + //0.1 units (for rates of 10 u/h or more); (or 1%) + + +// +// +// Minimed_511(10003, "Minimed 511", "mm_515_715.jpg", "INSTRUCTIONS_MINIMED", MinimedDeviceType.Minimed_511, +// DeviceImplementationStatus.Planned, DeviceCompanyDefinition.Minimed, DeviceHandlerType.MinimedPumpHandler, +// DevicePortParameterType.PackedParameters, DeviceConnectionProtocol.Serial_USBBridge, +// DeviceProgressStatus.Special, "", 0.1f, 0.1f, null, -1, 0, PumpProfileDefinition.MinimedProfile), // TODO +// +// Minimed_512_712(10004, "Minimed 512/712", "mm_515_715.jpg", "INSTRUCTIONS_MINIMED", +// MinimedDeviceType.Minimed_512_712, DeviceImplementationStatus.Planned, DeviceCompanyDefinition.Minimed, +// DeviceHandlerType.MinimedPumpHandler, DevicePortParameterType.PackedParameters, +// DeviceConnectionProtocol.USB_Hid, DeviceProgressStatus.Special, "", 0.1f, 0.1f, null, -1, 0, +// PumpProfileDefinition.MinimedProfile), // TODO +// +// Minimed_515_715(10005, "Minimed 515/715", "mm_515_715.jpg", "INSTRUCTIONS_MINIMED", +// MinimedDeviceType.Minimed_515_715, DeviceImplementationStatus.Planned, DeviceCompanyDefinition.Minimed, +// DeviceHandlerType.MinimedPumpHandler, DevicePortParameterType.PackedParameters, +// DeviceConnectionProtocol.Serial_USBBridge, DeviceProgressStatus.Special, "", 0.1f, 0.1f, null, -1, 0, +// PumpProfileDefinition.MinimedProfile), // TODO +// +// Minimed_522_722(10006, "Minimed 522/722", "mm_522_722.jpg", "INSTRUCTIONS_MINIMED", +// MinimedDeviceType.Minimed_522_722, DeviceImplementationStatus.Planned, DeviceCompanyDefinition.Minimed, +// DeviceHandlerType.MinimedPumpHandler, DevicePortParameterType.PackedParameters, +// DeviceConnectionProtocol.Serial_USBBridge, DeviceProgressStatus.Special, "", 0.1f, 0.1f, null, -1, 0, +// PumpProfileDefinition.MinimedProfile), // TODO +// +// Minimed_523_723(10007, "Minimed 523/723", "mm_522_722.jpg", "INSTRUCTIONS_MINIMED", +// MinimedDeviceType.Minimed_523_723, DeviceImplementationStatus.Planned, DeviceCompanyDefinition.Minimed, +// DeviceHandlerType.MinimedPumpHandler, DevicePortParameterType.PackedParameters, +// DeviceConnectionProtocol.Serial_USBBridge, DeviceProgressStatus.Special, "", 0.1f, 0.1f, null, -1, 0, +// PumpProfileDefinition.MinimedProfile), // TODO +// +// Minimed_553_753_Revel(10008, "Minimed 553/753 (Revel)", "mm_554_veo.jpg", "INSTRUCTIONS_MINIMED", +// MinimedDeviceType.Minimed_553_753_Revel, DeviceImplementationStatus.Planned, +// DeviceCompanyDefinition.Minimed, DeviceHandlerType.MinimedPumpHandler, +// DevicePortParameterType.PackedParameters, DeviceConnectionProtocol.Serial_USBBridge, +// DeviceProgressStatus.Special, "", 0.1f, 0.1f, null, -1, 0, PumpProfileDefinition.MinimedProfile), // TODO +// +// Minimed_554_754_Veo(10009, "Minimed 554/754 (Veo)", "mm_554_veo.jpg", "INSTRUCTIONS_MINIMED", +// MinimedDeviceType.Minimed_554_754_Veo, DeviceImplementationStatus.Planned, DeviceCompanyDefinition.Minimed, +// DeviceHandlerType.MinimedPumpHandler, DevicePortParameterType.PackedParameters, +// DeviceConnectionProtocol.Serial_USBBridge, DeviceProgressStatus.Special, "", 0.1f, 0.1f, null, -1, 0, +// PumpProfileDefinition.MinimedProfile), // TODO +// + + + + + + private static Map mapByCode; + + private String code; + private int reservoir; + private double basalStep; + private double bolusStep; + private int maxTbrPercent; + private double maxTbrUnit; + + static + { + mapByCode = new HashMap<>(); + + for (MedtronicPumpType medtronicPumpType : values()) { + mapByCode.put(medtronicPumpType.getCode(), medtronicPumpType); + } + } + + MedtronicPumpType(String code, double basalStep, double bolusStep, int maxTbrPercent, double maxTbrUnit, int reservoir) + { + this.code = code; + this.reservoir = reservoir; + this.basalStep = basalStep; + this.bolusStep = bolusStep; + this.maxTbrPercent = maxTbrPercent; + this.maxTbrUnit = maxTbrUnit; + } + + + public static MedtronicPumpType getByCode(String code) { + + if (mapByCode.containsKey(code)) + { + return mapByCode.get(code); + } + else + { + return MedtronicPumpType.Unknown; + } + } + + public String getCode() { + return code; + } + + public int getReservoir() { + return reservoir; + } + + public double getBasalStep() { + return basalStep; + } + + public double getBolusStep() { + return bolusStep; + } + + public int getMaxTbrPercent() { + return maxTbrPercent; + } + + public double getMaxTbrUnit() { + return maxTbrUnit; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpPluginAbstract.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpPluginAbstract.java deleted file mode 100644 index ddce470f65..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpPluginAbstract.java +++ /dev/null @@ -1,8 +0,0 @@ -package info.nightscout.androidaps.plugins; - -/** - * Created by andy on 23.04.18. - */ - -public class PumpPluginAbstract { -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpRileyLink/RileyLinkPumpAbstract.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpRileyLink/RileyLinkPumpAbstract.java deleted file mode 100644 index 22d2321f81..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpRileyLink/RileyLinkPumpAbstract.java +++ /dev/null @@ -1,8 +0,0 @@ -package info.nightscout.androidaps.plugins.PumpRileyLink; - -/** - * Created by andy on 23.04.18. - */ - -public class RileyLinkPumpAbstract { -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpDriver.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpDriver.java new file mode 100644 index 0000000000..e3ff6fc52f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpDriver.java @@ -0,0 +1,394 @@ +package info.nightscout.androidaps.plugins.PumpVirtual; + +import android.os.SystemClock; + +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; + +import info.nightscout.androidaps.BuildConfig; +import info.nightscout.androidaps.Config; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.db.ExtendedBolus; +import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.interfaces.PumpDescription; +import info.nightscout.androidaps.interfaces.TreatmentsInterface; +import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; +import info.nightscout.androidaps.plugins.PumpCommon.driver.PumpDriverAbstract; +import info.nightscout.androidaps.plugins.PumpCommon.driver.PumpDriverInterface; +import info.nightscout.androidaps.plugins.PumpVirtual.events.EventVirtualPumpUpdateGui; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.NSUpload; +import info.nightscout.utils.SP; + +/** + * Created by andy on 4/28/18. + */ + +public class VirtualPumpDriver extends PumpDriverAbstract { + + private static final Logger LOG = LoggerFactory.getLogger(VirtualPumpDriver.class); + + public static boolean fromNSAreCommingFakedExtendedBoluses = false; + + + public VirtualPumpDriver() + { + setFakingStatus(true); + pumpDescription.isBolusCapable = true; + pumpDescription.bolusStep = 0.1d; + + pumpDescription.isExtendedBolusCapable = true; + pumpDescription.extendedBolusStep = 0.05d; + pumpDescription.extendedBolusDurationStep = 30; + pumpDescription.extendedBolusMaxDuration = 8 * 60; + + pumpDescription.isTempBasalCapable = true; + pumpDescription.tempBasalStyle = PumpDescription.PERCENT | PumpDescription.ABSOLUTE; + + pumpDescription.maxTempPercent = 500; + pumpDescription.tempPercentStep = 10; + + pumpDescription.tempDurationStep = 30; + pumpDescription.tempMaxDuration = 24 * 60; + + + pumpDescription.isSetBasalProfileCapable = true; + pumpDescription.basalStep = 0.01d; + pumpDescription.basalMinimumRate = 0.01d; + + pumpDescription.isRefillingCapable = false; + + pumpStatusData = new VirtualPumpStatus(pumpDescription); + } + + + + private static void loadFakingStatus() { + fromNSAreCommingFakedExtendedBoluses = SP.getBoolean("fromNSAreCommingFakedExtendedBoluses", false); + } + + + public static void setFakingStatus(boolean newStatus) { + fromNSAreCommingFakedExtendedBoluses = newStatus; + SP.putBoolean("fromNSAreCommingFakedExtendedBoluses", fromNSAreCommingFakedExtendedBoluses); + } + + + public static boolean getFakingStatus() { + return fromNSAreCommingFakedExtendedBoluses; + } + + + + + + @Override + public String deviceID() { + return null; + } + + + @Override + public String shortStatus(boolean veryShort) { + return null; + } + + + @Override + public boolean isFakingTempsByExtendedBoluses() { + return (Config.NSCLIENT || Config.G5UPLOADER) && fromNSAreCommingFakedExtendedBoluses; + } + + @Override + public boolean isInitialized() { + return true; + } + + @Override + public boolean isSuspended() { + return false; + } + + @Override + public boolean isBusy() { + return false; + } + + @Override + public boolean isConnected() { + return true; + } + + @Override + public boolean isConnecting() { + return false; + } + + @Override + public void connect(String reason) { + if (!Config.NSCLIENT && !Config.G5UPLOADER) + NSUpload.uploadDeviceStatus(); + pumpStatusData.setLastDataTimeToNow(); + } + + @Override + public void disconnect(String reason) { + } + + @Override + public void stopConnecting() { + } + + @Override + public void getPumpStatus() { + pumpStatusData.setLastDataTimeToNow(); + } + + @Override + public PumpEnactResult setNewBasalProfile(Profile profile) { + pumpStatusData.setLastDataTimeToNow(); + // Do nothing here. we are using MainApp.getConfigBuilder().getActiveProfile().getProfile(); + PumpEnactResult result = new PumpEnactResult(); + result.success = true; + Notification notification = new Notification(Notification.PROFILE_SET_OK, MainApp.sResources.getString(R.string.profile_set_ok), Notification.INFO, 60); + MainApp.bus().post(new EventNewNotification(notification)); + return result; + } + + @Override + public boolean isThisProfileSet(Profile profile) { + return true; + } + + @Override + public Date lastDataTime() { + return pumpStatusData.lastDataTime; + } + + @Override + public double getBaseBasalRate() { + Profile profile = MainApp.getConfigBuilder().getProfile(); + if (profile != null) + return profile.getBasal() != null ? profile.getBasal() : 0d; + else + return 0d; + } + + @Override + public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { + PumpEnactResult result = new PumpEnactResult(); + result.success = true; + result.bolusDelivered = detailedBolusInfo.insulin; + result.carbsDelivered = detailedBolusInfo.carbs; + result.enacted = result.bolusDelivered > 0 || result.carbsDelivered > 0; + result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + + Double delivering = 0d; + + while (delivering < detailedBolusInfo.insulin) { + SystemClock.sleep(200); + EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); + bolusingEvent.status = String.format(MainApp.sResources.getString(R.string.bolusdelivering), delivering); + bolusingEvent.percent = Math.min((int) (delivering / detailedBolusInfo.insulin * 100), 100); + MainApp.bus().post(bolusingEvent); + delivering += 0.1d; + } + SystemClock.sleep(200); + EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); + bolusingEvent.status = String.format(MainApp.sResources.getString(R.string.bolusdelivered), detailedBolusInfo.insulin); + bolusingEvent.percent = 100; + MainApp.bus().post(bolusingEvent); + SystemClock.sleep(1000); + if (Config.logPumpComm) + LOG.debug("Delivering treatment insulin: " + detailedBolusInfo.insulin + "U carbs: " + detailedBolusInfo.carbs + "g " + result); + MainApp.bus().post(new EventVirtualPumpUpdateGui()); + pumpStatusData.setLastDataTimeToNow(); + MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + return result; + } + + @Override + public void stopBolusDelivering() { + + } + + @Override + public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew) { + TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); + TemporaryBasal tempBasal = new TemporaryBasal(); + tempBasal.date = System.currentTimeMillis(); + tempBasal.isAbsolute = true; + tempBasal.absoluteRate = absoluteRate; + tempBasal.durationInMinutes = durationInMinutes; + tempBasal.source = Source.USER; + PumpEnactResult result = new PumpEnactResult(); + result.success = true; + result.enacted = true; + result.isTempCancel = false; + result.absolute = absoluteRate; + result.duration = durationInMinutes; + result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + treatmentsInterface.addToHistoryTempBasal(tempBasal); + if (Config.logPumpComm) + LOG.debug("Setting temp basal absolute: " + result); + MainApp.bus().post(new EventVirtualPumpUpdateGui()); + pumpStatusData.setLastDataTimeToNow(); + return result; + } + + @Override + public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, boolean enforceNew) { + TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); + PumpEnactResult result = new PumpEnactResult(); + if (MainApp.getConfigBuilder().isTempBasalInProgress()) { + result = cancelTempBasal(false); + if (!result.success) + return result; + } + TemporaryBasal tempBasal = new TemporaryBasal(); + tempBasal.date = System.currentTimeMillis(); + tempBasal.isAbsolute = false; + tempBasal.percentRate = percent; + tempBasal.durationInMinutes = durationInMinutes; + tempBasal.source = Source.USER; + result.success = true; + result.enacted = true; + result.percent = percent; + result.isPercent = true; + result.isTempCancel = false; + result.duration = durationInMinutes; + result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + treatmentsInterface.addToHistoryTempBasal(tempBasal); + if (Config.logPumpComm) + LOG.debug("Settings temp basal percent: " + result); + MainApp.bus().post(new EventVirtualPumpUpdateGui()); + pumpStatusData.setLastDataTimeToNow(); + + return result; + } + + @Override + public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) { + TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); + PumpEnactResult result = cancelExtendedBolus(); + if (!result.success) + return result; + ExtendedBolus extendedBolus = new ExtendedBolus(); + extendedBolus.date = System.currentTimeMillis(); + extendedBolus.insulin = insulin; + extendedBolus.durationInMinutes = durationInMinutes; + extendedBolus.source = Source.USER; + result.success = true; + result.enacted = true; + result.bolusDelivered = insulin; + result.isTempCancel = false; + result.duration = durationInMinutes; + result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + treatmentsInterface.addToHistoryExtendedBolus(extendedBolus); + if (Config.logPumpComm) + LOG.debug("Setting extended bolus: " + result); + MainApp.bus().post(new EventVirtualPumpUpdateGui()); + pumpStatusData.setLastDataTimeToNow(); + + return result; + } + + @Override + public PumpEnactResult cancelTempBasal(boolean force) { + TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); + PumpEnactResult result = new PumpEnactResult(); + result.success = true; + result.isTempCancel = true; + result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + if (treatmentsInterface.isTempBasalInProgress()) { + result.enacted = true; + TemporaryBasal tempStop = new TemporaryBasal(System.currentTimeMillis()); + tempStop.source = Source.USER; + treatmentsInterface.addToHistoryTempBasal(tempStop); + //tempBasal = null; + if (Config.logPumpComm) + LOG.debug("Canceling temp basal: " + result); + MainApp.bus().post(new EventVirtualPumpUpdateGui()); + } + pumpStatusData.setLastDataTimeToNow(); + + return result; + } + + @Override + public PumpEnactResult cancelExtendedBolus() { + TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); + PumpEnactResult result = new PumpEnactResult(); + if (treatmentsInterface.isInHistoryExtendedBoluslInProgress()) { + ExtendedBolus exStop = new ExtendedBolus(System.currentTimeMillis()); + exStop.source = Source.USER; + treatmentsInterface.addToHistoryExtendedBolus(exStop); + } + result.success = true; + result.enacted = true; + result.isTempCancel = true; + result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + if (Config.logPumpComm) + LOG.debug("Canceling extended basal: " + result); + MainApp.bus().post(new EventVirtualPumpUpdateGui()); + pumpStatusData.setLastDataTimeToNow(); + + return result; + } + + @Override + public JSONObject getJSONStatus() { + if (!SP.getBoolean("virtualpump_uploadstatus", false)) { + return null; + } + JSONObject pump = new JSONObject(); + JSONObject battery = new JSONObject(); + JSONObject status = new JSONObject(); + JSONObject extended = new JSONObject(); + try { + battery.put("percent", pumpStatusData.batteryRemaining); + status.put("status", "normal"); + extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); + try { + extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName()); + } catch (Exception e) { + } + TemporaryBasal tb = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + if (tb != null) { + extended.put("TempBasalAbsoluteRate", tb.tempBasalConvertedToAbsolute(System.currentTimeMillis())); + extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date)); + extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes()); + } + ExtendedBolus eb = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); + if (eb != null) { + extended.put("ExtendedBolusAbsoluteRate", eb.absoluteRate()); + extended.put("ExtendedBolusStart", DateUtil.dateAndTimeString(eb.date)); + extended.put("ExtendedBolusRemaining", eb.getPlannedRemainingMinutes()); + } + status.put("timestamp", DateUtil.toISOString(new Date())); + + pump.put("battery", battery); + pump.put("status", status); + pump.put("extended", extended); + pump.put("reservoir", pumpStatusData.reservoirRemainingUnits); + pump.put("clock", DateUtil.toISOString(new Date())); + } catch (JSONException e) { + LOG.error("Unhandled exception", e); + } + return pump; + } + + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpStatus.java new file mode 100644 index 0000000000..8db42ba407 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpStatus.java @@ -0,0 +1,25 @@ +package info.nightscout.androidaps.plugins.PumpVirtual; + +import info.nightscout.androidaps.interfaces.PumpDescription; +import info.nightscout.androidaps.plugins.PumpCommon.data.PumpStatus; + +/** + * Created by andy on 4/28/18. + */ + +public class VirtualPumpStatus extends PumpStatus { + + public VirtualPumpStatus(PumpDescription pumpDescription) { + super(pumpDescription); + } + + @Override + public void initSettings() { + + } + + @Override + public String getErrorInfo() { + return null; + } +} diff --git a/app/src/main/res/layout/medtronic_fragment.xml b/app/src/main/res/layout/medtronic_fragment.xml new file mode 100644 index 0000000000..ca4bec23f6 --- /dev/null +++ b/app/src/main/res/layout/medtronic_fragment.xml @@ -0,0 +1,536 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +