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
import android.text.Html
import android.text.SpannableStringBuilder
import android.text.Spanned
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) {
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.NSAndroidRxClient
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.RemoteDeviceStatus
import io.reactivex.rxjava3.core.Single
import kotlinx.coroutines.rx3.rxSingle
import retrofit2.http.Query
class NSAndroidRxClientImpl(private val client: NSAndroidClient) : NSAndroidRxClient {
override fun getVersion(): Single<String> = rxSingle { client.getVersion() }
override fun getStatus(): Single<Status> = rxSingle { client.getStatus() }
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 getStatus(): Status
suspend fun getLastModified(): LastModified
suspend fun getSgvs(): List<NSSgvV3>
suspend fun getSgvsModifiedSince(from: Long, limit: Long): ReadResponse<List<NSSgvV3>>
suspend fun getSgvsNewerThan(from: Long, limit: Long): List<NSSgvV3>
suspend fun createSvg(nsSgvV3: NSSgvV3): CreateUpdateResponse
suspend fun updateSvg(nsSgvV3: NSSgvV3): CreateUpdateResponse
suspend fun getTreatmentsNewerThan(createdAt: String, limit: Long): 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 createTreatment(nsTreatment: NSTreatment): CreateUpdateResponse
suspend fun updateTreatment(nsTreatment: NSTreatment): CreateUpdateResponse
suspend fun getFoods(limit: Long): List<NSFood>
//suspend fun getFoodsModifiedSince(from: Long, limit: Long): ReadResponse<List<NSFood>>
suspend fun createFood(nsFood: NSFood): CreateUpdateResponse
suspend fun updateFood(nsFood: NSFood): CreateUpdateResponse

View file

@ -1,10 +1,7 @@
package info.nightscout.sdk.interfaces
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.RemoteDeviceStatus
import io.reactivex.rxjava3.core.Single
interface NSAndroidRxClient {
@ -12,8 +9,5 @@ interface NSAndroidRxClient {
fun getVersion(): Single<String>
fun getStatus(): Single<Status>
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}")
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")
suspend fun createTreatment(@Body remoteTreatment: RemoteTreatment): Response<NSResponse<RemoteCreateUpdateResponse>>
@PATCH("v3/treatments/{identifier}")
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")
suspend fun getFoods(@Query("limit") limit: Long): Response<NSResponse<List<RemoteFood>>>
/*

View file

@ -11,10 +11,12 @@ import org.json.JSONObject
**/
@Serializable
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("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("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("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("app") var app: String? = null,
@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("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("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("device") val device: String?, // "openaps://samsung SM-G970F"

View file

@ -28,6 +28,7 @@ dependencies {
testImplementation "androidx.work:work-testing:$work_version"
testImplementation project(':implementation')
testImplementation project(':plugins:aps')
// NSClient, Tidepool
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.toNSTemporaryTarget
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.LoadLastModificationWorker
import info.nightscout.plugins.sync.nsclientV3.workers.LoadStatusWorker
@ -330,8 +331,36 @@ class NSClientV3Plugin @Inject constructor(
enum class Operation { CREATE, UPDATE }
private val gson: Gson = GsonBuilder().create()
private fun dbOperation(collection: String, dataPair: DataSyncSelector.DataPair, progress: String, operation: Operation) {
if (collection == "entries") {
private fun dbOperationDeviceStatus(collection: String = "devicestatus", dataPair: DataSyncSelector.DataPair, progress: String) {
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) {
Operation.CREATE -> nsAndroidClient?.let { return@let it::createSvg }
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) {
Operation.CREATE -> nsAndroidClient?.let { return@let it::createFood }
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) {
Operation.CREATE -> nsAndroidClient?.let { return@let it::createTreatment }
Operation.UPDATE -> nsAndroidClient?.let { return@let it::updateTreatment }
@ -458,6 +489,7 @@ class NSClientV3Plugin @Inject constructor(
}
dataPair.value.toNSTemporaryBasal(profile)
}
is DataSyncSelector.PairExtendedBolus -> {
val profile = profileFunction.getProfile(dataPair.value.timestamp)
if (profile == null) {
@ -467,6 +499,7 @@ class NSClientV3Plugin @Inject constructor(
}
dataPair.value.toNSExtendedBolus(profile)
}
is DataSyncSelector.PairProfileSwitch -> dataPair.value.toNSProfileSwitch(dateUtil)
is DataSyncSelector.PairEffectiveProfileSwitch -> dataPair.value.toNSEffectiveProfileSwitch(dateUtil)
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() {

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
@Suppress("SpellCheckingInspection")
internal class OfflineEventKtTest {
internal class OfflineEventExtensionKtTest {
@Test
fun toOfflineEvent() {