Merge pull request #2388 from nightscout/xdrip

New xDrip sync
This commit is contained in:
Milos Kozak 2023-02-04 18:45:37 +01:00 committed by GitHub
commit 6c536d7eee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1145 additions and 308 deletions

View file

@ -0,0 +1,21 @@
package info.nightscout.rx.events
import java.text.SimpleDateFormat
import java.util.Locale
class EventXdripNewLog(val action: String, val logText: String?) : Event() {
var date = System.currentTimeMillis()
private var timeFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
fun toPreparedHtml(): StringBuilder {
val stringBuilder = StringBuilder()
stringBuilder.append(timeFormat.format(date))
stringBuilder.append(" <b>")
stringBuilder.append(action)
stringBuilder.append("</b> ")
stringBuilder.append(logText)
stringBuilder.append("<br>")
return stringBuilder
}
}

View file

@ -28,5 +28,6 @@ enum class LTag(val tag: String, val defaultValue : Boolean = true, val requires
UI("UI", defaultValue = false),
WEAR("WEAR"),
WIDGET("WIDGET"),
WORKER("WORKER")
WORKER("WORKER"),
XDRIP("XDRIP")
}

View file

@ -2,7 +2,7 @@
buildscript {
ext {
kotlin_version = '1.8.0'
kotlin_version = '1.8.10'
core_version = '1.9.0'
rxjava_version = '3.1.6'
rxandroid_version = '3.0.2'

View file

@ -1,18 +1,6 @@
package info.nightscout.interfaces
import info.nightscout.database.entities.Bolus
import info.nightscout.database.entities.Carbs
import info.nightscout.database.entities.DeviceStatus
import info.nightscout.database.entities.EffectiveProfileSwitch
import info.nightscout.database.entities.ExtendedBolus
import info.nightscout.database.entities.GlucoseValue
import info.nightscout.database.entities.OfflineEvent
import info.nightscout.database.entities.ProfileSwitch
import info.nightscout.database.entities.TemporaryBasal
import info.nightscout.database.entities.TemporaryTarget
import info.nightscout.database.entities.TherapyEvent
import org.json.JSONArray
import org.json.JSONObject
import info.nightscout.interfaces.sync.DataSyncSelector
/**
* Send data to xDrip+ via Inter-app settings
@ -24,64 +12,19 @@ interface XDripBroadcast {
* Accepting must be enabled in Inter-app settings - Accept Calibrations
*/
fun sendCalibration(bg: Double): Boolean
fun sendIn640gMode(glucoseValue: GlucoseValue)
fun sendProfile(profileStoreJson: JSONObject)
fun sendTreatments(addedOrUpdatedTreatments: JSONArray)
fun sendSgvs(sgvs: JSONArray)
/**
* Send data to xDrip+
* Accepting must be enabled in Inter-app settings - Accept Glucose
*
* Accepting must be enabled in Inter-app settings - Accept Glucose/Treatments
*/
fun send(gv: GlucoseValue)
fun sendToXdrip(collection: String, dataPair: DataSyncSelector.DataPair, progress: String)
/**
* Send data to xDrip+
* Accepting must be enabled in Inter-app settings - Accept treatments
*
* Accepting must be enabled in Inter-app settings - Accept Glucose/Treatments
*/
fun send(bolus: Bolus)
/**
* Send data to xDrip+
* Accepting must be enabled in Inter-app settings - Accept treatments
*/
fun send(carbs: Carbs)
/**
* Send data to xDrip+
* Accepting must be enabled in Inter-app settings - Accept treatments
*/
fun send(tt: TemporaryTarget)
/**
* Send data to xDrip+
* Accepting must be enabled in Inter-app settings - Accept treatments
*/
fun send(te: TherapyEvent)
/**
* Send data to xDrip+
* Accepting must be enabled in Inter-app settings - Accept treatments
*/
fun send(deviceStatus: DeviceStatus)
/**
* Send data to xDrip+
* Accepting must be enabled in Inter-app settings - Accept treatments
*/
fun send(tb: TemporaryBasal)
/**
* Send data to xDrip+
* Accepting must be enabled in Inter-app settings - Accept treatments
*/
fun send(eb: ExtendedBolus)
/**
* Send data to xDrip+
* Accepting must be enabled in Inter-app settings - Accept treatments
*/
fun send(ps: ProfileSwitch)
/**
* Send data to xDrip+
* Accepting must be enabled in Inter-app settings - Accept treatments
*/
fun send(ps: EffectiveProfileSwitch)
/**
* Send data to xDrip+
* Accepting must be enabled in Inter-app settings - Accept treatments
*/
fun send(ps: OfflineEvent)
fun sendToXdrip(collection: String, dataPairs: List<DataSyncSelector.DataPair>, progress:
String)
}

View file

@ -8,6 +8,8 @@ interface Intents {
// AAPS -> Xdrip
const val ACTION_NEW_TREATMENT = "info.nightscout.client.NEW_TREATMENT"
const val ACTION_NEW_PROFILE = "info.nightscout.client.NEW_PROFILE"
const val ACTION_NEW_DEVICE_STATUS = "info.nightscout.client.NEW_DEVICESTATUS"
const val ACTION_NEW_FOOD = "info.nightscout.client.NEW_FOOD"
const val ACTION_NEW_SGV = "info.nightscout.client.NEW_SGV"
const val EXTRA_STATUSLINE = "com.eveningoutpost.dexdrip.Extras.Statusline"

View file

@ -70,8 +70,7 @@
<string name="key_autotune_additional_log" translatable="false">autotune_additional_log</string>
<string name="key_autotune_plugin" translatable="false">key_autotune_plugin</string>
<string name="key_autotune_last_run" translatable="false">key_autotune_last_run</string>
<string name="key_dexcomg5_xdripupload" translatable="false">dexcomg5_xdripupload</string>
<string name="key_nsclient_localbroadcasts" translatable="false">nsclient_localbroadcasts</string>
<string name="key_xdrip_local_broadcasts" translatable="false">xdrip_local_broadcasts</string>
<string name="key_usebolusreminder" translatable="false">use_bolus_reminder</string>
<string name="key_carbs_button_increment_1" translatable="false">carbs_button_increment_1</string>
<string name="key_carbs_button_increment_2" translatable="false">carbs_button_increment_2</string>

View file

@ -16,7 +16,6 @@ import info.nightscout.core.utils.worker.LoggingWorker
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.GlucoseUnit
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.notifications.Notification
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.plugin.PluginBase
@ -443,13 +442,11 @@ class ProfilePlugin @Inject constructor(
@Inject lateinit var sp: SP
@Inject lateinit var config: Config
@Inject lateinit var profilePlugin: ProfilePlugin
@Inject lateinit var xDripBroadcast: XDripBroadcast
@Inject lateinit var instantiator: Instantiator
override suspend fun doWorkAndLog(): Result {
val profileJson = dataWorkerStorage.pickupJSONObject(inputData.getLong(DataWorkerStorage.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data"))
xDripBroadcast.sendProfile(profileJson)
if (sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_receive_profile_store, true) || config.NSCLIENT) {
val store = instantiator.provideProfileStore(profileJson)
val createdAt = store.getStartDate()

View file

@ -201,7 +201,7 @@ class BGSourceFragment : DaggerFragment(), MenuProvider {
R.string.tomato -> Sources.Tomato
R.string.glunovo -> Sources.Glunovo
R.string.intelligo -> Sources.Intelligo
R.string.xdrip -> Sources.Xdrip
R.string.source_xdrip -> Sources.Xdrip
R.string.aidex -> Sources.Aidex
else -> Sources.Unknown
}

View file

@ -20,7 +20,6 @@ import info.nightscout.database.impl.transactions.CgmSourceTransaction
import info.nightscout.database.impl.transactions.InvalidateGlucoseValueTransaction
import info.nightscout.database.transactions.TransactionGlucoseValue
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.logging.UserEntryLogger
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription
@ -91,7 +90,6 @@ class DexcomPlugin @Inject constructor(
@Inject lateinit var sp: SP
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var dataWorkerStorage: DataWorkerStorage
@Inject lateinit var xDripBroadcast: XDripBroadcast
@Inject lateinit var repository: AppRepository
@Inject lateinit var uel: UserEntryLogger
@ -185,11 +183,9 @@ class DexcomPlugin @Inject constructor(
}
}
}
xDripBroadcast.sendIn640gMode(result.inserted[i])
aapsLogger.debug(LTag.DATABASE, "Inserted bg ${result.inserted[i]}")
}
result.updated.forEach {
xDripBroadcast.sendIn640gMode(it)
aapsLogger.debug(LTag.DATABASE, "Updated bg $it")
}
result.sensorInsertionsInserted.forEach {

View file

@ -12,7 +12,6 @@ import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.transactions.CgmSourceTransaction
import info.nightscout.database.impl.transactions.InsertIfNewByTimestampTherapyEventTransaction
import info.nightscout.database.transactions.TransactionGlucoseValue
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType
@ -61,7 +60,6 @@ class EversensePlugin @Inject constructor(
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var dataWorkerStorage: DataWorkerStorage
@Inject lateinit var repository: AppRepository
@Inject lateinit var xDripBroadcast: XDripBroadcast
override suspend fun doWorkAndLog(): Result {
var ret = Result.success()
@ -114,7 +112,6 @@ class EversensePlugin @Inject constructor(
.blockingGet()
.also { savedValues ->
savedValues.inserted.forEach {
xDripBroadcast.sendIn640gMode(it)
aapsLogger.debug(LTag.DATABASE, "Inserted bg $it")
}
}

View file

@ -10,7 +10,6 @@ import info.nightscout.database.entities.GlucoseValue
import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.transactions.CgmSourceTransaction
import info.nightscout.database.transactions.TransactionGlucoseValue
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType
@ -50,7 +49,6 @@ class GlimpPlugin @Inject constructor(
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var glimpPlugin: GlimpPlugin
@Inject lateinit var repository: AppRepository
@Inject lateinit var xDripBroadcast: XDripBroadcast
override suspend fun doWorkAndLog(): Result {
var ret = Result.success()
@ -74,7 +72,6 @@ class GlimpPlugin @Inject constructor(
.blockingGet()
.also { savedValues ->
savedValues.inserted.forEach {
xDripBroadcast.sendIn640gMode(it)
aapsLogger.debug(LTag.DATABASE, "Inserted bg $it")
}
}

View file

@ -14,7 +14,6 @@ import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.transactions.CgmSourceTransaction
import info.nightscout.database.transactions.TransactionGlucoseValue
import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.logging.UserEntryLogger
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription
@ -38,7 +37,6 @@ class GlunovoPlugin @Inject constructor(
private val sp: SP,
private val context: Context,
private val repository: AppRepository,
private val xDripBroadcast: XDripBroadcast,
private val dateUtil: DateUtil,
private val uel: UserEntryLogger,
private val fabricPrivacy: FabricPrivacy
@ -148,7 +146,6 @@ class GlunovoPlugin @Inject constructor(
.blockingGet()
.also { savedValues ->
savedValues.inserted.forEach {
xDripBroadcast.sendIn640gMode(it)
aapsLogger.debug(LTag.DATABASE, "Inserted bg $it")
}
savedValues.calibrationsInserted.forEach { calibration ->

View file

@ -16,7 +16,6 @@ import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.transactions.CgmSourceTransaction
import info.nightscout.database.transactions.TransactionGlucoseValue
import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.logging.UserEntryLogger
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription
@ -41,7 +40,6 @@ class IntelligoPlugin @Inject constructor(
private val sp: SP,
private val context: Context,
private val repository: AppRepository,
private val xDripBroadcast: XDripBroadcast,
private val dateUtil: DateUtil,
private val uel: UserEntryLogger,
private val fabricPrivacy: FabricPrivacy
@ -159,7 +157,6 @@ class IntelligoPlugin @Inject constructor(
.blockingGet()
.also { savedValues ->
savedValues.inserted.forEach {
xDripBroadcast.sendIn640gMode(it)
aapsLogger.debug(LTag.DATABASE, "Inserted bg $it")
}
savedValues.calibrationsInserted.forEach { calibration ->

View file

@ -4,13 +4,11 @@ import android.content.Context
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import dagger.android.HasAndroidInjector
import info.nightscout.core.utils.receivers.DataWorkerStorage
import info.nightscout.core.utils.worker.LoggingWorker
import info.nightscout.database.entities.GlucoseValue
import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.transactions.CgmSourceTransaction
import info.nightscout.database.transactions.TransactionGlucoseValue
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType
@ -51,9 +49,7 @@ class MM640gPlugin @Inject constructor(
@Inject lateinit var mM640gPlugin: MM640gPlugin
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var dataWorkerStorage: DataWorkerStorage
@Inject lateinit var repository: AppRepository
@Inject lateinit var xDripBroadcast: XDripBroadcast
override suspend fun doWorkAndLog(): Result {
var ret = Result.success()
@ -90,7 +86,6 @@ class MM640gPlugin @Inject constructor(
.blockingGet()
.also { savedValues ->
savedValues.all().forEach {
xDripBroadcast.sendIn640gMode(it)
aapsLogger.debug(LTag.DATABASE, "Inserted bg $it")
}
}

View file

@ -10,7 +10,6 @@ import info.nightscout.database.entities.GlucoseValue
import info.nightscout.database.impl.AppRepository
import info.nightscout.database.transactions.TransactionGlucoseValue
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.notifications.Notification
import info.nightscout.interfaces.nsclient.NSSgv
import info.nightscout.interfaces.nsclient.StoreDataForDb
@ -95,7 +94,6 @@ class NSClientSourcePlugin @Inject constructor(
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var dataWorkerStorage: DataWorkerStorage
@Inject lateinit var repository: AppRepository
@Inject lateinit var xDripBroadcast: XDripBroadcast
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var storeDataForDb: StoreDataForDb
@ -141,8 +139,6 @@ class NSClientSourcePlugin @Inject constructor(
try {
if (sgvs is JSONArray) { // V1 client
xDripBroadcast.sendSgvs(sgvs)
for (i in 0 until sgvs.length()) {
val sgv = toGv(sgvs.getJSONObject(i)) ?: continue
if (sgv.timestamp < dateUtil.now() && sgv.timestamp > latestDateInReceivedData) latestDateInReceivedData = sgv.timestamp

View file

@ -10,7 +10,6 @@ import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.transactions.CgmSourceTransaction
import info.nightscout.database.transactions.TransactionGlucoseValue
import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType
@ -52,7 +51,6 @@ class PoctechPlugin @Inject constructor(
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var poctechPlugin: PoctechPlugin
@Inject lateinit var repository: AppRepository
@Inject lateinit var xDripBroadcast: XDripBroadcast
override suspend fun doWorkAndLog(): Result {
var ret = Result.success()
@ -83,7 +81,6 @@ class PoctechPlugin @Inject constructor(
.blockingGet()
.also { savedValues ->
savedValues.inserted.forEach {
xDripBroadcast.sendIn640gMode(it)
aapsLogger.debug(LTag.DATABASE, "Inserted bg $it")
}
}

View file

@ -10,7 +10,6 @@ import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.transactions.CgmSourceTransaction
import info.nightscout.database.transactions.TransactionGlucoseValue
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType
@ -38,7 +37,6 @@ class RandomBgPlugin @Inject constructor(
aapsLogger: AAPSLogger,
private val sp: SP,
private val repository: AppRepository,
private val xDripBroadcast: XDripBroadcast,
private val virtualPump: VirtualPump,
private val config: Config
) : PluginBase(
@ -119,7 +117,6 @@ class RandomBgPlugin @Inject constructor(
disposable += repository.runTransactionForResult(CgmSourceTransaction(glucoseValues, emptyList(), null))
.subscribe({ savedValues ->
savedValues.inserted.forEach {
xDripBroadcast.sendIn640gMode(it)
aapsLogger.debug(LTag.DATABASE, "Inserted bg $it")
}
}, { aapsLogger.error(LTag.DATABASE, "Error while saving values from Random plugin", it) }

View file

@ -9,7 +9,6 @@ import info.nightscout.database.entities.GlucoseValue
import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.transactions.CgmSourceTransaction
import info.nightscout.database.transactions.TransactionGlucoseValue
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType
@ -50,7 +49,6 @@ class TomatoPlugin @Inject constructor(
@Inject lateinit var tomatoPlugin: TomatoPlugin
@Inject lateinit var sp: SP
@Inject lateinit var repository: AppRepository
@Inject lateinit var xDripBroadcast: XDripBroadcast
@Suppress("SpellCheckingInspection")
override suspend fun doWorkAndLog(): Result {
@ -74,7 +72,6 @@ class TomatoPlugin @Inject constructor(
.blockingGet()
.also { savedValues ->
savedValues.inserted.forEach {
xDripBroadcast.sendIn640gMode(it)
aapsLogger.debug(LTag.DATABASE, "Inserted bg $it")
}
}

View file

@ -34,7 +34,7 @@ class XdripSourcePlugin @Inject constructor(
.mainType(PluginType.BGSOURCE)
.fragmentClass(BGSourceFragment::class.java.name)
.pluginIcon((info.nightscout.core.main.R.drawable.ic_blooddrop_48))
.pluginName(R.string.xdrip)
.pluginName(R.string.source_xdrip)
.description(R.string.description_source_xdrip),
aapsLogger, rh, injector
), BgSource, DoingOwnUploadSource, XDrip {

View file

@ -8,7 +8,7 @@
<string name="ns_client_bg">NSClient BG</string>
<string name="ns_client_bg_short">NS BG</string>
<string name="description_source_ns_client">Downloads BG data from Nightscout</string>
<string name="xdrip">xDrip+</string>
<string name="source_xdrip">xDrip+ BG</string>
<string name="description_source_xdrip">Receive BG values from xDrip+.</string>
<string name="dexcom_app_patched">BYODA</string>
<string name="dexcom_short">BYODA</string>

View file

@ -12,12 +12,6 @@
android:key="@string/key_do_ns_upload"
android:title="@string/do_ns_upload_title" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_dexcomg5_xdripupload"
android:summary="@string/do_xdrip_upload_summary"
android:title="@string/do_xdrip_upload_title" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View file

@ -12,12 +12,6 @@
android:key="@string/key_do_ns_upload"
android:title="@string/do_ns_upload_title" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_dexcomg5_xdripupload"
android:summary="@string/do_xdrip_upload_summary"
android:title="@string/do_xdrip_upload_title" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_dexcom_log_ns_sensor_change"

View file

@ -33,7 +33,9 @@ import info.nightscout.plugins.sync.nsclientV3.workers.LoadTreatmentsWorker
import info.nightscout.plugins.sync.nsclientV3.workers.ProcessFoodWorker
import info.nightscout.plugins.sync.nsclientV3.workers.ProcessTreatmentsWorker
import info.nightscout.plugins.sync.tidepool.TidepoolFragment
import info.nightscout.plugins.sync.xdrip.XdripFragment
import info.nightscout.plugins.sync.xdrip.XdripPlugin
import info.nightscout.plugins.sync.xdrip.workers.XdripDataSyncWorker
@Module(
includes = [
@ -68,6 +70,8 @@ abstract class SyncModule {
@ContributesAndroidInjector abstract fun contributesFoodWorker(): ProcessFoodWorker
@ContributesAndroidInjector abstract fun contributesTidepoolFragment(): TidepoolFragment
@ContributesAndroidInjector abstract fun contributesXdripFragment(): XdripFragment
@ContributesAndroidInjector abstract fun contributesXdripDataSyncWorker(): XdripDataSyncWorker
@Module
open class Provide {

View file

@ -48,7 +48,6 @@ import info.nightscout.database.impl.transactions.UpdateNsIdTherapyEventTransact
import info.nightscout.database.transactions.TransactionGlucoseValue
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.logging.UserEntryLogger
import info.nightscout.interfaces.notifications.Notification
import info.nightscout.interfaces.nsclient.StoreDataForDb
@ -78,7 +77,6 @@ class StoreDataForDbImpl @Inject constructor(
private val dateUtil: DateUtil,
private val config: Config,
private val nsClientSource: NSClientSource,
private val xDripBroadcast: XDripBroadcast,
private val virtualPump: VirtualPump,
private val uiInteraction: UiInteraction
) : StoreDataForDb {
@ -174,19 +172,16 @@ class StoreDataForDbImpl @Inject constructor(
.also { result ->
glucoseValues.clear()
result.updated.forEach {
xDripBroadcast.sendIn640gMode(it)
nsClientSource.detectSource(it)
aapsLogger.debug(LTag.DATABASE, "Updated bg $it")
updated.inc(GlucoseValue::class.java.simpleName)
}
result.inserted.forEach {
xDripBroadcast.sendIn640gMode(it)
nsClientSource.detectSource(it)
aapsLogger.debug(LTag.DATABASE, "Inserted bg $it")
inserted.inc(GlucoseValue::class.java.simpleName)
}
result.updatedNsId.forEach {
xDripBroadcast.sendIn640gMode(it)
nsClientSource.detectSource(it)
aapsLogger.debug(LTag.DATABASE, "Updated nsId bg $it")
nsIdUpdated.inc(GlucoseValue::class.java.simpleName)

View file

@ -44,7 +44,6 @@ class NSClientAddUpdateWorker(
@Inject lateinit var repository: AppRepository
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var rxBus: RxBus
@Inject lateinit var xDripBroadcast: XDripBroadcast
@Inject lateinit var storeDataForDb: StoreDataForDb
override suspend fun doWorkAndLog(): Result {
@ -164,7 +163,6 @@ class NSClientAddUpdateWorker(
}
storeDataForDb.storeTreatmentsToDb()
activePlugin.activeNsClient?.updateLatestTreatmentReceivedIfNewer(latestDateInReceivedData)
xDripBroadcast.sendTreatments(treatments)
return ret
}
}

View file

@ -626,6 +626,7 @@ class NSClientV3Plugin @Inject constructor(
lastLoadedSrvModified = LastModified(LastModified.Collections())
initialLoadFinished = false
storeLastLoadedSrvModified()
dataSyncSelector.resetToNextFullSync()
}
override fun nsAdd(collection: String, dataPair: DataSyncSelector.DataPair, progress: String) {

View file

@ -0,0 +1,562 @@
package info.nightscout.plugins.sync.xdrip
import dagger.Lazy
import info.nightscout.database.ValueWrapper
import info.nightscout.database.impl.AppRepository
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.sync.DataSyncSelector
import info.nightscout.interfaces.utils.JsonHelper
import info.nightscout.plugins.sync.R
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class XdripDataSyncSelectorImplementation @Inject constructor(
private val sp: SP,
private val aapsLogger: AAPSLogger,
private val dateUtil: DateUtil,
private val profileFunction: ProfileFunction,
private val activePlugin: ActivePlugin,
private val xdripBroadcast: Lazy<XDripBroadcast>,
private val appRepository: AppRepository
) : DataSyncSelector {
class QueueCounter(
var bolusesRemaining: Long = -1L,
var carbsRemaining: Long = -1L,
var bcrRemaining: Long = -1L,
var ttsRemaining: Long = -1L,
var foodsRemaining: Long = -1L,
var gvsRemaining: Long = -1L,
var tesRemaining: Long = -1L,
var dssRemaining: Long = -1L,
var tbrsRemaining: Long = -1L,
var ebsRemaining: Long = -1L,
var pssRemaining: Long = -1L,
var epssRemaining: Long = -1L,
var oesRemaining: Long = -1L
) {
fun size(): Long =
bolusesRemaining +
carbsRemaining +
bcrRemaining +
ttsRemaining +
foodsRemaining +
gvsRemaining +
tesRemaining +
dssRemaining +
tbrsRemaining +
ebsRemaining +
pssRemaining +
epssRemaining +
oesRemaining
}
private val queueCounter = QueueCounter()
private val isEnabled get() = sp.getBoolean(info.nightscout.core.utils.R.string.key_xdrip_local_broadcasts, false)
private val xdripPlugin get() = xdripBroadcast.get()
private val maxAge get() = T.days(1).msecs()
private fun isOld(timestamp: Long) = timestamp < dateUtil.now() - maxAge
private val preparedEntries = mutableListOf<DataSyncSelector.DataPair>()
private val preparedTreatments = mutableListOf<DataSyncSelector.DataPair>()
private val preparedFoods = mutableListOf<DataSyncSelector.DataPair>()
override fun queueSize(): Long = queueCounter.size()
override fun doUpload() {
if (isEnabled) {
processChangedGlucoseValues()
processChangedBoluses()
processChangedCarbs()
// not supported at the moment
//processChangedBolusCalculatorResults()
// not supported at the moment
//processChangedTemporaryBasals()
processChangedExtendedBoluses()
processChangedProfileSwitches()
// not supported at the moment
//processChangedEffectiveProfileSwitches()
processChangedTempTargets()
// not supported at the moment
//processChangedFoods()
processChangedTherapyEvents()
// not supported at the moment
//processChangedDeviceStatuses()
// not supported at the moment
//processChangedOfflineEvents()
// not supported at the moment
//processChangedProfileStore()
}
}
override fun resetToNextFullSync() {
sp.remove(R.string.key_xdrip_glucose_value_last_synced_id)
sp.remove(R.string.key_xdrip_temporary_basal_last_synced_id)
sp.remove(R.string.key_xdrip_temporary_target_last_synced_id)
sp.remove(R.string.key_xdrip_extended_bolus_last_synced_id)
sp.remove(R.string.key_xdrip_food_last_synced_id)
sp.remove(R.string.key_xdrip_bolus_last_synced_id)
sp.remove(R.string.key_xdrip_carbs_last_synced_id)
sp.remove(R.string.key_xdrip_bolus_calculator_result_last_synced_id)
sp.remove(R.string.key_xdrip_therapy_event_last_synced_id)
sp.remove(R.string.key_xdrip_profile_switch_last_synced_id)
sp.remove(R.string.key_xdrip_effective_profile_switch_last_synced_id)
sp.remove(R.string.key_xdrip_offline_event_last_synced_id)
sp.remove(R.string.key_xdrip_profile_store_last_synced_timestamp)
val lastDeviceStatusDbIdWrapped = appRepository.getLastDeviceStatusIdWrapped().blockingGet()
if (lastDeviceStatusDbIdWrapped is ValueWrapper.Existing) sp.putLong(R.string.key_xdrip_device_status_last_synced_id, lastDeviceStatusDbIdWrapped.value)
else sp.remove(R.string.key_xdrip_device_status_last_synced_id)
}
override fun confirmLastGlucoseValueIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_xdrip_glucose_value_last_synced_id, 0)) {
//aapsLogger.debug(LTag.XDRIP, "Setting GlucoseValue data sync from $lastSynced")
sp.putLong(R.string.key_xdrip_glucose_value_last_synced_id, lastSynced)
}
}
override fun processChangedGlucoseValues() {
var progress: String
val lastDbIdWrapped = appRepository.getLastGlucoseValueIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
while (true) {
if (!isEnabled) return
var startId = sp.getLong(R.string.key_xdrip_glucose_value_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_xdrip_glucose_value_last_synced_id, 0)
startId = 0
}
queueCounter.gvsRemaining = lastDbId - startId
progress = "$startId/$lastDbId"
appRepository.getNextSyncElementGlucoseValue(startId).blockingGet()?.let { gv ->
aapsLogger.info(LTag.XDRIP, "Loading GlucoseValue data Start: $startId ${gv.first} forID: ${gv.second.id} ")
if (!isOld(gv.first.timestamp))
preparedEntries.add(DataSyncSelector.PairGlucoseValue(gv.first, gv.second.id))
sendEntries(force = false, progress)
confirmLastGlucoseValueIdIfGreater(gv.second.id)
} ?: break
}
sendEntries(force = true, progress)
}
private fun sendEntries(force: Boolean, progress: String) {
if (preparedEntries.isNotEmpty() && (preparedEntries.size >= 100 || force)) {
xdripPlugin.sendToXdrip("entries", preparedEntries, progress)
preparedEntries.clear()
}
}
private fun sendTreatments(force: Boolean, progress: String) {
if (preparedTreatments.isNotEmpty() && (preparedTreatments.size >= 100 || force)) {
xdripPlugin.sendToXdrip("treatments", preparedTreatments, progress)
preparedTreatments.clear()
}
}
private fun sendFoods(force: Boolean, progress: String) {
if (preparedFoods.isNotEmpty() && (preparedFoods.size >= 100 || force)) {
xdripPlugin.sendToXdrip("food", preparedFoods, progress)
preparedFoods.clear()
}
}
override fun confirmLastBolusIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_xdrip_bolus_last_synced_id, 0)) {
//aapsLogger.debug(LTag.XDRIP, "Setting Bolus data sync from $lastSynced")
sp.putLong(R.string.key_xdrip_bolus_last_synced_id, lastSynced)
}
}
override fun processChangedBoluses() {
var progress: String
val lastDbIdWrapped = appRepository.getLastBolusIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
while (true) {
if (!isEnabled) return
var startId = sp.getLong(R.string.key_xdrip_bolus_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_xdrip_bolus_last_synced_id, 0)
startId = 0
}
queueCounter.bolusesRemaining = lastDbId - startId
progress = "$startId/$lastDbId"
appRepository.getNextSyncElementBolus(startId).blockingGet()?.let { bolus ->
aapsLogger.info(LTag.XDRIP, "Loading Bolus data Start: $startId ${bolus.first} forID: ${bolus.second.id} ")
if (!isOld(bolus.first.timestamp))
preparedTreatments.add(DataSyncSelector.PairBolus(bolus.first, bolus.second.id))
sendTreatments(force = false, progress)
confirmLastBolusIdIfGreater(bolus.second.id)
} ?: break
}
sendTreatments(force = true, progress)
}
override fun confirmLastCarbsIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_xdrip_carbs_last_synced_id, 0)) {
//aapsLogger.debug(LTag.XDRIP, "Setting Carbs data sync from $lastSynced")
sp.putLong(R.string.key_xdrip_carbs_last_synced_id, lastSynced)
}
}
override fun processChangedCarbs() {
var progress: String
val lastDbIdWrapped = appRepository.getLastCarbsIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
while (true) {
if (!isEnabled) return
var startId = sp.getLong(R.string.key_xdrip_carbs_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_xdrip_carbs_last_synced_id, 0)
startId = 0
}
queueCounter.carbsRemaining = lastDbId - startId
progress = "$startId/$lastDbId"
appRepository.getNextSyncElementCarbs(startId).blockingGet()?.let { carb ->
aapsLogger.info(LTag.XDRIP, "Loading Carbs data Start: $startId ${carb.first} forID: ${carb.second.id} ")
if (!isOld(carb.first.timestamp))
preparedTreatments.add(DataSyncSelector.PairCarbs(carb.first, carb.second.id))
sendTreatments(force = false, progress)
confirmLastCarbsIdIfGreater(carb.second.id)
} ?: break
}
sendTreatments(force = true, progress)
}
override fun confirmLastBolusCalculatorResultsIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_xdrip_bolus_calculator_result_last_synced_id, 0)) {
//aapsLogger.debug(LTag.XDRIP, "Setting BolusCalculatorResult data sync from $lastSynced")
sp.putLong(R.string.key_xdrip_bolus_calculator_result_last_synced_id, lastSynced)
}
}
override fun processChangedBolusCalculatorResults() {
var progress: String
val lastDbIdWrapped = appRepository.getLastBolusCalculatorResultIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
while (true) {
if (!isEnabled) return
var startId = sp.getLong(R.string.key_xdrip_bolus_calculator_result_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_xdrip_bolus_calculator_result_last_synced_id, 0)
startId = 0
}
queueCounter.bcrRemaining = lastDbId - startId
progress = "$startId/$lastDbId"
appRepository.getNextSyncElementBolusCalculatorResult(startId).blockingGet()?.let { bolusCalculatorResult ->
aapsLogger.info(LTag.XDRIP, "Loading BolusCalculatorResult data Start: $startId ${bolusCalculatorResult.first} forID: ${bolusCalculatorResult.second.id} ")
if (!isOld(bolusCalculatorResult.first.timestamp))
preparedTreatments.add(DataSyncSelector.PairBolusCalculatorResult(bolusCalculatorResult.first, bolusCalculatorResult.second.id))
sendTreatments(force = false, progress)
confirmLastBolusCalculatorResultsIdIfGreater(bolusCalculatorResult.second.id)
} ?: break
}
sendTreatments(force = true, progress)
}
override fun confirmLastTempTargetsIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_xdrip_temporary_target_last_synced_id, 0)) {
//aapsLogger.debug(LTag.XDRIP, "Setting TemporaryTarget data sync from $lastSynced")
sp.putLong(R.string.key_xdrip_temporary_target_last_synced_id, lastSynced)
}
}
override fun processChangedTempTargets() {
var progress: String
val lastDbIdWrapped = appRepository.getLastTempTargetIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
while (true) {
if (!isEnabled) return
var startId = sp.getLong(R.string.key_xdrip_temporary_target_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_xdrip_temporary_target_last_synced_id, 0)
startId = 0
}
queueCounter.ttsRemaining = lastDbId - startId
progress = "$startId/$lastDbId"
appRepository.getNextSyncElementTemporaryTarget(startId).blockingGet()?.let { tt ->
aapsLogger.info(LTag.XDRIP, "Loading TemporaryTarget data Start: $startId ${tt.first} forID: ${tt.second.id} ")
if (!isOld(tt.first.timestamp))
preparedTreatments.add(DataSyncSelector.PairTemporaryTarget(tt.first, tt.second.id))
sendTreatments(force = false, progress)
confirmLastTempTargetsIdIfGreater(tt.second.id)
} ?: break
}
sendTreatments(force = true, progress)
}
override fun confirmLastFoodIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_xdrip_food_last_synced_id, 0)) {
//aapsLogger.debug(LTag.XDRIP, "Setting Food data sync from $lastSynced")
sp.putLong(R.string.key_xdrip_food_last_synced_id, lastSynced)
}
}
override fun processChangedFoods() {
var progress: String
val lastDbIdWrapped = appRepository.getLastFoodIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
while (true) {
if (!isEnabled) return
var startId = sp.getLong(R.string.key_xdrip_food_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_xdrip_food_last_synced_id, 0)
startId = 0
}
queueCounter.foodsRemaining = lastDbId - startId
progress = "$startId/$lastDbId"
appRepository.getNextSyncElementFood(startId).blockingGet()?.let { food ->
aapsLogger.info(LTag.XDRIP, "Loading Food data Start: $startId ${food.first} forID: ${food.second.id} ")
preparedFoods.add(DataSyncSelector.PairFood(food.first, food.second.id))
sendFoods(force = false, progress)
confirmLastFoodIdIfGreater(food.second.id)
} ?: break
}
sendFoods(force = true, progress)
}
override fun confirmLastTherapyEventIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_xdrip_therapy_event_last_synced_id, 0)) {
//aapsLogger.debug(LTag.XDRIP, "Setting TherapyEvents data sync from $lastSynced")
sp.putLong(R.string.key_xdrip_therapy_event_last_synced_id, lastSynced)
}
}
override fun processChangedTherapyEvents() {
var progress: String
val lastDbIdWrapped = appRepository.getLastTherapyEventIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
while (true) {
if (!isEnabled) return
var startId = sp.getLong(R.string.key_xdrip_therapy_event_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_xdrip_therapy_event_last_synced_id, 0)
startId = 0
}
queueCounter.tesRemaining = lastDbId - startId
progress = "$startId/$lastDbId"
appRepository.getNextSyncElementTherapyEvent(startId).blockingGet()?.let { te ->
aapsLogger.info(LTag.XDRIP, "Loading TherapyEvents data Start: $startId ${te.first} forID: ${te.second.id} ")
if (!isOld(te.first.timestamp))
preparedTreatments.add(DataSyncSelector.PairTherapyEvent(te.first, te.second.id))
sendTreatments(force = false, progress)
confirmLastTherapyEventIdIfGreater(te.second.id)
} ?: break
}
sendTreatments(force = true, progress)
}
override fun confirmLastDeviceStatusIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_xdrip_device_status_last_synced_id, 0)) {
//aapsLogger.debug(LTag.XDRIP, "Setting DeviceStatus data sync from $lastSynced")
sp.putLong(R.string.key_xdrip_device_status_last_synced_id, lastSynced)
}
}
override fun processChangedDeviceStatuses() {
val lastDbIdWrapped = appRepository.getLastDeviceStatusIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
while (true) {
if (!isEnabled) return
var startId = sp.getLong(R.string.key_xdrip_device_status_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_xdrip_device_status_last_synced_id, 0)
startId = 0
}
queueCounter.dssRemaining = lastDbId - startId
appRepository.getNextSyncElementDeviceStatus(startId).blockingGet()?.let { deviceStatus ->
aapsLogger.info(LTag.XDRIP, "Loading DeviceStatus data Start: $startId $deviceStatus")
if (!isOld(deviceStatus.timestamp))
xdripPlugin.sendToXdrip("devicestatus", DataSyncSelector.PairDeviceStatus(deviceStatus, lastDbId), "$startId/$lastDbId")
confirmLastDeviceStatusIdIfGreater(lastDbId)
} ?: break
}
}
override fun confirmLastTemporaryBasalIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_xdrip_temporary_basal_last_synced_id, 0)) {
//aapsLogger.debug(LTag.XDRIP, "Setting TemporaryBasal data sync from $lastSynced")
sp.putLong(R.string.key_xdrip_temporary_basal_last_synced_id, lastSynced)
}
}
override fun processChangedTemporaryBasals() {
var progress: String
val lastDbIdWrapped = appRepository.getLastTemporaryBasalIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
while (true) {
if (!isEnabled) return
var startId = sp.getLong(R.string.key_xdrip_temporary_basal_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_xdrip_temporary_basal_last_synced_id, 0)
startId = 0
}
queueCounter.tbrsRemaining = lastDbId - startId
progress = "$startId/$lastDbId"
appRepository.getNextSyncElementTemporaryBasal(startId).blockingGet()?.let { tb ->
aapsLogger.info(LTag.XDRIP, "Loading TemporaryBasal data Start: $startId ${tb.first} forID: ${tb.second.id} ")
if (!isOld(tb.first.timestamp))
preparedTreatments.add(DataSyncSelector.PairTemporaryBasal(tb.first, tb.second.id))
sendTreatments(force = false, progress)
confirmLastTemporaryBasalIdIfGreater(tb.second.id)
} ?: break
}
sendTreatments(force = true, progress)
}
override fun confirmLastExtendedBolusIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_xdrip_extended_bolus_last_synced_id, 0)) {
//aapsLogger.debug(LTag.XDRIP, "Setting ExtendedBolus data sync from $lastSynced")
sp.putLong(R.string.key_xdrip_extended_bolus_last_synced_id, lastSynced)
}
}
override fun processChangedExtendedBoluses() {
var progress: String
val lastDbIdWrapped = appRepository.getLastExtendedBolusIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
while (true) {
if (!isEnabled) return
var startId = sp.getLong(R.string.key_xdrip_extended_bolus_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_xdrip_extended_bolus_last_synced_id, 0)
startId = 0
}
queueCounter.ebsRemaining = lastDbId - startId
progress = "$startId/$lastDbId"
appRepository.getNextSyncElementExtendedBolus(startId).blockingGet()?.let { eb ->
aapsLogger.info(LTag.XDRIP, "Loading ExtendedBolus data Start: $startId ${eb.first} forID: ${eb.second.id} ")
val profile = profileFunction.getProfile(eb.first.timestamp)
if (profile != null && !isOld(eb.first.timestamp)) {
preparedTreatments.add(DataSyncSelector.PairExtendedBolus(eb.first, eb.second.id))
sendTreatments(force = false, progress)
} else
aapsLogger.info(LTag.XDRIP, "Ignoring ExtendedBolus. No profile: ${eb.second.id} ")
confirmLastExtendedBolusIdIfGreater(eb.second.id)
} ?: break
}
sendTreatments(force = true, progress)
}
override fun confirmLastProfileSwitchIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_xdrip_profile_switch_last_synced_id, 0)) {
//aapsLogger.debug(LTag.XDRIP, "Setting ProfileSwitch data sync from $lastSynced")
sp.putLong(R.string.key_xdrip_profile_switch_last_synced_id, lastSynced)
}
}
override fun processChangedProfileSwitches() {
var progress: String
val lastDbIdWrapped = appRepository.getLastProfileSwitchIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
while (true) {
if (!isEnabled) return
var startId = sp.getLong(R.string.key_xdrip_profile_switch_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_xdrip_profile_switch_last_synced_id, 0)
startId = 0
}
queueCounter.pssRemaining = lastDbId - startId
progress = "$startId/$lastDbId"
appRepository.getNextSyncElementProfileSwitch(startId).blockingGet()?.let { ps ->
aapsLogger.info(LTag.XDRIP, "Loading ProfileSwitch data Start: $startId ${ps.first} forID: ${ps.second.id} ")
if (!isOld(ps.first.timestamp))
preparedTreatments.add(DataSyncSelector.PairProfileSwitch(ps.first, ps.second.id))
sendTreatments(force = false, progress)
confirmLastProfileSwitchIdIfGreater(ps.second.id)
} ?: break
}
sendTreatments(force = true, progress)
}
override fun confirmLastEffectiveProfileSwitchIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_xdrip_effective_profile_switch_last_synced_id, 0)) {
//aapsLogger.debug(LTag.XDRIP, "Setting EffectiveProfileSwitch data sync from $lastSynced")
sp.putLong(R.string.key_xdrip_effective_profile_switch_last_synced_id, lastSynced)
}
}
override fun processChangedEffectiveProfileSwitches() {
var progress: String
val lastDbIdWrapped = appRepository.getLastEffectiveProfileSwitchIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
while (true) {
if (!isEnabled) return
var startId = sp.getLong(R.string.key_xdrip_effective_profile_switch_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_xdrip_effective_profile_switch_last_synced_id, 0)
startId = 0
}
queueCounter.epssRemaining = lastDbId - startId
progress = "$startId/$lastDbId"
appRepository.getNextSyncElementEffectiveProfileSwitch(startId).blockingGet()?.let { ps ->
aapsLogger.info(LTag.XDRIP, "Loading EffectiveProfileSwitch data Start: $startId ${ps.first} forID: ${ps.second.id} ")
if (!isOld(ps.first.timestamp))
preparedTreatments.add(DataSyncSelector.PairEffectiveProfileSwitch(ps.first, ps.second.id))
sendTreatments(force = false, progress)
confirmLastEffectiveProfileSwitchIdIfGreater(ps.second.id)
} ?: break
}
sendTreatments(force = true, progress)
}
override fun confirmLastOfflineEventIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_xdrip_offline_event_last_synced_id, 0)) {
//aapsLogger.debug(LTag.XDRIP, "Setting OfflineEvent data sync from $lastSynced")
sp.putLong(R.string.key_xdrip_offline_event_last_synced_id, lastSynced)
}
}
override fun processChangedOfflineEvents() {
var progress: String
val lastDbIdWrapped = appRepository.getLastOfflineEventIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
while (true) {
if (!isEnabled) return
var startId = sp.getLong(R.string.key_xdrip_offline_event_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_xdrip_offline_event_last_synced_id, 0)
startId = 0
}
queueCounter.oesRemaining = lastDbId - startId
progress = "$startId/$lastDbId"
appRepository.getNextSyncElementOfflineEvent(startId).blockingGet()?.let { oe ->
aapsLogger.info(LTag.XDRIP, "Loading OfflineEvent data Start: $startId ${oe.first} forID: ${oe.second.id} ")
if (!isOld(oe.first.timestamp))
preparedTreatments.add(DataSyncSelector.PairOfflineEvent(oe.first, oe.second.id))
sendTreatments(force = false, progress)
confirmLastOfflineEventIdIfGreater(oe.second.id)
} ?: break
}
sendTreatments(force = true, progress)
}
override fun confirmLastProfileStore(lastSynced: Long) {
sp.putLong(R.string.key_xdrip_profile_store_last_synced_timestamp, lastSynced)
}
override fun processChangedProfileStore() {
if (!isEnabled) return
val lastSync = sp.getLong(R.string.key_xdrip_profile_store_last_synced_timestamp, 0)
val lastChange = sp.getLong(info.nightscout.core.utils.R.string.key_local_profile_last_change, 0)
if (lastChange == 0L) return
if (lastChange > lastSync) {
if (activePlugin.activeProfileSource.profile?.allProfilesValid != true) return
val profileStore = activePlugin.activeProfileSource.profile
val profileJson = profileStore?.data ?: return
// add for v3
if (JsonHelper.safeGetLongAllowNull(profileJson, "date") == null)
profileJson.put("date", profileStore.getStartDate())
xdripPlugin.sendToXdrip("profile", DataSyncSelector.PairProfileStore(profileJson, dateUtil.now()), "")
}
}
}

