update profiles

This commit is contained in:
James Woglom
2024-09-29 23:06:42 -04:00
parent abe8ea32aa
commit 362888cb14
12 changed files with 795 additions and 92 deletions
+1
View File
@@ -19,6 +19,7 @@ requests = {extras = ["socks"], version = "*"}
requests-oidc = "*"
pyjwt = "*"
cryptography = "*"
dataclasses-json = "*"
[scripts]
tconnectsync = "python3 main.py"
Generated
+44 -4
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "6f4a6601549eabda985f699484cd6f9328e06f95b05a4294b3d675599cf2fe70"
"sha256": "586b477d4c73da1d1034f852a536bf3c20a1a358406bfb5dfc1b1164f606441b"
},
"pipfile-spec": 6,
"requires": {},
@@ -258,6 +258,15 @@
"markers": "python_version >= '3.7'",
"version": "==43.0.1"
},
"dataclasses-json": {
"hashes": [
"sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a",
"sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0"
],
"index": "pypi",
"markers": "python_version >= '3.7' and python_version < '4.0'",
"version": "==0.6.7"
},
"idna": {
"hashes": [
"sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9",
@@ -411,6 +420,22 @@
"markers": "python_version >= '3.6'",
"version": "==5.3.0"
},
"marshmallow": {
"hashes": [
"sha256:4972f529104a220bb8637d595aa4c9762afbe7f7a77d82dc58c1615d70c5823e",
"sha256:71a2dce49ef901c3f97ed296ae5051135fd3febd2bf43afe0ae9a82143a494d9"
],
"markers": "python_version >= '3.8'",
"version": "==3.22.0"
},
"mypy-extensions": {
"hashes": [
"sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
"sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.0"
},
"oauthlib": {
"hashes": [
"sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca",
@@ -419,6 +444,14 @@
"markers": "python_version >= '3.6'",
"version": "==3.2.2"
},
"packaging": {
"hashes": [
"sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002",
"sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"
],
"markers": "python_version >= '3.8'",
"version": "==24.1"
},
"platformdirs": {
"hashes": [
"sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3",
@@ -567,6 +600,13 @@
"markers": "python_version >= '3.8'",
"version": "==4.12.2"
},
"typing-inspect": {
"hashes": [
"sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f",
"sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"
],
"version": "==0.9.0"
},
"urllib3": {
"hashes": [
"sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
@@ -603,11 +643,11 @@
},
"prompt-toolkit": {
"hashes": [
"sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10",
"sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"
"sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90",
"sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"
],
"markers": "python_full_version >= '3.7.0'",
"version": "==3.0.47"
"version": "==3.0.48"
},
"ptpython": {
"hashes": [
@@ -0,0 +1,52 @@
from dataclasses import dataclass
from dataclasses_json import dataclass_json
from typing import List
@dataclass_json
@dataclass
class PumpProfileSegment:
startTime: int # minutes
basalRate: int # milliunits
isf: int
carbRatio: int
targetBg: int
@property
def skip(self):
return self.startTime == 0 and self.basalRate == 0 and self.isf == 0 and self.carbRatio == 0 and self.targetBg == 0
@dataclass_json
@dataclass
class PumpProfile:
name: str
idp: int
tDependentSegs: List[PumpProfileSegment]
insulinDuration: int # minutes
carbEntry: int # 1 / 0
maxBolus: int # milliunits
@dataclass_json
@dataclass
class PumpProfiles:
activeIdp: int
profile: List[PumpProfile]
@dataclass_json
@dataclass
class PumpGlucoseAlertSettings:
mgPerDl: int
enabled: int # 1 / 0
duration: int # minutes
status: int # unknown
@dataclass_json
@dataclass
class PumpCgmSettings:
highGlucoseAlert: PumpGlucoseAlertSettings
lowGlucoseAlert: PumpGlucoseAlertSettings
@dataclass_json
@dataclass
class PumpSettings:
profiles: PumpProfiles
cgmSettings: PumpCgmSettings
+3
View File
@@ -1,9 +1,12 @@
header = '''# THIS FILE IS AUTOGENERATED. DO NOT EDIT.
import struct
import logging
from dataclasses import dataclass
from enum import Enum, IntFlag
from .raw_event import RawEvent
logger = logging.getLogger(__name__)
EVENT_LEN = 26
@dataclass
+383 -76
View File
@@ -1,9 +1,12 @@
# THIS FILE IS AUTOGENERATED. DO NOT EDIT.
import struct
import logging
from dataclasses import dataclass
from enum import Enum, IntFlag
from .raw_event import RawEvent
logger = logging.getLogger(__name__)
EVENT_LEN = 26
@dataclass
@@ -55,7 +58,11 @@ class LidBasalRateChange(BaseEvent):
@property
def changetype(self):
return self.ChangetypeBitmask(self.changetypeRaw)
try: return self.ChangetypeBitmask(self.changetypeRaw)
except ValueError as e:
logger.error("Invalid changetypeRaw in ChangetypeBitmask for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -228,7 +235,11 @@ class LidAlertActivated(BaseEvent):
@property
def alertid(self):
return self.AlertidEnum(self.alertidRaw)
try: return self.AlertidEnum(self.alertidRaw)
except ValueError as e:
logger.error("Invalid alertidRaw in Alertid for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -401,7 +412,11 @@ class LidAlarmActivated(BaseEvent):
@property
def alarmid(self):
return self.AlarmidEnum(self.alarmidRaw)
try: return self.AlarmidEnum(self.alarmidRaw)
except ValueError as e:
logger.error("Invalid alarmidRaw in Alarmid for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -491,7 +506,11 @@ class LidPumpingSuspended(BaseEvent):
@property
def suspendreason(self):
return self.SuspendreasonEnum(self.suspendreasonRaw)
try: return self.SuspendreasonEnum(self.suspendreasonRaw)
except ValueError as e:
logger.error("Invalid suspendreasonRaw in Suspendreason for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -643,7 +662,11 @@ class LidBgReadingTaken(BaseEvent):
@property
def selectediob(self):
return self.SelectediobEnum(self.selectediobRaw)
try: return self.SelectediobEnum(self.selectediobRaw)
except ValueError as e:
logger.error("Invalid selectediobRaw in Selectediob for "+str(self))
logger.error(e)
return None
BgentrytypeMap = {
"0": "Manual Entry by the User via Numpad",
@@ -656,7 +679,11 @@ class LidBgReadingTaken(BaseEvent):
@property
def bgentrytype(self):
return self.BgentrytypeEnum(self.bgentrytypeRaw)
try: return self.BgentrytypeEnum(self.bgentrytypeRaw)
except ValueError as e:
logger.error("Invalid bgentrytypeRaw in Bgentrytype for "+str(self))
logger.error(e)
return None
BgsourcetypeMap = {
"0": "Local Pump entry",
@@ -669,7 +696,11 @@ class LidBgReadingTaken(BaseEvent):
@property
def bgsourcetype(self):
return self.BgsourcetypeEnum(self.bgsourcetypeRaw)
try: return self.BgsourcetypeEnum(self.bgsourcetypeRaw)
except ValueError as e:
logger.error("Invalid bgsourcetypeRaw in Bgsourcetype for "+str(self))
logger.error(e)
return None
CgmcalibrationMap = {
"0": "No, it was not used to Calibrate.",
@@ -682,7 +713,11 @@ class LidBgReadingTaken(BaseEvent):
@property
def cgmcalibration(self):
return self.CgmcalibrationEnum(self.cgmcalibrationRaw)
try: return self.CgmcalibrationEnum(self.cgmcalibrationRaw)
except ValueError as e:
logger.error("Invalid cgmcalibrationRaw in Cgmcalibration for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -751,7 +786,11 @@ class LidBolusCompleted(BaseEvent):
@property
def completionstatus(self):
return self.CompletionstatusEnum(self.completionstatusRaw)
try: return self.CompletionstatusEnum(self.completionstatusRaw)
except ValueError as e:
logger.error("Invalid completionstatusRaw in Completionstatus for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -814,7 +853,11 @@ class LidBolexCompleted(BaseEvent):
@property
def completionstatus(self):
return self.CompletionstatusEnum(self.completionstatusRaw)
try: return self.CompletionstatusEnum(self.completionstatusRaw)
except ValueError as e:
logger.error("Invalid completionstatusRaw in Completionstatus for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -985,7 +1028,11 @@ class LidAlertCleared(BaseEvent):
@property
def alertid(self):
return self.AlertidEnum(self.alertidRaw)
try: return self.AlertidEnum(self.alertidRaw)
except ValueError as e:
logger.error("Invalid alertidRaw in Alertid for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -1151,7 +1198,11 @@ class LidAlarmCleared(BaseEvent):
@property
def alarmid(self):
return self.AlarmidEnum(self.alarmidRaw)
try: return self.AlarmidEnum(self.alarmidRaw)
except ValueError as e:
logger.error("Invalid alarmidRaw in Alarmid for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -1268,7 +1319,11 @@ class LidBolusActivated(BaseEvent):
@property
def selectediob(self):
return self.SelectediobEnum(self.selectediobRaw)
try: return self.SelectediobEnum(self.selectediobRaw)
except ValueError as e:
logger.error("Invalid selectediobRaw in Selectediob for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -1317,7 +1372,11 @@ class LidBolexActivated(BaseEvent):
@property
def selectediob(self):
return self.SelectediobEnum(self.selectediobRaw)
try: return self.SelectediobEnum(self.selectediobRaw)
except ValueError as e:
logger.error("Invalid selectediobRaw in Selectediob for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -1399,7 +1458,11 @@ class LidCannulaFilled(BaseEvent):
@property
def completionstatus(self):
return self.CompletionstatusEnum(self.completionstatusRaw)
try: return self.CompletionstatusEnum(self.completionstatusRaw)
except ValueError as e:
logger.error("Invalid completionstatusRaw in Completionstatus for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -1447,7 +1510,11 @@ class LidTubingFilled(BaseEvent):
@property
def completionstatus(self):
return self.CompletionstatusEnum(self.completionstatusRaw)
try: return self.CompletionstatusEnum(self.completionstatusRaw)
except ValueError as e:
logger.error("Invalid completionstatusRaw in Completionstatus for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -1501,7 +1568,11 @@ class LidBolusRequestedMsg1(BaseEvent):
@property
def bolustype(self):
return self.BolustypeEnum(self.bolustypeRaw)
try: return self.BolustypeEnum(self.bolustypeRaw)
except ValueError as e:
logger.error("Invalid bolustypeRaw in Bolustype for "+str(self))
logger.error(e)
return None
CorrectionbolusincludedMap = {
"0": "No",
@@ -1514,7 +1585,11 @@ class LidBolusRequestedMsg1(BaseEvent):
@property
def correctionbolusincluded(self):
return self.CorrectionbolusincludedEnum(self.correctionbolusincludedRaw)
try: return self.CorrectionbolusincludedEnum(self.correctionbolusincludedRaw)
except ValueError as e:
logger.error("Invalid correctionbolusincludedRaw in Correctionbolusincluded for "+str(self))
logger.error(e)
return None
@property
def carbratio(self):
@@ -1578,7 +1653,11 @@ class LidBolusRequestedMsg2(BaseEvent):
@property
def selectediob(self):
return self.SelectediobEnum(self.selectediobRaw)
try: return self.SelectediobEnum(self.selectediobRaw)
except ValueError as e:
logger.error("Invalid selectediobRaw in Selectediob for "+str(self))
logger.error(e)
return None
OptionsMap = {
"0": "Standard Bolus",
@@ -1603,7 +1682,11 @@ class LidBolusRequestedMsg2(BaseEvent):
@property
def options(self):
return self.OptionsEnum(self.optionsRaw)
try: return self.OptionsEnum(self.optionsRaw)
except ValueError as e:
logger.error("Invalid optionsRaw in Options for "+str(self))
logger.error(e)
return None
UseroverrideMap = {
"0": "\"No\", user did not override the bolus size",
@@ -1616,7 +1699,11 @@ class LidBolusRequestedMsg2(BaseEvent):
@property
def useroverride(self):
return self.UseroverrideEnum(self.useroverrideRaw)
try: return self.UseroverrideEnum(self.useroverrideRaw)
except ValueError as e:
logger.error("Invalid useroverrideRaw in Useroverride for "+str(self))
logger.error(e)
return None
DeclinedcorrectionMap = {
"0": "\"No\", user did not decline the recommended correction",
@@ -1629,7 +1716,11 @@ class LidBolusRequestedMsg2(BaseEvent):
@property
def declinedcorrection(self):
return self.DeclinedcorrectionEnum(self.declinedcorrectionRaw)
try: return self.DeclinedcorrectionEnum(self.declinedcorrectionRaw)
except ValueError as e:
logger.error("Invalid declinedcorrectionRaw in Declinedcorrection for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -1810,7 +1901,11 @@ class LidPlgsPeriodic(BaseEvent):
@property
def fmrstatus(self):
return self.FmrstatusEnum(self.fmrstatusRaw)
try: return self.FmrstatusEnum(self.fmrstatusRaw)
except ValueError as e:
logger.error("Invalid fmrstatusRaw in Fmrstatus for "+str(self))
logger.error(e)
return None
PgvvalidMap = {
"0": "FALSE",
@@ -1823,7 +1918,11 @@ class LidPlgsPeriodic(BaseEvent):
@property
def pgvvalid(self):
return self.PgvvalidEnum(self.pgvvalidRaw)
try: return self.PgvvalidEnum(self.pgvvalidRaw)
except ValueError as e:
logger.error("Invalid pgvvalidRaw in Pgvvalid for "+str(self))
logger.error(e)
return None
RulestateMap = {
"0": "HO_SUSPEND_RULE",
@@ -1838,7 +1937,11 @@ class LidPlgsPeriodic(BaseEvent):
@property
def rulestate(self):
return self.RulestateBitmask(self.rulestateRaw)
try: return self.RulestateBitmask(self.rulestateRaw)
except ValueError as e:
logger.error("Invalid rulestateRaw in RulestateBitmask for "+str(self))
logger.error(e)
return None
HominstateMap = {
"0": "On and available",
@@ -1855,7 +1958,11 @@ class LidPlgsPeriodic(BaseEvent):
@property
def hominstate(self):
return self.HominstateEnum(self.hominstateRaw)
try: return self.HominstateEnum(self.hominstateRaw)
except ValueError as e:
logger.error("Invalid hominstateRaw in Hominstate for "+str(self))
logger.error(e)
return None
StatusMap = {
"0": "Suspend Predicted",
@@ -1889,7 +1996,11 @@ class LidPlgsPeriodic(BaseEvent):
@property
def status(self):
return self.StatusBitmask(self.statusRaw)
try: return self.StatusBitmask(self.statusRaw)
except ValueError as e:
logger.error("Invalid statusRaw in StatusBitmask for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -1956,7 +2067,11 @@ class LidCgmAlertActivated(BaseEvent):
@property
def dalertid(self):
return self.DalertidEnum(self.dalertidRaw)
try: return self.DalertidEnum(self.dalertidRaw)
except ValueError as e:
logger.error("Invalid dalertidRaw in Dalertid for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -2012,7 +2127,11 @@ class LidCgmAlertCleared(BaseEvent):
@property
def dalertid(self):
return self.DalertidEnum(self.dalertidRaw)
try: return self.DalertidEnum(self.dalertidRaw)
except ValueError as e:
logger.error("Invalid dalertidRaw in Dalertid for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -2095,7 +2214,11 @@ class LidUpdateStatus(BaseEvent):
@property
def updatesuccessful(self):
return self.UpdatesuccessfulEnum(self.updatesuccessfulRaw)
try: return self.UpdatesuccessfulEnum(self.updatesuccessfulRaw)
except ValueError as e:
logger.error("Invalid updatesuccessfulRaw in Updatesuccessful for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -2206,7 +2329,11 @@ class LidCgmJoinSessionGx(BaseEvent):
@property
def sessionjoinreason(self):
return self.SessionjoinreasonEnum(self.sessionjoinreasonRaw)
try: return self.SessionjoinreasonEnum(self.sessionjoinreasonRaw)
except ValueError as e:
logger.error("Invalid sessionjoinreasonRaw in Sessionjoinreason for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -2278,7 +2405,11 @@ class LidCgmStopSessionGx(BaseEvent):
@property
def sessionstopreason(self):
return self.SessionstopreasonEnum(self.sessionstopreasonRaw)
try: return self.SessionstopreasonEnum(self.sessionstopreasonRaw)
except ValueError as e:
logger.error("Invalid sessionstopreasonRaw in Sessionstopreason for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -2334,7 +2465,11 @@ class LidAaUserModeChange(BaseEvent):
@property
def exercisechoice(self):
return self.ExercisechoiceEnum(self.exercisechoiceRaw)
try: return self.ExercisechoiceEnum(self.exercisechoiceRaw)
except ValueError as e:
logger.error("Invalid exercisechoiceRaw in Exercisechoice for "+str(self))
logger.error(e)
return None
CurrentusermodeMap = {
"0": "Normal",
@@ -2351,7 +2486,11 @@ class LidAaUserModeChange(BaseEvent):
@property
def currentusermode(self):
return self.CurrentusermodeEnum(self.currentusermodeRaw)
try: return self.CurrentusermodeEnum(self.currentusermodeRaw)
except ValueError as e:
logger.error("Invalid currentusermodeRaw in Currentusermode for "+str(self))
logger.error(e)
return None
PrevioususermodeMap = {
"0": "Normal",
@@ -2368,7 +2507,11 @@ class LidAaUserModeChange(BaseEvent):
@property
def previoususermode(self):
return self.PrevioususermodeEnum(self.previoususermodeRaw)
try: return self.PrevioususermodeEnum(self.previoususermodeRaw)
except ValueError as e:
logger.error("Invalid previoususermodeRaw in Previoususermode for "+str(self))
logger.error(e)
return None
RequestedactionMap = {
"0": "No User Request",
@@ -2393,7 +2536,11 @@ class LidAaUserModeChange(BaseEvent):
@property
def requestedaction(self):
return self.RequestedactionEnum(self.requestedactionRaw)
try: return self.RequestedactionEnum(self.requestedactionRaw)
except ValueError as e:
logger.error("Invalid requestedactionRaw in Requestedaction for "+str(self))
logger.error(e)
return None
SleepstartedbyguiMap = {
"0": "FALSE",
@@ -2406,7 +2553,11 @@ class LidAaUserModeChange(BaseEvent):
@property
def sleepstartedbygui(self):
return self.SleepstartedbyguiEnum(self.sleepstartedbyguiRaw)
try: return self.SleepstartedbyguiEnum(self.sleepstartedbyguiRaw)
except ValueError as e:
logger.error("Invalid sleepstartedbyguiRaw in Sleepstartedbygui for "+str(self))
logger.error(e)
return None
ExercisestoppedbytimerMap = {
"0": "False",
@@ -2419,7 +2570,11 @@ class LidAaUserModeChange(BaseEvent):
@property
def exercisestoppedbytimer(self):
return self.ExercisestoppedbytimerEnum(self.exercisestoppedbytimerRaw)
try: return self.ExercisestoppedbytimerEnum(self.exercisestoppedbytimerRaw)
except ValueError as e:
logger.error("Invalid exercisestoppedbytimerRaw in Exercisestoppedbytimer for "+str(self))
logger.error(e)
return None
ActivesleepscheduleMap = {
"0": "Sleep Schedule 1 is Active",
@@ -2436,7 +2591,11 @@ class LidAaUserModeChange(BaseEvent):
@property
def activesleepschedule(self):
return self.ActivesleepscheduleBitmask(self.activesleepscheduleRaw)
try: return self.ActivesleepscheduleBitmask(self.activesleepscheduleRaw)
except ValueError as e:
logger.error("Invalid activesleepscheduleRaw in ActivesleepscheduleBitmask for "+str(self))
logger.error(e)
return None
EatingsoonstoppedbytimerMap = {
"0": "False",
@@ -2449,7 +2608,11 @@ class LidAaUserModeChange(BaseEvent):
@property
def eatingsoonstoppedbytimer(self):
return self.EatingsoonstoppedbytimerEnum(self.eatingsoonstoppedbytimerRaw)
try: return self.EatingsoonstoppedbytimerEnum(self.eatingsoonstoppedbytimerRaw)
except ValueError as e:
logger.error("Invalid eatingsoonstoppedbytimerRaw in Eatingsoonstoppedbytimer for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -2515,7 +2678,11 @@ class LidAaPcmChange(BaseEvent):
@property
def currentpcm(self):
return self.CurrentpcmEnum(self.currentpcmRaw)
try: return self.CurrentpcmEnum(self.currentpcmRaw)
except ValueError as e:
logger.error("Invalid currentpcmRaw in Currentpcm for "+str(self))
logger.error(e)
return None
PreviouspcmMap = {
"0": "No Control",
@@ -2532,7 +2699,11 @@ class LidAaPcmChange(BaseEvent):
@property
def previouspcm(self):
return self.PreviouspcmEnum(self.previouspcmRaw)
try: return self.PreviouspcmEnum(self.previouspcmRaw)
except ValueError as e:
logger.error("Invalid previouspcmRaw in Previouspcm for "+str(self))
logger.error(e)
return None
PumpsuspendedMap = {
"0": "FALSE",
@@ -2545,7 +2716,11 @@ class LidAaPcmChange(BaseEvent):
@property
def pumpsuspended(self):
return self.PumpsuspendedEnum(self.pumpsuspendedRaw)
try: return self.PumpsuspendedEnum(self.pumpsuspendedRaw)
except ValueError as e:
logger.error("Invalid pumpsuspendedRaw in Pumpsuspended for "+str(self))
logger.error(e)
return None
CalculationavailableMap = {
"0": "FALSE",
@@ -2558,7 +2733,11 @@ class LidAaPcmChange(BaseEvent):
@property
def calculationavailable(self):
return self.CalculationavailableEnum(self.calculationavailableRaw)
try: return self.CalculationavailableEnum(self.calculationavailableRaw)
except ValueError as e:
logger.error("Invalid calculationavailableRaw in Calculationavailable for "+str(self))
logger.error(e)
return None
CgmavailableMap = {
"0": "FALSE",
@@ -2571,7 +2750,11 @@ class LidAaPcmChange(BaseEvent):
@property
def cgmavailable(self):
return self.CgmavailableEnum(self.cgmavailableRaw)
try: return self.CgmavailableEnum(self.cgmavailableRaw)
except ValueError as e:
logger.error("Invalid cgmavailableRaw in Cgmavailable for "+str(self))
logger.error(e)
return None
ClosedlooppreferredMap = {
"0": "FALSE",
@@ -2584,7 +2767,11 @@ class LidAaPcmChange(BaseEvent):
@property
def closedlooppreferred(self):
return self.ClosedlooppreferredEnum(self.closedlooppreferredRaw)
try: return self.ClosedlooppreferredEnum(self.closedlooppreferredRaw)
except ValueError as e:
logger.error("Invalid closedlooppreferredRaw in Closedlooppreferred for "+str(self))
logger.error(e)
return None
SufficientclosedloopparamsMap = {
"0": "FALSE",
@@ -2597,7 +2784,11 @@ class LidAaPcmChange(BaseEvent):
@property
def sufficientclosedloopparams(self):
return self.SufficientclosedloopparamsEnum(self.sufficientclosedloopparamsRaw)
try: return self.SufficientclosedloopparamsEnum(self.sufficientclosedloopparamsRaw)
except ValueError as e:
logger.error("Invalid sufficientclosedloopparamsRaw in Sufficientclosedloopparams for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -2659,7 +2850,11 @@ class LidCgmDataGxb(BaseEvent):
@property
def glucosevaluestatus(self):
return self.GlucosevaluestatusEnum(self.glucosevaluestatusRaw)
try: return self.GlucosevaluestatusEnum(self.glucosevaluestatusRaw)
except ValueError as e:
logger.error("Invalid glucosevaluestatusRaw in Glucosevaluestatus for "+str(self))
logger.error(e)
return None
CgmdatatypeMap = {
"0": "Five Minute Reading (FMR)",
@@ -2678,7 +2873,11 @@ class LidCgmDataGxb(BaseEvent):
@property
def cgmDataType(self):
return self.CgmdatatypeBitmask(self.cgmDataTypeRaw)
try: return self.CgmdatatypeBitmask(self.cgmDataTypeRaw)
except ValueError as e:
logger.error("Invalid cgmDataTypeRaw in CgmdatatypeBitmask for "+str(self))
logger.error(e)
return None
@property
def rate(self):
@@ -2709,7 +2908,11 @@ class LidCgmDataGxb(BaseEvent):
@property
def egvInfoBitmask(self):
return self.EgvinfobitmaskBitmask(self.egvInfoBitmaskRaw)
try: return self.EgvinfobitmaskBitmask(self.egvInfoBitmaskRaw)
except ValueError as e:
logger.error("Invalid egvInfoBitmaskRaw in EgvinfobitmaskBitmask for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -2775,7 +2978,11 @@ class LidBasalDelivery(BaseEvent):
@property
def commandedRateSource(self):
return self.CommandedratesourceEnum(self.commandedRateSourceRaw)
try: return self.CommandedratesourceEnum(self.commandedRateSourceRaw)
except ValueError as e:
logger.error("Invalid commandedRateSourceRaw in Commandedratesource for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -2832,7 +3039,11 @@ class LidBolusDelivery(BaseEvent):
@property
def bolusDeliveryStatus(self):
return self.BolusdeliverystatusEnum(self.bolusDeliveryStatusRaw)
try: return self.BolusdeliverystatusEnum(self.bolusDeliveryStatusRaw)
except ValueError as e:
logger.error("Invalid bolusDeliveryStatusRaw in Bolusdeliverystatus for "+str(self))
logger.error(e)
return None
BolustypeMap = {
"0": "Now",
@@ -2853,7 +3064,11 @@ class LidBolusDelivery(BaseEvent):
@property
def bolusType(self):
return self.BolustypeBitmask(self.bolusTypeRaw)
try: return self.BolustypeBitmask(self.bolusTypeRaw)
except ValueError as e:
logger.error("Invalid bolusTypeRaw in BolustypeBitmask for "+str(self))
logger.error(e)
return None
BolussourceMap = {
"0": "Pump Button",
@@ -2878,7 +3093,11 @@ class LidBolusDelivery(BaseEvent):
@property
def bolusSource(self):
return self.BolussourceEnum(self.bolusSourceRaw)
try: return self.BolussourceEnum(self.bolusSourceRaw)
except ValueError as e:
logger.error("Invalid bolusSourceRaw in Bolussource for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -2979,7 +3198,11 @@ class LidAaDailyStatus(BaseEvent):
@property
def pumpcontrolstate(self):
return self.PumpcontrolstateEnum(self.pumpcontrolstateRaw)
try: return self.PumpcontrolstateEnum(self.pumpcontrolstateRaw)
except ValueError as e:
logger.error("Invalid pumpcontrolstateRaw in Pumpcontrolstate for "+str(self))
logger.error(e)
return None
UsermodeMap = {
"0": "Normal",
@@ -2994,7 +3217,11 @@ class LidAaDailyStatus(BaseEvent):
@property
def usermode(self):
return self.UsermodeEnum(self.usermodeRaw)
try: return self.UsermodeEnum(self.usermodeRaw)
except ValueError as e:
logger.error("Invalid usermodeRaw in Usermode for "+str(self))
logger.error(e)
return None
SensortypeMap = {
"0": "CGM_TYPE_NONE",
@@ -3011,7 +3238,11 @@ class LidAaDailyStatus(BaseEvent):
@property
def sensortype(self):
return self.SensortypeEnum(self.sensortypeRaw)
try: return self.SensortypeEnum(self.sensortypeRaw)
except ValueError as e:
logger.error("Invalid sensortypeRaw in Sensortype for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -3069,7 +3300,11 @@ class LidCgmAlertActivatedDex(BaseEvent):
@property
def dalertid(self):
return self.DalertidEnum(self.dalertidRaw)
try: return self.DalertidEnum(self.dalertidRaw)
except ValueError as e:
logger.error("Invalid dalertidRaw in Dalertid for "+str(self))
logger.error(e)
return None
SensortypeMap = {
"0": "Invalid",
@@ -3084,7 +3319,11 @@ class LidCgmAlertActivatedDex(BaseEvent):
@property
def sensortype(self):
return self.SensortypeEnum(self.sensortypeRaw)
try: return self.SensortypeEnum(self.sensortypeRaw)
except ValueError as e:
logger.error("Invalid sensortypeRaw in Sensortype for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -3143,7 +3382,11 @@ class LidCgmAlertClearedDex(BaseEvent):
@property
def dalertid(self):
return self.DalertidEnum(self.dalertidRaw)
try: return self.DalertidEnum(self.dalertidRaw)
except ValueError as e:
logger.error("Invalid dalertidRaw in Dalertid for "+str(self))
logger.error(e)
return None
SensortypeMap = {
"0": "Invalid",
@@ -3158,7 +3401,11 @@ class LidCgmAlertClearedDex(BaseEvent):
@property
def sensortype(self):
return self.SensortypeEnum(self.sensortypeRaw)
try: return self.SensortypeEnum(self.sensortypeRaw)
except ValueError as e:
logger.error("Invalid sensortypeRaw in Sensortype for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -3212,7 +3459,11 @@ class LidCgmAlertAckDex(BaseEvent):
@property
def dalertid(self):
return self.DalertidEnum(self.dalertidRaw)
try: return self.DalertidEnum(self.dalertidRaw)
except ValueError as e:
logger.error("Invalid dalertidRaw in Dalertid for "+str(self))
logger.error(e)
return None
SensortypeMap = {
"0": "Invalid",
@@ -3227,7 +3478,11 @@ class LidCgmAlertAckDex(BaseEvent):
@property
def sensortype(self):
return self.SensortypeEnum(self.sensortypeRaw)
try: return self.SensortypeEnum(self.sensortypeRaw)
except ValueError as e:
logger.error("Invalid sensortypeRaw in Sensortype for "+str(self))
logger.error(e)
return None
AcksourceMap = {
"0": "Alert Acknowledged by User",
@@ -3240,7 +3495,11 @@ class LidCgmAlertAckDex(BaseEvent):
@property
def acksource(self):
return self.AcksourceEnum(self.acksourceRaw)
try: return self.AcksourceEnum(self.acksourceRaw)
except ValueError as e:
logger.error("Invalid acksourceRaw in Acksource for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -3294,7 +3553,11 @@ class LidCgmDataFsl2(BaseEvent):
@property
def glucosevaluestatus(self):
return self.GlucosevaluestatusEnum(self.glucosevaluestatusRaw)
try: return self.GlucosevaluestatusEnum(self.glucosevaluestatusRaw)
except ValueError as e:
logger.error("Invalid glucosevaluestatusRaw in Glucosevaluestatus for "+str(self))
logger.error(e)
return None
CgmdatatypeMap = {
"0": "Five Minute Reading (FMR)",
@@ -3313,7 +3576,11 @@ class LidCgmDataFsl2(BaseEvent):
@property
def cgmDataType(self):
return self.CgmdatatypeBitmask(self.cgmDataTypeRaw)
try: return self.CgmdatatypeBitmask(self.cgmDataTypeRaw)
except ValueError as e:
logger.error("Invalid cgmDataTypeRaw in CgmdatatypeBitmask for "+str(self))
logger.error(e)
return None
@property
def rate(self):
@@ -3342,7 +3609,11 @@ class LidCgmDataFsl2(BaseEvent):
@property
def algorithmstate(self):
return self.AlgorithmstateEnum(self.algorithmstateRaw)
try: return self.AlgorithmstateEnum(self.algorithmstateRaw)
except ValueError as e:
logger.error("Invalid algorithmstateRaw in Algorithmstate for "+str(self))
logger.error(e)
return None
EgvinfobitmaskMap = {
"0": "Five Minute Reading (FMR)",
@@ -3373,7 +3644,11 @@ class LidCgmDataFsl2(BaseEvent):
@property
def egvInfoBitmask(self):
return self.EgvinfobitmaskBitmask(self.egvInfoBitmaskRaw)
try: return self.EgvinfobitmaskBitmask(self.egvInfoBitmaskRaw)
except ValueError as e:
logger.error("Invalid egvInfoBitmaskRaw in EgvinfobitmaskBitmask for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -3472,7 +3747,11 @@ class LidCgmDataG7(BaseEvent):
@property
def glucosevaluestatus(self):
return self.GlucosevaluestatusEnum(self.glucosevaluestatusRaw)
try: return self.GlucosevaluestatusEnum(self.glucosevaluestatusRaw)
except ValueError as e:
logger.error("Invalid glucosevaluestatusRaw in Glucosevaluestatus for "+str(self))
logger.error(e)
return None
CgmdatatypeMap = {
"0": "FMR",
@@ -3491,7 +3770,11 @@ class LidCgmDataG7(BaseEvent):
@property
def cgmDataType(self):
return self.CgmdatatypeBitmask(self.cgmDataTypeRaw)
try: return self.CgmdatatypeBitmask(self.cgmDataTypeRaw)
except ValueError as e:
logger.error("Invalid cgmDataTypeRaw in CgmdatatypeBitmask for "+str(self))
logger.error(e)
return None
@property
def rate(self):
@@ -3526,7 +3809,11 @@ class LidCgmDataG7(BaseEvent):
@property
def algorithmstate(self):
return self.AlgorithmstateEnum(self.algorithmstateRaw)
try: return self.AlgorithmstateEnum(self.algorithmstateRaw)
except ValueError as e:
logger.error("Invalid algorithmstateRaw in Algorithmstate for "+str(self))
logger.error(e)
return None
EgvinfobitmaskMap = {
"0": "Five Minute Reading (FMR)",
@@ -3555,7 +3842,11 @@ class LidCgmDataG7(BaseEvent):
@property
def egvInfoBitmask(self):
return self.EgvinfobitmaskBitmask(self.egvInfoBitmaskRaw)
try: return self.EgvinfobitmaskBitmask(self.egvInfoBitmaskRaw)
except ValueError as e:
logger.error("Invalid egvInfoBitmaskRaw in EgvinfobitmaskBitmask for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -3773,7 +4064,11 @@ class LidCgmAlertActivatedFsl2(BaseEvent):
@property
def dalertid(self):
return self.DalertidEnum(self.dalertidRaw)
try: return self.DalertidEnum(self.dalertidRaw)
except ValueError as e:
logger.error("Invalid dalertidRaw in Dalertid for "+str(self))
logger.error(e)
return None
SensortypeMap = {
"0": "Invalid",
@@ -3786,7 +4081,11 @@ class LidCgmAlertActivatedFsl2(BaseEvent):
@property
def sensortype(self):
return self.SensortypeEnum(self.sensortypeRaw)
try: return self.SensortypeEnum(self.sensortypeRaw)
except ValueError as e:
logger.error("Invalid sensortypeRaw in Sensortype for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
@@ -3845,7 +4144,11 @@ class LidCgmAlertClearedFsl2(BaseEvent):
@property
def dalertid(self):
return self.DalertidEnum(self.dalertidRaw)
try: return self.DalertidEnum(self.dalertidRaw)
except ValueError as e:
logger.error("Invalid dalertidRaw in Dalertid for "+str(self))
logger.error(e)
return None
SensortypeMap = {
"0": "Invalid",
@@ -3858,7 +4161,11 @@ class LidCgmAlertClearedFsl2(BaseEvent):
@property
def sensortype(self):
return self.SensortypeEnum(self.sensortypeRaw)
try: return self.SensortypeEnum(self.sensortypeRaw)
except ValueError as e:
logger.error("Invalid sensortypeRaw in Sensortype for "+str(self))
logger.error(e)
return None
@staticmethod
def build(raw):
+2 -1
View File
@@ -139,5 +139,6 @@ CGM_ALERTS_DICT = {
"20": "CGM Transmitter Error",
"26": "CGM Temperature",
"27": "CGM Failed Connection",
"39": "CGM Transmitter Expired"
"39": "CGM Transmitter Expired",
"40": "Pump Bluetooth Error"
}
+13 -3
View File
@@ -53,7 +53,12 @@ def transform_enum(event_def, name, name_fmt, field, tx):
out += [
'@property',
f'def {name_fmt}(self):',
f' return self.{enumNameFormat(name_fmt)}Enum(self.{name_fmt}Raw)',
f' try:'
f' return self.{enumNameFormat(name_fmt)}Enum(self.{name_fmt}Raw)',
f' except ValueError as e:',
f' logger.error("Invalid {name_fmt}Raw in {enumNameFormat(name_fmt)} for "+str(self))',
f' logger.error(e)',
f' return None',
''
]
@@ -84,8 +89,13 @@ def transform_bitmask(event_def, name, name_fmt, field, tx):
out += [
'@property',
f'def {name_fmt}(self):',
f' return self.{enumNameFormat(name_fmt)}Bitmask(self.{name_fmt}Raw)',
''
f' try:'
f' return self.{enumNameFormat(name_fmt)}Bitmask(self.{name_fmt}Raw)',
f' except ValueError as e:',
f' logger.error("Invalid {name_fmt}Raw in {enumNameFormat(name_fmt)}Bitmask for "+str(self))',
f' logger.error(e)',
f' return None',
f''
]
return out
+60 -1
View File
@@ -1,6 +1,7 @@
import arrow
from ..domain.device_settings import Profile, DeviceSettings
from ..domain.tandemsource.pump_settings import PumpProfile, PumpSettings
from ..secret import TIMEZONE_NAME, NIGHTSCOUT_PROFILE_CARBS_HR_VALUE, NIGHTSCOUT_PROFILE_DELAY_VALUE
ENTERED_BY = "Pump (tconnectsync)"
@@ -47,7 +48,7 @@ class NightscoutEntry:
FINGER = "Finger"
@staticmethod
def bolus(bolus, carbs, created_at, notes="", bg="", bg_type=""):
def bolus(bolus, carbs, created_at, notes="", bg="", bg_type="", pump_event_id=""):
data = {
"eventType": BOLUS_EVENTTYPE,
"created_at": created_at,
@@ -55,6 +56,7 @@ class NightscoutEntry:
"insulin": float(bolus),
"notes": notes,
"enteredBy": ENTERED_BY,
"pump_event_id": pump_event_id
}
if bg:
if bg_type:
@@ -241,6 +243,57 @@ class NightscoutEntry:
"units": "mg/dl"
}
# TandemSource profile to Nightscout profile store entry
@staticmethod
def tandemsource_profile_store(profile: PumpProfile, pump_settings: PumpSettings) -> dict:
return {
# insulin duration in hours; Nightscout JS bug requires all top-level fields to be strings
"dia": "%s" % (profile.insulinDuration / 60),
"carbratio": [
{
"time": minutes_to_ns_time(segment.startTime),
"timeAsSeconds": segment.startTime * 60,
"value": segment.carbRatio / 1000 # milliunits->units
} for segment in profile.tDependentSegs
],
"carbs_hr": NIGHTSCOUT_PROFILE_CARBS_HR_VALUE,
"delay": NIGHTSCOUT_PROFILE_DELAY_VALUE,
"sens": [ # Correction factor / isf
{
"time": minutes_to_ns_time(segment.startTime),
"timeAsSeconds": segment.startTime * 60,
"value": segment.isf
} for segment in profile.tDependentSegs
],
"basal": [
{
"time": minutes_to_ns_time(segment.startTime),
"timeAsSeconds": segment.startTime * 60,
"value": segment.basalRate / 1000 # milliunits->units
} for segment in profile.tDependentSegs
],
"target_low": [
{
"time": "00:00",
"timeAsSeconds": 0,
"value": pump_settings.cgmSettings.lowGlucoseAlert.mgPerDl
}
],
"target_high": [
{
"time": "00:00",
"timeAsSeconds": 0,
"value": pump_settings.cgmSettings.highGlucoseAlert.mgPerDl
}
],
"timezone": TIMEZONE_NAME, # tconnectsync settings timezone
"startDate": "1970-01-01T00:00:00.000Z",
"units": "mg/dl"
}
def tandem_to_ns_time(tandem_time: str) -> str:
numbers, ampm = tandem_time.split(' ')
hr, min = numbers.split(':')
@@ -259,6 +312,12 @@ def tandem_to_ns_time_seconds(tandem_time: str) -> int:
return 60 * (60 * (12 + (int(hr) % 12)) + int(min))
raise InvalidTimeException(tandem_time)
def minutes_to_ns_time(minutes_time: int) -> str:
hr = minutes_time // 60
mn = minutes_time % 60
return "%02d:%02d" % (hr, mn)
class InvalidBolusTypeException(RuntimeError):
pass
+14
View File
@@ -15,6 +15,7 @@ from .process_cgm_alert import ProcessCGMAlert
from .process_cgm_start_join_stop import ProcessCGMStartJoinStop
from .process_cgm_reading import ProcessCGMReading
from .process_user_mode import ProcessUserMode
from .update_profiles import UpdateProfiles
logger = logging.getLogger(__name__)
@@ -40,6 +41,10 @@ class ProcessTimeRange:
EventClass.USER_MODE.name: ProcessUserMode,
}
updater_classes = [
UpdateProfiles
]
def process(self, time_start, time_end):
logger.info(f"ProcessTimeRange {time_start=} {time_end=} {self.tconnect_device_id=} {self.features=}")
@@ -77,6 +82,15 @@ class ProcessTimeRange:
else:
logger.info("Skipping %s, is not enabled from features %s" % (clazz, self.features))
for updater_class in self.updater_classes:
c = updater_class(self.tconnect, self.nightscout, self.tconnect_device_id, self.pretend, self.features)
if c.enabled():
logger.info("%s is enabled from features %s" % (updater_class.__name__, self.features))
done = c.update(self.pretend)
logger.info("%s completed with update required: %s" % (updater_class.__name__, done))
else:
logger.info("Skipping %s, is not enabled from features %s" % (updater_class.__name__, self.features))
return processed_count
@@ -60,9 +60,9 @@ class ProcessBolus:
ns_entries.append(self.bolus_to_nsentry(
bolusCompleted,
bolusRequested1 = m.get(events.LidBolusRequestedMsg1),
bolusRequested2 = m.get(events.LidBolusRequestedMsg2),
bolusRequested3 = m.get(events.LidBolusRequestedMsg3),
bolusRequested1 = m.get(eventtypes.LidBolusRequestedMsg1),
bolusRequested2 = m.get(eventtypes.LidBolusRequestedMsg2),
bolusRequested3 = m.get(eventtypes.LidBolusRequestedMsg3),
))
return ns_entries
@@ -93,14 +93,18 @@ class ProcessBolus:
event_ids = []
for e in [bolusCompleted, bolusRequested1, bolusRequested2, bolusRequested3]:
if e:
event_ids.append(e.eventId)
event_ids.append(str(e.eventId))
notes = ''
if bolusRequested2 and str(bolusRequested2.optionsRaw) in eventtypes.LidBolusRequestedMsg2.OptionsMap:
notes = eventtypes.LidBolusRequestedMsg2.OptionsMap['%d' % bolusRequested2.optionsRaw]
return NightscoutEntry.bolus(
bolus = bolusCompleted.insulindelivered,
carbs = bolusRequested1.carbamount if bolusRequested1 and bolusRequested1.carbamount>0 else None,
created_at = bolusCompleted.eventTimestamp.format(),
notes = (eventtypes.LidBolusRequestedMsg2.OptionsMap[bolusRequested2.optionsRaw] if bolusRequested2 else '') + suffix,
notes = notes + suffix,
bg = bolusRequested1.BG if bolusRequested1 and bolusRequested1.BG > 0 else None,
pump_event_id = ",".join(event_ids)
)
@@ -30,8 +30,10 @@ class ProcessCGMReading:
logger.debug("ProcessCGMReading: querying for last uploaded entry")
last_upload = self.nightscout.last_uploaded_bg_entry(time_start=time_start, time_end=time_end)
last_upload_time = None
if last_upload:
last_upload_time = arrow.get(last_upload["created_at"])
if last_upload and "dateString" in last_upload:
last_upload_time = arrow.get(last_upload["dateString"])
elif last_upload and "date" in last_upload:
last_upload_time = arrow.get(last_upload["date"])
logger.info("ProcessCGMReading: Last Nightscout bg upload: %s" % last_upload_time)
readings = []
@@ -0,0 +1,210 @@
import logging
import arrow
import copy
import json
from typing import Tuple
from ...features import DEFAULT_FEATURES
from ... import features
from ...domain.tandemsource.pump_settings import PumpSettings
from ...parser.nightscout import (
NightscoutEntry
)
from ...secret import NIGHTSCOUT_PROFILE_UPLOAD_MODE
logger = logging.getLogger(__name__)
def _get_default_upload_mode():
return NIGHTSCOUT_PROFILE_UPLOAD_MODE
class UpdateProfiles:
def __init__(self, tconnect, nightscout, tconnect_device_id, pretend, features=DEFAULT_FEATURES):
self.tconnect = tconnect
self.nightscout = nightscout
self.tconnect_device_id = tconnect_device_id
self.pretend = pretend
self.features = features
def enabled(self):
return features.PROFILES in self.features
def update(self, pretend):
upload_mode = _get_default_upload_mode()
logger.debug("UpdateProfiles: getting Tandem Source profile data")
all_metadata = self.tconnect.tandemsource.pump_event_metadata()
pump_meta = None
for m in all_metadata:
if m['tconnectDeviceId'] == self.tconnect_device_id:
pump_meta = m
if not pump_meta:
return False
raw_settings = pump_meta.get("lastUpload", {}).get("settings")
if not raw_settings:
return False
pump_settings = PumpSettings.from_dict(raw_settings)
logger.info("Current pump settings: %s" % pump_settings)
ns_profile_obj = self.nightscout.current_profile()
logger.info("Current Nightscout profile: %s" % ns_profile_obj)
if ns_profile_obj is None:
ns_profile_obj = {}
diff, ns_profile_new = self.compare_profiles(pump_settings, ns_profile_obj)
if not diff:
logger.info("Pump and Nightscout profiles up to date")
return False
if upload_mode == 'add':
profile_to_upload = self.setup_new_profile(ns_profile_new)
logger.info("Adding new Nightscout profiles object: %s", profile_to_upload)
if not pretend:
self.nightscout.upload_entry(profile_to_upload, entity='profile')
return True
elif upload_mode == 'replace':
logger.info("Replacing new Nightscout profiles object: %s", ns_profile_new)
if not pretend:
self.nightscout.put_entry(ns_profile_new, entity='profile')
return True
else:
raise RuntimeError('invalid upload_mode: %s' % upload_mode)
"""
Compare pump device and Nightscout profiles, and return a final dictionary of
Nightscout profile objects, with the pump profile settings overriding what is
currently in Nightscout.
ns_profile_obj is the output from NightscoutApi.current_profile() and should be the most
recent profile object in mongo.
Returns the new Nightscout profile and whether it was changed.
"""
def compare_profiles(self, pump_settings: PumpSettings, ns_profile_obj: dict) -> Tuple[bool, dict]:
device = {profile.name: profile for profile in pump_settings.profiles.profile}
activeIdp = pump_settings.profiles.activeIdp
ns = ns_profile_obj.get('store', {})
logger.info("compare_profiles profile names: device: %s ns: %s", device.keys(), ns.keys())
new_ns_profile = copy.deepcopy(ns_profile_obj)
if not 'store' in new_ns_profile:
new_ns_profile['store'] = {}
updated_ns_profile = False
missing_profiles_in_ns = set(device.keys()) - set(ns.keys())
for profile_name in missing_profiles_in_ns:
logger.info("Missing %s profile in Nightscout: %s", profile_name, device.get(profile_name))
pump_configured_profile = device[profile_name]
ns_translated_profile = NightscoutEntry.tandemsource_profile_store(pump_configured_profile, pump_settings)
logger.info("Will add %s profile to Nightscout: %s", profile_name, ns_translated_profile)
new_ns_profile['store'][profile_name] = ns_translated_profile
updated_ns_profile = True
existent_profiles_in_ns = set(device.keys()) & set(ns.keys())
for profile_name in existent_profiles_in_ns:
logger.debug("Checking for differences for %s profile between pump and nightscout", profile_name)
pump_configured_profile = device[profile_name]
ns_translated_profile = NightscoutEntry.tandemsource_profile_store(pump_configured_profile, pump_settings)
ns_configured_profile = ns[profile_name]
logger.debug("Comparing %s profile from pump: %s to nightscout: %s", profile_name, ns_translated_profile, ns_configured_profile)
if self.nightscout_profiles_identical(ns_configured_profile, ns_translated_profile):
logger.info("Profile %s identical between pump and nightscout", profile_name)
continue
logger.info("Profile %s needs update in nightscout: %s", profile_name, ns_translated_profile)
new_ns_profile['store'][profile_name] = ns_translated_profile
updated_ns_profile = True
current_pump_profile = None
for profile in pump_settings.profiles.profile:
if profile.idp == activeIdp:
current_pump_profile = profile.name
if not current_pump_profile:
logger.error('No current pump profile, so skipping profile update')
return False, ns_profile_obj
current_ns_profile = ns_profile_obj.get('defaultProfile')
if current_pump_profile != current_ns_profile:
logger.info("Current profile changed: pump: %s nightscout: %s", current_pump_profile, current_ns_profile)
new_ns_profile['defaultProfile'] = current_pump_profile
updated_ns_profile = True
if not updated_ns_profile:
logger.info("No Nightscout profile changes")
return False, ns_profile_obj
logger.info("New Nightscout profile object: %s", new_ns_profile)
return True, new_ns_profile
def nightscout_profiles_identical(self, configured: dict, translated: dict) -> bool:
if configured == translated:
logger.debug("direct dicts equal")
return True
if json.dumps(configured, sort_keys=True, indent=None) == json.dumps(translated, sort_keys=True, indent=None):
logger.debug("initial JSON dump identical")
return True
# convert all JSON values into strings
def map_nested_dicts_modify(ob, func):
for k, v in ob.items():
if isinstance(v, dict):
map_nested_dicts_modify(v, func)
elif isinstance(v, list):
map_nested_lists_modify(v, func)
else:
ob[k] = func(v)
def map_nested_lists_modify(ob, func):
for i in range(len(ob)):
v = ob[i]
if isinstance(v, dict):
map_nested_dicts_modify(v, func)
elif isinstance(v, list):
map_nested_lists_modify(v, func)
else:
ob[i] = func(v)
def to_numeric(x):
if type(x) in [int, float]:
return '%f' % x
try:
return '%f' % float(x)
except (ValueError, TypeError):
return x
convert_func = lambda x: to_numeric(x)
configured_str = json.loads(json.dumps(configured))
map_nested_dicts_modify(configured_str, convert_func)
translated_str = json.loads(json.dumps(translated))
map_nested_dicts_modify(translated_str, convert_func)
if json.dumps(configured_str, sort_keys=True, indent=None) == json.dumps(translated_str, sort_keys=True, indent=None):
logger.debug("map_nested_dicts JSON dump identical")
return True
logger.debug("profiles not identical")
return False
def setup_new_profile(self, ns_profile: dict) -> dict:
if '_id' in ns_profile:
del ns_profile['_id']
now = arrow.now().isoformat()
ns_profile['startDate'] = now
ns_profile['created_at'] = now
return ns_profile