diff --git a/app/src/main/kotlin/app/aaps/di/PluginsListModule.kt b/app/src/main/kotlin/app/aaps/di/PluginsListModule.kt index 4ad2be2d8ac..b708e1365cd 100644 --- a/app/src/main/kotlin/app/aaps/di/PluginsListModule.kt +++ b/app/src/main/kotlin/app/aaps/di/PluginsListModule.kt @@ -35,6 +35,7 @@ import app.aaps.plugins.smoothing.AvgSmoothingPlugin import app.aaps.plugins.smoothing.ExponentialSmoothingPlugin import app.aaps.plugins.smoothing.NoSmoothingPlugin import app.aaps.plugins.source.DexcomPlugin +import app.aaps.plugins.source.EversensePlugin import app.aaps.plugins.source.GlimpPlugin import app.aaps.plugins.source.GlunovoPlugin import app.aaps.plugins.source.IntelligoPlugin @@ -390,7 +391,13 @@ abstract class PluginsListModule { @IntoMap @IntKey(410) abstract fun bindNSClientSourcePlugin(plugin: NSClientSourcePlugin): PluginBase - + + @Binds + @AllConfigs + @IntoMap + @IntKey(415) + abstract fun bindEversenseSourcePlugin(plugin: EversensePlugin): PluginBase + @Binds @AllConfigs @IntoMap diff --git a/core/data/src/main/kotlin/app/aaps/core/data/model/SourceSensor.kt b/core/data/src/main/kotlin/app/aaps/core/data/model/SourceSensor.kt index fe09dec7adb..d47fa2e9df5 100644 --- a/core/data/src/main/kotlin/app/aaps/core/data/model/SourceSensor.kt +++ b/core/data/src/main/kotlin/app/aaps/core/data/model/SourceSensor.kt @@ -29,7 +29,8 @@ enum class SourceSensor(val text: String) { OTTAI("Ottai"), SIBIONIC("SI App"), SINO("Sino App"), - EVERSENSE("Eversense"), + EVERSENSE_E3("Eversense E3"), + EVERSENSE_365("Eversense 365"), AIDEX("GlucoRx Aidex"), SYAI_TAG("Syai Tag"), RANDOM("Random"), diff --git a/database/impl/src/main/kotlin/app/aaps/database/entities/GlucoseValue.kt b/database/impl/src/main/kotlin/app/aaps/database/entities/GlucoseValue.kt index fb8614d4a80..156b0e1e27c 100644 --- a/database/impl/src/main/kotlin/app/aaps/database/entities/GlucoseValue.kt +++ b/database/impl/src/main/kotlin/app/aaps/database/entities/GlucoseValue.kt @@ -93,7 +93,8 @@ data class GlucoseValue( GLUNOVO_NATIVE, INTELLIGO_NATIVE, MM_600_SERIES, - EVERSENSE, + EVERSENSE_E3, + EVERSENSE_365, AIDEX, RANDOM, UNKNOWN, diff --git a/database/persistence/src/main/kotlin/app/aaps/database/persistence/converters/SourceSensorExtension.kt b/database/persistence/src/main/kotlin/app/aaps/database/persistence/converters/SourceSensorExtension.kt index 59065baec9f..0c369acc57b 100644 --- a/database/persistence/src/main/kotlin/app/aaps/database/persistence/converters/SourceSensorExtension.kt +++ b/database/persistence/src/main/kotlin/app/aaps/database/persistence/converters/SourceSensorExtension.kt @@ -30,7 +30,8 @@ fun GlucoseValue.SourceSensor.fromDb(): SourceSensor = GlucoseValue.SourceSensor.GLUNOVO_NATIVE -> SourceSensor.GLUNOVO_NATIVE GlucoseValue.SourceSensor.INTELLIGO_NATIVE -> SourceSensor.INTELLIGO_NATIVE GlucoseValue.SourceSensor.MM_600_SERIES -> SourceSensor.MM_600_SERIES - GlucoseValue.SourceSensor.EVERSENSE -> SourceSensor.EVERSENSE + GlucoseValue.SourceSensor.EVERSENSE_E3 -> SourceSensor.EVERSENSE_E3 + GlucoseValue.SourceSensor.EVERSENSE_365 -> SourceSensor.EVERSENSE_365 GlucoseValue.SourceSensor.AIDEX -> SourceSensor.AIDEX GlucoseValue.SourceSensor.RANDOM -> SourceSensor.RANDOM GlucoseValue.SourceSensor.UNKNOWN -> SourceSensor.UNKNOWN @@ -73,7 +74,8 @@ fun SourceSensor.toDb(): GlucoseValue.SourceSensor = SourceSensor.GLUNOVO_NATIVE -> GlucoseValue.SourceSensor.GLUNOVO_NATIVE SourceSensor.INTELLIGO_NATIVE -> GlucoseValue.SourceSensor.INTELLIGO_NATIVE SourceSensor.MM_600_SERIES -> GlucoseValue.SourceSensor.MM_600_SERIES - SourceSensor.EVERSENSE -> GlucoseValue.SourceSensor.EVERSENSE + SourceSensor.EVERSENSE_E3 -> GlucoseValue.SourceSensor.EVERSENSE_E3 + SourceSensor.EVERSENSE_365 -> GlucoseValue.SourceSensor.EVERSENSE_365 SourceSensor.AIDEX -> GlucoseValue.SourceSensor.AIDEX SourceSensor.RANDOM -> GlucoseValue.SourceSensor.RANDOM SourceSensor.UNKNOWN -> GlucoseValue.SourceSensor.UNKNOWN diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7a7711270c6..166ab08c795 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -75,7 +75,7 @@ kotlin-stdlib-jdk8 = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" } kotlinx-serialization-bom = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-bom", version.ref = "kotlinx-serialization" } -kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization" } kotlinx-serialization-protobuf = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-protobuf" } kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core" } diff --git a/plugins/eversense/.gitignore b/plugins/eversense/.gitignore new file mode 100644 index 00000000000..42afabfd2ab --- /dev/null +++ b/plugins/eversense/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/plugins/eversense/build.gradle.kts b/plugins/eversense/build.gradle.kts new file mode 100644 index 00000000000..1c1f990baed --- /dev/null +++ b/plugins/eversense/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.ksp) + + id("kotlin-android") + id("kotlinx-serialization") + id("android-module-dependencies") +} + +android { + namespace = "com.nightscout.eversense" +} + +dependencies { + api(libs.androidx.core) + api(libs.kotlinx.serialization.json) + + api(libs.org.slf4j.api) + api(libs.com.github.tony19.logback.android) +} \ No newline at end of file diff --git a/plugins/eversense/consumer-rules.pro b/plugins/eversense/consumer-rules.pro new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/eversense/proguard-rules.pro b/plugins/eversense/proguard-rules.pro new file mode 100644 index 00000000000..481bb434814 --- /dev/null +++ b/plugins/eversense/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/plugins/eversense/src/main/AndroidManifest.xml b/plugins/eversense/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..9189b499de4 --- /dev/null +++ b/plugins/eversense/src/main/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/EversenseCGMPlugin.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/EversenseCGMPlugin.kt new file mode 100644 index 00000000000..363965ed9ea --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/EversenseCGMPlugin.kt @@ -0,0 +1,143 @@ +package com.nightscout.eversense + +import android.annotation.SuppressLint +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothManager +import android.bluetooth.le.ScanFilter +import android.bluetooth.le.ScanSettings +import android.content.Context +import android.content.SharedPreferences +import android.os.ParcelUuid +import com.nightscout.eversense.callbacks.EversenseScanCallback +import com.nightscout.eversense.callbacks.EversenseWatcher +import com.nightscout.eversense.models.EversenseState +import com.nightscout.eversense.models.EversenseTransmitterSettings +import com.nightscout.eversense.packets.EversenseE3Communicator +import kotlinx.serialization.json.Json + +class EversenseCGMPlugin { + private var context: Context? = null + + private var bluetoothManager: BluetoothManager? = null + private var preferences: SharedPreferences? = null + private var gattCallback: EversenseGattCallback? = null + + private var scanner: EversenseScanner? = null + var watchers: List = listOf() + + fun setContext(context: Context, loggingEnabled: Boolean) { + this.context = context + EversenseLogger.instance.enableLogging(loggingEnabled) + + val preference = context.getSharedPreferences(TAG, Context.MODE_PRIVATE) + + bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + preferences = preference + gattCallback = EversenseGattCallback(this, preference) + } + + fun addWatcher(watcher: EversenseWatcher) { + this.watchers += watcher + } + + fun isConnected(): Boolean { + val gattCallback = this.gattCallback ?:run { + return false + } + + return gattCallback.isConnected() + } + + fun getCurrentState(): EversenseState? { + val preferences = preferences ?:run { + EversenseLogger.error(TAG, "No preferences available. Make sure setContext has been called") + return null + } + + val stateJson = preferences.getString(StorageKeys.STATE, null) ?: "{}" + return Json.decodeFromString(stateJson) + } + + @SuppressLint("MissingPermission") + fun startScan(callback: EversenseScanCallback) { + val bluetoothScanner = this.bluetoothManager?.adapter?.bluetoothLeScanner ?:run { + EversenseLogger.error(TAG, "No bluetooth manager available. Make sure setContext has been called") + return + } + + scanner = EversenseScanner(callback) + val filters = listOf( + ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(EversenseGattCallback.serviceUUID)).build() + ) + val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build() + bluetoothScanner.startScan(filters, settings, scanner) + } + + @SuppressLint("MissingPermission") + fun connect(device: BluetoothDevice?): Boolean { + val bluetoothManager = this.bluetoothManager ?:run { + EversenseLogger.error(TAG, "No bluetooth manager available. Make sure setContext has been called") + return false + } + + val gattCallback = this.gattCallback ?:run { + EversenseLogger.error(TAG, "No gattCallback available. Make sure setContext has been called") + return false + } + + if (scanner != null) { + bluetoothManager.adapter.bluetoothLeScanner.stopScan(scanner) + } + + if (gattCallback.isConnected()) { + EversenseLogger.info(TAG, "Already connected!") + return true + } + + if (device != null) { + EversenseLogger.info(TAG, "Connecting to ${device.name}") + device.connectGatt(context, true, gattCallback) + return true + } + + val address = preferences?.getString(StorageKeys.REMOTE_DEVICE_KEY, null) ?:run { + EversenseLogger.error(TAG, "Remote device not stored. Make sure you've connected once and bonded to this device") + return false + } + + val remoteDevice = bluetoothManager.adapter.getRemoteDevice(address) ?:run { + EversenseLogger.error(TAG, "Remote device not found. Make sure you've connected once and bonded to this device") + return false + } + + remoteDevice.connectGatt(context, true, gattCallback) + return true + } + + fun writeSettings(settings: EversenseTransmitterSettings): Boolean { + val preferences = preferences ?:run { + EversenseLogger.error(TAG, "No preferences available. Make sure setContext has been called") + return false + } + + val gattCallback = this.gattCallback ?:run { + EversenseLogger.error(TAG, "No gattCallback available. Make sure transmitter is connected before writing settings") + return false + } + + if (!gattCallback.isConnected()) { + EversenseLogger.error(TAG, "Transmitter is not connected...") + return false + } + + return EversenseE3Communicator.writeSettings(gattCallback, preferences, settings) + } + + companion object { + private const val TAG = "EversenseCGMManager" + + val instance:EversenseCGMPlugin by lazy { + EversenseCGMPlugin() + } + } +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/EversenseGattCallback.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/EversenseGattCallback.kt new file mode 100644 index 00000000000..bd63f3be4be --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/EversenseGattCallback.kt @@ -0,0 +1,290 @@ +package com.nightscout.eversense + +import android.annotation.SuppressLint +import android.bluetooth.BluetoothGatt +import android.bluetooth.BluetoothGattCallback +import android.bluetooth.BluetoothGattCharacteristic +import android.bluetooth.BluetoothGattDescriptor +import android.bluetooth.BluetoothGattService +import android.bluetooth.BluetoothProfile +import android.content.SharedPreferences +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.exceptions.EversenseWriteException +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversenseE3Communicator +import com.nightscout.eversense.packets.e3.EversenseE3Packets +import com.nightscout.eversense.packets.e3.SaveBondingInformationPacket +import java.util.UUID +import java.util.concurrent.Executors +import kotlin.jvm.Throws +import androidx.core.content.edit + +class EversenseGattCallback( + private val plugin: EversenseCGMPlugin, + private val preferences: SharedPreferences +) : BluetoothGattCallback() { + + companion object { + private val TAG = "EversenseGattCallback" + + const val serviceUUID = "c3230001-9308-47ae-ac12-3d030892a211" + + private const val requestUUID = "6eb0f021-a7ba-7e7d-66c9-6d813f01d273" + private const val requestSecureV2UUID = "c3230002-9308-47ae-ac12-3d030892a211" + + private const val responseUUID = "6eb0f024-bd60-7aaa-25a7-0029573f4f23" + private const val responseSecureV2UUID = "c3230003-9308-47ae-ac12-3d030892a211" + private const val magicDescriptorUUID = "00002902-0000-1000-8000-00805f9b34fb" + } + + private val executor = Executors.newSingleThreadExecutor() + private var bluetoothGatt: BluetoothGatt? = null + private var eversenseBluetoothService: BluetoothGattService? = null + private var requestCharacteristic: BluetoothGattCharacteristic? = null + private var responseCharacteristic: BluetoothGattCharacteristic? = null + + private var payloadSize: Int = 20 + private var security: EversenseSecurityType = EversenseSecurityType.None + var currentPacket: EversenseBasePacket? = null + + fun isConnected(): Boolean { + return this.bluetoothGatt != null + } + + @SuppressLint("MissingPermission") + override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { + EversenseLogger.info(TAG, "Connection state changed - state: $status, newState: $newState") + + if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) { + bluetoothGatt = gatt + + preferences.edit(commit = true) { + putString(StorageKeys.REMOTE_DEVICE_KEY, gatt.device.address) + } + + for (watcher in plugin.watchers) { + watcher.onConnectionChanged(true) + } + + if (!gatt.requestMtu(512)) { + EversenseLogger.error(TAG, "Failed to request MTU") + } + return + } + + if (newState == BluetoothProfile.STATE_DISCONNECTED) { + EversenseLogger.warning(TAG, "Disconnected...") + bluetoothGatt = null + + for (watcher in plugin.watchers) { + watcher.onConnectionChanged(false) + } + } + } + + @SuppressLint("MissingPermission") + override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) { + if (status == 0) { + payloadSize = mtu - 3 + EversenseLogger.debug(TAG, "New payload size: $payloadSize") + } else { + payloadSize = 20 + EversenseLogger.error(TAG, "Failed to set payload size - status: $status") + } + + val success = gatt?.discoverServices() + EversenseLogger.info(TAG, "Trigger discover services - success: $success") + } + + @SuppressLint("MissingPermission") + override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) { + EversenseLogger.info(TAG, "Discovered services - status: $status") + + if (gatt == null) { + EversenseLogger.error(TAG, "Gatt is empty") + return + } + + val service = gatt.services.first { it.uuid.toString() == serviceUUID } + if (service == null) { + EversenseLogger.error(TAG, "Service is empty -> disconnecting from device") + gatt.disconnect() + return + } + + eversenseBluetoothService = service + if (service.characteristics.isEmpty()) { + EversenseLogger.error(TAG, "Service has no characteristics -> disconnecting from device") + gatt.disconnect() + return + } + + var requestChar = service.characteristics.find { it.uuid.toString() == requestUUID } + var responseChar = service.characteristics.find { it.uuid.toString() == responseUUID } + if (requestChar != null && responseChar != null) { + EversenseLogger.info(TAG, "Connected to Eversense E3!") + security = EversenseSecurityType.None + requestCharacteristic = requestChar + responseCharacteristic = responseChar + + gatt.setCharacteristicNotification(requestChar, true) + gatt.setCharacteristicNotification(responseChar, true) + + enableNotify(gatt, responseChar) + return + } + + requestChar = service.characteristics.find { it.uuid.toString() == requestSecureV2UUID } + responseChar = service.characteristics.find { it.uuid.toString() == responseSecureV2UUID } + if (requestChar == null || responseChar == null) { + EversenseLogger.error(TAG, "No Eversense request/response characteristic found -> Disconnect from device...") + gatt.disconnect() + return + } + + EversenseLogger.info(TAG, "Connected to Eversense 365!") + security = EversenseSecurityType.SecureV2 + requestCharacteristic = requestChar + responseCharacteristic = responseChar + + gatt.setCharacteristicNotification(requestChar, true) + gatt.setCharacteristicNotification(responseChar, true) + enableNotify(gatt, responseChar) + } + + override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) { + EversenseLogger.debug(TAG, "onDescriptor (${descriptor.uuid}) write for characteristic (${descriptor.characteristic.uuid}) - status $status") + + if (status == BluetoothGatt.GATT_SUCCESS && descriptor.uuid.toString() == magicDescriptorUUID) { + if (descriptor.characteristic.uuid.toString() == responseUUID) { + executor.submit { authE3flow() } + } else if (descriptor.characteristic.uuid.toString() == responseSecureV2UUID) { + executor.submit { authV2flow() } + } + } + } + + @OptIn(ExperimentalStdlibApi::class) + override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { + EversenseLogger.debug(TAG, "Received data: ${characteristic.value.toHexString()}") + + var data = characteristic.value + if (security == EversenseSecurityType.SecureV2) { + data = data.drop(3).toByteArray() + } + + if (EversenseE3Packets.isPushPacket(data[0])) { + EversenseLogger.debug(TAG, "Keep Alive packet received!") + executor.submit { + EversenseE3Communicator.readGlucose(this, preferences, plugin.watchers) + EversenseE3Communicator.fullSync(this, preferences, plugin.watchers) + } + return + } + + val packet = currentPacket ?:run { + EversenseLogger.warning(TAG, "currentPacket is empty -> Skip packet...") + return + } + + synchronized(packet) { + val packetAnnotation = packet.getAnnotation() ?:run { + EversenseLogger.warning(TAG, "annotation is empty -> Skip packet...") + return + } + + if (EversenseE3Packets.isErrorPacket(data[0])) { + EversenseLogger.error(TAG, "Received error response - data: ${data.toHexString()}") + packet.notifyAll() + return + } + + if (packetAnnotation.responseId != data[0]) { + EversenseLogger.warning(TAG, "Incorrect responseId received - Expected: ${packetAnnotation.responseId}, got: ${data[0]}") + return + } + + if (security == EversenseSecurityType.None) { + packet.appendData(data.toUByteArray()) + packet.notifyAll() + } else { + if (packetAnnotation.responseType != data[1]) { + EversenseLogger.warning(TAG, "Incorrect responseType received - Expected: ${packetAnnotation.responseType}, got: ${data[1]}") + return + } + + packet.appendData(data.toUByteArray()) + packet.notifyAll() + } + } + } + + @Suppress("UNCHECKED_CAST") + @SuppressLint("MissingPermission") + @OptIn(ExperimentalStdlibApi::class) + @Throws(EversenseWriteException::class) + funwritePacket(packet: EversenseBasePacket): T { + val gatt = bluetoothGatt ?:run { + throw EversenseWriteException("Gatt is empty") + } + + val requestCharacteristic = requestCharacteristic ?:run { + throw EversenseWriteException("requestCharacteristic is empty") + } + + val requestData = packet.buildRequest() ?:run { + throw EversenseWriteException("Failed to build request data...") + } + + currentPacket = packet + + EversenseLogger.debug(TAG, "Writing data: ${requestData.toHexString()}") + + requestCharacteristic.setValue(requestData) + gatt.writeCharacteristic(requestCharacteristic) + + synchronized(packet) { + try { + packet.wait(5000) + } catch (e: Exception) { + EversenseLogger.error(TAG, "Exception during await - exception: $e") + e.printStackTrace() + } + } + + return try { + val response = packet.parseResponse() + currentPacket = null + response as? T ?: throw EversenseWriteException("Unable to cast response") + } catch(e: Exception) { + throw EversenseWriteException("Failed to parse response - exception: $e") + } + } + + private fun authE3flow() { + EversenseLogger.info(TAG, "Starting auth flow E3...") + + try { + writePacket(SaveBondingInformationPacket()) + } catch (exception: Exception) { + EversenseLogger.error(TAG, "Failed to do auth flow - exception: $exception") + return + } + + EversenseLogger.info(TAG, "Ready for full sync!!") + EversenseE3Communicator.fullSync(this, preferences, plugin.watchers) + } + + private fun authV2flow() { + // TODO: Implement + } + + @SuppressLint("MissingPermission") + @Suppress("DEPRECATION") + private fun enableNotify(gatt: BluetoothGatt, responseCharacteristic: BluetoothGattCharacteristic) { + responseCharacteristic.getDescriptor(UUID.fromString(magicDescriptorUUID))?.let { + it.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE + gatt.writeDescriptor(it) + } + } +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/EversenseLogger.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/EversenseLogger.kt new file mode 100644 index 00000000000..3020c8dc733 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/EversenseLogger.kt @@ -0,0 +1,119 @@ +package com.nightscout.eversense + +import ch.qos.logback.classic.LoggerContext +import ch.qos.logback.classic.joran.JoranConfigurator +import ch.qos.logback.core.joran.spi.JoranException +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.io.InputStream + +class EversenseLogger { + private val lc = LoggerContext() + private var isEnabled: Boolean = true + + init { + val config = JoranConfigurator() + config.setContext(lc) + + val stream: InputStream = ByteArrayInputStream(LOGBACK_XML.toByteArray()) + try { + config.doConfigure(stream) + } catch (e: JoranException) { + e.printStackTrace() + } + } + + private fun debug(tag: String, message: String) { + if (!isEnabled) { return } + + lc.getLogger(tag).info(logLocationPrefix() + message) + } + + private fun info(tag: String, message: String) { + if (!isEnabled) { return } + lc.getLogger(tag).info(logLocationPrefix() + message) + } + + private fun warning(tag: String, message: String) { + if (!isEnabled) { return } + lc.getLogger(tag).warn(logLocationPrefix() + message) + } + + private fun error(tag: String, message: String) { + if (!isEnabled) { return } + lc.getLogger(tag).error(logLocationPrefix() + message) + } + + fun enableLogging(value: Boolean) { + this.isEnabled = value + } + + private fun logLocationPrefix(): String { + val stackInfo = Throwable().stackTrace[4] + val className = stackInfo.className.substringAfterLast(".") + val methodName = stackInfo.methodName + val lineNumber = stackInfo.lineNumber + + return "$className.$methodName():$lineNumber]: " + } + + companion object { + val instance = EversenseLogger() + + fun debug(tag: String, message: String) { + instance.debug(tag, message) + } + + fun info(tag: String, message: String) { + instance.info(tag, message) + } + + fun warning(tag: String, message: String) { + instance.warning(tag, message) + } + + fun error(tag: String, message: String) { + instance.error(tag, message) + } + + private const val LOGBACK_XML: String = "\n" + + " \n" + + " \n" + + " \n" + + " \${EXT_FILES_DIR}/Eversense.log\n" + + " \n" + + " \n" + + " \${EXT_FILES_DIR}/Eversense._%d{yyyy-MM-dd}_%d{HH-mm-ss, aux}_.%i.zip\n" + + " \n" + + "\n" + + " \n" + + " 5MB\n" + + " \n" + + " \n" + + " 120\n" + + " \n" + + " \n" + + " [%d{HH:mm:ss.SSS} %.-1level/%logger %msg%n\n" + + " \n" + + " \n" + + "\n" + + " \n" + + " \n" + + " %logger{0}\n" + + " \n" + + " \n" + + " [%d{HH:mm:ss.SSS} %msg%n\n" + + " \n" + + " \n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "" + + } +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/EversenseScanner.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/EversenseScanner.kt new file mode 100644 index 00000000000..837e42ee605 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/EversenseScanner.kt @@ -0,0 +1,24 @@ +package com.nightscout.eversense + +import android.annotation.SuppressLint +import android.bluetooth.le.ScanCallback +import android.bluetooth.le.ScanResult +import android.util.Log +import com.nightscout.eversense.callbacks.EversenseScanCallback +import com.nightscout.eversense.models.EversenseScanResult + +class EversenseScanner(private val callback: EversenseScanCallback): ScanCallback() { + @SuppressLint("MissingPermission") + override fun onScanResult(callbackType: Int, scanRecord: ScanResult) { + if (scanRecord.device?.name == null) { + return + } + + EversenseLogger.info(TAG, "Found device: ${scanRecord.device.name}") + callback.onResult(EversenseScanResult(scanRecord.device.name, scanRecord.rssi, scanRecord.device)) + } + + companion object { + private val TAG = "EversenseScanner" + } +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/StorageKeys.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/StorageKeys.kt new file mode 100644 index 00000000000..ac3fe1823c4 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/StorageKeys.kt @@ -0,0 +1,8 @@ +package com.nightscout.eversense + +class StorageKeys { + companion object { + const val REMOTE_DEVICE_KEY = "remote_device" + const val STATE = "eversense_state" + } +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/callbacks/EversenseScanCallback.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/callbacks/EversenseScanCallback.kt new file mode 100644 index 00000000000..201c80d865e --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/callbacks/EversenseScanCallback.kt @@ -0,0 +1,7 @@ +package com.nightscout.eversense.callbacks + +import com.nightscout.eversense.models.EversenseScanResult + +interface EversenseScanCallback { + fun onResult(var0: EversenseScanResult) +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/callbacks/EversenseWatcher.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/callbacks/EversenseWatcher.kt new file mode 100644 index 00000000000..2d6168df913 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/callbacks/EversenseWatcher.kt @@ -0,0 +1,11 @@ +package com.nightscout.eversense.callbacks + +import com.nightscout.eversense.enums.EversenseType +import com.nightscout.eversense.models.EversenseCGMResult +import com.nightscout.eversense.models.EversenseState + +interface EversenseWatcher { + fun onCGMRead(type: EversenseType, readings: List) + fun onStateChanged(state: EversenseState) + fun onConnectionChanged(connected: Boolean) +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/enums/EversenseE3Memory.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/enums/EversenseE3Memory.kt new file mode 100644 index 00000000000..c438bf909cb --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/enums/EversenseE3Memory.kt @@ -0,0 +1,31 @@ +package com.nightscout.eversense.enums + +enum class EversenseE3Memory(private val address: Long) { + BatteryPercentage(0x0000_0406), + SensorInsertionDate(0x0000_0890), + SensorInsertionTime(0x0000_0892), + VibrateMode(0x0000_0902), + HighGlucoseAlarmEnabled(0x0000_1029), + HighGlucoseAlarmThreshold(0x0000_110C), + LowGlucoseAlarmThreshold(0x0000_110A), + PredictiveAlert(0x0000_1020), + PredictiveLowTime(0x0000_1021), + PredictiveHighTime(0x0000_1022), + PredictiveLowAlert(0x0000_1027), + PredictiveHighAlert(0x0000_1028), + PredictiveLowTarget(0x0000_1102), + PredictiveHighTarget(0x0000_1104), + RateAlert(0x0000_1010), + RateFallingAlert(0x0000_1025), + RateRisingAlert(0x0000_1026), + RateFallingThreshold(0x0000_1011), + RateRisingThreshold(0x0000_1012); + + fun getRequestData(): ByteArray { + return byteArrayOf( + this.address.toByte(), + (this.address shr 8).toByte(), + (this.address shr 16).toByte(), + ) + } +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/enums/EversenseSecurityType.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/enums/EversenseSecurityType.kt new file mode 100644 index 00000000000..8eee37eab41 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/enums/EversenseSecurityType.kt @@ -0,0 +1,10 @@ +package com.nightscout.eversense.enums + +enum class EversenseSecurityType { + // Eversense E3 + None, + + // Eversense 365 -> generation 2 + // Eversense 365 -> generation 1 is deprecated and not available anymore + SecureV2 +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/enums/EversenseTrendArrow.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/enums/EversenseTrendArrow.kt new file mode 100644 index 00000000000..dff7b445be8 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/enums/EversenseTrendArrow.kt @@ -0,0 +1,10 @@ +package com.nightscout.eversense.enums + +enum class EversenseTrendArrow(val type: String) { + NONE("NONE"), + SINGLE_UP("SingleUp"), + FORTY_FIVE_UP("FortyFiveUp"), + FLAT("Flat"), + FORTY_FIVE_DOWN("FortyFiveDown"), + SINGLE_DOWN("SingleDown") +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/enums/EversenseType.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/enums/EversenseType.kt new file mode 100644 index 00000000000..104f452afd1 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/enums/EversenseType.kt @@ -0,0 +1,6 @@ +package com.nightscout.eversense.enums + +enum class EversenseType { + EVERSENSE_E3, + EVERSENSE_365 +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/exceptions/EversenseWriteException.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/exceptions/EversenseWriteException.kt new file mode 100644 index 00000000000..604b3cbd50b --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/exceptions/EversenseWriteException.kt @@ -0,0 +1,4 @@ +package com.nightscout.eversense.exceptions + +class EversenseWriteException(override val message: String) : Exception(message) { +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/models/EversenseCGMResult.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/models/EversenseCGMResult.kt new file mode 100644 index 00000000000..c364e55b18e --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/models/EversenseCGMResult.kt @@ -0,0 +1,9 @@ +package com.nightscout.eversense.models + +import com.nightscout.eversense.enums.EversenseTrendArrow + +data class EversenseCGMResult( + val glucoseInMgDl: Int, + val datetime: Long, + val trend: EversenseTrendArrow +) diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/models/EversenseScanResult.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/models/EversenseScanResult.kt new file mode 100644 index 00000000000..b3a9bcffef7 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/models/EversenseScanResult.kt @@ -0,0 +1,5 @@ +package com.nightscout.eversense.models + +import android.bluetooth.BluetoothDevice + +data class EversenseScanResult(val name: String, val rssi: Int, val device: BluetoothDevice) diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/models/EversenseState.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/models/EversenseState.kt new file mode 100644 index 00000000000..c9c295eaf60 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/models/EversenseState.kt @@ -0,0 +1,39 @@ +package com.nightscout.eversense.models + +import kotlinx.serialization.Serializable + +@Serializable +class EversenseState { + var lastSync: Long = 0 + var insertionDate: Long = 0 + + var batteryPercentage: Int = 0 + + var recentGlucoseDatetime: Long = 0 + var recentGlucoseValue: Int = 0 + + var settings = EversenseTransmitterSettings() +} + +@Serializable +class EversenseTransmitterSettings { + var vibrateEnabled: Boolean = true + + var glucoseHighAlarmEnabled: Boolean = true + var glucoseHighAlarmThreshold: Int = 250 + var glucoseLowAlarmThreshold: Int = 60 + + var rateAlarmEnabled: Boolean = true + var rateFallingAlarmEnabled: Boolean = true + var rateFallingAlarmThreshold: Double = 1.5 + var rateRisingAlarmEnabled: Boolean = true + var rateRisingAlarmThreshold: Double = 1.5 + + var predictiveAlarmEnabled: Boolean = true + var predictiveHighAlarmEnabled: Boolean = true + var predictiveHighAlarmThreshold: Int = 180 + var predictiveHighAlarmMinutes: Int = 5 + var predictiveLowAlarmEnabled: Boolean = true + var predictiveLowAlarmThreshold: Int = 70 + var predictiveLowAlarmMinutes: Int = 5 +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/EversenseBasePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/EversenseBasePacket.kt new file mode 100644 index 00000000000..275db5260f5 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/EversenseBasePacket.kt @@ -0,0 +1,56 @@ +package com.nightscout.eversense.packets + +import com.nightscout.eversense.EversenseLogger +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.e3.EversenseE3Packets +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +abstract class EversenseBasePacket : Object() { + abstract fun getRequestData(): ByteArray + abstract fun parseResponse(): Response? + + protected var receivedData = UByteArray(0) + + fun getAnnotation(): EversensePacket? { + return this.javaClass.annotations.find { it.annotationClass == EversensePacket::class } as? EversensePacket + } + + protected fun getStartIndex(): Int { + val annotation = getAnnotation() ?:run { + EversenseLogger.error("EversenseBasePacket", this.javaClass.name + " does not have the EversensePacket annotation...") + return 0 + } + + return when(annotation.responseId) { + EversenseE3Packets.ReadSingleByteSerialFlashRegisterResponseId, + EversenseE3Packets.ReadTwoByteSerialFlashRegisterResponseId, + EversenseE3Packets.ReadFourByteSerialFlashRegisterResponseId -> 4 + + else -> 1 + } + } + + fun appendData(data: UByteArray) { + receivedData += data + } + + fun buildRequest(): ByteArray? { + val annotation = getAnnotation() ?:run { + EversenseLogger.error("EversenseBasePacket", this.javaClass.name + " does not have the EversensePacket annotation...") + return null + } + + if (annotation.securityType == EversenseSecurityType.None) { + var requestData = byteArrayOf(annotation.requestId) + requestData += this.getRequestData() + requestData += EversenseE3Writer.generateChecksumCRC16(requestData) + + return requestData + } else { + EversenseLogger.error("EversenseBasePacket", "TODO: Implement Eversense 365 request builder...") + return null + } + } + + abstract class Response {} +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/EversenseE3Communicator.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/EversenseE3Communicator.kt new file mode 100644 index 00000000000..d6e7bc766c8 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/EversenseE3Communicator.kt @@ -0,0 +1,217 @@ +package com.nightscout.eversense.packets + +import android.content.SharedPreferences +import androidx.core.content.edit +import com.nightscout.eversense.EversenseGattCallback +import com.nightscout.eversense.EversenseLogger +import com.nightscout.eversense.StorageKeys +import com.nightscout.eversense.callbacks.EversenseWatcher +import com.nightscout.eversense.enums.EversenseType +import com.nightscout.eversense.models.EversenseCGMResult +import com.nightscout.eversense.models.EversenseState +import com.nightscout.eversense.models.EversenseTransmitterSettings +import com.nightscout.eversense.packets.e3.GetBatteryPercentagePacket +import com.nightscout.eversense.packets.e3.GetCurrentDatetimePacket +import com.nightscout.eversense.packets.e3.GetCurrentGlucosePacket +import com.nightscout.eversense.packets.e3.GetInsertionDatePacket +import com.nightscout.eversense.packets.e3.GetInsertionTimePacket +import com.nightscout.eversense.packets.e3.GetSettingGlucoseHighEnabled +import com.nightscout.eversense.packets.e3.GetSettingGlucoseHighThresholdPacket +import com.nightscout.eversense.packets.e3.GetSettingGlucoseLowThresholdPacket +import com.nightscout.eversense.packets.e3.GetSettingPredictiveAlarmEnabledPacket +import com.nightscout.eversense.packets.e3.GetSettingPredictiveHighEnabledPacket +import com.nightscout.eversense.packets.e3.GetSettingPredictiveHighThresholdPacket +import com.nightscout.eversense.packets.e3.GetSettingPredictiveHighTimePacket +import com.nightscout.eversense.packets.e3.GetSettingPredictiveLowEnabledPacket +import com.nightscout.eversense.packets.e3.GetSettingPredictiveLowThresholdPacket +import com.nightscout.eversense.packets.e3.GetSettingPredictiveLowTimePacket +import com.nightscout.eversense.packets.e3.GetSettingRateEnabledPacket +import com.nightscout.eversense.packets.e3.GetSettingRateFallingEnabledPacket +import com.nightscout.eversense.packets.e3.GetSettingRateFallingThresholdPacket +import com.nightscout.eversense.packets.e3.GetSettingRateRisingEnabledPacket +import com.nightscout.eversense.packets.e3.GetSettingRateRisingThresholdPacket +import com.nightscout.eversense.packets.e3.GetSettingVibratePacket +import com.nightscout.eversense.packets.e3.SetCurrentDatetimePacket +import com.nightscout.eversense.packets.e3.SetSettingGlucoseHighEnablePacket +import com.nightscout.eversense.packets.e3.SetSettingGlucoseHighThresholdPacket +import com.nightscout.eversense.packets.e3.SetSettingGlucoseLowThresholdPacket +import com.nightscout.eversense.packets.e3.SetSettingPredictiveAlarmEnabledPacket +import com.nightscout.eversense.packets.e3.SetSettingPredictiveHighAlarmEnabledPacket +import com.nightscout.eversense.packets.e3.SetSettingPredictiveHighThresholdPacket +import com.nightscout.eversense.packets.e3.SetSettingPredictiveHighTimePacket +import com.nightscout.eversense.packets.e3.SetSettingPredictiveLowAlarmEnabledPacket +import com.nightscout.eversense.packets.e3.SetSettingPredictiveLowThresholdPacket +import com.nightscout.eversense.packets.e3.SetSettingPredictiveLowTimePacket +import com.nightscout.eversense.packets.e3.SetSettingRateEnabledPacket +import com.nightscout.eversense.packets.e3.SetSettingRateFallingEnabledPacket +import com.nightscout.eversense.packets.e3.SetSettingRateFallingThresholdPacket +import com.nightscout.eversense.packets.e3.SetSettingRateRisingEnabledPacket +import com.nightscout.eversense.packets.e3.SetSettingRateRisingThresholdPacket +import com.nightscout.eversense.packets.e3.SetSettingVibratePacket +import kotlinx.serialization.json.Json +import java.util.concurrent.TimeUnit + +class EversenseE3Communicator { + companion object { + private const val TAG = "EversenseE3Communicator" + private val JSON = Json { ignoreUnknownKeys = true } + + fun readGlucose(gatt: EversenseGattCallback, preferences: SharedPreferences, watchers: List) { + val stateJson = preferences.getString(StorageKeys.STATE, null) ?: "{}" + val state = JSON.decodeFromString(stateJson) + val fourHalfMinAgo = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(270) + + if (fourHalfMinAgo < state.recentGlucoseDatetime) { + EversenseLogger.warning(TAG, "Glucose data is still recent - lastReading: ${state.recentGlucoseDatetime}") + return + } + + try { + EversenseLogger.debug(TAG, "Reading current glucose...") + val currentGlucose = gatt.writePacket(GetCurrentGlucosePacket()) + if (currentGlucose.datetime <= state.recentGlucoseDatetime) { + EversenseLogger.warning(TAG, "Glucose data is still recent after reading - currentReading: ${currentGlucose.datetime}, lastReading: ${state.recentGlucoseDatetime}") + return + } + + if (currentGlucose.glucoseInMgDl > 1000) { + EversenseLogger.error(TAG, "recentGlucose exceeds range - received: ${currentGlucose.glucoseInMgDl}") + return + } + + val result = mutableListOf() + state.recentGlucoseDatetime = currentGlucose.datetime + state.recentGlucoseValue = currentGlucose.glucoseInMgDl + result += EversenseCGMResult( + glucoseInMgDl = currentGlucose.glucoseInMgDl, + datetime = currentGlucose.datetime, + trend = currentGlucose.trend + ) + + // TODO: read history for backfill + + preferences.edit(commit = true) { + putString(StorageKeys.STATE, JSON.encodeToString(state)) + } + + watchers.forEach { + it.onCGMRead(EversenseType.EVERSENSE_E3, result) + } + } catch (exception: Exception) { + EversenseLogger.error(TAG, "Got exception during readGlucose - exception $exception") + } + } + + fun fullSync(gatt: EversenseGattCallback, preferences: SharedPreferences, watchers: List) { + try { + val stateJson = preferences.getString(StorageKeys.STATE, null) ?: "{}" + val state = JSON.decodeFromString(stateJson) + val fourHalfMinAgo = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(270) + + if (fourHalfMinAgo < state.lastSync) { + EversenseLogger.warning(TAG, "State is still fresh - lastSync: ${state.lastSync}") + return + } + + EversenseLogger.debug(TAG, "Reading current datetime...") + val currentDatetime = gatt.writePacket(GetCurrentDatetimePacket()) + if (currentDatetime.needsTimeSync) { + EversenseLogger.debug(TAG, "Send SetCurrentDatetimePacket...") + gatt.writePacket(SetCurrentDatetimePacket()) + } + + EversenseLogger.debug(TAG, "Reading battery percentage...") + val batteryPercentage = gatt.writePacket(GetBatteryPercentagePacket()) + state.batteryPercentage = batteryPercentage.percentage + + EversenseLogger.debug(TAG, "Reading insertion datetime...") + val insertionDate = gatt.writePacket(GetInsertionDatePacket()) + val insertionTime = gatt.writePacket(GetInsertionTimePacket()) + state.insertionDate = insertionDate.date + insertionTime.time + + // Transmitter settings + EversenseLogger.debug(TAG, "Reading transmitter settings...") + val vibrateEnabled = gatt.writePacket(GetSettingVibratePacket()) + val glucoseHighEnabled = gatt.writePacket(GetSettingGlucoseHighEnabled()) + val glucoseHighThreshold = gatt.writePacket(GetSettingGlucoseHighThresholdPacket()) + val glucoseLowThreshold = gatt.writePacket(GetSettingGlucoseLowThresholdPacket()) + val rateEnabled = gatt.writePacket(GetSettingRateEnabledPacket()) + val rateFallingEnabled = gatt.writePacket(GetSettingRateFallingEnabledPacket()) + val rateFallingThreshold = gatt.writePacket(GetSettingRateFallingThresholdPacket()) + val rateRisingEnabled = gatt.writePacket(GetSettingRateRisingEnabledPacket()) + val rateRisingThreshold = gatt.writePacket(GetSettingRateRisingThresholdPacket()) + val predictiveEnabled = gatt.writePacket(GetSettingPredictiveAlarmEnabledPacket()) + val predictiveHighEnabled = gatt.writePacket(GetSettingPredictiveHighEnabledPacket()) + val predictiveHighTime = gatt.writePacket(GetSettingPredictiveHighTimePacket()) + val predictiveHighThreshold = gatt.writePacket(GetSettingPredictiveHighThresholdPacket()) + val predictiveLowEnabled = gatt.writePacket(GetSettingPredictiveLowEnabledPacket()) + val predictiveLowTime = gatt.writePacket(GetSettingPredictiveLowTimePacket()) + val predictiveLowThreshold = gatt.writePacket(GetSettingPredictiveLowThresholdPacket()) + + state.settings.vibrateEnabled = vibrateEnabled.enabled + state.settings.glucoseHighAlarmEnabled = glucoseHighEnabled.enabled + state.settings.glucoseHighAlarmThreshold = glucoseHighThreshold.threshold + state.settings.glucoseLowAlarmThreshold = glucoseLowThreshold.threshold + state.settings.rateAlarmEnabled = rateEnabled.enabled + state.settings.rateFallingAlarmEnabled = rateFallingEnabled.enabled + state.settings.rateFallingAlarmThreshold = rateFallingThreshold.threshold + state.settings.rateRisingAlarmEnabled = rateRisingEnabled.enabled + state.settings.rateRisingAlarmThreshold = rateRisingThreshold.threshold + state.settings.predictiveAlarmEnabled = predictiveEnabled.enabled + state.settings.predictiveHighAlarmEnabled = predictiveHighEnabled.enabled + state.settings.predictiveHighAlarmMinutes = predictiveHighTime.minutes + state.settings.predictiveHighAlarmThreshold = predictiveHighThreshold.threshold + state.settings.predictiveLowAlarmEnabled = predictiveLowEnabled.enabled + state.settings.predictiveLowAlarmMinutes = predictiveLowTime.minutes + state.settings.predictiveLowAlarmThreshold = predictiveLowThreshold.threshold + + state.lastSync = System.currentTimeMillis() + EversenseLogger.info(TAG, "Completed full sync - datetime: ${state.lastSync}") + preferences.edit(commit = true) { + putString(StorageKeys.STATE, JSON.encodeToString(state)) + } + watchers.forEach { + it.onStateChanged(state) + } + } catch (exception: Exception) { + EversenseLogger.error(TAG, "Failed to do full sync: $exception") + } + } + + fun writeSettings(gatt: EversenseGattCallback, preferences: SharedPreferences, settings: EversenseTransmitterSettings): Boolean { + try { + gatt.writePacket(SetSettingVibratePacket(settings.vibrateEnabled)) + + gatt.writePacket(SetSettingGlucoseHighEnablePacket(settings.glucoseHighAlarmEnabled)) + gatt.writePacket(SetSettingGlucoseHighThresholdPacket(settings.glucoseHighAlarmThreshold)) + gatt.writePacket(SetSettingGlucoseLowThresholdPacket(settings.glucoseLowAlarmThreshold)) + + gatt.writePacket(SetSettingRateEnabledPacket(settings.rateAlarmEnabled)) + gatt.writePacket(SetSettingRateFallingEnabledPacket(settings.rateFallingAlarmEnabled)) + gatt.writePacket(SetSettingRateFallingThresholdPacket(settings.rateFallingAlarmThreshold)) + gatt.writePacket(SetSettingRateRisingEnabledPacket(settings.rateRisingAlarmEnabled)) + gatt.writePacket(SetSettingRateRisingThresholdPacket(settings.rateRisingAlarmThreshold)) + + gatt.writePacket(SetSettingPredictiveAlarmEnabledPacket(settings.predictiveAlarmEnabled)) + gatt.writePacket(SetSettingPredictiveHighAlarmEnabledPacket(settings.predictiveHighAlarmEnabled)) + gatt.writePacket(SetSettingPredictiveHighTimePacket(settings.predictiveHighAlarmMinutes)) + gatt.writePacket(SetSettingPredictiveHighThresholdPacket(settings.predictiveHighAlarmThreshold)) + gatt.writePacket(SetSettingPredictiveLowAlarmEnabledPacket(settings.predictiveLowAlarmEnabled)) + gatt.writePacket(SetSettingPredictiveLowTimePacket(settings.predictiveLowAlarmMinutes)) + gatt.writePacket(SetSettingPredictiveLowThresholdPacket(settings.predictiveLowAlarmThreshold)) + + val stateJson = preferences.getString(StorageKeys.STATE, null) ?: "{}" + val state = JSON.decodeFromString(stateJson) + state.settings = settings + preferences.edit(commit = true) { + putString(StorageKeys.STATE, JSON.encodeToString(state)) + } + + return true + } catch (exception: Exception) { + EversenseLogger.error(TAG, "Failed to do full sync: $exception") + return false + } + } + } +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/EversensePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/EversensePacket.kt new file mode 100644 index 00000000000..dc04fe3d96a --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/EversensePacket.kt @@ -0,0 +1,17 @@ +package com.nightscout.eversense.packets + +import com.nightscout.eversense.enums.EversenseSecurityType + +annotation class EversensePacket( + /** The request id for the packet */ + val requestId: Byte, + + /** The expected response id for this packet */ + val responseId: Byte, + + /** The expected response id for this packet. Only relevant for 365 packets */ + val responseType: Byte, + + /** The required security protocol */ + val securityType: EversenseSecurityType +) diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/EversenseE3Packets.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/EversenseE3Packets.kt new file mode 100644 index 00000000000..0d9d1cb75c1 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/EversenseE3Packets.kt @@ -0,0 +1,117 @@ +package com.nightscout.eversense.packets.e3 + +class EversenseE3Packets { + companion object { + const val AssertSnoozeAgainsAlarmCommandId = 20.toByte() + const val AssertSnoozeAgainsAlarmResponseId = 148.toByte() + const val CalibrationAlertPush = 77.toByte() + const val CalibrationPush = 67.toByte() + const val CalibrationSwitchPush = 76.toByte() + const val ChangeTimingParametersCommandId = 117.toByte() + const val ChangeTimingParametersResponseId = 245.toByte() + const val ClearErrorFlagsCommandId = 4.toByte() + const val ClearErrorFlagsResponseId = 132.toByte() + const val DisconnectBLESavingBondingInformationCommandId = 116.toByte() + const val DisconnectBLESavingBondingInformationResponseId = 244.toByte() + const val EnterDiagnosticModeCommandId = 118.toByte() + const val EnterDiagnosticModeResponseId = 246.toByte() + const val ErrorResponseId = 128.toByte() + const val ExerciseVibrationCommandId = 106.toByte() + const val ExerciseVibrationResponseId = 234.toByte() + const val ExitDiagnosticModeCommandId = 119.toByte() + const val ExitDiagnosticModeResponseId = 247.toByte() + const val GlucoseLevelAlarmPush = 64.toByte() + const val GlucoseLevelAlertPush = 65.toByte() + const val HardwareStatusPush = 69.toByte() + const val KeepAlivePush = 80.toByte() + const val LinkTransmitterWithSensorCommandId = 2.toByte() + const val LinkTransmitterWithSensorResponseId = 130.toByte() + const val MarkPatientEventRecordAsDeletedCommandId = 29.toByte() + const val MarkPatientEventRecordAsDeletedResponseId = 157.toByte() + const val PingCommandId = 1.toByte() + const val PingResponseId = 129.toByte() + const val RateAndPredictiveAlertPush = 66.toByte() + const val ReadAllAvailableSensorsResponseId = 134.toByte() + const val ReadAllSensorGlucoseAlertsInSpecifiedRangeCommandId = 113.toByte() + const val ReadAllSensorGlucoseAlertsInSpecifiedRangeResponseId = 241.toByte() + const val ReadAllSensorGlucoseDataInSpecifiedRangeCommandId = 112.toByte() + const val ReadAllSensorGlucoseDataInSpecifiedRangeResponseId = 240.toByte() + const val ReadCurrentTransmitterDateAndTimeCommandId = 25.toByte() + const val ReadCurrentTransmitterDateAndTimeResponseId = 153.toByte() + const val ReadFirstAndLastBloodGlucoseDataRecordNumbersCommandId = 23.toByte() + const val ReadFirstAndLastBloodGlucoseDataRecordNumbersResponseId = 151.toByte() + const val ReadFirstAndLastErrorLogRecordNumbersCommandId = 39.toByte() + const val ReadFirstAndLastErrorLogRecordNumbersResponseId = 167.toByte() + const val ReadFirstAndLastMiscEventLogRecordNumbersCommandId = 35.toByte() + const val ReadFirstAndLastMiscEventLogRecordNumbersResponseId = 163.toByte() + const val ReadFirstAndLastPatientEventRecordNumbersCommandId = 28.toByte() + const val ReadFirstAndLastPatientEventRecordNumbersResponseId = 156.toByte() + const val ReadFirstAndLastSensorGlucoseAlertRecordNumbersCommandId = 18.toByte() + const val ReadFirstAndLastSensorGlucoseAlertRecordNumbersResponseId = 146.toByte() + const val ReadFirstAndLastSensorGlucoseRecordNumbersCommandId = 14.toByte() + const val ReadFirstAndLastSensorGlucoseRecordNumbersResponseId = 142.toByte() + const val ReadFourByteSerialFlashRegisterCommandId = 46.toByte() + const val ReadFourByteSerialFlashRegisterResponseId = 174.toByte() + const val ReadLogOfBloodGlucoseDataInSpecifiedRangeCommandId = 114.toByte() + const val ReadLogOfBloodGlucoseDataInSpecifiedRangeResponseId = 242.toByte() + const val ReadLogOfPatientEventsInSpecifiedRangeCommandId = 115.toByte() + const val ReadLogOfPatientEventsInSpecifiedRangeResponseId = 243.toByte() + const val ReadNByteSerialFlashRegisterCommandId = 48.toByte() + const val ReadNByteSerialFlashRegisterResponseId = 176.toByte() + const val ReadSensorGlucoseAlertsAndStatusCommandId = 16.toByte() + const val ReadSensorGlucoseAlertsAndStatusResponseId = 144.toByte() + const val ReadSensorGlucoseCommandId = 8.toByte() + const val ReadSensorGlucoseResponseId = 136.toByte() + const val ReadSingleBloodGlucoseDataRecordCommandId = 22.toByte() + const val ReadSingleBloodGlucoseDataRecordResponseId = 150.toByte() + const val ReadSingleByteSerialFlashRegisterCommandId = 42.toByte() + const val ReadSingleByteSerialFlashRegisterResponseId = 170.toByte() + const val ReadSingleMiscEventLogCommandId = 34.toByte() + const val ReadSingleMiscEventLogResponseId = 162.toByte() + const val ReadSinglePatientEventCommandId = 27.toByte() + const val ReadSinglePatientEventResponseId = 155.toByte() + const val ReadSingleSensorGlucoseAlertRecordCommandId = 17.toByte() + const val ReadSingleSensorGlucoseAlertRecordResponseId = 145.toByte() + const val ReadSingleSensorGlucoseDataRecordResponseId = 137.toByte() + const val ReadTwoByteSerialFlashRegisterCommandId = 44.toByte() + const val ReadTwoByteSerialFlashRegisterResponseId = 172.toByte() + const val ResetTransmitterCommandId = 3.toByte() + const val ResetTransmitterResponseId = 131.toByte() + const val SaveBLEBondingInformationCommandId = 105.toByte() + const val SaveBLEBondingInformationResponseId = 233.toByte() + const val SendBloodGlucoseDataCommandId = 21.toByte() + const val SendBloodGlucoseDataResponseId = 149.toByte() + const val SendBloodGlucoseDataWithTwoTimestampsCommandId = 60.toByte() + const val SendBloodGlucoseDataWithTwoTimestampsResponseId = 188.toByte() + const val SensorReadAlertPush = 73.toByte() + const val SensorReplacement2Push = 75.toByte() + const val SensorReplacementPush = 68.toByte() + const val SetCurrentTransmitterDateAndTimeCommandId = 7.toByte() + const val SetCurrentTransmitterDateAndTimeResponseId = 135.toByte() + const val StartSelfTestSequenceCommandId = 5.toByte() + const val StartSelfTestSequenceResponseId = 133.toByte() + const val TestResponseId = 224.toByte() + const val TransmitterBatteryPush = 71.toByte() + const val TransmitterEOLPush = 74.toByte() + const val WriteFourByteSerialFlashRegisterCommandId = 47.toByte() + const val WriteFourByteSerialFlashRegisterResponseId = 175.toByte() + const val WriteNByteSerialFlashRegisterCommandId = 49.toByte() + const val WriteNByteSerialFlashRegisterResponseId = 177.toByte() + const val WritePatientEventCommandId = 26.toByte() + const val WritePatientEventResponseId = 154.toByte() + const val WriteSingleByteSerialFlashRegisterCommandId = 43.toByte() + const val WriteSingleByteSerialFlashRegisterResponseId = 171.toByte() + const val WriteSingleMiscEventLogRecordCommandId = 36.toByte() + const val WriteSingleMiscEventLogRecordResponseId = 164.toByte() + const val WriteTwoByteSerialFlashRegisterCommandId = 45.toByte() + const val WriteTwoByteSerialFlashRegisterResponseId = 173.toByte() + + fun isPushPacket(data: Byte): Boolean { + return data == KeepAlivePush + } + + fun isErrorPacket(data: Byte): Boolean { + return data == ErrorResponseId + } + } +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetBatteryPercentagePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetBatteryPercentagePacket.kt new file mode 100644 index 00000000000..aa5743a8fdf --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetBatteryPercentagePacket.kt @@ -0,0 +1,47 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetBatteryPercentagePacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.BatteryPercentage.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(percentage = parseEnum(receivedData[getStartIndex()].toInt())) + } + + private fun parseEnum(value: Int): Int { + return when(value) { + 0 -> 0 //% + 1 -> 5 //% + 2 -> 10 //% + 3 -> 25 //% + 4 -> 35 //% + 5 -> 45 //% + 6 -> 55 //% + 7 -> 65 //% + 8 -> 75 //% + 9 -> 85 //% + 10 -> 95 //% + 11 -> 100 //% + else -> -1 + } + } + + data class Response(val percentage: Int) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetCurrentDatetimePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetCurrentDatetimePacket.kt new file mode 100644 index 00000000000..46b77105bd3 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetCurrentDatetimePacket.kt @@ -0,0 +1,49 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.EversenseLogger +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Parser +import java.util.TimeZone +import kotlin.math.abs + +@EversensePacket( + requestId = EversenseE3Packets.ReadCurrentTransmitterDateAndTimeCommandId, + responseId = EversenseE3Packets.ReadCurrentTransmitterDateAndTimeResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetCurrentDatetimePacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return ByteArray(0) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + val start = getStartIndex() + val date = EversenseE3Parser.readDate(receivedData, start) + val time = EversenseE3Parser.readTime(receivedData, start + 2) + val timeZoneOffset = EversenseE3Parser.readTimezone(receivedData, start + 4) + + var needsTimeSync = false + val actualTimeZoneOffset = TimeZone.getDefault().getOffset(System.currentTimeMillis()).toLong() + + // Allow time drift <10s + if (abs(System.currentTimeMillis() - (date + time)) > 10_000) { + EversenseLogger.warning("GetCurrentDatetimePacket", "time drift detected... drift: ${abs(System.currentTimeMillis() - (date + time))} ms") + needsTimeSync = true + } else if (actualTimeZoneOffset != timeZoneOffset) { + EversenseLogger.warning("GetCurrentDatetimePacket", "timezone mismatch - received: $timeZoneOffset, actual: $actualTimeZoneOffset") + needsTimeSync = true + } + + return Response(date + time, timeZoneOffset, needsTimeSync) + } + + data class Response(val datetime: Long, val timezoneOffset: Long, val needsTimeSync: Boolean): EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetCurrentGlucosePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetCurrentGlucosePacket.kt new file mode 100644 index 00000000000..c3f63b2af77 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetCurrentGlucosePacket.kt @@ -0,0 +1,45 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.enums.EversenseTrendArrow +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Parser + +@EversensePacket( + requestId = EversenseE3Packets.ReadSensorGlucoseCommandId, + responseId = EversenseE3Packets.ReadSensorGlucoseResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetCurrentGlucosePacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return ByteArray(0) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response( + datetime = EversenseE3Parser.readDate(receivedData, 4) + EversenseE3Parser.readTime(receivedData, 6), + glucoseInMgDl = EversenseE3Parser.readGlucose(receivedData, 9), + trend = parseTrend(receivedData[13].toInt()), + ) + } + + private fun parseTrend(value: Int): EversenseTrendArrow { + return when(value) { + 1 -> EversenseTrendArrow.SINGLE_DOWN + 2 -> EversenseTrendArrow.FORTY_FIVE_DOWN + 4 -> EversenseTrendArrow.FLAT + 8 -> EversenseTrendArrow.FORTY_FIVE_UP + 16 -> EversenseTrendArrow.SINGLE_UP + else -> EversenseTrendArrow.NONE + } + } + + data class Response(val datetime: Long, val glucoseInMgDl: Int, val trend: EversenseTrendArrow) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetInsertionDatePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetInsertionDatePacket.kt new file mode 100644 index 00000000000..4ab0e89d9e1 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetInsertionDatePacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Parser + +@EversensePacket( + requestId = EversenseE3Packets.ReadTwoByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadTwoByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetInsertionDatePacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.SensorInsertionDate.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(date = EversenseE3Parser.readDate(receivedData, getStartIndex())) + } + + data class Response(val date: Long) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetInsertionTimePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetInsertionTimePacket.kt new file mode 100644 index 00000000000..6bac00734e4 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetInsertionTimePacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Parser + +@EversensePacket( + requestId = EversenseE3Packets.ReadTwoByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadTwoByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetInsertionTimePacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.SensorInsertionTime.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(time = EversenseE3Parser.readTime(receivedData, getStartIndex())) + } + + data class Response(val time: Long) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingGlucoseHighEnabled.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingGlucoseHighEnabled.kt new file mode 100644 index 00000000000..0c05e0088a4 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingGlucoseHighEnabled.kt @@ -0,0 +1,29 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingGlucoseHighEnabled : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.HighGlucoseAlarmEnabled.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(enabled = receivedData[getStartIndex()].toInt() == 0x55) + } + + data class Response(val enabled: Boolean) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingGlucoseHighThresholdPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingGlucoseHighThresholdPacket.kt new file mode 100644 index 00000000000..30e4fe49bf3 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingGlucoseHighThresholdPacket.kt @@ -0,0 +1,32 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Parser + +@EversensePacket( + requestId = EversenseE3Packets.ReadTwoByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadTwoByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingGlucoseHighThresholdPacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.HighGlucoseAlarmThreshold.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response( + threshold = EversenseE3Parser.readGlucose(receivedData, getStartIndex()) + ) + } + + data class Response(val threshold: Int) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingGlucoseLowThresholdPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingGlucoseLowThresholdPacket.kt new file mode 100644 index 00000000000..59b2820d1fd --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingGlucoseLowThresholdPacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Parser + +@EversensePacket( + requestId = EversenseE3Packets.ReadTwoByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadTwoByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingGlucoseLowThresholdPacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.LowGlucoseAlarmThreshold.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(threshold = EversenseE3Parser.readGlucose(receivedData, getStartIndex())) + } + + data class Response(val threshold: Int): EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveAlarmEnabledPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveAlarmEnabledPacket.kt new file mode 100644 index 00000000000..aa0d93bb93b --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveAlarmEnabledPacket.kt @@ -0,0 +1,29 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingPredictiveAlarmEnabledPacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.PredictiveAlert.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(enabled = receivedData[getStartIndex()].toInt() == 0x55) + } + + data class Response(val enabled: Boolean) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveHighEnabledPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveHighEnabledPacket.kt new file mode 100644 index 00000000000..34d8a0f43de --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveHighEnabledPacket.kt @@ -0,0 +1,29 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingPredictiveHighEnabledPacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.PredictiveHighAlert.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(enabled = receivedData[getStartIndex()].toInt() == 0x55) + } + + data class Response(val enabled: Boolean) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveHighThresholdPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveHighThresholdPacket.kt new file mode 100644 index 00000000000..f84fef95d79 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveHighThresholdPacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Parser + +@EversensePacket( + requestId = EversenseE3Packets.ReadTwoByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadTwoByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingPredictiveHighThresholdPacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.PredictiveHighTarget.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(threshold = EversenseE3Parser.readGlucose(receivedData, getStartIndex())) + } + + data class Response(val threshold: Int) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveHighTimePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveHighTimePacket.kt new file mode 100644 index 00000000000..449e34de813 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveHighTimePacket.kt @@ -0,0 +1,29 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingPredictiveHighTimePacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.PredictiveHighTime.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(minutes = receivedData[getStartIndex()].toInt()) + } + + data class Response(val minutes: Int) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveLowEnabledPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveLowEnabledPacket.kt new file mode 100644 index 00000000000..4da3220c867 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveLowEnabledPacket.kt @@ -0,0 +1,29 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingPredictiveLowEnabledPacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.PredictiveLowAlert.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(enabled = receivedData[getStartIndex()].toInt() == 0x55) + } + + data class Response(val enabled: Boolean) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveLowThresholdPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveLowThresholdPacket.kt new file mode 100644 index 00000000000..65a10358adc --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveLowThresholdPacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Parser + +@EversensePacket( + requestId = EversenseE3Packets.ReadTwoByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadTwoByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingPredictiveLowThresholdPacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.PredictiveLowTarget.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(threshold = EversenseE3Parser.readGlucose(receivedData, getStartIndex())) + } + + data class Response(val threshold: Int) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveLowTimePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveLowTimePacket.kt new file mode 100644 index 00000000000..92118b40063 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingPredictiveLowTimePacket.kt @@ -0,0 +1,29 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingPredictiveLowTimePacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.PredictiveLowTime.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(minutes = receivedData[getStartIndex()].toInt()) + } + + data class Response(val minutes: Int) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateEnabledPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateEnabledPacket.kt new file mode 100644 index 00000000000..7830f3f05ec --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateEnabledPacket.kt @@ -0,0 +1,29 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingRateEnabledPacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.RateAlert.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(enabled = receivedData[getStartIndex()].toInt() == 0x55) + } + + data class Response(val enabled: Boolean) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateFallingEnabledPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateFallingEnabledPacket.kt new file mode 100644 index 00000000000..abe2049069a --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateFallingEnabledPacket.kt @@ -0,0 +1,29 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingRateFallingEnabledPacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.RateFallingAlert.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(enabled = receivedData[getStartIndex()].toInt() == 0x55) + } + + data class Response(val enabled: Boolean) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateFallingThresholdPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateFallingThresholdPacket.kt new file mode 100644 index 00000000000..367970389c2 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateFallingThresholdPacket.kt @@ -0,0 +1,29 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingRateFallingThresholdPacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.RateFallingThreshold.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(threshold = receivedData[getStartIndex()].toDouble() / 10) + } + + data class Response(val threshold: Double): EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateRisingEnabledPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateRisingEnabledPacket.kt new file mode 100644 index 00000000000..68d1f6df7a4 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateRisingEnabledPacket.kt @@ -0,0 +1,29 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingRateRisingEnabledPacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.RateRisingAlert.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(enabled = receivedData[getStartIndex()].toInt() == 0x55) + } + + data class Response(val enabled: Boolean) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateRisingThresholdPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateRisingThresholdPacket.kt new file mode 100644 index 00000000000..01e110c392e --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingRateRisingThresholdPacket.kt @@ -0,0 +1,29 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingRateRisingThresholdPacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.RateRisingThreshold.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response(threshold = receivedData[getStartIndex()].toDouble() / 10) + } + + data class Response(val threshold: Double) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingVibratePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingVibratePacket.kt new file mode 100644 index 00000000000..babdee49d2c --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/GetSettingVibratePacket.kt @@ -0,0 +1,31 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.ReadSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class GetSettingVibratePacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.VibrateMode.getRequestData() + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response( + enabled = receivedData[getStartIndex()].toInt() == 0x55 + ) + } + + data class Response(val enabled: Boolean) : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SaveBondingInformationPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SaveBondingInformationPacket.kt new file mode 100644 index 00000000000..588a359d7e9 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SaveBondingInformationPacket.kt @@ -0,0 +1,28 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.SaveBLEBondingInformationCommandId, + responseId = EversenseE3Packets.SaveBLEBondingInformationResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SaveBondingInformationPacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return ByteArray(0) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return SaveBondingInformationResponse() + } + + class SaveBondingInformationResponse() : Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetCurrentDatetimePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetCurrentDatetimePacket.kt new file mode 100644 index 00000000000..e1c45c77a00 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetCurrentDatetimePacket.kt @@ -0,0 +1,32 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.SetCurrentTransmitterDateAndTimeCommandId, + responseId = EversenseE3Packets.SetCurrentTransmitterDateAndTimeResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetCurrentDatetimePacket : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + val now = System.currentTimeMillis() + return EversenseE3Writer.writeDate(now) + + EversenseE3Writer.writeTime(now) + + EversenseE3Writer.writeTimezone(now) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response : EversenseBasePacket.Response() {} +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingGlucoseHighEnablePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingGlucoseHighEnablePacket.kt new file mode 100644 index 00000000000..91ffa1f2b93 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingGlucoseHighEnablePacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingGlucoseHighEnablePacket(private val enabled: Boolean) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.HighGlucoseAlarmEnabled.getRequestData() + EversenseE3Writer.writeBoolean(enabled) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response() : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingGlucoseHighThresholdPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingGlucoseHighThresholdPacket.kt new file mode 100644 index 00000000000..e4620c5ea40 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingGlucoseHighThresholdPacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.WriteTwoByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteTwoByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingGlucoseHighThresholdPacket(private val threshold: Int) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.HighGlucoseAlarmThreshold.getRequestData() + EversenseE3Writer.writeInt16(threshold) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response() : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingGlucoseLowThresholdPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingGlucoseLowThresholdPacket.kt new file mode 100644 index 00000000000..02f64c23242 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingGlucoseLowThresholdPacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.WriteTwoByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteTwoByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingGlucoseLowThresholdPacket(private val threshold: Int) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.LowGlucoseAlarmThreshold.getRequestData() + EversenseE3Writer.writeInt16(threshold) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response() : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveAlarmEnabledPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveAlarmEnabledPacket.kt new file mode 100644 index 00000000000..a92bb37fe6d --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveAlarmEnabledPacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingPredictiveAlarmEnabledPacket(private val enabled: Boolean) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.PredictiveAlert.getRequestData() + EversenseE3Writer.writeBoolean(enabled) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveHighAlarmEnabledPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveHighAlarmEnabledPacket.kt new file mode 100644 index 00000000000..bbe4d10e355 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveHighAlarmEnabledPacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingPredictiveHighAlarmEnabledPacket(private val enabled: Boolean) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.PredictiveHighAlert.getRequestData() + EversenseE3Writer.writeBoolean(enabled) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveHighThresholdPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveHighThresholdPacket.kt new file mode 100644 index 00000000000..d3d91f127ce --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveHighThresholdPacket.kt @@ -0,0 +1,31 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Parser +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.WriteTwoByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteTwoByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingPredictiveHighThresholdPacket(private val threshold: Int) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.PredictiveHighTarget.getRequestData() + EversenseE3Writer.writeInt16(threshold) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveHighTimePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveHighTimePacket.kt new file mode 100644 index 00000000000..61f0e39b535 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveHighTimePacket.kt @@ -0,0 +1,29 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingPredictiveHighTimePacket(private val minutes: Int) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.PredictiveHighTime.getRequestData() + byteArrayOf(minutes.toByte()) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveLowAlarmEnabledPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveLowAlarmEnabledPacket.kt new file mode 100644 index 00000000000..b2464e355f4 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveLowAlarmEnabledPacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingPredictiveLowAlarmEnabledPacket(private val enabled: Boolean) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.PredictiveLowAlert.getRequestData() + EversenseE3Writer.writeBoolean(enabled) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveLowThresholdPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveLowThresholdPacket.kt new file mode 100644 index 00000000000..c4012e2eded --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveLowThresholdPacket.kt @@ -0,0 +1,31 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Parser +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.WriteTwoByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteTwoByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingPredictiveLowThresholdPacket(private val threshold: Int) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.PredictiveLowTarget.getRequestData() + EversenseE3Writer.writeInt16(threshold) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveLowTimePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveLowTimePacket.kt new file mode 100644 index 00000000000..11e45db2503 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingPredictiveLowTimePacket.kt @@ -0,0 +1,29 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket + +@EversensePacket( + requestId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingPredictiveLowTimePacket(private val minutes: Int) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.PredictiveLowTime.getRequestData() + byteArrayOf(minutes.toByte()) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateEnabledPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateEnabledPacket.kt new file mode 100644 index 00000000000..c00810a5342 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateEnabledPacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingRateEnabledPacket(private val enabled: Boolean) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.RateAlert.getRequestData() + EversenseE3Writer.writeBoolean(enabled) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateFallingEnabledPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateFallingEnabledPacket.kt new file mode 100644 index 00000000000..ee2b0bb1418 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateFallingEnabledPacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingRateFallingEnabledPacket(private val enabled: Boolean) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.RateFallingAlert.getRequestData() + EversenseE3Writer.writeBoolean(enabled) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateFallingThresholdPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateFallingThresholdPacket.kt new file mode 100644 index 00000000000..fee749d1cac --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateFallingThresholdPacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingRateFallingThresholdPacket(private val threshold: Double) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.RateFallingThreshold.getRequestData() + EversenseE3Writer.writeDouble(threshold) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateRisingEnabledPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateRisingEnabledPacket.kt new file mode 100644 index 00000000000..b0aa477f8b3 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateRisingEnabledPacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingRateRisingEnabledPacket(private val enabled: Boolean) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.RateRisingAlert.getRequestData() + EversenseE3Writer.writeBoolean(enabled) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateRisingThresholdPacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateRisingThresholdPacket.kt new file mode 100644 index 00000000000..2d8d1da2200 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingRateRisingThresholdPacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingRateRisingThresholdPacket(private val threshold: Double) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.RateRisingThreshold.getRequestData() + EversenseE3Writer.writeDouble(threshold) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingVibratePacket.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingVibratePacket.kt new file mode 100644 index 00000000000..9e9361b9438 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/SetSettingVibratePacket.kt @@ -0,0 +1,30 @@ +package com.nightscout.eversense.packets.e3 + +import com.nightscout.eversense.enums.EversenseE3Memory +import com.nightscout.eversense.enums.EversenseSecurityType +import com.nightscout.eversense.packets.EversenseBasePacket +import com.nightscout.eversense.packets.EversensePacket +import com.nightscout.eversense.packets.e3.util.EversenseE3Writer + +@EversensePacket( + requestId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterCommandId, + responseId = EversenseE3Packets.WriteSingleByteSerialFlashRegisterResponseId, + responseType = 0, + securityType = EversenseSecurityType.None +) +class SetSettingVibratePacket(private val enabled: Boolean) : EversenseBasePacket() { + + override fun getRequestData(): ByteArray { + return EversenseE3Memory.VibrateMode.getRequestData() + EversenseE3Writer.writeBoolean(enabled) + } + + override fun parseResponse(): Response? { + if (receivedData.isEmpty()) { + return null + } + + return Response() + } + + class Response : EversenseBasePacket.Response() +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/util/EversenseE3Parser.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/util/EversenseE3Parser.kt new file mode 100644 index 00000000000..bfc6fd339d5 --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/util/EversenseE3Parser.kt @@ -0,0 +1,69 @@ +package com.nightscout.eversense.packets.e3.util + +import android.util.Log +import java.util.Calendar +import java.util.TimeZone + +class EversenseE3Parser { + companion object { + fun readDate(data: UByteArray, start: Int): Long { + val lowBit = data[start].toInt() + val highBit = data[start+1].toInt() + + val day = lowBit and 31 + var month = lowBit shr 5 + val year = (highBit shr 1) + 2000 + + if (highBit and 1 == 1) { + month += 8 + } + + val calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")) + calendar.set(Calendar.YEAR, year) + calendar.set(Calendar.MONTH, month - 1) + calendar.set(Calendar.DAY_OF_MONTH, day) + calendar.set(Calendar.HOUR_OF_DAY, 0) + calendar.set(Calendar.MINUTE, 0) + calendar.set(Calendar.SECOND, 0) + calendar.set(Calendar.MILLISECOND, 0) + + return calendar.timeInMillis + } + + fun readTime(data: UByteArray, start: Int): Long { + val lowBit = data[start].toInt() + val highBit = data[start+1].toInt() + + val hour = highBit shr 3 + val minute = ((highBit and 7) shl 3) or (lowBit shr 5) + val second = (lowBit and 31) * 2 + + + val calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")) + calendar.set(Calendar.YEAR, 1970) + calendar.set(Calendar.MONTH, 0) + calendar.set(Calendar.DAY_OF_MONTH, 1) + calendar.set(Calendar.HOUR_OF_DAY, hour) + calendar.set(Calendar.MINUTE, minute) + calendar.set(Calendar.SECOND, second) + calendar.set(Calendar.MILLISECOND, 0) + + return calendar.timeInMillis + } + + fun readTimezone(data: UByteArray, start: Int): Long { + var timezoneOffset = readTime(data, start) + if (data[start + 2] != 0.toUByte()) { + timezoneOffset *= -1 + } + + return timezoneOffset + } + + fun readGlucose(data: UByteArray, start: Int): Int { + val lowBit = data[start].toInt() + val highBit = data[start+1].toInt() shl 8 + return lowBit or highBit + } + } +} \ No newline at end of file diff --git a/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/util/EversenseE3Writer.kt b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/util/EversenseE3Writer.kt new file mode 100644 index 00000000000..922d3af4acf --- /dev/null +++ b/plugins/eversense/src/main/kotlin/com/nightscout/eversense/packets/e3/util/EversenseE3Writer.kt @@ -0,0 +1,77 @@ +package com.nightscout.eversense.packets.e3.util + +import java.util.Calendar +import java.util.TimeZone +import kotlin.math.PI + +class EversenseE3Writer { + companion object { + fun generateChecksumCRC16(data: ByteArray): ByteArray { + var crc = 0xFFFF + + for (byte in data) { + var currentByte = byte.toInt() and 0xFF + repeat(8) { + val xor = ((crc shr 15) and 0x01) xor ((currentByte shr 7) and 0x01) + crc = (crc shl 1) and 0xFFFF + if (xor != 0) { + crc = (crc xor 0x1021) and 0xFFFF + } + currentByte = (currentByte shl 1) and 0xFF + } + } + + return writeInt16(crc) + } + + fun writeDate(timestamp: Long): ByteArray { + val calendar = Calendar.getInstance() + calendar.setTimeInMillis(timestamp) + + val year = calendar.get(Calendar.YEAR) - 2000 + val month = calendar.get(Calendar.MONTH) + 1 + val day = calendar.get(Calendar.DAY_OF_MONTH) + + val byte1 = (month shl 5) or day + val byte2 = (year shl 1) or (if (month >= 8) 1 else 0) + + return byteArrayOf(byte1.toByte(), byte2.toByte()) + } + + fun writeTime(timestamp: Long): ByteArray { + val calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")) + calendar.setTimeInMillis(timestamp) + + val hour = calendar.get(Calendar.HOUR_OF_DAY) + val minute = calendar.get(Calendar.MINUTE) + val second = calendar.get(Calendar.SECOND) + + val byte1 = ((minute and 7) shl 5) or (second / 2) + val byte2 = (hour shl 3) or ((minute and 56) shr 3) + + return byteArrayOf(byte1.toByte(), byte2.toByte()) + } + + fun writeTimezone(timestamp: Long): ByteArray { + val timezoneOffset = TimeZone.getDefault().getOffset(timestamp) + val timezoneNegative = if (timezoneOffset < 0) 255 else 0 + + return writeTime(timezoneOffset.toLong()) + byteArrayOf(timezoneNegative.toByte()) + } + + fun writeBoolean(value: Boolean): ByteArray { + return byteArrayOf(if (value) 0x55 else 0x00) + } + + fun writeDouble(value: Double): ByteArray { + return writeInt16((value * 10).toInt()) + } + + fun writeInt16(value: Int): ByteArray { + return byteArrayOf( + value.toByte(), + (value shr 8).toByte() + ) + } + } +} \ No newline at end of file diff --git a/plugins/source/build.gradle.kts b/plugins/source/build.gradle.kts index b8f856c326b..dadbe70dc94 100644 --- a/plugins/source/build.gradle.kts +++ b/plugins/source/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(project(":core:utils")) implementation(project(":core:validators")) implementation(project(":shared:impl")) + implementation(project(":plugins:eversense")) testImplementation(libs.androidx.work.testing) diff --git a/plugins/source/src/main/kotlin/app/aaps/plugins/source/EversensePlugin.kt b/plugins/source/src/main/kotlin/app/aaps/plugins/source/EversensePlugin.kt new file mode 100644 index 00000000000..89a23b892c7 --- /dev/null +++ b/plugins/source/src/main/kotlin/app/aaps/plugins/source/EversensePlugin.kt @@ -0,0 +1,147 @@ +package app.aaps.plugins.source + +import android.content.Context +import androidx.preference.EditTextPreference +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceManager +import androidx.preference.PreferenceScreen +import app.aaps.core.data.model.GV +import app.aaps.core.data.model.SourceSensor +import app.aaps.core.data.model.TrendArrow +import app.aaps.core.data.plugin.PluginType +import app.aaps.core.data.ue.Sources +import app.aaps.core.interfaces.db.PersistenceLayer +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.logging.LTag +import app.aaps.core.interfaces.plugin.PluginDescription +import app.aaps.core.interfaces.resources.ResourceHelper +import app.aaps.core.interfaces.source.BgSource +import app.aaps.core.keys.interfaces.Preferences +import app.aaps.core.validators.preferences.AdaptiveStringPreference +import com.nightscout.eversense.EversenseCGMPlugin +import com.nightscout.eversense.callbacks.EversenseWatcher +import com.nightscout.eversense.enums.EversenseType +import com.nightscout.eversense.models.EversenseCGMResult +import com.nightscout.eversense.models.EversenseState +import kotlinx.serialization.json.Json +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import javax.inject.Inject + +class EversensePlugin @Inject constructor( + rh: ResourceHelper, + private val context: Context, + aapsLogger: AAPSLogger, + preferences: Preferences +) : AbstractBgSourcePlugin( + PluginDescription() + .mainType(PluginType.BGSOURCE) + .fragmentClass(BGSourceFragment::class.java.name) + // Fix: Explicitly point to the core object R file + .pluginIcon(app.aaps.core.objects.R.drawable.ic_blooddrop_48) + .preferencesId(PluginDescription.PREFERENCE_SCREEN) + .pluginName(R.string.source_eversense) + .preferencesVisibleInSimpleMode(false) + .description(R.string.description_source_eversense), + ownPreferences = emptyList(), + aapsLogger, rh, preferences +), BgSource, EversenseWatcher { + + @Inject lateinit var persistenceLayer: PersistenceLayer + + private val dateFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()) + + private var connectedPreference: Preference? = null + private var batteryPreference: Preference? = null + private var insertionPreference: Preference? = null + private var lastSyncPreference: Preference? = null + + // No extra overrides needed; the abstract class handles defaults. + init { + EversenseCGMPlugin.instance.setContext(context, true) + EversenseCGMPlugin.instance.addWatcher(this) + + EversenseCGMPlugin.instance.connect(null) + } + + override fun addPreferenceScreen(preferenceManager: PreferenceManager, parent: PreferenceScreen, context: Context, requiredKey: String?) { + super.addPreferenceScreen(preferenceManager, parent, context, requiredKey) + + val state = EversenseCGMPlugin.instance.getCurrentState() ?:run { return } + + val category = PreferenceCategory(context) + parent.addPreference(category) + category.apply { + title = rh.gs(R.string.eversense_information_title) + + val connected = Preference(context) + connected.key = "eversense_information_connected" + connected.title = rh.gs(R.string.eversense_information_connected) + connected.summary = if (EversenseCGMPlugin.instance.isConnected()) "✅" else "❌" + addPreference(connected) + connectedPreference = connected + + val battery = Preference(context) + battery.key = "eversense_information_battery" + battery.title = rh.gs(R.string.eversense_information_battery) + battery.summary = "${state.batteryPercentage}%" + addPreference(battery) + batteryPreference = battery + + val insertion = Preference(context) + insertion.key = "eversense_information_insertion_date" + insertion.title = rh.gs(R.string.eversense_information_insertion_date) + insertion.summary = dateFormatter.format(Date(state.insertionDate)) + addPreference(insertion) + insertionPreference = insertion + + val lastSync = Preference(context) + lastSync.key = "eversense_information_last_sync" + lastSync.title = rh.gs(R.string.eversense_information_last_sync) + lastSync.summary = dateFormatter.format(Date(state.lastSync)) + addPreference(lastSync) + lastSyncPreference = lastSync + } + } + + override fun onStateChanged(state: EversenseState) { + aapsLogger.info(LTag.BGSOURCE, "New state received: ${Json.encodeToString(state)}") + + batteryPreference?.let { it.summary = "${state.batteryPercentage}%" } + insertionPreference?.let { it.summary = dateFormatter.format(Date(state.insertionDate)) } + lastSyncPreference?.let { it.summary = dateFormatter.format(Date(state.lastSync)) } + } + + override fun onConnectionChanged(connected: Boolean) { + aapsLogger.info(LTag.BGSOURCE, "Connection changed - connected: $connected") + + connectedPreference?.let { it.summary = if (connected) "✅" else "❌" } + } + + override fun onCGMRead(type: EversenseType, readings: List) { + val glucoseValues = mutableListOf() + + for (reading in readings) { + glucoseValues += GV( + timestamp = reading.datetime, + value = reading.glucoseInMgDl.toDouble(), + noise = null, + raw = null, + trendArrow = TrendArrow.fromString(reading.trend.type), + sourceSensor = when (type) { + EversenseType.EVERSENSE_365 -> SourceSensor.EVERSENSE_365 + EversenseType.EVERSENSE_E3 -> SourceSensor.EVERSENSE_E3 + } + ) + } + + val result = persistenceLayer.insertCgmSourceData( + Sources.Eversense, + glucoseValues, + listOf(), + null + ).blockingGet() + } +} diff --git a/plugins/source/src/main/res/values/strings.xml b/plugins/source/src/main/res/values/strings.xml index e68863eb533..25a3c534435 100644 --- a/plugins/source/src/main/res/values/strings.xml +++ b/plugins/source/src/main/res/values/strings.xml @@ -25,7 +25,6 @@ Random BG Generate random BG data (Demo mode only) BG - BG source settings Accept sensor start Create event \"CGM Sensor Insert\" in Careportal automatically on received sensor start from BG source local broadcast @@ -40,4 +39,12 @@ Sino App Receive glucose data from Patched Sino App. + + Eversense + Eversense E3/365 driver + Information + Connected + Battery percentage + Insertion date + Last synchronisation \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 0e256df9b68..61c70a6a539 100644 --- a/settings.gradle +++ b/settings.gradle @@ -19,6 +19,7 @@ include ':plugins:aps' include ':plugins:automation' include ':plugins:configuration' include ':plugins:constraints' +include ':plugins:eversense' include ':plugins:insulin' include ':plugins:main' include ':plugins:sensitivity'