NSCv3: process DeviceStatus

This commit is contained in:
Milos Kozak 2023-01-04 11:51:58 +01:00
parent 854acc2992
commit d190b53713
12 changed files with 846 additions and 300 deletions

View file

@ -1,10 +1,15 @@
package info.nightscout.interfaces.utils package info.nightscout.interfaces.utils
import android.text.Html import android.text.Html
import android.text.SpannableStringBuilder
import android.text.Spanned import android.text.Spanned
object HtmlHelper { object HtmlHelper {
fun fromHtml(source: String): Spanned {
return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY) fun fromHtml(source: String): Spanned =
try {
Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY)
} catch (e: Exception) {
SpannableStringBuilder("")
} }
} }

View file

@ -227,6 +227,34 @@ class NSAndroidClientImpl(
} }
} }
override suspend fun createDeviceStatus(remoteDeviceStatus: RemoteDeviceStatus): CreateUpdateResponse = callWrapper(dispatcher) {
remoteDeviceStatus.app = "AAPS"
val response = api.createDeviceStatus(remoteDeviceStatus)
if (response.isSuccessful) {
if (response.code() == 200) {
return@callWrapper CreateUpdateResponse(
response = response.code(),
identifier = null,
isDeduplication = true,
deduplicatedIdentifier = null,
lastModified = null
)
} else if (response.code() == 201) {
return@callWrapper CreateUpdateResponse(
response = response.code(),
identifier = response.body()?.result?.identifier
?: throw UnknownResponseNightscoutException(),
isDeduplication = response.body()?.result?.isDeduplication ?: false,
deduplicatedIdentifier = response.body()?.result?.deduplicatedIdentifier,
lastModified = response.body()?.result?.lastModified
)
} else throw UnknownResponseNightscoutException()
} else {
throw UnsuccessfullNightscoutException()
}
}
override suspend fun createTreatment(nsTreatment: NSTreatment): CreateUpdateResponse = callWrapper(dispatcher) { override suspend fun createTreatment(nsTreatment: NSTreatment): CreateUpdateResponse = callWrapper(dispatcher) {
val remoteTreatment = nsTreatment.toRemoteTreatment() ?: throw InvalidFormatNightscoutException() val remoteTreatment = nsTreatment.toRemoteTreatment() ?: throw InvalidFormatNightscoutException()

View file

@ -3,22 +3,13 @@ package info.nightscout.sdk
import info.nightscout.sdk.interfaces.NSAndroidClient import info.nightscout.sdk.interfaces.NSAndroidClient
import info.nightscout.sdk.interfaces.NSAndroidRxClient import info.nightscout.sdk.interfaces.NSAndroidRxClient
import info.nightscout.sdk.localmodel.Status import info.nightscout.sdk.localmodel.Status
import info.nightscout.sdk.localmodel.entry.NSSgvV3
import info.nightscout.sdk.localmodel.treatment.NSTreatment
import info.nightscout.sdk.remotemodel.LastModified import info.nightscout.sdk.remotemodel.LastModified
import info.nightscout.sdk.remotemodel.RemoteDeviceStatus
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import kotlinx.coroutines.rx3.rxSingle import kotlinx.coroutines.rx3.rxSingle
import retrofit2.http.Query
class NSAndroidRxClientImpl(private val client: NSAndroidClient) : NSAndroidRxClient { class NSAndroidRxClientImpl(private val client: NSAndroidClient) : NSAndroidRxClient {
override fun getVersion(): Single<String> = rxSingle { client.getVersion() } override fun getVersion(): Single<String> = rxSingle { client.getVersion() }
override fun getStatus(): Single<Status> = rxSingle { client.getStatus() } override fun getStatus(): Single<Status> = rxSingle { client.getStatus() }
override fun getLastModified(): Single<LastModified> = rxSingle { client.getLastModified() } override fun getLastModified(): Single<LastModified> = rxSingle { client.getLastModified() }
override fun getSgvsModifiedSince(from: Long, limit: Long): Single<NSAndroidClient.ReadResponse<List<NSSgvV3>>> = rxSingle { client.getSgvsModifiedSince(from, limit) }
override fun getTreatmentsModifiedSince(from: Long, limit: Long): Single<NSAndroidClient.ReadResponse<List<NSTreatment>>> =
rxSingle { client.getTreatmentsModifiedSince(from, limit) }
override fun getDeviceStatusModifiedSince(from: Long): Single<List<RemoteDeviceStatus>> =
rxSingle { client.getDeviceStatusModifiedSince(from) }
} }

View file

@ -19,17 +19,23 @@ interface NSAndroidClient {
suspend fun getVersion(): String suspend fun getVersion(): String
suspend fun getStatus(): Status suspend fun getStatus(): Status
suspend fun getLastModified(): LastModified suspend fun getLastModified(): LastModified
suspend fun getSgvs(): List<NSSgvV3> suspend fun getSgvs(): List<NSSgvV3>
suspend fun getSgvsModifiedSince(from: Long, limit: Long): ReadResponse<List<NSSgvV3>> suspend fun getSgvsModifiedSince(from: Long, limit: Long): ReadResponse<List<NSSgvV3>>
suspend fun getSgvsNewerThan(from: Long, limit: Long): List<NSSgvV3> suspend fun getSgvsNewerThan(from: Long, limit: Long): List<NSSgvV3>
suspend fun createSvg(nsSgvV3: NSSgvV3): CreateUpdateResponse suspend fun createSvg(nsSgvV3: NSSgvV3): CreateUpdateResponse
suspend fun updateSvg(nsSgvV3: NSSgvV3): CreateUpdateResponse suspend fun updateSvg(nsSgvV3: NSSgvV3): CreateUpdateResponse
suspend fun getTreatmentsNewerThan(createdAt: String, limit: Long): List<NSTreatment> suspend fun getTreatmentsNewerThan(createdAt: String, limit: Long): List<NSTreatment>
suspend fun getTreatmentsModifiedSince(from: Long, limit: Long): ReadResponse<List<NSTreatment>> suspend fun getTreatmentsModifiedSince(from: Long, limit: Long): ReadResponse<List<NSTreatment>>
suspend fun createDeviceStatus(remoteDeviceStatus: RemoteDeviceStatus): CreateUpdateResponse
suspend fun getDeviceStatusModifiedSince(from: Long): List<RemoteDeviceStatus> suspend fun getDeviceStatusModifiedSince(from: Long): List<RemoteDeviceStatus>
suspend fun createTreatment(nsTreatment: NSTreatment): CreateUpdateResponse suspend fun createTreatment(nsTreatment: NSTreatment): CreateUpdateResponse
suspend fun updateTreatment(nsTreatment: NSTreatment): CreateUpdateResponse suspend fun updateTreatment(nsTreatment: NSTreatment): CreateUpdateResponse
suspend fun getFoods(limit: Long): List<NSFood> suspend fun getFoods(limit: Long): List<NSFood>
//suspend fun getFoodsModifiedSince(from: Long, limit: Long): ReadResponse<List<NSFood>> //suspend fun getFoodsModifiedSince(from: Long, limit: Long): ReadResponse<List<NSFood>>
suspend fun createFood(nsFood: NSFood): CreateUpdateResponse suspend fun createFood(nsFood: NSFood): CreateUpdateResponse
suspend fun updateFood(nsFood: NSFood): CreateUpdateResponse suspend fun updateFood(nsFood: NSFood): CreateUpdateResponse

View file

@ -1,10 +1,7 @@
package info.nightscout.sdk.interfaces package info.nightscout.sdk.interfaces
import info.nightscout.sdk.localmodel.Status import info.nightscout.sdk.localmodel.Status
import info.nightscout.sdk.localmodel.entry.NSSgvV3
import info.nightscout.sdk.localmodel.treatment.NSTreatment
import info.nightscout.sdk.remotemodel.LastModified import info.nightscout.sdk.remotemodel.LastModified
import info.nightscout.sdk.remotemodel.RemoteDeviceStatus
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
interface NSAndroidRxClient { interface NSAndroidRxClient {
@ -12,8 +9,5 @@ interface NSAndroidRxClient {
fun getVersion(): Single<String> fun getVersion(): Single<String>
fun getStatus(): Single<Status> fun getStatus(): Single<Status>
fun getLastModified(): Single<LastModified> fun getLastModified(): Single<LastModified>
fun getSgvsModifiedSince(from: Long, limit: Long): Single<NSAndroidClient.ReadResponse<List<NSSgvV3>>>
fun getTreatmentsModifiedSince(from: Long, limit: Long): Single<NSAndroidClient.ReadResponse<List<NSTreatment>>>
fun getDeviceStatusModifiedSince(from: Long): Single<List<RemoteDeviceStatus>>
} }

View file

@ -56,15 +56,18 @@ internal interface NightscoutRemoteService {
@GET("v3/treatments/history/{from}") @GET("v3/treatments/history/{from}")
suspend fun getTreatmentsModifiedSince(@Path("from") from: Long, @Query("limit") limit: Long): Response<NSResponse<List<RemoteTreatment>>> suspend fun getTreatmentsModifiedSince(@Path("from") from: Long, @Query("limit") limit: Long): Response<NSResponse<List<RemoteTreatment>>>
@GET("v3/devicestatus/history/{from}")
suspend fun getDeviceStatusModifiedSince(@Path("from") from: Long): Response<NSResponse<List<RemoteDeviceStatus>>>
@POST("v3/treatments") @POST("v3/treatments")
suspend fun createTreatment(@Body remoteTreatment: RemoteTreatment): Response<NSResponse<RemoteCreateUpdateResponse>> suspend fun createTreatment(@Body remoteTreatment: RemoteTreatment): Response<NSResponse<RemoteCreateUpdateResponse>>
@PATCH("v3/treatments/{identifier}") @PATCH("v3/treatments/{identifier}")
suspend fun updateTreatment(@Body remoteTreatment: RemoteTreatment, @Path("identifier") identifier: String): Response<NSResponse<RemoteCreateUpdateResponse>> suspend fun updateTreatment(@Body remoteTreatment: RemoteTreatment, @Path("identifier") identifier: String): Response<NSResponse<RemoteCreateUpdateResponse>>
@POST("v3/devicestatus")
suspend fun createDeviceStatus(@Body remoteDeviceStatus: RemoteDeviceStatus): Response<NSResponse<RemoteCreateUpdateResponse>>
@GET("v3/devicestatus/history/{from}")
suspend fun getDeviceStatusModifiedSince(@Path("from") from: Long): Response<NSResponse<List<RemoteDeviceStatus>>>
@GET("v3/food") @GET("v3/food")
suspend fun getFoods(@Query("limit") limit: Long): Response<NSResponse<List<RemoteFood>>> suspend fun getFoods(@Query("limit") limit: Long): Response<NSResponse<List<RemoteFood>>>
/* /*

View file

@ -11,10 +11,12 @@ import org.json.JSONObject
**/ **/
@Serializable @Serializable
data class RemoteDeviceStatus( data class RemoteDeviceStatus(
@SerializedName("identifier") val identifier: String?, // string Main addressing, required field that identifies document in the collection. The client should not create the identifier, the server automatically assigns it when the document is inserted. @SerializedName("app") var app: String? = null,
@SerializedName("srvCreated") val srvCreated: Long?, // integer($int64) example: 1525383610088 The server's timestamp of document insertion into the database (Unix epoch in ms). This field appears only for documents which were inserted by API v3. @SerializedName("identifier") val identifier: String? = null, // string Main addressing, required field that identifies document in the collection. The client should not create the identifier, the server automatically assigns it when the document is inserted.
@SerializedName("srvModified") val srvModified: Long?, // integer($int64) example: 1525383610088 The server's timestamp of the last document modification in the database (Unix epoch in ms). This field appears only for documents which were somehow modified by API v3 (inserted, updated or deleted). @SerializedName("srvCreated") val srvCreated: Long? = null, // integer($int64) example: 1525383610088 The server's timestamp of document insertion into the database (Unix epoch in ms). This field appears only for documents which were inserted by API v3.
@SerializedName("created_at") val createdAt: String?, // string or string timestamp on previous version of api, in my examples, a lot of treatments don't have date, only created_at, some of them with string others with long... @SerializedName("srvModified") val srvModified: Long? = null, // integer($int64) example: 1525383610088 The server's timestamp of the last document modification in the database (Unix epoch in ms). This field appears only for documents which were somehow modified by API v3 (inserted, updated or deleted).
@SerializedName("created_at") val createdAt: String? = null, // string or string timestamp on previous version of api, in my examples, a lot of treatments don't have date, only created_at, some of them with string others with long...
@SerializedName("date") val date: Long?, // date as milliseconds
@SerializedName("uploaderBattery") val uploaderBattery: Int?,// integer($int64) @SerializedName("uploaderBattery") val uploaderBattery: Int?,// integer($int64)
@SerializedName("device") val device: String?, // "openaps://samsung SM-G970F" @SerializedName("device") val device: String?, // "openaps://samsung SM-G970F"

View file

@ -28,6 +28,7 @@ dependencies {
testImplementation "androidx.work:work-testing:$work_version" testImplementation "androidx.work:work-testing:$work_version"
testImplementation project(':implementation') testImplementation project(':implementation')
testImplementation project(':plugins:aps')
// NSClient, Tidepool // NSClient, Tidepool
api("io.socket:socket.io-client:1.0.2") { api("io.socket:socket.io-client:1.0.2") {

View file

@ -47,6 +47,7 @@ import info.nightscout.plugins.sync.nsclientV3.extensions.toNSSvgV3
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSTemporaryBasal import info.nightscout.plugins.sync.nsclientV3.extensions.toNSTemporaryBasal
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSTemporaryTarget import info.nightscout.plugins.sync.nsclientV3.extensions.toNSTemporaryTarget
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSTherapyEvent import info.nightscout.plugins.sync.nsclientV3.extensions.toNSTherapyEvent
import info.nightscout.plugins.sync.nsclientV3.extensions.toRemoteDeviceStatus
import info.nightscout.plugins.sync.nsclientV3.workers.LoadBgWorker import info.nightscout.plugins.sync.nsclientV3.workers.LoadBgWorker
import info.nightscout.plugins.sync.nsclientV3.workers.LoadLastModificationWorker import info.nightscout.plugins.sync.nsclientV3.workers.LoadLastModificationWorker
import info.nightscout.plugins.sync.nsclientV3.workers.LoadStatusWorker import info.nightscout.plugins.sync.nsclientV3.workers.LoadStatusWorker
@ -330,8 +331,36 @@ class NSClientV3Plugin @Inject constructor(
enum class Operation { CREATE, UPDATE } enum class Operation { CREATE, UPDATE }
private val gson: Gson = GsonBuilder().create() private val gson: Gson = GsonBuilder().create()
private fun dbOperation(collection: String, dataPair: DataSyncSelector.DataPair, progress: String, operation: Operation) { private fun dbOperationDeviceStatus(collection: String = "devicestatus", dataPair: DataSyncSelector.DataPair, progress: String) {
if (collection == "entries") { val data = (dataPair as DataSyncSelector.PairDeviceStatus).value.toRemoteDeviceStatus()
scope.launch {
try {
rxBus.send(EventNSClientNewLog("ADD $collection", "Sent ${dataPair.javaClass.simpleName} ${gson.toJson(data)} $progress"))
nsAndroidClient?.createDeviceStatus(data)?.let { result ->
when (result.response) {
200 -> rxBus.send(EventNSClientNewLog("UPDATED", "OK ${dataPair.value.javaClass.simpleName}"))
201 -> rxBus.send(EventNSClientNewLog("ADDED", "OK ${dataPair.value.javaClass.simpleName} ${result.identifier}"))
else -> {
rxBus.send(EventNSClientNewLog("ERROR", "${dataPair.value.javaClass.simpleName} "))
return@launch
}
}
if (result.response == 201) { // created
dataPair.value.interfaceIDs.nightscoutId = result.identifier
storeDataForDb.nsIdDeviceStatuses.add(dataPair.value)
storeDataForDb.scheduleNsIdUpdate()
}
dataSyncSelector.confirmLastDeviceStatusIdIfGreater(dataPair.id)
dataSyncSelector.processChangedDeviceStatusesCompat()
}
} catch (e: Exception) {
aapsLogger.error(LTag.NSCLIENT, "Upload exception", e)
}
}
}
private fun dbOperationEntries(collection: String = "entries", dataPair: DataSyncSelector.DataPair, progress: String, operation: Operation) {
val call = when (operation) { val call = when (operation) {
Operation.CREATE -> nsAndroidClient?.let { return@let it::createSvg } Operation.CREATE -> nsAndroidClient?.let { return@let it::createSvg }
Operation.UPDATE -> nsAndroidClient?.let { return@let it::updateSvg } Operation.UPDATE -> nsAndroidClient?.let { return@let it::updateSvg }
@ -384,7 +413,8 @@ class NSClientV3Plugin @Inject constructor(
} }
} }
} }
if (collection == "food") {
private fun dbOperationFood(collection: String = "food", dataPair: DataSyncSelector.DataPair, progress: String, operation: Operation) {
val call = when (operation) { val call = when (operation) {
Operation.CREATE -> nsAndroidClient?.let { return@let it::createFood } Operation.CREATE -> nsAndroidClient?.let { return@let it::createFood }
Operation.UPDATE -> nsAndroidClient?.let { return@let it::updateFood } Operation.UPDATE -> nsAndroidClient?.let { return@let it::updateFood }
@ -437,7 +467,8 @@ class NSClientV3Plugin @Inject constructor(
} }
} }
} }
if (collection == "treatments") {
private fun dbOperationTreatments(collection: String = "treatments", dataPair: DataSyncSelector.DataPair, progress: String, operation: Operation) {
val call = when (operation) { val call = when (operation) {
Operation.CREATE -> nsAndroidClient?.let { return@let it::createTreatment } Operation.CREATE -> nsAndroidClient?.let { return@let it::createTreatment }
Operation.UPDATE -> nsAndroidClient?.let { return@let it::updateTreatment } Operation.UPDATE -> nsAndroidClient?.let { return@let it::updateTreatment }
@ -458,6 +489,7 @@ class NSClientV3Plugin @Inject constructor(
} }
dataPair.value.toNSTemporaryBasal(profile) dataPair.value.toNSTemporaryBasal(profile)
} }
is DataSyncSelector.PairExtendedBolus -> { is DataSyncSelector.PairExtendedBolus -> {
val profile = profileFunction.getProfile(dataPair.value.timestamp) val profile = profileFunction.getProfile(dataPair.value.timestamp)
if (profile == null) { if (profile == null) {
@ -467,6 +499,7 @@ class NSClientV3Plugin @Inject constructor(
} }
dataPair.value.toNSExtendedBolus(profile) dataPair.value.toNSExtendedBolus(profile)
} }
is DataSyncSelector.PairProfileSwitch -> dataPair.value.toNSProfileSwitch(dateUtil) is DataSyncSelector.PairProfileSwitch -> dataPair.value.toNSProfileSwitch(dateUtil)
is DataSyncSelector.PairEffectiveProfileSwitch -> dataPair.value.toNSEffectiveProfileSwitch(dateUtil) is DataSyncSelector.PairEffectiveProfileSwitch -> dataPair.value.toNSEffectiveProfileSwitch(dateUtil)
is DataSyncSelector.PairOfflineEvent -> dataPair.value.toNSOfflineEvent() is DataSyncSelector.PairOfflineEvent -> dataPair.value.toNSOfflineEvent()
@ -606,6 +639,14 @@ class NSClientV3Plugin @Inject constructor(
} }
} }
} }
private fun dbOperation(collection: String, dataPair: DataSyncSelector.DataPair, progress: String, operation: Operation) {
when (collection) {
"devicestatus" -> dbOperationDeviceStatus(dataPair = dataPair, progress = progress)
"entries" -> dbOperationEntries(dataPair = dataPair, progress = progress, operation = operation)
"food" -> dbOperationFood(dataPair = dataPair, progress = progress, operation = operation)
"treatments" -> dbOperationTreatments(dataPair = dataPair, progress = progress, operation = operation)
}
} }
fun storeLastLoadedSrvModified() { fun storeLastLoadedSrvModified() {

View file

@ -0,0 +1,389 @@
package info.nightscout.plugins.sync.nsclientV3.extensions
import com.google.gson.GsonBuilder
import com.google.gson.JsonDeserializer
import info.nightscout.database.entities.DeviceStatus
import info.nightscout.sdk.remotemodel.RemoteDeviceStatus
import org.json.JSONObject
fun DeviceStatus.toRemoteDeviceStatus(): RemoteDeviceStatus {
val deserializer: JsonDeserializer<JSONObject?> =
JsonDeserializer<JSONObject?> { json, _, _ ->
JSONObject(json.asJsonObject.toString())
}
val gson = GsonBuilder().also {
it.registerTypeAdapter(JSONObject::class.java, deserializer)
}.create()
val pump = gson.fromJson(pump, RemoteDeviceStatus.Pump::class.java)
val openAps = RemoteDeviceStatus.OpenAps(
suggested = suggested?.let { JSONObject(it) },
enacted = enacted?.let { JSONObject(it) },
iob = iob?.let { JSONObject(it) },
)
return RemoteDeviceStatus(
date = timestamp,
device = device,
pump = pump,
openaps = openAps,
uploaderBattery = if (uploaderBattery != 0) uploaderBattery else null,
configuration = gson.fromJson(configuration, RemoteDeviceStatus.Configuration::class.java),
uploader = null
)
}
/*
{
"_id": "576cfd15217b0bed77d63641",
"device": "openaps://indy2",
"pump": {
"battery": {
"status": "normal",
"voltage": 1.56
},
"status": {
"status": "normal",
"timestamp": "2016-06-24T09:26:38.000Z",
"bolusing": false,
"suspended": false
},
"reservoir": 31.25,
"clock": "2016-06-24T02:26:16-07:00"
},
"openaps": {
"suggested": {
"bg": 173,
"temp": "absolute",
"snoozeBG": 194,
"timestamp": "2016-06-24T09:27:40.000Z",
"predBGs": {
"IOB": [173, 178, 183, 187, 191, 194, 196, 197, 198, 197, 197, 195, 192, 190, 187, 184, 181, 178, 175, 172, 169, 167, 164, 162, 160, 158, 156, 154, 152, 151, 149, 148, 147, 146, 146, 145]
},
"reason": "COB: 0, Dev: 46, BGI: -1.92, ISF: 80, Target: 115; Eventual BG 194>=115, adj. req. rate:2.7 to maxSafeBasal:2.3, temp 2.25 >~ req 2.3U/hr",
"COB": 0,
"eventualBG": 194,
"tick": "+6",
"IOB": 0.309
},
"iob": [{
"netbasalinsulin": -0.3,
"activity": 0.0048,
"basaliob": 0.078,
"time": "2016-06-24T09:26:16.000Z",
"hightempinsulin": 0.25,
"bolussnooze": 0,
"iob": 0.309
}, {
"netbasalinsulin": -0.15,
"activity": 0.0041,
"basaliob": 0.238,
"time": "2016-06-24T09:31:16.000Z",
"hightempinsulin": 0.4,
"bolussnooze": 0,
"iob": 0.438
}, {
"netbasalinsulin": 0,
"activity": 0.0036,
"basaliob": 0.345,
"time": "2016-06-24T09:36:16.000Z",
"hightempinsulin": 0.5,
"bolussnooze": 0,
"iob": 0.52
}, {
"netbasalinsulin": 0.2,
"activity": 0.0036,
"basaliob": 0.5,
"time": "2016-06-24T09:41:16.000Z",
"hightempinsulin": 0.65,
"bolussnooze": 0,
"iob": 0.653
}, {
"netbasalinsulin": 0.35,
"activity": 0.0038,
"basaliob": 0.602,
"time": "2016-06-24T09:46:16.000Z",
"hightempinsulin": 0.75,
"bolussnooze": 0,
"iob": 0.734
}, {
"netbasalinsulin": 0.45,
"activity": 0.0042,
"basaliob": 0.651,
"time": "2016-06-24T09:51:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.763
}, {
"netbasalinsulin": 0.45,
"activity": 0.0045,
"basaliob": 0.647,
"time": "2016-06-24T09:56:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.74
}, {
"netbasalinsulin": 0.5,
"activity": 0.0048,
"basaliob": 0.639,
"time": "2016-06-24T10:01:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.716
}, {
"netbasalinsulin": 0.5,
"activity": 0.0052,
"basaliob": 0.628,
"time": "2016-06-24T10:06:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.691
}, {
"netbasalinsulin": 0.5,
"activity": 0.0055,
"basaliob": 0.614,
"time": "2016-06-24T10:11:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.663
}, {
"netbasalinsulin": 0.5,
"activity": 0.0059,
"basaliob": 0.596,
"time": "2016-06-24T10:16:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.633
}, {
"netbasalinsulin": 0.55,
"activity": 0.0063,
"basaliob": 0.575,
"time": "2016-06-24T10:21:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.602
}, {
"netbasalinsulin": 0.55,
"activity": 0.0067,
"basaliob": 0.549,
"time": "2016-06-24T10:26:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.568
}, {
"netbasalinsulin": 0.55,
"activity": 0.0071,
"basaliob": 0.521,
"time": "2016-06-24T10:31:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.533
}, {
"netbasalinsulin": 0.6,
"activity": 0.0074,
"basaliob": 0.489,
"time": "2016-06-24T10:36:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.496
}, {
"netbasalinsulin": 0.6,
"activity": 0.0075,
"basaliob": 0.456,
"time": "2016-06-24T10:41:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.458
}, {
"netbasalinsulin": 0.6,
"activity": 0.0075,
"basaliob": 0.42,
"time": "2016-06-24T10:46:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.421
}, {
"netbasalinsulin": 0.6,
"activity": 0.0073,
"basaliob": 0.384,
"time": "2016-06-24T10:51:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.384
}, {
"netbasalinsulin": 0.65,
"activity": 0.0071,
"basaliob": 0.349,
"time": "2016-06-24T10:56:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.349
}, {
"netbasalinsulin": 0.65,
"activity": 0.0069,
"basaliob": 0.314,
"time": "2016-06-24T11:01:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.314
}, {
"netbasalinsulin": 0.65,
"activity": 0.0066,
"basaliob": 0.281,
"time": "2016-06-24T11:06:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.281
}, {
"netbasalinsulin": 0.65,
"activity": 0.0062,
"basaliob": 0.25,
"time": "2016-06-24T11:11:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.25
}, {
"netbasalinsulin": 0.65,
"activity": 0.0059,
"basaliob": 0.221,
"time": "2016-06-24T11:16:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.221
}, {
"netbasalinsulin": 0.65,
"activity": 0.0055,
"basaliob": 0.193,
"time": "2016-06-24T11:21:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.193
}, {
"netbasalinsulin": 0.65,
"activity": 0.0052,
"basaliob": 0.167,
"time": "2016-06-24T11:26:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.167
}, {
"netbasalinsulin": 0.7,
"activity": 0.0049,
"basaliob": 0.143,
"time": "2016-06-24T11:31:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.143
}, {
"netbasalinsulin": 0.7,
"activity": 0.0045,
"basaliob": 0.12,
"time": "2016-06-24T11:36:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.12
}, {
"netbasalinsulin": 0.7,
"activity": 0.0041,
"basaliob": 0.1,
"time": "2016-06-24T11:41:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.1
}, {
"netbasalinsulin": 0.7,
"activity": 0.0037,
"basaliob": 0.081,
"time": "2016-06-24T11:46:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.081
}, {
"netbasalinsulin": 0.75,
"activity": 0.0034,
"basaliob": 0.064,
"time": "2016-06-24T11:51:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.064
}, {
"netbasalinsulin": 0.75,
"activity": 0.003,
"basaliob": 0.049,
"time": "2016-06-24T11:56:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.049
}, {
"netbasalinsulin": 0.8,
"activity": 0.0026,
"basaliob": 0.036,
"time": "2016-06-24T12:01:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.036
}, {
"netbasalinsulin": 0.8,
"activity": 0.0021,
"basaliob": 0.026,
"time": "2016-06-24T12:06:16.000Z",
"hightempinsulin": 0.8,
"bolussnooze": 0,
"iob": 0.026
}, {
"netbasalinsulin": 0.75,
"activity": 0.0017,
"basaliob": 0.017,
"time": "2016-06-24T12:11:16.000Z",
"hightempinsulin": 0.75,
"bolussnooze": 0,
"iob": 0.017
}, {
"netbasalinsulin": 0.75,
"activity": 0.0013,
"basaliob": 0.011,
"time": "2016-06-24T12:16:16.000Z",
"hightempinsulin": 0.75,
"bolussnooze": 0,
"iob": 0.011
}, {
"netbasalinsulin": 0.65,
"activity": 0.0009,
"basaliob": 0.006,
"time": "2016-06-24T12:21:16.000Z",
"hightempinsulin": 0.65,
"bolussnooze": 0,
"iob": 0.006
}],
"enacted": {
"bg": 161,
"temp": "absolute",
"snoozeBG": 181,
"recieved": true,
"predBGs": {
"IOB": [161, 164, 166, 168, 170, 172, 174, 175, 176, 177, 177, 176, 175, 175, 174, 173, 173, 172, 172, 171, 171, 171, 171, 170, 170, 170, 170, 170, 169, 169, 169, 169, 169, 168]
},
"reason": "COB: undefined, Dev: 33, BGI: -2.56, ISF: 80, Target: 115; Eventual BG 181>=115, adj. req. rate:2.4 to maxSafeBasal:2.3, temp 1<2.3U/hr",
"rate": 2.25,
"eventualBG": 181,
"timestamp": "2016-06-24T09:19:06.000Z",
"duration": 30,
"tick": "+5",
"IOB": 0.166
}
},
"mmtune": {
"scanDetails": [
["916.564", 5, -78],
["916.588", 3, -80],
["916.612", 4, -68],
["916.636", 5, -65],
["916.660", 5, -60],
["916.684", 5, -67],
["916.708", 5, -71]
],
"setFreq": 916.66,
"timestamp": "2016-06-24T09:26:22.000Z",
"usedDefault": false
},
"created_at": "2016-06-24T09:27:49.230Z"
}
*/

View file

@ -0,0 +1,86 @@
package info.nightscout.plugins.sync.nsclientV3.extensions
import dagger.android.AndroidInjector
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.TestBase
import info.nightscout.database.entities.DeviceStatus
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.configBuilder.RunningConfiguration
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.nsclient.ProcessedDeviceStatusData
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.profile.Instantiator
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.plugins.aps.APSResultObject
import info.nightscout.plugins.sync.nsclient.data.NSDeviceStatusHandler
import info.nightscout.plugins.sync.nsclient.data.ProcessedDeviceStatusDataImpl
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mock
@Suppress("SpellCheckingInspection")
internal class DeviceStatusExtensionKtTest : TestBase() {
@Mock lateinit var constraintChecker: Constraints
@Mock lateinit var sp: SP
@Mock lateinit var activePlugin: ActivePlugin
@Mock lateinit var iobCobCalculator: IobCobCalculator
@Mock lateinit var profileFunction: ProfileFunction
@Mock lateinit var rh: ResourceHelper
@Mock lateinit var dateUtil: DateUtil
@Mock lateinit var config: Config
@Mock lateinit var runningConfiguration: RunningConfiguration
@Mock lateinit var instantiator: Instantiator
private lateinit var processedDeviceStatusData: ProcessedDeviceStatusData
private lateinit var nsDeviceStatusHandler: NSDeviceStatusHandler
val injector = HasAndroidInjector {
AndroidInjector {
if (it is APSResultObject) {
it.aapsLogger = aapsLogger
it.constraintChecker = constraintChecker
it.sp = sp
it.activePlugin = activePlugin
it.iobCobCalculator = iobCobCalculator
it.profileFunction = profileFunction
it.rh = rh
it.dateUtil = dateUtil
}
}
}
@BeforeEach
fun setup() {
processedDeviceStatusData = ProcessedDeviceStatusDataImpl(rh, dateUtil, sp, instantiator)
nsDeviceStatusHandler = NSDeviceStatusHandler(sp, config, dateUtil, runningConfiguration, processedDeviceStatusData)
}
@Test
fun dotest() {
val deviceStatus = DeviceStatus(
timestamp = 10000,
suggested = "{\"temp\":\"absolute\",\"bg\":133,\"tick\":-6,\"eventualBG\":67,\"targetBG\":99,\"insulinReq\":0,\"deliverAt\":\"2023-01-02T15:29:33.374Z\",\"sensitivityRatio\":1,\"variable_sens\":97.5,\"predBGs\":{\"IOB\":[133,127,121,116,111,106,101,97,93,89,85,81,78,75,72,69,67,65,62,60,58,57,55,54,52,51,50,49,48,47,46,45,45,44,43,43,42,42,41,41,41,41,40,40,40,40,39],\"ZT\":[133,127,121,115,110,105,101,96,92,88,84,81,77,74,71,69,66,64,62,59,58,56,54,53,51,50,49,48,47,46,45,44,44,43,42,42,41,41,40,40,40,39,39,39,39,39,39,39],\"UAM\":[133,127,121,115,110,105,101,96,92,88,84,81,77,74,71,69,66,64,62,59,58,56,54,53,51,50,49,48,47,46,45,44,44,43,42,42,41,41,40,40,40,39]},\"reason\":\"COB: 0, Dev: 0.1, BGI: -0.3, ISF: 5.4, CR: 13, Target: 5.5, minPredBG 2.2, minGuardBG 2.1, IOBpredBG 2.2, UAMpredBG 2.2; minGuardBG 2.1<4.0\",\"COB\":0,\"IOB\":0.692,\"duration\":90,\"rate\":0,\"timestamp\":\"2023-01-02T15:29:39.460Z\"}",
iob = "{\"iob\":0.692,\"basaliob\":-0.411,\"activity\":0.0126,\"time\":\"2023-01-02T15:29:39.460Z\"}",
enacted = "{\"temp\":\"absolute\",\"bg\":133,\"tick\":-6,\"eventualBG\":67,\"targetBG\":99,\"insulinReq\":0,\"deliverAt\":\"2023-01-02T15:29:33.374Z\",\"sensitivityRatio\":1,\"variable_sens\":97.5,\"predBGs\":{\"IOB\":[133,127,121,116,111,106,101,97,93,89,85,81,78,75,72,69,67,65,62,60,58,57,55,54,52,51,50,49,48,47,46,45,45,44,43,43,42,42,41,41,41,41,40,40,40,40,39],\"ZT\":[133,127,121,115,110,105,101,96,92,88,84,81,77,74,71,69,66,64,62,59,58,56,54,53,51,50,49,48,47,46,45,44,44,43,42,42,41,41,40,40,40,39,39,39,39,39,39,39],\"UAM\":[133,127,121,115,110,105,101,96,92,88,84,81,77,74,71,69,66,64,62,59,58,56,54,53,51,50,49,48,47,46,45,44,44,43,42,42,41,41,40,40,40,39]},\"reason\":\"COB: 0, Dev: 0.1, BGI: -0.3, ISF: 5.4, CR: 13, Target: 5.5, minPredBG 2.2, minGuardBG 2.1, IOBpredBG 2.2, UAMpredBG 2.2; minGuardBG 2.1<4.0\",\"COB\":0,\"IOB\":0.692,\"duration\":90,\"rate\":0,\"timestamp\":\"2023-01-02T15:29:39.460Z\"}",
device = "openaps://samsung SM-G970F",
pump = "{\"battery\":{\"percent\":75},\"status\":{\"status\":\"normal\",\"timestamp\":\"2023-01-02T15:20:20.656Z\"},\"extended\":{\"Version\":\"3.1.0.3-dev-e-295e1ad18f-2022.12.24\"," +
"\"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,
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 remoteDeviceStatus = deviceStatus.toRemoteDeviceStatus()
nsDeviceStatusHandler.handleNewData(arrayOf(remoteDeviceStatus))
Assertions.assertEquals(75, processedDeviceStatusData.pumpData?.percent)
}
}

View file

@ -8,7 +8,7 @@ import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
internal class OfflineEventKtTest { internal class OfflineEventExtensionKtTest {
@Test @Test
fun toOfflineEvent() { fun toOfflineEvent() {