Skip to content

Commit

Permalink
NSCv3: process Food, first load based on created_at
Browse files Browse the repository at this point in the history
  • Loading branch information
MilosKozak committed Dec 28, 2022
1 parent 10e8e32 commit d9c6cd6
Show file tree
Hide file tree
Showing 25 changed files with 603 additions and 159 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package info.nightscout.androidaps.workflow

import info.nightscout.interfaces.workflow.WorkerClasses
import info.nightscout.plugins.general.food.FoodPlugin
import info.nightscout.plugins.profile.ProfilePlugin
import info.nightscout.source.NSClientSourcePlugin
import javax.inject.Inject
Expand All @@ -10,5 +9,4 @@ class WorkerClassesImpl @Inject constructor(): WorkerClasses{

override val nsClientSourceWorker = NSClientSourcePlugin.NSClientSourceWorker::class.java
override val nsProfileWorker = ProfilePlugin.NSProfileWorker::class.java
override val foodWorker = FoodPlugin.FoodWorker::class.java
}
1 change: 1 addition & 0 deletions connectwsa.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
adb connect 127.0.0.1:58526
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ interface StoreDataForDb {
val temporaryBasals: MutableList<TemporaryBasal>
val profileSwitches: MutableList<ProfileSwitch>
val offlineEvents: MutableList<OfflineEvent>
val foods: MutableList<Food>

val nsIdGlucoseValues: MutableList<GlucoseValue>
val nsIdBoluses: MutableList<Bolus>
val nsIdCarbs: MutableList<Carbs>
val nsIdFoods: MutableList<Food>
val nsIdTemporaryTargets: MutableList<TemporaryTarget>
val nsIdEffectiveProfileSwitches: MutableList<EffectiveProfileSwitch>
val nsIdBolusCalculatorResults: MutableList<BolusCalculatorResult>
Expand All @@ -41,8 +41,10 @@ interface StoreDataForDb {
val nsIdProfileSwitches: MutableList<ProfileSwitch>
val nsIdOfflineEvents: MutableList<OfflineEvent>
val nsIdDeviceStatuses: MutableList<DeviceStatus>
val nsIdFoods: MutableList<Food>

fun storeTreatmentsToDb()
fun storeGlucoseValuesToDb()
fun storeFoodsToDb()
fun scheduleNsIdUpdate()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface NsClient : Sync {
fun textLog(): Spanned
fun clearLog()

enum class Collection { ENTRIES, TREATMENTS}
enum class Collection { ENTRIES, TREATMENTS, FOODS }
/**
* NSC v3 does first load of all data
* next loads are using srvModified property for sync
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@ import androidx.work.ListenableWorker
interface WorkerClasses {
val nsClientSourceWorker: Class<out ListenableWorker>
val nsProfileWorker: Class<out ListenableWorker>
val foodWorker: Class<out ListenableWorker>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,25 @@ import android.content.Context
import info.nightscout.sdk.exceptions.DateHeaderOutOfToleranceException
import info.nightscout.sdk.exceptions.InvalidAccessTokenException
import info.nightscout.sdk.exceptions.InvalidFormatNightscoutException
import info.nightscout.sdk.exceptions.UnsuccessfullNightscoutException
import info.nightscout.sdk.exceptions.UnknownResponseNightscoutException
import info.nightscout.sdk.exceptions.UnsuccessfullNightscoutException
import info.nightscout.sdk.interfaces.NSAndroidClient
import info.nightscout.sdk.localmodel.Status
import info.nightscout.sdk.localmodel.entry.NSSgvV3
import info.nightscout.sdk.localmodel.food.NSFood
import info.nightscout.sdk.localmodel.treatment.CreateUpdateResponse
import info.nightscout.sdk.localmodel.treatment.NSTreatment
import info.nightscout.sdk.mapper.toLocal
import info.nightscout.sdk.mapper.toNSFood
import info.nightscout.sdk.mapper.toRemoteFood
import info.nightscout.sdk.mapper.toRemoteTreatment
import info.nightscout.sdk.mapper.toSgv
import info.nightscout.sdk.mapper.toTreatment
import info.nightscout.sdk.networking.NetworkStackBuilder
import info.nightscout.sdk.remotemodel.LastModified
import info.nightscout.sdk.remotemodel.RemoteDeviceStatus
import info.nightscout.sdk.remotemodel.RemoteEntry
import info.nightscout.sdk.remotemodel.RemoteFood
import info.nightscout.sdk.remotemodel.RemoteTreatment
import info.nightscout.sdk.utils.retry
import info.nightscout.sdk.utils.toNotNull
Expand Down Expand Up @@ -140,9 +144,9 @@ class NSAndroidClientImpl(
}
}

override suspend fun getTreatmentsNewerThan(from: Long, limit: Long): List<NSTreatment> = callWrapper(dispatcher) {
override suspend fun getTreatmentsNewerThan(createdAt: String, limit: Long): List<NSTreatment> = callWrapper(dispatcher) {

val response = api.getTreatmentsNewerThan(from, limit)
val response = api.getTreatmentsNewerThan(createdAt, limit)
if (response.isSuccessful) {
return@callWrapper response.body()?.result?.map(RemoteTreatment::toTreatment).toNotNull()
} else {
Expand All @@ -154,7 +158,8 @@ class NSAndroidClientImpl(

val response = api.getTreatmentsModifiedSince(from, limit)
val eTagString = response.headers()["ETag"]
val eTag = eTagString?.substring(3, eTagString.length - 1)?.toLong() ?: throw UnsuccessfullNightscoutException()
val eTag = eTagString?.substring(3, eTagString.length - 1)?.toLong()
?: throw UnsuccessfullNightscoutException()
if (response.isSuccessful) {
return@callWrapper NSAndroidClient.ReadResponse(eTag, response.body()?.result?.map(RemoteTreatment::toTreatment).toNotNull())
} else {
Expand Down Expand Up @@ -207,6 +212,64 @@ class NSAndroidClientImpl(
}
}

override suspend fun getFoods(limit: Long): List<NSFood> = callWrapper(dispatcher) {

val response = api.getFoods(limit)
if (response.isSuccessful) {
return@callWrapper response.body()?.result?.map(RemoteFood::toNSFood).toNotNull()
} else {
throw UnsuccessfullNightscoutException()
}
}

/*
override suspend fun getFoodsModifiedSince(from: Long, limit: Long): NSAndroidClient.ReadResponse<List<NSFood>> = callWrapper(dispatcher) {
val response = api.getFoodsModifiedSince(from, limit)
val eTagString = response.headers()["ETag"]
val eTag = eTagString?.substring(3, eTagString.length - 1)?.toLong() ?: throw UnsuccessfullNightscoutException()
if (response.isSuccessful) {
return@callWrapper NSAndroidClient.ReadResponse(eTag, response.body()?.result?.map(RemoteFood::toNSFood).toNotNull())
} else {
throw UnsuccessfullNightscoutException()
}
}
*/
override suspend fun createFood(nsFood: NSFood): CreateUpdateResponse = callWrapper(dispatcher) {

val remoteFood = nsFood.toRemoteFood()
remoteFood.app = "AAPS"
val response = api.createFood(remoteFood)
if (response.isSuccessful) {
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 UnsuccessfullNightscoutException()
}
}

override suspend fun updateFood(nsFood: NSFood): CreateUpdateResponse = callWrapper(dispatcher) {

val remoteFood = nsFood.toRemoteFood()
val response = api.updateFood(remoteFood)
if (response.isSuccessful) {
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 UnsuccessfullNightscoutException()
}
}

private suspend fun <T> callWrapper(dispatcher: CoroutineDispatcher, block: suspend () -> T): T =
withContext(dispatcher) {
retry(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package info.nightscout.sdk.interfaces

import info.nightscout.sdk.localmodel.Status
import info.nightscout.sdk.localmodel.entry.NSSgvV3
import info.nightscout.sdk.localmodel.food.NSFood
import info.nightscout.sdk.localmodel.treatment.CreateUpdateResponse
import info.nightscout.sdk.localmodel.treatment.NSTreatment
import info.nightscout.sdk.remotemodel.LastModified
Expand All @@ -23,9 +24,13 @@ interface NSAndroidClient {
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 getTreatmentsNewerThan(from: Long, 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 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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package info.nightscout.sdk.localmodel.food

import info.nightscout.sdk.localmodel.entry.NsUnits

data class NSFood(
val date: Long,
val device: String? = null,
val identifier: String?,
val units: NsUnits? = null,
val srvModified: Long? = null,
val srvCreated: Long? = null,
val subject: String? = null,
var isReadOnly: Boolean = false,
val isValid: Boolean,
var app: String? = null,
var name: String,
var category: String? = null,
var subCategory: String? = null,
// Example:
// name="juice" portion=250 units="ml" carbs=12
// means 250ml of juice has 12g of carbs

var portion: Double, // common portion in "units"
var carbs: Int, // in grams
var fat: Int? = null, // in grams
var protein: Int? = null, // in grams
var energy: Int? = null, // in kJ
var unit: String = "g",
var gi: Int? = null // not used yet
)
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ enum class EventType(val text: String) {
@SerializedName("Temp Basal Start") TEMPORARY_BASAL_START("Temp Basal Start"),
@SerializedName("Temp Basal End") TEMPORARY_BASAL_END("Temp Basal End"),

@SerializedName("") ERROR(""),
@SerializedName("<none>") NONE("<none>");

companion object {
Expand Down
64 changes: 64 additions & 0 deletions core/ns-sdk/src/main/java/info/nightscout/sdk/mapper/FoodMapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package info.nightscout.sdk.mapper

import info.nightscout.sdk.localmodel.food.NSFood
import info.nightscout.sdk.remotemodel.RemoteFood

/**
* Convert to [RemoteFood] and back to [NSFood]
* testing purpose only
*
* @return treatment after double conversion
*/
fun NSFood.convertToRemoteAndBack(): NSFood? =
toRemoteFood().toNSFood()

internal fun RemoteFood.toNSFood(): NSFood? {
when (type) {
"food" ->
return NSFood(
date = date ?: 0L,
device = device,
identifier = identifier,
unit = unit ?: "g",
srvModified = srvModified,
srvCreated = srvCreated,
subject = subject,
isReadOnly = isReadOnly ?: false,
isValid = isValid ?: true,
name = name,
category = category,
subCategory = subcategory,
portion = portion,
carbs = carbs,
fat = fat,
protein = protein,
energy = energy,
gi = gi
)

else -> return null
}
}

internal fun NSFood.toRemoteFood(): RemoteFood =
RemoteFood(
type = "food",
date = date,
device = device,
identifier = identifier,
unit = unit,
srvModified = srvModified,
srvCreated = srvCreated,
subject = subject,
isReadOnly = isReadOnly,
isValid = isValid,
name = name,
category = category,
subcategory = subCategory,
portion = portion,
carbs = carbs,
fat = fat,
protein = protein,
energy = energy,
gi = gi
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import info.nightscout.sdk.remotemodel.NSResponse
import info.nightscout.sdk.remotemodel.RemoteCreateUpdateResponse
import info.nightscout.sdk.remotemodel.RemoteDeviceStatus
import info.nightscout.sdk.remotemodel.RemoteEntry
import info.nightscout.sdk.remotemodel.RemoteFood
import info.nightscout.sdk.remotemodel.RemoteStatusResponse
import info.nightscout.sdk.remotemodel.RemoteTreatment
import retrofit2.Response
Expand Down Expand Up @@ -48,7 +49,7 @@ internal interface NightscoutRemoteService {
suspend fun getSgvsModifiedSince(@Path("from") from: Long, @Query("limit") limit: Long): Response<NSResponse<List<RemoteEntry>>>

@GET("v3/treatments")
suspend fun getTreatmentsNewerThan(@Query(value = "date\$gt", encoded = true) date: Long, @Query("limit") limit: Long): Response<NSResponse<List<RemoteTreatment>>>
suspend fun getTreatmentsNewerThan(@Query(value = "created_at\$gt", encoded = true) createdAt: String, @Query("limit") limit: Long): Response<NSResponse<List<RemoteTreatment>>>

@GET("v3/treatments/history/{from}")
suspend fun getTreatmentsModifiedSince(@Path("from") from: Long, @Query("limit") limit: Long): Response<NSResponse<List<RemoteTreatment>>>
Expand All @@ -62,4 +63,16 @@ internal interface NightscoutRemoteService {
@PUT("v3/treatments")
suspend fun updateTreatment(@Body remoteTreatment: RemoteTreatment): Response<NSResponse<RemoteCreateUpdateResponse>>

@GET("v3/food")
suspend fun getFoods(@Query("limit") limit: Long): Response<NSResponse<List<RemoteFood>>>
/*
@GET("v3/food/history/{from}")
suspend fun getFoodsModifiedSince(@Path("from") from: Long, @Query("limit") limit: Long): Response<NSResponse<List<RemoteFood>>>
*/
@POST("v3/food")
suspend fun createFood(@Body remoteFood: RemoteFood): Response<NSResponse<RemoteCreateUpdateResponse>>

@PUT("v3/food")
suspend fun updateFood(@Body remoteFood: RemoteFood): Response<NSResponse<RemoteCreateUpdateResponse>>

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ data class LastModified(
@SerializedName("devicestatus") var devicestatus: Long = 0, // devicestatus collection
@SerializedName("entries") var entries: Long = 0, // entries collection
@SerializedName("profile") var profile: Long = 0, // profile collection
@SerializedName("treatments") var treatments: Long = 0 // treatments collection
@SerializedName("treatments") var treatments: Long = 0, // treatments collection
@SerializedName("foods") var foods: Long = 0 // foods collection
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package info.nightscout.sdk.remotemodel

import com.google.gson.annotations.SerializedName

/**
* Depending on the type, different other fields are present.
* Those technically need to be optional.
*
* On upload a sanity check still needs to be done to verify that all mandatory fields for that type are there.
*
**/
internal data class RemoteFood(
@SerializedName("type") val type: String, // we are interesting in type "food"
@SerializedName("date") val date: Long?,
@SerializedName("name") val name: String,
@SerializedName("category") val category: String?,
@SerializedName("subcategory") val subcategory: String?,
@SerializedName("unit") val unit: String?,
@SerializedName("portion") val portion: Double,
@SerializedName("carbs") val carbs: Int,
@SerializedName("gi") val gi: Int?,
@SerializedName("energy") val energy: Int?,
@SerializedName("protein") val protein: Int?,
@SerializedName("fat") val fat: Int?,
@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("isValid")
val isValid: Boolean?, // A flag set by the server only for deleted documents. This field appears only within history operation and for documents which were deleted by API v3 (and they always have a false value)
@SerializedName("isReadOnly")
val isReadOnly: Boolean?, // A flag set by client that locks the document from any changes. Every document marked with isReadOnly=true is forever immutable and cannot even be deleted.
@SerializedName("app") var app: String? = null, // Application or system in which the record was entered by human or device for the first time.
@SerializedName("device") val device: String? = null, // string The device from which the data originated (including serial number of the device, if it is relevant and safe).
@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("subject")
val subject: String? = null, // string Name of the security subject (within Nightscout scope) which has created the document. This field is automatically set by the server from the passed token or JWT.
@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("modifiedBy")
val modifiedBy: String? = null // string Name of the security subject (within Nightscout scope) which has patched or deleted the document for the last time. This field is automatically set by the server.
)
Loading

0 comments on commit d9c6cd6

Please sign in to comment.