Added MedtrumService

This commit is contained in:
jbr7rr 2023-02-21 10:59:31 +01:00
parent b635ad26d8
commit baa4376f68
12 changed files with 503 additions and 200 deletions

View file

@ -54,7 +54,7 @@ import info.nightscout.plugins.sync.xdrip.XdripPlugin
import info.nightscout.pump.combo.ComboPlugin
import info.nightscout.pump.combov2.ComboV2Plugin
import info.nightscout.pump.diaconn.DiaconnG8Plugin
import info.nightscout.pump.medtrum.MedtrumPumpPlugin
import info.nightscout.pump.medtrum.MedtrumPlugin
import info.nightscout.pump.virtual.VirtualPumpPlugin
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventPreferenceChange
@ -123,7 +123,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
@Inject lateinit var wearPlugin: WearPlugin
@Inject lateinit var maintenancePlugin: MaintenancePlugin
@Inject lateinit var eopatchPumpPlugin: EopatchPumpPlugin
@Inject lateinit var medtrumPumpPlugin: MedtrumPumpPlugin
@Inject lateinit var MedtrumPlugin: MedtrumPlugin
@Inject lateinit var passwordCheck: PasswordCheck
@Inject lateinit var nsSettingStatus: NSSettingsStatus
@ -214,7 +214,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
addPreferencesFromResourceIfEnabled(medtronicPumpPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(diaconnG8Plugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(eopatchPumpPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(medtrumPumpPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(MedtrumPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResource(R.xml.pref_pump, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(virtualPumpPlugin, rootKey)
addPreferencesFromResourceIfEnabled(insulinOrefFreePeakPlugin, rootKey)

View file

@ -31,9 +31,9 @@ import info.nightscout.pump.common.di.PumpCommonModule
import info.nightscout.pump.dana.di.DanaHistoryModule
import info.nightscout.pump.dana.di.DanaModule
import info.nightscout.pump.danars.di.DanaRSModule
import info.nightscout.pump.medtrum.di.MedtrumModule
import info.nightscout.pump.diaconn.di.DiaconnG8Module
import info.nightscout.pump.virtual.di.VirtualPumpModule
import info.nightscout.pump.medtrum.di.MedtrumPumpModule
import info.nightscout.rx.di.RxModule
import info.nightscout.shared.di.SharedModule
import info.nightscout.shared.impl.di.SharedImplModule
@ -88,7 +88,7 @@ import javax.inject.Singleton
OmnipodErosModule::class,
PumpCommonModule::class,
RileyLinkModule::class,
MedtrumPumpModule::class,
MedtrumModule::class,
VirtualPumpModule::class
]
)

View file

@ -46,7 +46,7 @@ import info.nightscout.plugins.sync.tidepool.TidepoolPlugin
import info.nightscout.plugins.sync.xdrip.XdripPlugin
import info.nightscout.pump.combo.ComboPlugin
import info.nightscout.pump.combov2.ComboV2Plugin
import info.nightscout.pump.medtrum.MedtrumPumpPlugin
import info.nightscout.pump.medtrum.MedtrumPlugin
import info.nightscout.pump.diaconn.DiaconnG8Plugin
import info.nightscout.pump.virtual.VirtualPumpPlugin
import info.nightscout.sensitivity.SensitivityAAPSPlugin
@ -214,7 +214,7 @@ abstract class PluginsListModule {
@PumpDriver
@IntoMap
@IntKey(160)
abstract fun bindMedtrumPumpPlugin(plugin: MedtrumPumpPlugin): PluginBase
abstract fun bindMedtrumPlugin(plugin: MedtrumPlugin): PluginBase
@Binds
@AllConfigs

View file

@ -1,3 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<application>
<service
android:name=".services.MedtrumService"
android:enabled="true"
android:exported="false" />
</application>
</manifest>

View file

@ -1,6 +1,12 @@
package info.nightscout.pump.medtrum
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import dagger.android.HasAndroidInjector
import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType
@ -21,33 +27,38 @@ import info.nightscout.interfaces.queue.CustomCommand
import info.nightscout.interfaces.ui.UiInteraction
import info.nightscout.interfaces.utils.TimeChangeType
import info.nightscout.pump.medtrum.ui.MedtrumPumpFragment
import info.nightscout.pump.medtrum.services.MedtrumService
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventAppExit
import info.nightscout.rx.events.EventAppInitialized
import info.nightscout.rx.events.EventOverviewBolusProgress
import info.nightscout.rx.events.EventPreferenceChange
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.subjects.BehaviorSubject
import org.json.JSONException
import org.json.JSONObject
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class MedtrumPumpPlugin @Inject constructor(
class MedtrumPlugin @Inject constructor(
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
rh: ResourceHelper,
commandQueue: CommandQueue,
private val sp: SP,
private val aapsSchedulers: AapsSchedulers,
private val rxBus: RxBus,
private val context: Context,
private val fabricPrivacy: FabricPrivacy,
private val dateUtil: DateUtil,
private val pumpSync: PumpSync,
@ -55,7 +66,7 @@ class MedtrumPumpPlugin @Inject constructor(
private val profileFunction: ProfileFunction
) : PumpPluginBase(
PluginDescription()
.mainType(PluginType.PUMP) // TODO Prefs etc
.mainType(PluginType.PUMP)
.fragmentClass(MedtrumPumpFragment::class.java.name)
.pluginIcon(info.nightscout.core.ui.R.drawable.ic_eopatch2_128) // TODO
.pluginName(R.string.medtrum)
@ -64,16 +75,51 @@ class MedtrumPumpPlugin @Inject constructor(
.description(R.string.medtrum_pump_description), injector, aapsLogger, rh, commandQueue
), Pump {
private val disposable = CompositeDisposable()
private var medtrumService: MedtrumService? = null
private var mPumpType: PumpType = PumpType.MEDTRUM_NANO
private val mPumpDescription = PumpDescription(mPumpType)
private var mDeviceSN: Long = 0
override fun onStart() {
super.onStart()
aapsLogger.debug(LTag.PUMP, "MedtrumPlugin onStart()")
val intent = Intent(context, MedtrumService::class.java)
context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
disposable += rxBus
.toObservable(EventAppExit::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ context.unbindService(mConnection) }, fabricPrivacy::logException)
changePump()
}
override fun onStop() {
aapsLogger.debug(LTag.PUMP, "MedtrumPlugin onStop()")
context.unbindService(mConnection)
disposable.clear()
super.onStop()
aapsLogger.debug(LTag.PUMP, "MedtrumPumpPlugin onStop()")
}
private val mConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName) {
aapsLogger.debug(LTag.PUMP, "Service is disconnected")
medtrumService = null
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {
aapsLogger.debug(LTag.PUMP, "Service is connected")
val mLocalBinder = service as MedtrumService.LocalBinder
medtrumService = mLocalBinder.serviceInstance
}
}
fun changePump() { // TODO: Call this on inputfield change?
try {
mDeviceSN = sp.getString(info.nightscout.pump.medtrum.R.string.key_snInput, " ").toLong(radix = 16)
commandQueue.readStatus(rh.gs(info.nightscout.core.ui.R.string.device_changed), null)
} catch (e: NumberFormatException) {
aapsLogger.debug(LTag.PUMP, "changePump: invalid input!")
}
}
override fun isInitialized(): Boolean {
@ -88,33 +134,35 @@ class MedtrumPumpPlugin @Inject constructor(
return false
}
override fun isConnected(): Boolean {
return false
}
override fun isConnecting(): Boolean {
return false
}
override fun isHandshakeInProgress(): Boolean {
return false
}
override fun isConnected(): Boolean = medtrumService?.isConnected ?: false
override fun isConnecting(): Boolean = medtrumService?.isConnecting ?: false
override fun isHandshakeInProgress(): Boolean = false
override fun finishHandshaking() {
}
override fun connect(reason: String) {
aapsLogger.debug(LTag.PUMP, "Medtrum connect - reason:$reason")
aapsLogger.debug(LTag.PUMP, "Medtrum connect - service::$medtrumService")
aapsLogger.debug(LTag.PUMP, "Medtrum connect - mDeviceSN:$mDeviceSN")
if (medtrumService != null && mDeviceSN != 0.toLong()) {
aapsLogger.debug(LTag.PUMP, "Medtrum connect - Attempt connection!")
val success = medtrumService?.connect(reason, mDeviceSN) ?: false
if (!success) ToastUtils.errorToast(context, info.nightscout.core.ui.R.string.ble_not_supported_or_not_paired)
}
}
override fun disconnect(reason: String) {
aapsLogger.debug(LTag.PUMP, "Medtrum disconnect - reason:$reason")
aapsLogger.debug(LTag.PUMP, "RS disconnect from: $reason")
medtrumService?.disconnect(reason)
}
override fun stopConnecting() {
medtrumService?.stopConnecting()
}
override fun getPumpStatus(reason: String) {
// TODO
}
override fun setNewBasalProfile(profile: Profile): PumpEnactResult {
@ -177,7 +225,7 @@ class MedtrumPumpPlugin @Inject constructor(
}
override fun model(): PumpType {
return mPumpType
return mPumpType
}
override fun serialNumber(): String {

View file

@ -14,15 +14,15 @@ class ManufacturerData(private val manufacturerDataBytes: ByteArray) {
fun setData(inputData: ByteArray) {
var index = 0
val deviceIDBytes: ByteArray = manufacturerDataBytes.copyOfRange(index, index + 4)
val deviceIDBytes: ByteArray = inputData.copyOfRange(index, index + 4)
deviceID = deviceIDBytes.toLong()
index += 4
deviceType = (manufacturerDataBytes[index] and 0xff.toByte()).toInt()
deviceType = (inputData[index] and 0xff.toByte()).toInt()
index += 1
version = (manufacturerDataBytes[index] and 0xff.toByte()).toInt()
version = (inputData[index] and 0xff.toByte()).toInt()
}
fun getDeviceID(): Long{
fun getDeviceSN(): Long{
return deviceID
}

View file

@ -1,13 +1,12 @@
package info.nightscout.pump.medtrum.di
import dagger.Binds
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.pump.medtrum.ui.MedtrumPumpFragment
@Module
@Suppress("unused")
abstract class MedtrumPumpModule {
abstract class MedtrumActivitiesModule {
@ContributesAndroidInjector abstract fun contributesMedtrumPumpFragment(): MedtrumPumpFragment

View file

@ -0,0 +1,9 @@
package info.nightscout.pump.medtrum.di
import dagger.Module
@Module(includes = [
MedtrumActivitiesModule::class,
MedtrumServicesModule::class
])
open class MedtrumModule

View file

@ -0,0 +1,11 @@
package info.nightscout.pump.medtrum.di
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.pump.medtrum.services.MedtrumService
@Module
@Suppress("unused")
abstract class MedtrumServicesModule {
@ContributesAndroidInjector abstract fun contributesDanaRSService(): MedtrumService
}

View file

@ -79,7 +79,7 @@ class BLEComm @Inject internal constructor(
var isConnecting = false
private var uartWrite: BluetoothGattCharacteristic? = null
private var deviceID: Long = 0
private var mDeviceSN: Long = 0
/** Connect flow: 1. Start scanning for our device (SN entered in settings) */
@SuppressLint("MissingPermission")
@ -98,7 +98,6 @@ class BLEComm @Inject internal constructor(
.build()
val filters = mutableListOf<ScanFilter>()
if (deviceID == 0.toLong()) deviceID = rh.gs(info.nightscout.pump.medtrum.R.string.key_snInput).toLong(radix = 16)
isConnected = false
// TODO: Maybe replace this by (or add) a isScanning parameter?
@ -119,17 +118,43 @@ class BLEComm @Inject internal constructor(
mBluetoothAdapter?.bluetoothLeScanner?.stopScan(mScanCallback)
}
fun connect(from: String, deviceSN: Long): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
) {
ToastUtils.errorToast(context, context.getString(info.nightscout.core.ui.R.string.need_connect_permission))
aapsLogger.error(LTag.PUMPBTCOMM, "missing permission: $from")
return false
}
aapsLogger.debug(LTag.PUMPBTCOMM, "Initializing BLEComm.")
if (mBluetoothAdapter == null) {
aapsLogger.error("Unable to obtain a BluetoothAdapter.")
return false
}
mDeviceSN = deviceSN
isConnecting = true
startScan()
return true
}
/** Connect flow: 2. When device is found this is called by onScanResult() */
@SuppressLint("MissingPermission")
@Synchronized
fun connect(device: BluetoothDevice) {
fun connectGatt(device: BluetoothDevice) {
mBluetoothGatt =
device.connectGatt(context, false, mGattCallback, BluetoothDevice.TRANSPORT_LE)
}
@SuppressLint("MissingPermission")
@Synchronized
fun disconnect() {
fun disconnect(from: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
) {
aapsLogger.error(LTag.PUMPBTCOMM, "missing permission: $from")
return
}
aapsLogger.debug(LTag.PUMPBTCOMM, "disconnect from: $from")
mBluetoothGatt?.disconnect()
mBluetoothGatt = null
}
@ -156,11 +181,12 @@ class BLEComm @Inject internal constructor(
result.scanRecord?.getManufacturerSpecificData(MANUFACTURER_ID)
?.let { ManufacturerData(it) }
aapsLogger.debug(LTag.PUMPBTCOMM, "Found DeviceID: " + manufacturerData?.getDeviceID())
aapsLogger.debug(LTag.PUMPBTCOMM, "Found deviceSN: " + manufacturerData?.getDeviceSN())
if (manufacturerData?.getDeviceID() == deviceID) {
if (manufacturerData?.getDeviceSN() == mDeviceSN) {
aapsLogger.debug(LTag.PUMPBTCOMM, "Found our device! deviceSN: " + manufacturerData.getDeviceSN())
stopScan()
connect(result.device)
connectGatt(result.device)
}
}
@ -230,170 +256,166 @@ class BLEComm @Inject internal constructor(
checkDescriptor(descriptor)
}
}
}
@SuppressLint("MissingPermission")
@Synchronized
private fun readDescriptor(descriptor: BluetoothGattDescriptor?) {
aapsLogger.debug(LTag.PUMPBTCOMM, "readDescriptor")
if (mBluetoothAdapter == null || mBluetoothGatt == null || descriptor == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
return
}
mBluetoothGatt?.readDescriptor(descriptor)
@SuppressLint("MissingPermission")
@Synchronized
private fun readDescriptor(descriptor: BluetoothGattDescriptor?) {
aapsLogger.debug(LTag.PUMPBTCOMM, "readDescriptor")
if (mBluetoothAdapter == null || mBluetoothGatt == null || descriptor == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
return
}
mBluetoothGatt?.readDescriptor(descriptor)
}
private fun checkDescriptor(descriptor: BluetoothGattDescriptor) {
aapsLogger.debug(LTag.PUMPBTCOMM, "checkDescriptor")
val service = getGattService()
if (mBluetoothAdapter == null || mBluetoothGatt == null || service == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
return
}
if (descriptor.value.toInt() > 0) {
var notificationEnabled = true
val characteristics = service.characteristics
for (j in 0 until characteristics.size) {
val configDescriptor =
characteristics[j].getDescriptor(UUID.fromString(CHARACTERISTIC_CONFIG_UUID))
if (configDescriptor.value == null || configDescriptor.value.toInt() <= 0) {
notificationEnabled = false
}
}
if (notificationEnabled) {
aapsLogger.debug(LTag.PUMPBTCOMM, "Notifications enabled!")
authorize()
@Suppress("DEPRECATION")
private fun checkDescriptor(descriptor: BluetoothGattDescriptor) {
aapsLogger.debug(LTag.PUMPBTCOMM, "checkDescriptor")
val service = getGattService()
if (mBluetoothAdapter == null || mBluetoothGatt == null || service == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
return
}
if (descriptor.value.toInt() > 0) {
var notificationEnabled = true
val characteristics = service.characteristics
for (j in 0 until characteristics.size) {
val configDescriptor =
characteristics[j].getDescriptor(UUID.fromString(CHARACTERISTIC_CONFIG_UUID))
if (configDescriptor.value == null || configDescriptor.value.toInt() <= 0) {
notificationEnabled = false
}
}
}
@Suppress("DEPRECATION")
@SuppressLint("MissingPermission")
@Synchronized
private fun setCharacteristicNotification(characteristic: BluetoothGattCharacteristic?, enabled: Boolean) {
aapsLogger.debug(LTag.PUMPBTCOMM, "setCharacteristicNotification")
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
return
}
mBluetoothGatt?.setCharacteristicNotification(characteristic, enabled)
characteristic?.getDescriptor(UUID.fromString(CHARACTERISTIC_CONFIG_UUID))?.let {
if (characteristic.properties and 0x10 > 0) {
it.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
mBluetoothGatt?.writeDescriptor(it)
} else if (characteristic.properties and 0x20 > 0) {
it.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
mBluetoothGatt?.writeDescriptor(it)
} else {
}
}
}
/** Connect flow: 3. When we are connected discover services*/
@SuppressLint("MissingPermission")
@Synchronized
private fun onConnectionStateChangeSynchronized(gatt: BluetoothGatt, status: Int, newState: Int) {
aapsLogger.debug(LTag.PUMPBTCOMM, "onConnectionStateChange newState: " + newState + " status: " + status)
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
close()
isConnected = false
isConnecting = false
rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED))
aapsLogger.debug(LTag.PUMPBTCOMM, "Device was disconnected " + gatt.device.name) //Device was disconnected
disconnect()
startScan()
}
}
private fun readDataParsing(receivedData: ByteArray) {
aapsLogger.debug(LTag.PUMPBTCOMM, "<<<readDataParsing>>> " + Arrays.toString(receivedData))
// TODO
/** Connect flow: 6. Authorized */ // TODO place this at the correct place
}
private fun authorize() {
aapsLogger.debug(LTag.PUMPBTCOMM, "Start auth!")
val role = 2 // Fixed to 2 for pump
val key = mCrypt.keyGen(deviceID)
val commandData = byteArrayOf(COMMAND_AUTH_REQ) + byteArrayOf(role.toByte()) + 0.toByteArray(4) + key.toByteArray(4)
sendMessage(commandData)
}
@Suppress("DEPRECATION")
@SuppressLint("MissingPermission")
@Synchronized
private fun sendMessage(message: ByteArray) {
// TODO: Handle packages which consist of multiple, Create a queue of packages
aapsLogger.debug(LTag.PUMPBTCOMM, "sendMessage message = " + Arrays.toString(message))
val writePacket = WriteCommandPackets(message)
val value: ByteArray? = writePacket.getNextPacket()
// TODO: queue
writeCharacteristic(uartWriteBTGattChar, value)
}
private fun getGattService(): BluetoothGattService? {
aapsLogger.debug(LTag.PUMPBTCOMM, "getGattService")
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
return null
}
return mBluetoothGatt?.getService(UUID.fromString(SERVICE_UUID))
}
private fun getGattCharacteristic(uuid: UUID): BluetoothGattCharacteristic? {
aapsLogger.debug(LTag.PUMPBTCOMM, "getGattCharacteristic $uuid")
val service = getGattService()
if (mBluetoothAdapter == null || mBluetoothGatt == null || service == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
return null
}
return service.getCharacteristic(uuid)
}
@Suppress("DEPRECATION")
@SuppressLint("MissingPermission")
@Synchronized
private fun writeCharacteristic(characteristic: BluetoothGattCharacteristic, data: ByteArray?) {
Thread(Runnable {
SystemClock.sleep(WRITE_DELAY_MILLIS)
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
return@Runnable
}
characteristic.value = data
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
aapsLogger.debug("writeCharacteristic:" + Arrays.toString(data))
mBluetoothGatt?.writeCharacteristic(characteristic)
}).start()
SystemClock.sleep(WRITE_DELAY_MILLIS)
}
private val uartWriteBTGattChar: BluetoothGattCharacteristic
get() = uartWrite
?: BluetoothGattCharacteristic(UUID.fromString(WRITE_UUID), BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT, 0).also { uartWrite = it }
/** Connect flow: 4. When services are discovered find characteristics and set notifications*/
private fun findCharacteristic() {
val gattService = getGattService() ?: return
val gattCharacteristics = gattService.characteristics
for (gattCharacteristic in gattCharacteristics) {
setCharacteristicNotification(gattCharacteristic, true)
if (notificationEnabled) {
aapsLogger.debug(LTag.PUMPBTCOMM, "Notifications enabled!")
authorize()
}
}
}
@Suppress("DEPRECATION")
@SuppressLint("MissingPermission")
@Synchronized
private fun setCharacteristicNotification(characteristic: BluetoothGattCharacteristic?, enabled: Boolean) {
aapsLogger.debug(LTag.PUMPBTCOMM, "setCharacteristicNotification")
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
return
}
mBluetoothGatt?.setCharacteristicNotification(characteristic, enabled)
characteristic?.getDescriptor(UUID.fromString(CHARACTERISTIC_CONFIG_UUID))?.let {
if (characteristic.properties and 0x10 > 0) {
it.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
mBluetoothGatt?.writeDescriptor(it)
} else if (characteristic.properties and 0x20 > 0) {
it.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
mBluetoothGatt?.writeDescriptor(it)
} else {
}
}
}
/** Connect flow: 3. When we are connected discover services*/
@SuppressLint("MissingPermission")
@Synchronized
private fun onConnectionStateChangeSynchronized(gatt: BluetoothGatt, status: Int, newState: Int) {
aapsLogger.debug(LTag.PUMPBTCOMM, "onConnectionStateChange newState: " + newState + " status: " + status)
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
close()
isConnected = false
isConnecting = false
rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED))
aapsLogger.debug(LTag.PUMPBTCOMM, "Device was disconnected " + gatt.device.name) //Device was disconnected
}
}
private fun readDataParsing(receivedData: ByteArray) {
aapsLogger.debug(LTag.PUMPBTCOMM, "<<<readDataParsing>>> " + Arrays.toString(receivedData))
// TODO
/** Connect flow: 6. Authorized */ // TODO place this at the correct place
}
private fun authorize() {
aapsLogger.debug(LTag.PUMPBTCOMM, "Start auth!")
val role = 2 // Fixed to 2 for pump
val key = mCrypt.keyGen(mDeviceSN)
val commandData = byteArrayOf(COMMAND_AUTH_REQ) + byteArrayOf(role.toByte()) + 0.toByteArray(4) + key.toByteArray(4)
sendMessage(commandData)
}
fun sendMessage(message: ByteArray) {
// TODO: Handle packages which consist of multiple, Create a queue of packages
aapsLogger.debug(LTag.PUMPBTCOMM, "sendMessage message = " + Arrays.toString(message))
val writePacket = WriteCommandPackets(message)
val value: ByteArray? = writePacket.getNextPacket()
// TODO: queue
writeCharacteristic(uartWriteBTGattChar, value)
}
private fun getGattService(): BluetoothGattService? {
aapsLogger.debug(LTag.PUMPBTCOMM, "getGattService")
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
return null
}
return mBluetoothGatt?.getService(UUID.fromString(SERVICE_UUID))
}
private fun getGattCharacteristic(uuid: UUID): BluetoothGattCharacteristic? {
aapsLogger.debug(LTag.PUMPBTCOMM, "getGattCharacteristic $uuid")
val service = getGattService()
if (mBluetoothAdapter == null || mBluetoothGatt == null || service == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
return null
}
return service.getCharacteristic(uuid)
}
@Suppress("DEPRECATION")
@SuppressLint("MissingPermission")
@Synchronized
private fun writeCharacteristic(characteristic: BluetoothGattCharacteristic, data: ByteArray?) {
Thread(Runnable {
SystemClock.sleep(WRITE_DELAY_MILLIS)
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
return@Runnable
}
characteristic.value = data
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
aapsLogger.debug("writeCharacteristic:" + Arrays.toString(data))
mBluetoothGatt?.writeCharacteristic(characteristic)
}).start()
SystemClock.sleep(WRITE_DELAY_MILLIS)
}
private val uartWriteBTGattChar: BluetoothGattCharacteristic
get() = uartWrite
?: BluetoothGattCharacteristic(UUID.fromString(WRITE_UUID), BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT, 0).also { uartWrite = it }
/** Connect flow: 4. When services are discovered find characteristics and set notifications*/
private fun findCharacteristic() {
val gattService = getGattService() ?: return
val gattCharacteristics = gattService.characteristics
for (gattCharacteristic in gattCharacteristics) {
setCharacteristicNotification(gattCharacteristic, true)
}
}
}

View file

@ -0,0 +1,201 @@
package info.nightscout.pump.medtrum.services
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.os.SystemClock
import dagger.android.DaggerService
import dagger.android.HasAndroidInjector
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.notifications.Notification
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.profile.Profile
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.pump.BolusProgressData
import info.nightscout.interfaces.pump.PumpEnactResult
import info.nightscout.interfaces.pump.PumpSync
import info.nightscout.interfaces.queue.Callback
import info.nightscout.interfaces.queue.Command
import info.nightscout.interfaces.queue.CommandQueue
import info.nightscout.interfaces.ui.UiInteraction
import info.nightscout.pump.medtrum.MedtrumPlugin
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventAppExit
import info.nightscout.rx.events.EventInitializationChanged
import info.nightscout.rx.events.EventOverviewBolusProgress
import info.nightscout.rx.events.EventProfileSwitchChanged
import info.nightscout.rx.events.EventPumpStatusChanged
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.abs
import kotlin.math.min
class MedtrumService : DaggerService() {
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var rxBus: RxBus
@Inject lateinit var sp: SP
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var commandQueue: CommandQueue
@Inject lateinit var context: Context
@Inject lateinit var medtrumPlugin: MedtrumPlugin
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var constraintChecker: Constraints
@Inject lateinit var uiInteraction: UiInteraction
@Inject lateinit var bleComm: BLEComm
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var pumpSync: PumpSync
@Inject lateinit var dateUtil: DateUtil
private val disposable = CompositeDisposable()
private val mBinder: IBinder = LocalBinder()
override fun onCreate() {
super.onCreate()
disposable += rxBus
.toObservable(EventAppExit::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ stopSelf() }, fabricPrivacy::logException)
}
override fun onDestroy() {
disposable.clear()
super.onDestroy()
}
val isConnected: Boolean
get() = bleComm.isConnected
val isConnecting: Boolean
get() = bleComm.isConnecting
fun connect(from: String, deviceSN: Long): Boolean {
// TODO Check we might want to replace this with start scan?
return bleComm.connect(from, deviceSN)
}
fun stopConnecting() {
bleComm.stopConnecting()
}
fun disconnect(from: String) {
bleComm.disconnect(from)
}
fun sendMessage(message: ByteArray) { // TODO Check what we use here?
// TODO
bleComm.sendMessage(message)
}
fun readPumpStatus() {
// TODO
}
fun loadEvents(): PumpEnactResult {
if (!medtrumPlugin.isInitialized()) {
val result = PumpEnactResult(injector).success(false)
result.comment = "pump not initialized"
return result
}
// TODO need this? Check
val result = PumpEnactResult(injector)
return result
}
fun setUserSettings(): PumpEnactResult {
// TODO need this? Check
val result = PumpEnactResult(injector)
return result
}
fun bolus(insulin: Double, carbs: Int, carbTime: Long, t: EventOverviewBolusProgress.Treatment): Boolean {
if (!isConnected) return false
// TODO
return false
}
fun bolusStop() {
// TODO
}
fun tempBasal(percent: Int, durationInHours: Int): Boolean {
// TODO
return false
}
fun highTempBasal(percent: Int): Boolean {
// TODO
return false
}
fun tempBasalShortDuration(percent: Int, durationInMinutes: Int): Boolean {
if (durationInMinutes != 15 && durationInMinutes != 30) {
aapsLogger.error(LTag.PUMPCOMM, "Wrong duration param")
return false
}
// TODO
return false
}
fun tempBasalStop(): Boolean {
if (!isConnected) return false
// TODO
return false
}
fun extendedBolus(insulin: Double, durationInHalfHours: Int): Boolean {
if (!isConnected) return false
// TODO
return false
}
fun extendedBolusStop(): Boolean {
if (!isConnected) return false
// TODO
return false
}
fun updateBasalsInPump(profile: Profile): Boolean {
if (!isConnected) return false
// TODO
return false
}
fun loadHistory(type: Byte): PumpEnactResult {
val result = PumpEnactResult(injector)
if (!isConnected) return result
// TODO
return result
}
inner class LocalBinder : Binder() {
val serviceInstance: MedtrumService
get() = this@MedtrumService
}
override fun onBind(intent: Intent): IBinder {
return mBinder
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
return Service.START_STICKY
}
}

View file

@ -13,7 +13,7 @@ import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.pump.medtrum.databinding.MedtrumPumpFragmentBinding
import info.nightscout.pump.medtrum.events.EventMedtrumPumpUpdateGui
import info.nightscout.pump.medtrum.MedtrumPumpPlugin
import info.nightscout.pump.medtrum.MedtrumPlugin
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventExtendedBolusChange
@ -31,7 +31,7 @@ class MedtrumPumpFragment : DaggerFragment() {
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var medtrumPumpPlugin: MedtrumPumpPlugin
@Inject lateinit var MedtrumPlugin: MedtrumPlugin
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var iobCobCalculator: IobCobCalculator
@Inject lateinit var aapsSchedulers: AapsSchedulers
@ -90,7 +90,7 @@ class MedtrumPumpFragment : DaggerFragment() {
private fun updateGui() {
if (_binding == null) return
val profile = profileFunction.getProfile() ?: return
binding.baseBasalRate.text = rh.gs(info.nightscout.core.ui.R.string.pump_base_basal_rate, medtrumPumpPlugin.baseBasalRate)
binding.baseBasalRate.text = rh.gs(info.nightscout.core.ui.R.string.pump_base_basal_rate, MedtrumPlugin.baseBasalRate)
binding.tempbasal.text = iobCobCalculator.getTempBasal(dateUtil.now())?.toStringFull(profile, dateUtil)
?: ""
binding.extendedbolus.text = iobCobCalculator.getExtendedBolus(dateUtil.now())?.toStringFull(dateUtil)
@ -98,6 +98,6 @@ class MedtrumPumpFragment : DaggerFragment() {
binding.battery.text = rh.gs(info.nightscout.core.ui.R.string.format_percent, 0) // TODO
binding.reservoir.text = rh.gs(info.nightscout.interfaces.R.string.format_insulin_units, 0.0) // TODO
binding.serialNumber.text = medtrumPumpPlugin.serialNumber()
binding.serialNumber.text = MedtrumPlugin.serialNumber()
}
}