View file

@ -0,0 +1,123 @@
package info.nightscout.plugins.sync.xdrip
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.ScrollView
import androidx.core.view.MenuProvider
import androidx.lifecycle.Lifecycle
import dagger.android.support.DaggerFragment
import info.nightscout.core.ui.dialogs.OKDialog
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginFragment
import info.nightscout.plugins.sync.R
import info.nightscout.plugins.sync.databinding.XdripFragmentBinding
import info.nightscout.plugins.sync.xdrip.events.EventXdripUpdateGUI
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class XdripFragment : DaggerFragment(), MenuProvider, PluginFragment {
@Inject lateinit var sp: SP
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var rxBus: RxBus
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var dataSyncSelector: XdripDataSyncSelectorImplementation
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var xdripPlugin: XdripPlugin
@Inject lateinit var config: Config
companion object {
const val ID_MENU_CLEAR_LOG = 511
const val ID_MENU_FULL_SYNC = 512
}
override var plugin: PluginBase? = null
private val disposable = CompositeDisposable()
private var _binding: XdripFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
XdripFragmentBinding.inflate(inflater, container, false).also {
_binding = it
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
}.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.autoscroll.isChecked = sp.getBoolean(R.string.key_ns_client_autoscroll, true)
binding.autoscroll.setOnCheckedChangeListener { _, isChecked ->
sp.putBoolean(R.string.key_ns_client_autoscroll, isChecked)
updateGui()
}
}
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
menu.add(Menu.FIRST, ID_MENU_CLEAR_LOG, 0, rh.gs(R.string.clear_log)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
menu.add(Menu.FIRST, ID_MENU_FULL_SYNC, 0, rh.gs(R.string.full_sync)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
menu.setGroupDividerEnabled(true)
}
override fun onMenuItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
ID_MENU_CLEAR_LOG -> {
xdripPlugin.clearLog()
true
}
ID_MENU_FULL_SYNC -> {
context?.let { context ->
OKDialog.showConfirmation(
context, rh.gs(R.string.ns_client), rh.gs(R.string.full_sync_comment),
Runnable { dataSyncSelector.resetToNextFullSync() }
)
}
true
}
else -> false
}
@Synchronized
override fun onResume() {
super.onResume()
disposable += rxBus
.toObservable(EventXdripUpdateGUI::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ updateGui() }, fabricPrivacy::logException)
updateGui()
}
@Synchronized override fun onPause() {
super.onPause()
disposable.clear()
}
private fun updateGui() {
if (_binding == null) return
binding.log.text = xdripPlugin.textLog()
if (sp.getBoolean(R.string.key_ns_client_autoscroll, true)) binding.logScrollview.fullScroll(ScrollView.FOCUS_DOWN)
val size = dataSyncSelector.queueSize()
binding.queue.text = if (size >= 0) size.toString() else rh.gs(info.nightscout.core.ui.R.string.value_unavailable_short)
}
}

View file

@ -3,23 +3,21 @@ package info.nightscout.plugins.sync.xdrip
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.text.Spanned
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkInfo
import androidx.work.WorkManager
import dagger.android.HasAndroidInjector
import info.nightscout.core.extensions.toJson
import info.nightscout.core.extensions.toStringShort
import info.nightscout.core.iob.generateCOBString
import info.nightscout.core.iob.round
import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.database.entities.Bolus
import info.nightscout.database.entities.Carbs
import info.nightscout.database.entities.DeviceStatus
import info.nightscout.database.entities.EffectiveProfileSwitch
import info.nightscout.database.entities.ExtendedBolus
import info.nightscout.database.entities.GlucoseValue
import info.nightscout.database.entities.OfflineEvent
import info.nightscout.database.entities.ProfileSwitch
import info.nightscout.database.entities.TemporaryBasal
import info.nightscout.database.entities.TemporaryTarget
import info.nightscout.database.entities.TherapyEvent
import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.GlucoseUnit
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.aps.Loop
@ -30,26 +28,35 @@ import info.nightscout.interfaces.plugin.PluginType
import info.nightscout.interfaces.profile.Profile
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.receivers.Intents
import info.nightscout.interfaces.sync.DataSyncSelector
import info.nightscout.interfaces.ui.UiInteraction
import info.nightscout.interfaces.utils.DecimalFormatter
import info.nightscout.interfaces.utils.HtmlHelper
import info.nightscout.plugins.sync.R
import info.nightscout.plugins.sync.nsclient.extensions.toJson
import info.nightscout.plugins.sync.xdrip.events.EventXdripUpdateGUI
import info.nightscout.plugins.sync.xdrip.extensions.toXdripJson
import info.nightscout.plugins.sync.xdrip.workers.XdripDataSyncWorker
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventAppExit
import info.nightscout.rx.events.EventAppInitialized
import info.nightscout.rx.events.EventAutosensCalculationFinished
import info.nightscout.rx.events.EventNewBG
import info.nightscout.rx.events.EventNewHistoryData
import info.nightscout.rx.events.EventRefreshOverview
import info.nightscout.rx.events.EventXdripNewLog
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import info.nightscout.shared.extensions.safeQueryBroadcastReceivers
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
@ -65,30 +72,56 @@ class XdripPlugin @Inject constructor(
private val loop: Loop,
private val iobCobCalculator: IobCobCalculator,
private val rxBus: RxBus,
private val uiInteraction: UiInteraction,
private val dataSyncSelector: XdripDataSyncSelectorImplementation,
private val dateUtil: DateUtil,
aapsLogger: AAPSLogger
) : PluginBase(
) : XDripBroadcast, PluginBase(
PluginDescription()
.mainType(PluginType.SYNC)
.fragmentClass(XdripFragment::class.java.name)
.pluginIcon((info.nightscout.core.main.R.drawable.ic_blooddrop_48))
.pluginName(R.string.xdrip)
.shortName(R.string.xdrip_shortname)
.neverVisible(true)
.preferencesId(R.xml.pref_xdrip)
.description(R.string.description_xdrip),
aapsLogger, rh, injector
), XDripBroadcast {
) {
@Suppress("PrivatePropertyName")
private val XDRIP_JOB_NAME: String = this::class.java.simpleName
private val disposable = CompositeDisposable()
private val handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
private val listLog: MutableList<EventXdripNewLog> = ArrayList()
private var lastLoopStatus = false
override fun onStart() {
super.onStart()
disposable += rxBus.toObservable(EventRefreshOverview::class.java)
disposable += rxBus
.toObservable(EventAppExit::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ if (lastLoopStatus != loop.isEnabled()) sendStatusLine() }, fabricPrivacy::logException)
.subscribe({ WorkManager.getInstance(context).cancelUniqueWork(XDRIP_JOB_NAME) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventXdripNewLog::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event ->
addToLog(event)
aapsLogger.debug(LTag.XDRIP, event.action + " " + event.logText)
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventNewBG::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
sendStatusLine()
delayAndScheduleExecution("NEW_BG")
}, fabricPrivacy::logException)
disposable += rxBus.toObservable(EventNewHistoryData::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ sendStatusLine() }, fabricPrivacy::logException)
.subscribe({
sendStatusLine()
delayAndScheduleExecution("NEW_DATA")
}, fabricPrivacy::logException)
disposable += rxBus.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ sendStatusLine() }, fabricPrivacy::logException)
@ -99,8 +132,39 @@ class XdripPlugin @Inject constructor(
override fun onStop() {
super.onStop()
handler.removeCallbacksAndMessages(null)
disposable.clear()
sendStatusLine()
}
fun clearLog() {
handler.post {
synchronized(listLog) { listLog.clear() }
rxBus.send(EventXdripUpdateGUI())
}
}
private fun addToLog(ev: EventXdripNewLog) {
synchronized(listLog) {
listLog.add(ev)
// remove the first line if log is too large
if (listLog.size >= Constants.MAX_LOG_LINES) {
listLog.removeAt(0)
}
}
rxBus.send(EventXdripUpdateGUI())
}
fun textLog(): Spanned {
try {
val newTextLog = StringBuilder()
synchronized(listLog) {
for (log in listLog) newTextLog.append(log.toPreparedHtml())
}
return HtmlHelper.fromHtml(newTextLog.toString())
} catch (e: OutOfMemoryError) {
uiInteraction.showToastAndNotification(context, "Out of memory!\nStop using this phone !!!", info.nightscout.core.ui.R.raw.error)
}
return HtmlHelper.fromHtml("")
}
private fun sendStatusLine() {
@ -115,6 +179,45 @@ class XdripPlugin @Inject constructor(
}
}
private fun executeLoop(origin: String) {
if (workIsRunning(arrayOf(XDRIP_JOB_NAME))) {
rxBus.send(EventXdripNewLog("RUN", "Already running $origin"))
return
}
rxBus.send(EventXdripNewLog("RUN", "Starting next round $origin"))
WorkManager.getInstance(context)
.beginUniqueWork(
XDRIP_JOB_NAME,
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequest.Builder(XdripDataSyncWorker::class.java).build()
)
.enqueue()
}
private fun workIsRunning(workNames: Array<String>): Boolean {
for (workName in workNames)
for (workInfo in WorkManager.getInstance(context).getWorkInfosForUniqueWork(workName).get())
if (workInfo.state == WorkInfo.State.BLOCKED || workInfo.state == WorkInfo.State.ENQUEUED || workInfo.state == WorkInfo.State.RUNNING)
return true
return false
}
private val eventWorker = Executors.newSingleThreadScheduledExecutor()
private var scheduledEventPost: ScheduledFuture<*>? = null
private fun delayAndScheduleExecution(origin: String) {
class PostRunnable : Runnable {
override fun run() {
scheduledEventPost = null
executeLoop(origin)
}
}
// cancel waiting task to prevent sending multiple posts
scheduledEventPost?.cancel(false)
val task: Runnable = PostRunnable()
scheduledEventPost = eventWorker.schedule(task, 10, TimeUnit.SECONDS)
}
private fun buildStatusLine(profile: Profile): String {
val status = StringBuilder()
@Suppress("LiftReturnOrAssignment")
@ -169,145 +272,144 @@ class XdripPlugin @Inject constructor(
}
}
// sent in 640G mode
// com.eveningoutpost.dexdrip.NSEmulatorReceiver
override fun sendIn640gMode(glucoseValue: GlucoseValue) {
if (sp.getBoolean(info.nightscout.core.utils.R.string.key_dexcomg5_xdripupload, false)) {
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US)
try {
val entriesBody = JSONArray()
val json = JSONObject()
json.put("sgv", glucoseValue.value)
json.put("direction", glucoseValue.trendArrow.text)
json.put("device", "G5")
json.put("type", "sgv")
json.put("date", glucoseValue.timestamp)
json.put("dateString", format.format(glucoseValue.timestamp))
entriesBody.put(json)
val bundle = Bundle()
bundle.putString("action", "add")
bundle.putString("collection", "entries")
bundle.putString("data", entriesBody.toString())
val intent = Intent(Intents.XDRIP_PLUS_NS_EMULATOR)
intent.putExtras(bundle).addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
context.sendBroadcast(intent)
val receivers = context.packageManager.safeQueryBroadcastReceivers(intent, 0)
if (receivers.isEmpty()) {
//NSUpload.log.debug("No xDrip receivers found. ")
aapsLogger.debug(LTag.BGSOURCE, "No xDrip receivers found.")
} else {
aapsLogger.debug(LTag.BGSOURCE, "${receivers.size} xDrip receivers")
/*
// sent in 640G mode
// com.eveningoutpost.dexdrip.NSEmulatorReceiver
override fun sendIn640gMode(glucoseValue: GlucoseValue) {
if (sp.getBoolean(info.nightscout.core.utils.R.string.key_dexcomg5_xdripupload, false)) {
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US)
try {
val entriesBody = JSONArray()
val json = JSONObject()
json.put("sgv", glucoseValue.value)
json.put("direction", glucoseValue.trendArrow.text)
json.put("device", "G5")
json.put("type", "sgv")
json.put("date", glucoseValue.timestamp)
json.put("dateString", format.format(glucoseValue.timestamp))
entriesBody.put(json)
val bundle = Bundle()
bundle.putString("action", "add")
bundle.putString("collection", "entries")
bundle.putString("data", entriesBody.toString())
val intent = Intent(Intents.XDRIP_PLUS_NS_EMULATOR)
intent.putExtras(bundle).addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
context.sendBroadcast(intent)
val receivers = context.packageManager.safeQueryBroadcastReceivers(intent, 0)
if (receivers.isEmpty()) {
//NSUpload.log.debug("No xDrip receivers found. ")
aapsLogger.debug(LTag.BGSOURCE, "No xDrip receivers found.")
} else {
aapsLogger.debug(LTag.BGSOURCE, "${receivers.size} xDrip receivers")
}
} catch (e: JSONException) {
aapsLogger.error(LTag.BGSOURCE, "Unhandled exception", e)
}
} catch (e: JSONException) {
aapsLogger.error(LTag.BGSOURCE, "Unhandled exception", e)
}
}
*/
override fun sendToXdrip(collection: String, dataPair: DataSyncSelector.DataPair, progress: String) {
when (collection) {
"profile" -> sendProfileStore(dataPair = dataPair, progress = progress)
"devicestatus" -> sendDeviceStatus(dataPair = dataPair, progress = progress)
else -> throw IllegalStateException()
}
}
// sent in NSClient dbaccess mode
override fun sendProfile(profileStoreJson: JSONObject) {
if (sp.getBoolean(info.nightscout.core.utils.R.string.key_nsclient_localbroadcasts, false))
broadcast(
Intent(Intents.ACTION_NEW_PROFILE).apply {
addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
putExtras(Bundle().apply { putString("profile", profileStoreJson.toString()) })
}
)
}
// sent in NSClient dbaccess mode
override fun sendTreatments(addedOrUpdatedTreatments: JSONArray) {
if (sp.getBoolean(info.nightscout.core.utils.R.string.key_nsclient_localbroadcasts, false))
splitArray(addedOrUpdatedTreatments).forEach { part ->
broadcast(
Intent(Intents.ACTION_NEW_TREATMENT).apply {
addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
putExtras(Bundle().apply { putString("treatments", part.toString()) })
}
)
}
}
// sent in NSClient dbaccess mode
override fun sendSgvs(sgvs: JSONArray) {
if (sp.getBoolean(info.nightscout.core.utils.R.string.key_nsclient_localbroadcasts, false))
splitArray(sgvs).forEach { part ->
broadcast(
Intent(Intents.ACTION_NEW_SGV).apply {
addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
putExtras(Bundle().apply { putString("sgvs", part.toString()) })
}
)
}
}
override fun send(gv: GlucoseValue) {
TODO("Not yet implemented")
}
override fun send(bolus: Bolus) {
TODO("Not yet implemented")
}
override fun send(carbs: Carbs) {
TODO("Not yet implemented")
}
override fun send(tt: TemporaryTarget) {
TODO("Not yet implemented")
}
override fun send(te: TherapyEvent) {
TODO("Not yet implemented")
}
override fun send(deviceStatus: DeviceStatus) {
TODO("Not yet implemented")
}
override fun send(tb: TemporaryBasal) {
TODO("Not yet implemented")
}
override fun send(eb: ExtendedBolus) {
TODO("Not yet implemented")
}
override fun send(ps: ProfileSwitch) {
TODO("Not yet implemented")
}
override fun send(ps: EffectiveProfileSwitch) {
TODO("Not yet implemented")
}
override fun send(ps: OfflineEvent) {
TODO("Not yet implemented")
}
private fun splitArray(array: JSONArray): List<JSONArray> {
var ret: MutableList<JSONArray> = ArrayList()
try {
val size = array.length()
var count = 0
var newarr: JSONArray? = null
for (i in 0 until size) {
if (count == 0) {
if (newarr != null) ret.add(newarr)
newarr = JSONArray()
count = 20
}
newarr?.put(array[i])
--count
}
if (newarr != null && newarr.length() > 0) ret.add(newarr)
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
ret = ArrayList()
ret.add(array)
override fun sendToXdrip(collection: String, dataPairs: List<DataSyncSelector.DataPair>, progress: String) {
when (collection) {
"entries" -> sendEntries(dataPairs = dataPairs, progress = progress)
"food" -> sendFood(dataPairs = dataPairs, progress = progress)
"treatments" -> sendTreatments(dataPairs = dataPairs, progress = progress)
else -> throw IllegalStateException()
}
return ret
}
private fun sendProfileStore(dataPair: DataSyncSelector.DataPair, progress: String) {
val data = (dataPair as DataSyncSelector.PairProfileStore).value
rxBus.send(EventXdripNewLog("SENDING", "Sent 1 PROFILE ($progress)"))
broadcast(
Intent(Intents.ACTION_NEW_PROFILE)
.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
.putExtras(Bundle().apply { putString("profile", data.toString()) })
)
dataSyncSelector.confirmLastProfileStore(dataPair.id)
dataSyncSelector.processChangedProfileStore()
}
private fun sendDeviceStatus(dataPair: DataSyncSelector.DataPair, progress: String) {
val data = (dataPair as DataSyncSelector.PairDeviceStatus).value.toJson(dateUtil)
rxBus.send(EventXdripNewLog("SENDING", "Sent 1 DEVICESTATUS ($progress)"))
broadcast(
Intent(Intents.ACTION_NEW_DEVICE_STATUS)
.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
.putExtras(Bundle().apply { putString("devicestatus", data.toString()) })
)
}
private fun sendEntries(dataPairs: List<DataSyncSelector.DataPair>, progress: String) {
val array = JSONArray()
for (dataPair in dataPairs) {
val data = (dataPair as DataSyncSelector.PairGlucoseValue).value.toXdripJson()
array.put(data)
}
rxBus.send(EventXdripNewLog("SENDING", "Sent ${array.length()} BGs ($progress)"))
broadcast(
Intent(Intents.ACTION_NEW_SGV)
.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
.putExtras(Bundle().apply { putString("sgvs", array.toString()) })
)
}
private fun sendFood(dataPairs: List<DataSyncSelector.DataPair>, progress: String) {
val array = JSONArray()
for (dataPair in dataPairs) {
val data = (dataPair as DataSyncSelector.PairFood).value.toJson(true)
array.put(data)
}
rxBus.send(EventXdripNewLog("SENDING", "Sent ${array.length()} FOODs ($progress)"))
broadcast(
Intent(Intents.ACTION_NEW_FOOD)
.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
.putExtras(Bundle().apply { putString("foods", array.toString()) })
)
}
private fun sendTreatments(dataPairs: List<DataSyncSelector.DataPair>, progress: String) {
val array = JSONArray()
for (dataPair in dataPairs) {
when (dataPair) {
is DataSyncSelector.PairBolus -> dataPair.value.toJson(true, dateUtil)
is DataSyncSelector.PairCarbs -> dataPair.value.toJson(true, dateUtil)
is DataSyncSelector.PairBolusCalculatorResult -> dataPair.value.toJson(true, dateUtil, profileFunction)
is DataSyncSelector.PairTemporaryTarget -> dataPair.value.toJson(true, profileFunction.getUnits(), dateUtil)
is DataSyncSelector.PairTherapyEvent -> dataPair.value.toJson(true, dateUtil)
is DataSyncSelector.PairTemporaryBasal -> {
val profile = profileFunction.getProfile(dataPair.value.timestamp) ?: return
dataPair.value.toJson(true, profile, dateUtil)
}
is DataSyncSelector.PairExtendedBolus -> {
val profile = profileFunction.getProfile(dataPair.value.timestamp) ?: return
dataPair.value.toJson(true, profile, dateUtil)
}
is DataSyncSelector.PairProfileSwitch -> dataPair.value.toJson(true, dateUtil)
is DataSyncSelector.PairEffectiveProfileSwitch -> dataPair.value.toJson(true, dateUtil)
is DataSyncSelector.PairOfflineEvent -> dataPair.value.toJson(true, dateUtil)
else -> null
}?.let {
array.put(it)
}
}
rxBus.send(EventXdripNewLog("SENDING", "Sent ${array.length()} TRs ($progress)"))
broadcast(
Intent(Intents.ACTION_NEW_FOOD)
.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
.putExtras(Bundle().apply { putString("treatments", array.toString()) })
)
}
private fun broadcast(intent: Intent) {
@ -315,7 +417,10 @@ class XdripPlugin @Inject constructor(
resolveInfo.activityInfo.packageName?.let {
intent.setPackage(it)
context.sendBroadcast(intent)
aapsLogger.debug(LTag.CORE, "Sending broadcast " + intent.action + " to: " + it)
aapsLogger.debug(LTag.XDRIP, "Sending broadcast " + intent.action + " to: " + it)
rxBus.send(EventXdripNewLog("RECIPIENT", it))
rxBus.send(EventXdripUpdateGUI())
Thread.sleep(100)
}
}
}

View file

@ -0,0 +1,5 @@
package info.nightscout.plugins.sync.xdrip.events
import info.nightscout.rx.events.EventUpdateGui
class EventXdripUpdateGUI : EventUpdateGui()

View file

@ -0,0 +1,19 @@
package info.nightscout.plugins.sync.xdrip.extensions
import info.nightscout.database.entities.GlucoseValue
import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.GlucoseUnit
import info.nightscout.interfaces.iob.InMemoryGlucoseValue
import info.nightscout.interfaces.utils.DecimalFormatter
import info.nightscout.shared.utils.DateUtil
import org.json.JSONObject
fun GlucoseValue.toXdripJson(): JSONObject =
JSONObject()
.put("device", sourceSensor.text)
.put("mills", timestamp)
.put("isValid", isValid)
.put("mgdl", value)
.put("direction", trendArrow.text)

View file

@ -0,0 +1,31 @@
package info.nightscout.plugins.sync.xdrip.workers
import android.content.Context
import androidx.work.WorkerParameters
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.core.utils.worker.LoggingWorker
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.plugins.sync.xdrip.XdripDataSyncSelectorImplementation
import info.nightscout.plugins.sync.xdrip.events.EventXdripUpdateGUI
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventXdripNewLog
import kotlinx.coroutines.Dispatchers
import javax.inject.Inject
@OpenForTesting
class XdripDataSyncWorker(
context: Context, params: WorkerParameters
) : LoggingWorker(context, params, Dispatchers.IO) {
@Inject lateinit var dataSyncSelector: XdripDataSyncSelectorImplementation
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var rxBus: RxBus
override suspend fun doWorkAndLog(): Result {
rxBus.send(EventXdripNewLog("UPL", "Start"))
dataSyncSelector.doUpload()
rxBus.send(EventXdripNewLog("UPL", "End"))
rxBus.send(EventXdripUpdateGUI())
return Result.success()
}
}

View file

@ -0,0 +1,64 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="info.nightscout.plugins.sync.xdrip.XdripFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:layout_marginBottom="5dp"
android:gravity="center_horizontal"
android:orientation="horizontal">
<CheckBox
android:id="@+id/autoscroll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="10dp"
android:text="@string/ns_client_autoscroll"
tools:ignore="RtlHardcoded" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/queue" />
<TextView
android:id="@+id/queue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp" />
</LinearLayout>
<ScrollView
android:id="@+id/log_scrollview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp">
<TextView
android:id="@+id/log"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="" />
</ScrollView>
</LinearLayout>

View file

@ -106,8 +106,6 @@
<string name="ns_alarm_stale_data_value_label">Stale data threshold [min]</string>
<string name="ns_alarm_urgent_stale_data_value_label">Urgent stale data threshold [min]</string>
<string name="ns_log_app_started_event">Log app start to NS</string>
<string name="ns_local_broadcasts">Enable broadcasts to other apps (like xDrip+). Do not enable if you have more than one instance of AAPS or AAPSClient installed!</string>
<string name="ns_local_broadcasts_title">Enable local Broadcasts.</string>
<string name="copy_existing_values">Copy NS settings (if exists)?</string>
<!-- Tidepool -->
@ -161,6 +159,24 @@
<string name="xdrip_not_installed">xDrip+ not installed</string>
<string name="calibration_sent">Calibration sent to xDrip+</string>
<string name="key_xdrip_temporary_target_last_synced_id" translatable="false">xdrip_temporary_target_last_sync</string>
<string name="key_xdrip_glucose_value_last_synced_id" translatable="false">xdrip_glucose_value_last_sync</string>
<string name="key_xdrip_food_last_synced_id" translatable="false">xdrip_food_last_sync</string>
<string name="key_xdrip_therapy_event_last_synced_id" translatable="false">xdrip_therapy_event_last_sync</string>
<string name="key_xdrip_bolus_calculator_result_last_synced_id" translatable="false">xdrip_bolus_calculator_result_last_synced_id</string>
<string name="key_xdrip_carbs_last_synced_id" translatable="false">xdrip_carbs_last_synced_id</string>
<string name="key_xdrip_bolus_last_synced_id" translatable="false">xdrip_bolus_last_synced_id</string>
<string name="key_xdrip_device_status_last_synced_id" translatable="false">xdrip_device_status_last_synced_id</string>
<string name="key_xdrip_temporary_basal_last_synced_id" translatable="false">xdrip_temporary_basal_last_synced_id</string>
<string name="key_xdrip_extended_bolus_last_synced_id" translatable="false">xdrip_extended_bolus_last_synced_id</string>
<string name="key_xdrip_profile_switch_last_synced_id" translatable="false">profile_switch_last_synced_id</string>
<string name="key_xdrip_effective_profile_switch_last_synced_id" translatable="false">xdrip_effective_profile_switch_last_synced_id</string>
<string name="key_xdrip_offline_event_last_synced_id" translatable="false">xdrip_offline_event_last_synced_id</string>
<string name="key_xdrip_profile_store_last_synced_timestamp" translatable="false">xdrip_profile_store_last_synced_timestamp</string>
<string name="xdrip_local_broadcasts_summary">Send glucose and treatments data to xDrip+. Data Source \"xDrip+ Sync Follower\" must be selected and accepting of data must be enabled in Settings - Inter-app settings - Accept Glucose/Treatments</string>
<string name="xdrip_local_broadcasts_title">Enable broadcasts to xDrip+.</string>
<!-- DataBroadcast-->
<string name="data_broadcaster" translatable="false">Data Broadcaster</string>

View file

@ -198,12 +198,6 @@
android:summary="@string/ns_create_announcements_from_carbs_req_summary"
android:title="@string/ns_create_announcements_from_carbs_req_title" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_nsclient_localbroadcasts"
android:summary="@string/ns_local_broadcasts"
android:title="@string/ns_local_broadcasts_title" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_ns_sync_slow"

View file

@ -204,12 +204,6 @@
android:summary="@string/ns_create_announcements_from_carbs_req_summary"
android:title="@string/ns_create_announcements_from_carbs_req_title" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_nsclient_localbroadcasts"
android:summary="@string/ns_local_broadcasts"
android:title="@string/ns_local_broadcasts_title" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_ns_sync_slow"

View file

@ -7,6 +7,12 @@
android:title="@string/xdrip"
app:initialExpandedChildrenCount="0">
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_xdrip_local_broadcasts"
android:summary="@string/xdrip_local_broadcasts_summary"
android:title="@string/xdrip_local_broadcasts_title" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_xdrip_send_status"

View file

@ -73,7 +73,7 @@ internal class NSClientV3PluginTest : TestBaseWithProfile() {
@BeforeEach
fun prepare() {
storeDataForDb = StoreDataForDbImpl(aapsLogger, rxBus, repository, sp, uel, dateUtil, config, nsClientSource, xDripBroadcast, virtualPump, uiInteraction)
storeDataForDb = StoreDataForDbImpl(aapsLogger, rxBus, repository, sp, uel, dateUtil, config, nsClientSource, virtualPump, uiInteraction)
sut =
NSClientV3Plugin(
injector, aapsLogger, aapsSchedulers, rxBus, rh, context, fabricPrivacy,
@ -98,6 +98,7 @@ internal class NSClientV3PluginTest : TestBaseWithProfile() {
"\"LastBolus\":\"02.01.23 15:24\",\"LastBolusAmount\":\"1\",\"TempBasalAbsoluteRate\":\"0\",\"TempBasalStart\":\"02.01.23 16:20\",\"TempBasalRemaining\":\"55\",\"BaseBasalRate\":\"0" +
".41\",\"ActiveProfile\":\"L29_U200 IC\"},\"reservoir\":\"133\",\"clock\":\"2023-01-02T15:25:05.826Z\"}",
uploaderBattery = 60,
isCharging = false,
configuration = "{\"insulin\":5,\"insulinConfiguration\":{},\"sensitivity\":2,\"sensitivityConfiguration\":{\"openapsama_min_5m_carbimpact\":8,\"absorption_cutoff\":4,\"autosens_max\":1.2,\"autosens_min\":0.7},\"overviewConfiguration\":{\"units\":\"mmol\",\"QuickWizard\":\"[]\",\"eatingsoon_duration\":60,\"eatingsoon_target\":4,\"activity_duration\":180,\"activity_target\":7.5,\"hypo_duration\":90,\"hypo_target\":8,\"low_mark\":3.9,\"high_mark\":10,\"statuslights_cage_warning\":72,\"statuslights_cage_critical\":96,\"statuslights_iage_warning\":120,\"statuslights_iage_critical\":150,\"statuslights_sage_warning\":168,\"statuslights_sage_critical\":336,\"statuslights_sbat_warning\":25,\"statuslights_sbat_critical\":5,\"statuslights_bage_warning\":720,\"statuslights_bage_critical\":800,\"statuslights_res_warning\":30,\"statuslights_res_critical\":10,\"statuslights_bat_warning\":50,\"statuslights_bat_critical\":25,\"boluswizard_percentage\":70},\"safetyConfiguration\":{\"age\":\"resistantadult\",\"treatmentssafety_maxbolus\":10,\"treatmentssafety_maxcarbs\":70}}"
)
val dataPair = DataSyncSelector.PairDeviceStatus(deviceStatus, 1000)

View file

@ -76,6 +76,7 @@ internal class DeviceStatusExtensionKtTest : TestBase() {
".41\"," +
"\"ActiveProfile\":\"L29_U200 IC\"},\"reservoir\":\"133\",\"clock\":\"2023-01-02T15:25:05.826Z\"}",
uploaderBattery = 60,
isCharging = false,
configuration = "{\"insulin\":5,\"insulinConfiguration\":{},\"sensitivity\":2,\"sensitivityConfiguration\":{\"openapsama_min_5m_carbimpact\":8,\"absorption_cutoff\":4,\"autosens_max\":1.2,\"autosens_min\":0.7},\"overviewConfiguration\":{\"units\":\"mmol\",\"QuickWizard\":\"[]\",\"eatingsoon_duration\":60,\"eatingsoon_target\":4,\"activity_duration\":180,\"activity_target\":7.5,\"hypo_duration\":90,\"hypo_target\":8,\"low_mark\":3.9,\"high_mark\":10,\"statuslights_cage_warning\":72,\"statuslights_cage_critical\":96,\"statuslights_iage_warning\":120,\"statuslights_iage_critical\":150,\"statuslights_sage_warning\":168,\"statuslights_sage_critical\":336,\"statuslights_sbat_warning\":25,\"statuslights_sbat_critical\":5,\"statuslights_bage_warning\":720,\"statuslights_bage_critical\":800,\"statuslights_res_warning\":30,\"statuslights_res_critical\":10,\"statuslights_bat_warning\":50,\"statuslights_bat_critical\":25,\"boluswizard_percentage\":70},\"safetyConfiguration\":{\"age\":\"resistantadult\",\"treatmentssafety_maxbolus\":10,\"treatmentssafety_maxcarbs\":70}}"
)

View file

@ -38,6 +38,7 @@ import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
@ -124,7 +125,7 @@ internal class LoadBgWorkerTest : TestBase() {
Assertions.assertTrue(result is ListenableWorker.Result.Success)
Assertions.assertTrue(result.outputData.getString("Result") == "Load not enabled")
Mockito.verify(workManager, Mockito.times(1)).enqueueUniqueWork(
eq(NSClientV3Plugin.JOB_NAME),
eq(nsClientV3Plugin.JOB_NAME),
eq(ExistingWorkPolicy.APPEND_OR_REPLACE),
any<OneTimeWorkRequest>()
)
@ -138,13 +139,13 @@ internal class LoadBgWorkerTest : TestBase() {
nsClientV3Plugin.lastLoadedSrvModified.collections.entries = 0L // first load
nsClientV3Plugin.firstLoadContinueTimestamp.collections.entries = now - 1000
sut = TestListenableWorkerBuilder<LoadBgWorker>(context).build()
Mockito.`when`(nsAndroidClient.getSgvsNewerThan(anyLong(), anyLong())).thenReturn(NSAndroidClient.ReadResponse(200, 0, emptyList()))
Mockito.`when`(nsAndroidClient.getSgvsNewerThan(anyLong(), anyInt())).thenReturn(NSAndroidClient.ReadResponse(200, 0, emptyList()))
val result = sut.doWorkAndLog()
Assertions.assertEquals(now - 1000, nsClientV3Plugin.lastLoadedSrvModified.collections.entries)
Assertions.assertTrue(result is ListenableWorker.Result.Success)
Mockito.verify(workManager, Mockito.times(1)).beginUniqueWork(
eq(NSClientV3Plugin.JOB_NAME),
eq(nsClientV3Plugin.JOB_NAME),
eq(ExistingWorkPolicy.APPEND_OR_REPLACE),
any<OneTimeWorkRequest>()
)
@ -175,12 +176,12 @@ internal class LoadBgWorkerTest : TestBase() {
nsClientV3Plugin.lastLoadedSrvModified.collections.entries = 0L // first load
nsClientV3Plugin.firstLoadContinueTimestamp.collections.entries = now - 1000
sut = TestListenableWorkerBuilder<LoadBgWorker>(context).build()
Mockito.`when`(nsAndroidClient.getSgvsNewerThan(anyLong(), anyLong())).thenReturn(NSAndroidClient.ReadResponse(200, 0, listOf(glucoseValue.toNSSvgV3())))
Mockito.`when`(nsAndroidClient.getSgvsNewerThan(anyLong(), anyInt())).thenReturn(NSAndroidClient.ReadResponse(200, 0, listOf(glucoseValue.toNSSvgV3())))
val result = sut.doWorkAndLog()
Assertions.assertTrue(result is ListenableWorker.Result.Success)
Mockito.verify(workManager, Mockito.times(1)).beginUniqueWork(
eq(NSClientV3Plugin.JOB_NAME),
eq(nsClientV3Plugin.JOB_NAME),
eq(ExistingWorkPolicy.APPEND_OR_REPLACE),
any<OneTimeWorkRequest>()
)
@ -197,13 +198,13 @@ internal class LoadBgWorkerTest : TestBase() {
nsClientV3Plugin.firstLoadContinueTimestamp.collections.entries = now - 1000
nsClientV3Plugin.newestDataOnServer?.collections?.entries = now - 2000
sut = TestListenableWorkerBuilder<LoadBgWorker>(context).build()
Mockito.`when`(nsAndroidClient.getSgvsNewerThan(anyLong(), anyLong())).thenReturn(NSAndroidClient.ReadResponse(200, 0, emptyList()))
Mockito.`when`(nsAndroidClient.getSgvsNewerThan(anyLong(), anyInt())).thenReturn(NSAndroidClient.ReadResponse(200, 0, emptyList()))
val result = sut.doWorkAndLog()
Assertions.assertEquals(now - 1000, nsClientV3Plugin.lastLoadedSrvModified.collections.entries)
Assertions.assertTrue(result is ListenableWorker.Result.Success)
Mockito.verify(workManager, Mockito.times(1)).beginUniqueWork(
eq(NSClientV3Plugin.JOB_NAME),
eq(nsClientV3Plugin.JOB_NAME),
eq(ExistingWorkPolicy.APPEND_OR_REPLACE),
any<OneTimeWorkRequest>()
)