mirror of
https://github.com/bckelley/tconnectsync.git
synced 2026-08-24 03:34:12 -05:00
V2 substantial completion
This commit is contained in:
@@ -9,18 +9,27 @@ class EventClass(set, Enum):
|
||||
BASAL_SUSPENSION = {events.LidPumpingSuspended}
|
||||
BASAL_RESUME = {events.LidPumpingResumed}
|
||||
ALARM = {events.LidAlarmActivated, events.LidMalfunctionActivated}
|
||||
BOLUS = {events.LidBolusCompleted, events.LidBolexCompleted}
|
||||
BOLUS = {
|
||||
events.LidBolusRequestedMsg1, # carb amount, bg, iob
|
||||
events.LidBolusRequestedMsg2, # more robust bolus type
|
||||
events.LidBolusRequestedMsg3, # total bolus requested amount
|
||||
events.LidBolusCompleted, # final event showing amount delivered
|
||||
events.LidBolexCompleted # extended bolus
|
||||
}
|
||||
CARTRIDGE = {events.LidCartridgeFilled, events.LidCannulaFilled, events.LidTubingFilled}
|
||||
CGM_ALERT = {events.LidCgmAlertActivated, events.LidCgmAlertActivatedDex, events.LidCgmAlertActivatedFsl2}
|
||||
CGM_START = {events.LidCgmStartSessionGx, events.LidCgmStartSessionFsl2}
|
||||
CGM_JOIN = {events.LidCgmJoinSessionGx, events.LidCgmJoinSessionG7, events.LidCgmJoinSessionFsl2}
|
||||
CGM_STOP = {events.LidCgmStopSessionGx, events.LidCgmStopSessionG7, events.LidCgmStopSessionFsl2}
|
||||
_CGM_START = {events.LidCgmStartSessionGx, events.LidCgmStartSessionFsl2}
|
||||
_CGM_JOIN = {events.LidCgmJoinSessionGx, events.LidCgmJoinSessionG7, events.LidCgmJoinSessionFsl2}
|
||||
_CGM_STOP = {events.LidCgmStopSessionGx, events.LidCgmStopSessionG7, events.LidCgmStopSessionFsl2}
|
||||
CGM_START_JOIN_STOP = {*_CGM_START, *_CGM_JOIN, *_CGM_STOP}
|
||||
CGM_READING = {events.LidCgmDataGxb, events.LidCgmDataG7, events.LidCgmDataFsl2}
|
||||
USER_MODE = {events.LidAaUserModeChange}
|
||||
|
||||
@staticmethod
|
||||
def for_event(evt):
|
||||
for typ, vals in EventClass.__members__.items():
|
||||
if typ.startswith('_'):
|
||||
continue
|
||||
if type(evt) == type and evt in vals:
|
||||
return EventClass.__members__[typ]
|
||||
elif type(evt) in vals:
|
||||
|
||||
@@ -1935,7 +1935,29 @@ class LidCgmAlertActivated(BaseEvent):
|
||||
param1: int
|
||||
param2: float
|
||||
|
||||
# Dictionary unknown: dalerts
|
||||
DalertidMap = {
|
||||
"11": "CGM Sensor Fail",
|
||||
"13": "CGM Sensor Expired",
|
||||
"14": "CGM Out Of Range",
|
||||
"20": "CGM Transmitter Error",
|
||||
"26": "CGM Temperature",
|
||||
"27": "CGM Failed Connection",
|
||||
"39": "CGM Transmitter Expired"
|
||||
}
|
||||
|
||||
class DalertidEnum(Enum):
|
||||
CgmSensorFail = 11
|
||||
CgmSensorExpired = 13
|
||||
CgmOutOfRange = 14
|
||||
CgmTransmitterError = 20
|
||||
CgmTemperature = 26
|
||||
CgmFailedConnection = 27
|
||||
CgmTransmitterExpired = 39
|
||||
|
||||
@property
|
||||
def dalertid(self):
|
||||
return self.DalertidEnum(self.dalertidRaw)
|
||||
|
||||
@staticmethod
|
||||
def build(raw):
|
||||
dalertid, = struct.unpack_from(UINT32, raw[:EVENT_LEN], 10)
|
||||
@@ -1969,7 +1991,29 @@ class LidCgmAlertCleared(BaseEvent):
|
||||
raw: RawEvent
|
||||
dalertidRaw: int
|
||||
|
||||
# Dictionary unknown: dalerts
|
||||
DalertidMap = {
|
||||
"11": "CGM Sensor Fail",
|
||||
"13": "CGM Sensor Expired",
|
||||
"14": "CGM Out Of Range",
|
||||
"20": "CGM Transmitter Error",
|
||||
"26": "CGM Temperature",
|
||||
"27": "CGM Failed Connection",
|
||||
"39": "CGM Transmitter Expired"
|
||||
}
|
||||
|
||||
class DalertidEnum(Enum):
|
||||
CgmSensorFail = 11
|
||||
CgmSensorExpired = 13
|
||||
CgmOutOfRange = 14
|
||||
CgmTransmitterError = 20
|
||||
CgmTemperature = 26
|
||||
CgmFailedConnection = 27
|
||||
CgmTransmitterExpired = 39
|
||||
|
||||
@property
|
||||
def dalertid(self):
|
||||
return self.DalertidEnum(self.dalertidRaw)
|
||||
|
||||
@staticmethod
|
||||
def build(raw):
|
||||
dalertid, = struct.unpack_from(UINT32, raw[:EVENT_LEN], 10)
|
||||
@@ -3004,7 +3048,29 @@ class LidCgmAlertActivatedDex(BaseEvent):
|
||||
param1: int
|
||||
param2: float
|
||||
|
||||
# Dictionary unknown: dalerts
|
||||
DalertidMap = {
|
||||
"11": "CGM Sensor Fail",
|
||||
"13": "CGM Sensor Expired",
|
||||
"14": "CGM Out Of Range",
|
||||
"20": "CGM Transmitter Error",
|
||||
"26": "CGM Temperature",
|
||||
"27": "CGM Failed Connection",
|
||||
"39": "CGM Transmitter Expired"
|
||||
}
|
||||
|
||||
class DalertidEnum(Enum):
|
||||
CgmSensorFail = 11
|
||||
CgmSensorExpired = 13
|
||||
CgmOutOfRange = 14
|
||||
CgmTransmitterError = 20
|
||||
CgmTemperature = 26
|
||||
CgmFailedConnection = 27
|
||||
CgmTransmitterExpired = 39
|
||||
|
||||
@property
|
||||
def dalertid(self):
|
||||
return self.DalertidEnum(self.dalertidRaw)
|
||||
|
||||
SensortypeMap = {
|
||||
"0": "Invalid",
|
||||
"1": "CGM_TYPE_DEXCOM_G6",
|
||||
@@ -3056,7 +3122,29 @@ class LidCgmAlertClearedDex(BaseEvent):
|
||||
dalertidRaw: int
|
||||
sensortypeRaw: int
|
||||
|
||||
# Dictionary unknown: dalerts
|
||||
DalertidMap = {
|
||||
"11": "CGM Sensor Fail",
|
||||
"13": "CGM Sensor Expired",
|
||||
"14": "CGM Out Of Range",
|
||||
"20": "CGM Transmitter Error",
|
||||
"26": "CGM Temperature",
|
||||
"27": "CGM Failed Connection",
|
||||
"39": "CGM Transmitter Expired"
|
||||
}
|
||||
|
||||
class DalertidEnum(Enum):
|
||||
CgmSensorFail = 11
|
||||
CgmSensorExpired = 13
|
||||
CgmOutOfRange = 14
|
||||
CgmTransmitterError = 20
|
||||
CgmTemperature = 26
|
||||
CgmFailedConnection = 27
|
||||
CgmTransmitterExpired = 39
|
||||
|
||||
@property
|
||||
def dalertid(self):
|
||||
return self.DalertidEnum(self.dalertidRaw)
|
||||
|
||||
SensortypeMap = {
|
||||
"0": "Invalid",
|
||||
"1": "CGM_TYPE_DEXCOM_G6",
|
||||
@@ -3103,7 +3191,29 @@ class LidCgmAlertAckDex(BaseEvent):
|
||||
sensortypeRaw: int
|
||||
acksourceRaw: int
|
||||
|
||||
# Dictionary unknown: dalerts
|
||||
DalertidMap = {
|
||||
"11": "CGM Sensor Fail",
|
||||
"13": "CGM Sensor Expired",
|
||||
"14": "CGM Out Of Range",
|
||||
"20": "CGM Transmitter Error",
|
||||
"26": "CGM Temperature",
|
||||
"27": "CGM Failed Connection",
|
||||
"39": "CGM Transmitter Expired"
|
||||
}
|
||||
|
||||
class DalertidEnum(Enum):
|
||||
CgmSensorFail = 11
|
||||
CgmSensorExpired = 13
|
||||
CgmOutOfRange = 14
|
||||
CgmTransmitterError = 20
|
||||
CgmTemperature = 26
|
||||
CgmFailedConnection = 27
|
||||
CgmTransmitterExpired = 39
|
||||
|
||||
@property
|
||||
def dalertid(self):
|
||||
return self.DalertidEnum(self.dalertidRaw)
|
||||
|
||||
SensortypeMap = {
|
||||
"0": "Invalid",
|
||||
"1": "CGM_TYPE_DEXCOM_G6",
|
||||
@@ -3642,7 +3752,29 @@ class LidCgmAlertActivatedFsl2(BaseEvent):
|
||||
param1: int
|
||||
param2: float
|
||||
|
||||
# Dictionary unknown: dalerts
|
||||
DalertidMap = {
|
||||
"11": "CGM Sensor Fail",
|
||||
"13": "CGM Sensor Expired",
|
||||
"14": "CGM Out Of Range",
|
||||
"20": "CGM Transmitter Error",
|
||||
"26": "CGM Temperature",
|
||||
"27": "CGM Failed Connection",
|
||||
"39": "CGM Transmitter Expired"
|
||||
}
|
||||
|
||||
class DalertidEnum(Enum):
|
||||
CgmSensorFail = 11
|
||||
CgmSensorExpired = 13
|
||||
CgmOutOfRange = 14
|
||||
CgmTransmitterError = 20
|
||||
CgmTemperature = 26
|
||||
CgmFailedConnection = 27
|
||||
CgmTransmitterExpired = 39
|
||||
|
||||
@property
|
||||
def dalertid(self):
|
||||
return self.DalertidEnum(self.dalertidRaw)
|
||||
|
||||
SensortypeMap = {
|
||||
"0": "Invalid",
|
||||
"2": "CGM_TYPE_LIBRE2"
|
||||
@@ -3692,7 +3824,29 @@ class LidCgmAlertClearedFsl2(BaseEvent):
|
||||
dalertidRaw: int
|
||||
sensortypeRaw: int
|
||||
|
||||
# Dictionary unknown: dalerts
|
||||
DalertidMap = {
|
||||
"11": "CGM Sensor Fail",
|
||||
"13": "CGM Sensor Expired",
|
||||
"14": "CGM Out Of Range",
|
||||
"20": "CGM Transmitter Error",
|
||||
"26": "CGM Temperature",
|
||||
"27": "CGM Failed Connection",
|
||||
"39": "CGM Transmitter Expired"
|
||||
}
|
||||
|
||||
class DalertidEnum(Enum):
|
||||
CgmSensorFail = 11
|
||||
CgmSensorExpired = 13
|
||||
CgmOutOfRange = 14
|
||||
CgmTransmitterError = 20
|
||||
CgmTemperature = 26
|
||||
CgmFailedConnection = 27
|
||||
CgmTransmitterExpired = 39
|
||||
|
||||
@property
|
||||
def dalertid(self):
|
||||
return self.DalertidEnum(self.dalertidRaw)
|
||||
|
||||
SensortypeMap = {
|
||||
"0": "Invalid",
|
||||
"2": "CGM_TYPE_LIBRE2"
|
||||
|
||||
@@ -130,4 +130,14 @@ ALARMS_DICT = {
|
||||
"61": "DEFAULT_ALARM_61",
|
||||
"62": "DEFAULT_ALARM_62",
|
||||
"63": "DEFAULT_ALARM_63",
|
||||
}
|
||||
|
||||
CGM_ALERTS_DICT = {
|
||||
"11": "CGM Sensor Fail",
|
||||
"13": "CGM Sensor Expired",
|
||||
"14": "CGM Out Of Range",
|
||||
"20": "CGM Transmitter Error",
|
||||
"26": "CGM Temperature",
|
||||
"27": "CGM Failed Connection",
|
||||
"39": "CGM Transmitter Expired"
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import json
|
||||
try:
|
||||
from static_dicts import ALERTS_DICT, ALARMS_DICT
|
||||
from static_dicts import ALERTS_DICT, ALARMS_DICT, CGM_ALERTS_DICT
|
||||
except ImportError:
|
||||
from .static_dicts import ALERTS_DICT, ALARMS_DICT
|
||||
from .static_dicts import ALERTS_DICT, ALARMS_DICT, CGM_ALERTS_DICT
|
||||
|
||||
def enumNameFormat(text):
|
||||
if not text:
|
||||
@@ -65,6 +65,9 @@ def transform_dictionary(event_def, name, name_fmt, field, tx):
|
||||
|
||||
if tx == 'alarms':
|
||||
return transform_enum(event_def, name, name_fmt, field, ALARMS_DICT)
|
||||
|
||||
if tx == 'dalerts':
|
||||
return transform_enum(event_def, name, name_fmt, field, CGM_ALERTS_DICT)
|
||||
return [f'# Dictionary unknown: {tx}']
|
||||
|
||||
def transform_bitmask(event_def, name, name_fmt, field, tx):
|
||||
|
||||
@@ -9,6 +9,7 @@ CGM = "CGM"
|
||||
PUMP_EVENTS = "PUMP_EVENTS"
|
||||
PUMP_EVENTS_BASAL_SUSPENSION = "PUMP_EVENTS_BASAL_SUSPENSION"
|
||||
PROFILES = "PROFILES"
|
||||
CGM_ALERTS = "CGM_ALERTS"
|
||||
|
||||
DEFAULT_FEATURES = [
|
||||
BASAL,
|
||||
@@ -22,13 +23,14 @@ ALL_FEATURES = [
|
||||
IOB,
|
||||
PUMP_EVENTS,
|
||||
PUMP_EVENTS_BASAL_SUSPENSION,
|
||||
PROFILES
|
||||
PROFILES,
|
||||
CGM,
|
||||
CGM_ALERTS,
|
||||
]
|
||||
|
||||
|
||||
# These modes are not yet ready for wide use.
|
||||
if ENABLE_TESTING_MODES:
|
||||
ALL_FEATURES += [
|
||||
BOLUS_BG,
|
||||
CGM
|
||||
BOLUS_BG
|
||||
]
|
||||
@@ -14,6 +14,10 @@ ACTIVITY_EVENTTYPE = "Activity"
|
||||
EXERCISE_EVENTTYPE = "Exercise"
|
||||
SLEEP_EVENTTYPE = "Sleep"
|
||||
ALARM_EVENTTYPE = "Alarm"
|
||||
CGM_ALERT_EVENTTYPE = "CGM Alert"
|
||||
CGM_START_EVENTTYPE = "CGM Start Session"
|
||||
CGM_JOIN_EVENTTYPE = "CGM Join Session"
|
||||
CGM_STOP_EVENTTYPE = "CGM Stop Session"
|
||||
|
||||
IOB_ACTIVITYTYPE = "tconnect_iob"
|
||||
|
||||
@@ -23,7 +27,7 @@ Conversion methods for parsing data into Nightscout objects.
|
||||
"""
|
||||
class NightscoutEntry:
|
||||
@staticmethod
|
||||
def basal(value, duration_mins, created_at, reason=""):
|
||||
def basal(value, duration_mins, created_at, reason="", pump_event_id=""):
|
||||
return {
|
||||
"eventType": BASAL_EVENTTYPE,
|
||||
"reason": reason,
|
||||
@@ -33,7 +37,8 @@ class NightscoutEntry:
|
||||
"created_at": created_at,
|
||||
"carbs": None,
|
||||
"insulin": None,
|
||||
"enteredBy": ENTERED_BY
|
||||
"enteredBy": ENTERED_BY,
|
||||
"pump_event_id": pump_event_id
|
||||
}
|
||||
|
||||
# Note that Nightscout is not consistent and uses "Sensor"/"Finger"
|
||||
@@ -46,19 +51,24 @@ class NightscoutEntry:
|
||||
data = {
|
||||
"eventType": BOLUS_EVENTTYPE,
|
||||
"created_at": created_at,
|
||||
"carbs": int(carbs),
|
||||
"carbs": int(carbs) if carbs else None,
|
||||
"insulin": float(bolus),
|
||||
"notes": notes,
|
||||
"enteredBy": ENTERED_BY,
|
||||
}
|
||||
if bg:
|
||||
if bg_type not in (NightscoutEntry.SENSOR, NightscoutEntry.FINGER):
|
||||
raise InvalidBolusTypeException("bg_type: %s (%s)" % (bg_type, data))
|
||||
if bg_type:
|
||||
if bg_type not in (NightscoutEntry.SENSOR, NightscoutEntry.FINGER):
|
||||
raise InvalidBolusTypeException("bg_type: %s (%s)" % (bg_type, data))
|
||||
|
||||
data.update({
|
||||
"glucose": str(bg),
|
||||
"glucoseType": bg_type
|
||||
})
|
||||
data.update({
|
||||
"glucose": str(bg),
|
||||
"glucoseType": bg_type
|
||||
})
|
||||
else:
|
||||
data.update({
|
||||
"glucose": str(bg)
|
||||
})
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
@@ -71,65 +81,115 @@ class NightscoutEntry:
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def entry(sgv, created_at):
|
||||
def entry(sgv, created_at, pump_event_id=""):
|
||||
return {
|
||||
"type": "sgv",
|
||||
"sgv": int(sgv),
|
||||
"date": int(1000 * arrow.get(created_at).timestamp()),
|
||||
"dateString": arrow.get(created_at).strftime('%Y-%m-%dT%H:%M:%S%z'),
|
||||
"device": ENTERED_BY,
|
||||
"pump_event_id": pump_event_id,
|
||||
# delta, direction are undefined
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def sitechange(created_at, reason=""):
|
||||
def sitechange(created_at, reason="", pump_event_id=""):
|
||||
return {
|
||||
"eventType": SITECHANGE_EVENTTYPE,
|
||||
"reason": reason,
|
||||
"notes": reason,
|
||||
"created_at": created_at,
|
||||
"enteredBy": ENTERED_BY
|
||||
"enteredBy": ENTERED_BY,
|
||||
"pump_event_id": pump_event_id
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def basalsuspension(created_at, reason=""):
|
||||
def basalsuspension(created_at, reason="", pump_event_id=""):
|
||||
return {
|
||||
"eventType": BASALSUSPENSION_EVENTTYPE,
|
||||
"reason": reason,
|
||||
"notes": reason,
|
||||
"created_at": created_at,
|
||||
"enteredBy": ENTERED_BY
|
||||
"enteredBy": ENTERED_BY,
|
||||
"pump_event_id": pump_event_id
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def basalresume(created_at):
|
||||
def basalresume(created_at, pump_event_id=""):
|
||||
return {
|
||||
"eventType": BASALRESUME_EVENTTYPE,
|
||||
"reason": "Basal resumed",
|
||||
"notes": "Basal resumed",
|
||||
"created_at": created_at,
|
||||
"enteredBy": ENTERED_BY
|
||||
"enteredBy": ENTERED_BY,
|
||||
"pump_event_id": pump_event_id
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def alarm(created_at, reason=""):
|
||||
def alarm(created_at, reason="", pump_event_id=""):
|
||||
return {
|
||||
"eventType": ALARM_EVENTTYPE,
|
||||
"reason": reason,
|
||||
"notes": reason,
|
||||
"created_at": created_at,
|
||||
"enteredBy": ENTERED_BY
|
||||
"enteredBy": ENTERED_BY,
|
||||
"pump_event_id": pump_event_id
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def activity(created_at, duration, reason="", event_type=ACTIVITY_EVENTTYPE):
|
||||
def cgm_alert(created_at, reason="", pump_event_id=""):
|
||||
return {
|
||||
"eventType": CGM_ALERT_EVENTTYPE,
|
||||
"reason": reason,
|
||||
"notes": reason,
|
||||
"created_at": created_at,
|
||||
"enteredBy": ENTERED_BY,
|
||||
"pump_event_id": pump_event_id
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def cgm_start(created_at, reason="", pump_event_id=""):
|
||||
return {
|
||||
"eventType": CGM_START_EVENTTYPE,
|
||||
"reason": reason,
|
||||
"notes": reason,
|
||||
"created_at": created_at,
|
||||
"enteredBy": ENTERED_BY,
|
||||
"pump_event_id": pump_event_id
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def cgm_join(created_at, reason="", pump_event_id=""):
|
||||
return {
|
||||
"eventType": CGM_JOIN_EVENTTYPE,
|
||||
"reason": reason,
|
||||
"notes": reason,
|
||||
"created_at": created_at,
|
||||
"enteredBy": ENTERED_BY,
|
||||
"pump_event_id": pump_event_id
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def cgm_stop(created_at, reason="", pump_event_id=""):
|
||||
return {
|
||||
"eventType": CGM_STOP_EVENTTYPE,
|
||||
"reason": reason,
|
||||
"notes": reason,
|
||||
"created_at": created_at,
|
||||
"enteredBy": ENTERED_BY,
|
||||
"pump_event_id": pump_event_id
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def activity(created_at, duration, reason="", event_type=ACTIVITY_EVENTTYPE, pump_event_id=""):
|
||||
return {
|
||||
"eventType": event_type,
|
||||
"reason": reason,
|
||||
"notes": reason,
|
||||
"duration": float(duration),
|
||||
"created_at": created_at,
|
||||
"enteredBy": ENTERED_BY
|
||||
"enteredBy": ENTERED_BY,
|
||||
"pump_event_id": pump_event_id
|
||||
}
|
||||
|
||||
# Tandem-scraped profile to Nightscout profile store entry
|
||||
|
||||
@@ -9,6 +9,12 @@ from .process_basal import ProcessBasal
|
||||
from .process_basal_suspension import ProcessBasalSuspension
|
||||
from .process_basal_resume import ProcessBasalResume
|
||||
from .process_alarm import ProcessAlarm
|
||||
from .process_bolus import ProcessBolus
|
||||
from .process_cartridge import ProcessCartridge
|
||||
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
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -25,7 +31,13 @@ class ProcessTimeRange:
|
||||
EventClass.BASAL.name: ProcessBasal,
|
||||
EventClass.BASAL_SUSPENSION.name: ProcessBasalSuspension,
|
||||
EventClass.BASAL_RESUME.name: ProcessBasalResume,
|
||||
EventClass.ALARM.name: ProcessAlarm
|
||||
EventClass.ALARM.name: ProcessAlarm,
|
||||
EventClass.BOLUS.name: ProcessBolus,
|
||||
EventClass.CARTRIDGE.name: ProcessCartridge,
|
||||
EventClass.CGM_ALERT.name: ProcessCGMAlert,
|
||||
EventClass.CGM_START_JOIN_STOP.name: ProcessCGMStartJoinStop,
|
||||
EventClass.CGM_READING.name: ProcessCGMReading,
|
||||
EventClass.USER_MODE.name: ProcessUserMode,
|
||||
}
|
||||
|
||||
def process(self, time_start, time_end):
|
||||
@@ -59,8 +71,12 @@ class ProcessTimeRange:
|
||||
for clazz, events in for_eventclass.items():
|
||||
if clazz in self.event_classes.keys():
|
||||
c = self.event_classes[clazz](self.tconnect, self.nightscout, self.tconnect_device_id, self.pretend, self.features)
|
||||
ns_entries = c.process(events, events_first_time, events_last_time)
|
||||
processed_count += c.write(ns_entries)
|
||||
if c.enabled():
|
||||
logger.info("%s is enabled from features %s" % (clazz, self.features))
|
||||
ns_entries = c.process(events, events_first_time, events_last_time)
|
||||
processed_count += c.write(ns_entries)
|
||||
else:
|
||||
logger.info("Skipping %s, is not enabled from features %s" % (clazz, self.features))
|
||||
|
||||
|
||||
return processed_count
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
import arrow
|
||||
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from ... import features
|
||||
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
from ...eventparser.utils import bitmask_to_list
|
||||
from ...eventparser import events as eventtypes
|
||||
@@ -21,6 +22,9 @@ class ProcessAlarm:
|
||||
self.pretend = pretend
|
||||
self.features = features
|
||||
|
||||
def enabled(self):
|
||||
return features.PUMP_EVENTS in self.features
|
||||
|
||||
def process(self, events, time_start, time_end):
|
||||
logger.debug("ProcessAlarm: querying for last uploaded alarm")
|
||||
last_upload = self.nightscout.last_uploaded_entry(ALARM_EVENTTYPE, time_start=time_start, time_end=time_end)
|
||||
@@ -58,10 +62,12 @@ class ProcessAlarm:
|
||||
if type(event) == eventtypes.LidAlarmActivated:
|
||||
return NightscoutEntry.alarm(
|
||||
created_at = event.eventTimestamp,
|
||||
reason = event.alarmid
|
||||
reason = event.alarmid,
|
||||
pump_event_id = event.eventId
|
||||
)
|
||||
elif type(event) == eventtypes.LidMalfunctionActivated:
|
||||
return NightscoutEntry.alarm(
|
||||
created_at = event.eventTimestamp,
|
||||
reason = "Malfunction"
|
||||
reason = "Malfunction",
|
||||
pump_event_id = event.eventId
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
import arrow
|
||||
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from ... import features
|
||||
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
from ...eventparser.utils import bitmask_to_list
|
||||
from ...eventparser import events as eventtypes
|
||||
@@ -21,6 +22,9 @@ class ProcessBasal:
|
||||
self.pretend = pretend
|
||||
self.features = features
|
||||
|
||||
def enabled(self):
|
||||
return features.BASAL in self.features
|
||||
|
||||
def process(self, events, time_start, time_end):
|
||||
logger.debug("ProcessBasal: querying for last uploaded entry")
|
||||
last_upload = self.nightscout.last_uploaded_entry(BASAL_EVENTTYPE, time_start=time_start, time_end=time_end)
|
||||
@@ -68,12 +72,14 @@ class ProcessBasal:
|
||||
value = event.commandedbasalrate,
|
||||
duration_mins = duration.seconds / 60,
|
||||
created_at = start,
|
||||
reason = ', '.join(bitmask_to_list(event.changetype))
|
||||
reason = ', '.join(bitmask_to_list(event.changetype)),
|
||||
pump_event_id = event.eventId
|
||||
)
|
||||
if type(event) == eventtypes.LidBasalDelivery:
|
||||
return NightscoutEntry.basal(
|
||||
value = event.commandedRate,
|
||||
duration_mins = duration.seconds / 60,
|
||||
created_at = start,
|
||||
reason = ', '.join(bitmask_to_list(event.commandedRateSource))
|
||||
reason = ', '.join(bitmask_to_list(event.commandedRateSource)),
|
||||
pump_event_id = event.eventId
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
import arrow
|
||||
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from ... import features
|
||||
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
from ...eventparser.utils import bitmask_to_list
|
||||
from ...eventparser import events as eventtypes
|
||||
@@ -21,6 +22,9 @@ class ProcessBasalResume:
|
||||
self.pretend = pretend
|
||||
self.features = features
|
||||
|
||||
def enabled(self):
|
||||
return features.PUMP_EVENTS in self.features or features.BASAL in self.features
|
||||
|
||||
def process(self, events, time_start, time_end):
|
||||
logger.debug("ProcessBasalResume: querying for last uploaded resume-suspension")
|
||||
last_upload = self.nightscout.last_uploaded_entry(BASALRESUME_EVENTTYPE, time_start=time_start, time_end=time_end)
|
||||
@@ -57,5 +61,6 @@ class ProcessBasalResume:
|
||||
def resume_to_nsentry(self, event):
|
||||
if type(event) == eventtypes.LidPumpingResumed:
|
||||
return NightscoutEntry.basalresume(
|
||||
created_at = event.eventTimestamp
|
||||
created_at = event.eventTimestamp,
|
||||
pump_event_id = event.eventId
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
import arrow
|
||||
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from ... import features
|
||||
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
from ...eventparser.utils import bitmask_to_list
|
||||
from ...eventparser import events as eventtypes
|
||||
@@ -21,6 +22,9 @@ class ProcessBasalSuspension:
|
||||
self.pretend = pretend
|
||||
self.features = features
|
||||
|
||||
def enabled(self):
|
||||
return features.PUMP_EVENTS in self.features or features.BASAL in self.features
|
||||
|
||||
def process(self, events, time_start, time_end):
|
||||
logger.debug("ProcessBasalSuspension: querying for last uploaded suspension")
|
||||
last_upload = self.nightscout.last_uploaded_entry(BASALSUSPENSION_EVENTTYPE, time_start=time_start, time_end=time_end)
|
||||
@@ -58,5 +62,6 @@ class ProcessBasalSuspension:
|
||||
if type(event) == eventtypes.LidPumpingSuspended:
|
||||
return NightscoutEntry.basalsuspension(
|
||||
created_at = event.eventTimestamp,
|
||||
reason = ', '.join(bitmask_to_list(event.suspendreason))
|
||||
reason = ', '.join(bitmask_to_list(event.suspendreason)),
|
||||
pump_event_id = event.eventId
|
||||
)
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import logging
|
||||
import arrow
|
||||
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from ... import features
|
||||
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
from ...eventparser.utils import bitmask_to_list
|
||||
from ...eventparser import events as eventtypes
|
||||
from ...domain.tandemsource.event_class import EventClass
|
||||
from ...parser.nightscout import (
|
||||
BOLUS_EVENTTYPE,
|
||||
NightscoutEntry
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ProcessBolus:
|
||||
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.BOLUS in self.features
|
||||
|
||||
def process(self, events, time_start, time_end):
|
||||
logger.debug("ProcessBolus: querying for last uploaded entry")
|
||||
last_upload = self.nightscout.last_uploaded_entry(BOLUS_EVENTTYPE, time_start=time_start, time_end=time_end)
|
||||
last_upload_time = None
|
||||
if last_upload:
|
||||
last_upload_time = arrow.get(last_upload["created_at"])
|
||||
logger.info("Last Nightscout bolus upload: %s" % last_upload_time)
|
||||
|
||||
# TODO EXTENDED BOLUSES
|
||||
bolusCompletedEvents = []
|
||||
bolusEventsForId = {}
|
||||
for event in sorted(events, key=lambda x: x.eventTimestamp):
|
||||
if event.bolusid not in bolusEventsForId.keys():
|
||||
bolusEventsForId[event.bolusid] = {}
|
||||
|
||||
bolusEventsForId[event.bolusid][type(event)] = event
|
||||
|
||||
if type(event) == eventtypes.LidBolusCompleted:
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) < last_upload_time:
|
||||
if self.pretend:
|
||||
logger.info("Skipping bolusCompletedEvent before last upload time: %s (time range: %s - %s)" % (event, time_start, time_end))
|
||||
continue
|
||||
|
||||
bolusCompletedEvents.append(event)
|
||||
|
||||
bolusCompletedEvents.sort(lambda e: e.eventTimestamp)
|
||||
|
||||
|
||||
|
||||
ns_entries = []
|
||||
for bolusCompleted in bolusCompletedEvents:
|
||||
m = bolusEventsForId[bolusCompleted.bolusid]
|
||||
|
||||
ns_entries.append(self.bolus_to_nsentry(
|
||||
bolusCompleted,
|
||||
bolusRequested1 = m.get(events.LidBolusRequestedMsg1),
|
||||
bolusRequested2 = m.get(events.LidBolusRequestedMsg2),
|
||||
bolusRequested3 = m.get(events.LidBolusRequestedMsg3),
|
||||
))
|
||||
|
||||
return ns_entries
|
||||
|
||||
def write(self, ns_entries):
|
||||
count = 0
|
||||
for entry in ns_entries:
|
||||
if self.pretend:
|
||||
logger.info("Would upload to Nightscout: %s" % entry)
|
||||
else:
|
||||
logger.info("Uploading to Nightscout: %s" % entry)
|
||||
self.nightscout.upload_entry(entry)
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
|
||||
def bolus_to_nsentry(self, bolusCompleted, bolusRequested1, bolusRequested2, bolusRequested3):
|
||||
suffixes = []
|
||||
if bolusRequested2 and bolusRequested2.useroverride == eventtypes.LidBolusRequestedMsg2.UseroverrideEnum.Yes:
|
||||
suffixes.append('(Override)')
|
||||
|
||||
if bolusRequested2 and bolusRequested2.declinedcorrection == eventtypes.LidBolusRequestedMsg2.DeclinedcorrectionEnum.Yes:
|
||||
suffixes.append('(Declined Correction)')
|
||||
|
||||
suffix = (' ' + (' '.join(suffixes))) if suffixes else ''
|
||||
|
||||
event_ids = []
|
||||
for e in [bolusCompleted, bolusRequested1, bolusRequested2, bolusRequested3]:
|
||||
if e:
|
||||
event_ids.append(e.eventId)
|
||||
|
||||
|
||||
return NightscoutEntry.bolus(
|
||||
bolus = bolusCompleted.insulindelivered,
|
||||
carbs = bolusRequested1.carbamount if bolusRequested1 and bolusRequested1.carbamount>0 else None,
|
||||
created_at = bolusCompleted.eventTimestamp,
|
||||
notes = (eventtypes.LidBolusRequestedMsg2.OptionsMap[bolusRequested2.optionsRaw] if bolusRequested2 else '') + suffix,
|
||||
bg = bolusRequested1.BG if bolusRequested1 and bolusRequested1.BG > 0 else None,
|
||||
pump_event_id = ",".join(event_ids)
|
||||
)
|
||||
@@ -0,0 +1,100 @@
|
||||
import logging
|
||||
import arrow
|
||||
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from ... import features
|
||||
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
from ...eventparser.utils import bitmask_to_list
|
||||
from ...eventparser import events as eventtypes
|
||||
from ...domain.tandemsource.event_class import EventClass
|
||||
from ...parser.nightscout import (
|
||||
SITECHANGE_EVENTTYPE,
|
||||
NightscoutEntry
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ProcessCartridge:
|
||||
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.PUMP_EVENTS in self.features
|
||||
|
||||
def process(self, events, time_start, time_end):
|
||||
logger.debug("ProcessCartridge: querying for last uploaded entry")
|
||||
last_upload = self.nightscout.last_uploaded_entry(SITECHANGE_EVENTTYPE, time_start=time_start, time_end=time_end)
|
||||
last_upload_time = None
|
||||
if last_upload:
|
||||
last_upload_time = arrow.get(last_upload["created_at"])
|
||||
logger.info("Last Nightscout sitechange upload: %s" % last_upload_time)
|
||||
|
||||
cartFilledEvents = []
|
||||
cannulaFilledEvents = []
|
||||
tubingFilledEvents = []
|
||||
for event in sorted(events, key=lambda x: x.eventTimestamp):
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) < last_upload_time:
|
||||
if self.pretend:
|
||||
logger.info("Skipping %s before last upload time: %s (time range: %s - %s)" % (type(event), event, time_start, time_end))
|
||||
continue
|
||||
|
||||
if type(event) == eventtypes.LidCartridgeFilled:
|
||||
cartFilledEvents.append(event)
|
||||
elif type(event) == eventtypes.LidCannulaFilled:
|
||||
cannulaFilledEvents.append(event)
|
||||
elif type(event) == eventtypes.LidTubingFilled:
|
||||
tubingFilledEvents.append(event)
|
||||
|
||||
cartFilledEvents.sort(lambda e: e.eventTimestamp)
|
||||
cannulaFilledEvents.sort(lambda e: e.eventTimestamp)
|
||||
tubingFilledEvents.sort(lambda e: e.eventTimestamp)
|
||||
|
||||
ns_entries = []
|
||||
for cartFilled in cartFilledEvents:
|
||||
ns_entries.append(self.cart_to_nsentry(cartFilled))
|
||||
|
||||
for cannulaFilled in cannulaFilledEvents:
|
||||
ns_entries.append(self.cannula_to_nsentry(cannulaFilled))
|
||||
|
||||
for tubingFilled in tubingFilledEvents:
|
||||
ns_entries.append(self.tubing_to_nsentry(tubingFilled))
|
||||
|
||||
|
||||
return ns_entries
|
||||
|
||||
def write(self, ns_entries):
|
||||
count = 0
|
||||
for entry in ns_entries:
|
||||
if self.pretend:
|
||||
logger.info("Would upload to Nightscout: %s" % entry)
|
||||
else:
|
||||
logger.info("Uploading to Nightscout: %s" % entry)
|
||||
self.nightscout.upload_entry(entry)
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
def cart_to_nsentry(self, cartFilled):
|
||||
return NightscoutEntry.sitechange(
|
||||
created_at = cartFilled.eventTimestamp,
|
||||
reason = "Cartridge Filled" + (" (%du filled)" % round(cartFilled.v2Volume) if cartFilled.v2Volume else ""),
|
||||
pump_event_id = cartFilled.eventId
|
||||
)
|
||||
|
||||
def cannula_to_nsentry(self, cannulaFilled):
|
||||
return NightscoutEntry.sitechange(
|
||||
created_at = cannulaFilled.eventTimestamp,
|
||||
reason = "Cannula Filled" + (" (%du primed)" % round(cannulaFilled.primesize) if cannulaFilled.primesize else ""),
|
||||
pump_event_id = cannulaFilled.eventId
|
||||
)
|
||||
|
||||
def tubing_to_nsentry(self, tubingFilled):
|
||||
return NightscoutEntry.sitechange(
|
||||
created_at = tubingFilled.eventTimestamp,
|
||||
reason = "Tubing Filled" + (" (%du primed)" % round(tubingFilled.primesize) if tubingFilled.primesize else ""),
|
||||
pump_event_id = tubingFilled.eventId
|
||||
)
|
||||
@@ -0,0 +1,83 @@
|
||||
import logging
|
||||
import arrow
|
||||
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from ... import features
|
||||
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
from ...eventparser.utils import bitmask_to_list
|
||||
from ...eventparser import events as eventtypes
|
||||
from ...domain.tandemsource.event_class import EventClass
|
||||
from ...parser.nightscout import (
|
||||
CGM_ALERT_EVENTTYPE,
|
||||
NightscoutEntry
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ProcessCGMAlert:
|
||||
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.CGM_ALERTS in self.features
|
||||
|
||||
def process(self, events, time_start, time_end):
|
||||
logger.debug("ProcessCGMAlert: querying for last uploaded entry")
|
||||
last_upload = self.nightscout.last_uploaded_entry(CGM_ALERT_EVENTTYPE, time_start=time_start, time_end=time_end)
|
||||
last_upload_time = None
|
||||
if last_upload:
|
||||
last_upload_time = arrow.get(last_upload["created_at"])
|
||||
logger.info("Last Nightscout cgmalert upload: %s" % last_upload_time)
|
||||
|
||||
alertEvents = []
|
||||
for event in sorted(events, key=lambda x: x.eventTimestamp):
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) < last_upload_time:
|
||||
if self.pretend:
|
||||
logger.info("Skipping %s before last upload time: %s (time range: %s - %s)" % (type(event), event, time_start, time_end))
|
||||
continue
|
||||
|
||||
alertEvents.append(event)
|
||||
|
||||
alertEvents.sort(lambda e: e.eventTimestamp)
|
||||
|
||||
ns_entries = []
|
||||
for event in alertEvents:
|
||||
ns_entries.append(self.alert_to_nsentry(event))
|
||||
|
||||
return ns_entries
|
||||
|
||||
def write(self, ns_entries):
|
||||
count = 0
|
||||
for entry in ns_entries:
|
||||
if self.pretend:
|
||||
logger.info("Would upload to Nightscout: %s" % entry)
|
||||
else:
|
||||
logger.info("Uploading to Nightscout: %s" % entry)
|
||||
self.nightscout.upload_entry(entry)
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
def alert_to_nsentry(self, alert):
|
||||
if type(alert) == eventtypes.LidCgmAlertActivated:
|
||||
return NightscoutEntry.cgm_alert(
|
||||
created_at = alert.eventTimestamp,
|
||||
reason = ("CGM Alert (%s)" % alert.dalertid) if alert.dalertid else "CGM Alert (Unknown)",
|
||||
pump_event_id = alert.eventId
|
||||
)
|
||||
elif type(alert) == eventtypes.LidCgmAlertActivatedDex:
|
||||
return NightscoutEntry.cgm_alert(
|
||||
created_at = alert.eventTimestamp,
|
||||
reason = ("Dexcom CGM Alert (%s)" % alert.dalertid) if alert.dalertid else "Dexcom CGM Alert (Unknown)",
|
||||
pump_event_id = alert.eventId
|
||||
)
|
||||
elif type(alert) == eventtypes.LidCgmAlertActivatedFsl2:
|
||||
return NightscoutEntry.cgm_alert(
|
||||
created_at = alert.eventTimestamp,
|
||||
reason = ("Libre CGM Alert (%s)" % alert.dalertid) if alert.dalertid else "Libre CGM Alert (Unknown)",
|
||||
pump_event_id = alert.eventId
|
||||
)
|
||||
@@ -0,0 +1,74 @@
|
||||
import logging
|
||||
import arrow
|
||||
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from ... import features
|
||||
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
from ...eventparser.utils import bitmask_to_list
|
||||
from ...eventparser.raw_event import TANDEM_EPOCH
|
||||
from ...eventparser import events as eventtypes
|
||||
from ...domain.tandemsource.event_class import EventClass
|
||||
from ...parser.nightscout import (
|
||||
CGM_START_EVENTTYPE,
|
||||
NightscoutEntry
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ProcessCGMReading:
|
||||
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.CGM in self.features
|
||||
|
||||
def process(self, events, time_start, time_end):
|
||||
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"])
|
||||
logger.info("ProcessCGMReading: Last Nightscout bg upload: %s" % last_upload_time)
|
||||
|
||||
readings = []
|
||||
for event in sorted(events, key=lambda x: self.timestamp_for(x)):
|
||||
if last_upload_time and self.timestamp_for(event) < last_upload_time:
|
||||
if self.pretend:
|
||||
logger.info("ProcessCGMReading: Skipping %s before last upload time: %s (time range: %s - %s)" % (type(event), event, time_start, time_end))
|
||||
continue
|
||||
|
||||
readings.append(event)
|
||||
|
||||
ns_entries = []
|
||||
for event in readings:
|
||||
ns_entries.append(self.to_nsentry(event))
|
||||
|
||||
return ns_entries
|
||||
|
||||
def write(self, ns_entries):
|
||||
count = 0
|
||||
for entry in ns_entries:
|
||||
if self.pretend:
|
||||
logger.info("Would upload to Nightscout: %s" % entry)
|
||||
else:
|
||||
logger.info("Uploading to Nightscout: %s" % entry)
|
||||
self.nightscout.upload_entry(entry, entity='entries')
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
def timestamp_for(self, event):
|
||||
# For backfills the time the event was added to the pump's event store
|
||||
# might not be the time it actually occurred, so we use the egvTimestamp
|
||||
return arrow.get(TANDEM_EPOCH + event.egvTimestamp)
|
||||
|
||||
def to_nsentry(self, event):
|
||||
return NightscoutEntry.entry(
|
||||
sgv = event.currentglucosedisplayvalue,
|
||||
created_at = self.timestamp_for(event),
|
||||
pump_event_id = event.eventId,
|
||||
)
|
||||
@@ -0,0 +1,83 @@
|
||||
import logging
|
||||
import arrow
|
||||
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from ... import features
|
||||
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
from ...eventparser.utils import bitmask_to_list
|
||||
from ...eventparser import events as eventtypes
|
||||
from ...domain.tandemsource.event_class import EventClass
|
||||
from ...parser.nightscout import (
|
||||
CGM_START_EVENTTYPE,
|
||||
NightscoutEntry
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ProcessCGMStartJoinStop:
|
||||
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.PUMP_EVENTS in self.features or features.CGM_ALERTS in self.features
|
||||
|
||||
def process(self, events, time_start, time_end):
|
||||
logger.debug("ProcessCGMStartJoinStop: querying for last uploaded entry")
|
||||
last_upload = self.nightscout.last_uploaded_entry(CGM_START_EVENTTYPE, time_start=time_start, time_end=time_end)
|
||||
last_upload_time = None
|
||||
if last_upload:
|
||||
last_upload_time = arrow.get(last_upload["created_at"])
|
||||
logger.info("ProcessCGMStartJoinStop: Last Nightscout cgmstart upload: %s" % last_upload_time)
|
||||
|
||||
allEvents = []
|
||||
for event in sorted(events, key=lambda x: x.eventTimestamp):
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) < last_upload_time:
|
||||
if self.pretend:
|
||||
logger.info("ProcessCGMStartJoinStop: Skipping %s before last upload time: %s (time range: %s - %s)" % (type(event), event, time_start, time_end))
|
||||
continue
|
||||
|
||||
allEvents.append(event)
|
||||
|
||||
allEvents.sort(lambda e: e.eventTimestamp)
|
||||
|
||||
ns_entries = []
|
||||
for event in allEvents:
|
||||
ns_entries.append(self.to_nsentry(event))
|
||||
|
||||
return ns_entries
|
||||
|
||||
def write(self, ns_entries):
|
||||
count = 0
|
||||
for entry in ns_entries:
|
||||
if self.pretend:
|
||||
logger.info("Would upload to Nightscout: %s" % entry)
|
||||
else:
|
||||
logger.info("Uploading to Nightscout: %s" % entry)
|
||||
self.nightscout.upload_entry(entry)
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
def to_nsentry(self, event):
|
||||
if type(event) in EventClass._CGM_START:
|
||||
return NightscoutEntry.cgm_start(
|
||||
created_at = event.eventTimestamp,
|
||||
reason = "CGM Session Started",
|
||||
pump_event_id = event.eventId
|
||||
)
|
||||
elif type(event) in EventClass._CGM_JOIN:
|
||||
return NightscoutEntry.cgm_join(
|
||||
created_at = event.eventTimestamp,
|
||||
reason = "CGM Session Joined",
|
||||
pump_event_id = event.eventId
|
||||
)
|
||||
elif type(event) in EventClass._CGM_STOP:
|
||||
return NightscoutEntry.cgm_stop(
|
||||
created_at = event.eventTimestamp,
|
||||
reason = "CGM Session Stopped",
|
||||
pump_event_id = event.eventId
|
||||
)
|
||||
@@ -0,0 +1,244 @@
|
||||
import logging
|
||||
import arrow
|
||||
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from ... import features
|
||||
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
from ...eventparser.utils import bitmask_to_list
|
||||
from ...eventparser import events as eventtypes
|
||||
from ...domain.tandemsource.event_class import EventClass
|
||||
from ...parser.nightscout import (
|
||||
EXERCISE_EVENTTYPE,
|
||||
SLEEP_EVENTTYPE,
|
||||
NightscoutEntry
|
||||
)
|
||||
|
||||
NOT_ENDED = "Not Ended"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ProcessUserMode:
|
||||
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.PUMP_EVENTS in self.features
|
||||
|
||||
def process(self, events, time_start, time_end):
|
||||
logger.debug("ProcessUserMode: querying for last uploaded exercise entry")
|
||||
exercise_last_upload = self.nightscout.last_uploaded_entry(EXERCISE_EVENTTYPE, time_start=time_start, time_end=time_end)
|
||||
exercise_last_upload_time = None
|
||||
if exercise_last_upload:
|
||||
exercise_last_upload_time = arrow.get(exercise_last_upload["created_at"])
|
||||
logger.info("ProcessUserMode: Last Nightscout exercise upload: %s" % exercise_last_upload_time)
|
||||
|
||||
exercise_not_ended = False
|
||||
if exercise_last_upload and NOT_ENDED in exercise_last_upload.get("reason", ""):
|
||||
exercise_not_ended = True
|
||||
logger.info("ProcessUserMode: Last exercise not ended: %s" % exercise_last_upload)
|
||||
|
||||
|
||||
logger.debug("ProcessUserMode: querying for last uploaded sleep entry")
|
||||
sleep_last_upload = self.nightscout.last_uploaded_entry(SLEEP_EVENTTYPE, time_start=time_start, time_end=time_end)
|
||||
sleep_last_upload_time = None
|
||||
if sleep_last_upload:
|
||||
sleep_last_upload_time = arrow.get(sleep_last_upload["created_at"])
|
||||
logger.info("ProcessUserMode: Last Nightscout sleep upload: %s" % sleep_last_upload_time)
|
||||
|
||||
sleep_not_ended = False
|
||||
if sleep_last_upload and NOT_ENDED in sleep_last_upload.get("reason", ""):
|
||||
sleep_not_ended = True
|
||||
logger.info("ProcessUserMode: Last sleep not ended: %s" % sleep_last_upload)
|
||||
|
||||
last_upload_time = None
|
||||
if exercise_last_upload_time and sleep_last_upload_time:
|
||||
last_upload_time = max(exercise_last_upload_time, sleep_last_upload_time)
|
||||
elif exercise_last_upload_time:
|
||||
last_upload_time = exercise_last_upload_time
|
||||
elif sleep_last_upload_time:
|
||||
last_upload_time = sleep_last_upload_time
|
||||
|
||||
logger.info("ProcessUserMode: Last Nightscout usermode upload: %s" % last_upload_time)
|
||||
|
||||
|
||||
ns_entries = []
|
||||
|
||||
processed_sleep = []
|
||||
processed_exercise = []
|
||||
start_sleep = None
|
||||
start_exercise = None
|
||||
for event in sorted(events, key=lambda x: x.eventTimestamp):
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) < last_upload_time:
|
||||
if self.pretend:
|
||||
logger.info("ProcessUserMode: Skipping usermode event before last upload time: %s (time range: %s - %s)" % (event, time_start, time_end))
|
||||
continue
|
||||
|
||||
if self.is_start_sleep(event):
|
||||
start_sleep = event
|
||||
elif self.is_stop_sleep(event):
|
||||
if start_sleep:
|
||||
processed_sleep.append((start_sleep, event))
|
||||
start_sleep = None
|
||||
else:
|
||||
if sleep_not_ended:
|
||||
logger.info("ProcessUserMode: Found StopSleep without StartSleep, with incomplete sleep event in nightscout: %s NS: %s" % (event, sleep_last_upload))
|
||||
ns_entries.append(self.process_unended_sleep_stop(event, sleep_last_upload))
|
||||
else:
|
||||
logger.warning("ProcessUserMode: Found StopSleep without StartSleep, and no active sleep event in nightscout: %s" % event)
|
||||
elif self.is_start_exercise(event):
|
||||
start_exercise = event
|
||||
elif self.is_stop_exercise(event):
|
||||
if start_exercise:
|
||||
processed_exercise.append((start_exercise, event))
|
||||
start_exercise = None
|
||||
else:
|
||||
if exercise_not_ended:
|
||||
logger.info("ProcessUserMode: Found StopExercise without StartExercise, with incomplete exercise event in nightscout: %s NS: %s" % (event, exercise_last_upload))
|
||||
ns_entries.append(self.process_unended_exercise_stop(event, exercise_last_upload))
|
||||
else:
|
||||
logger.warning("ProcessUserMode: Found StopExercise without StartExercise, and no active exercise event in nightscout: %s" % event)
|
||||
else:
|
||||
logger.warning("ProcessUserMode: not sure how to process event: %s" % event)
|
||||
|
||||
if start_sleep:
|
||||
processed_sleep.append((start_sleep, None))
|
||||
logger.info("ProcessUserMode: sleep is active")
|
||||
if start_exercise:
|
||||
processed_exercise.append((start_exercise, None))
|
||||
logger.info("ProcessUserMode: exercise is active")
|
||||
|
||||
for items in processed_sleep:
|
||||
ns_entries.append(self.sleep_to_nsentry(start=items[0], stop=items[1], time_end=time_end))
|
||||
|
||||
for items in processed_exercise:
|
||||
ns_entries.append(self.exercise_to_nsentry(start=items[0], stop=items[1], time_end=time_end))
|
||||
|
||||
return ns_entries
|
||||
|
||||
def write(self, ns_entries):
|
||||
count = 0
|
||||
for entry in ns_entries:
|
||||
if self.pretend:
|
||||
logger.info("Would upload to Nightscout: %s" % entry)
|
||||
else:
|
||||
logger.info("Uploading to Nightscout: %s" % entry)
|
||||
self.nightscout.upload_entry(entry)
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
def is_start_sleep(self, event):
|
||||
return event.requestedaction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StartSleep
|
||||
def is_stop_sleep(self, event):
|
||||
return event.requestedaction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StopSleep or \
|
||||
event.requestedaction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StopAll
|
||||
def is_start_exercise(self, event):
|
||||
return event.requestedaction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StartExercise
|
||||
def is_stop_exercise(self, event):
|
||||
return event.requestedaction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StopExercise or \
|
||||
event.requestedaction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StopAll
|
||||
|
||||
|
||||
def sleep_to_nsentry(self, start, stop=None, time_end=None):
|
||||
if start and stop:
|
||||
reason = None
|
||||
if start.sleepstartedbygui == eventtypes.LidAaUserModeChange.SleepstartedbyguiEnum.TrueVal:
|
||||
reason = "Sleep (Manual)"
|
||||
elif start.activesleepschedule:
|
||||
reason = "Sleep (Scheduled)"
|
||||
|
||||
duration_mins = (stop.eventTimestamp - start.eventTimestamp).seconds / 60
|
||||
return NightscoutEntry.activity(
|
||||
created_at=start.eventTimestamp,
|
||||
reason=reason,
|
||||
duration=duration_mins,
|
||||
event_type=SLEEP_EVENTTYPE,
|
||||
pump_event_id = "%s,%s" % (start.eventId, stop.eventId)
|
||||
)
|
||||
elif start:
|
||||
reason = None
|
||||
if start.sleepstartedbygui == eventtypes.LidAaUserModeChange.SleepstartedbyguiEnum.TrueVal:
|
||||
reason = "Sleep (Manual)"
|
||||
elif start.activesleepscheduleRaw:
|
||||
reason = "Sleep (Scheduled)"
|
||||
|
||||
duration_mins = (time_end - start.eventTimestamp).seconds / 60
|
||||
return NightscoutEntry.activity(
|
||||
created_at=start.eventTimestamp,
|
||||
reason=reason + " - " + NOT_ENDED if reason else NOT_ENDED,
|
||||
duration=duration_mins,
|
||||
event_type=SLEEP_EVENTTYPE,
|
||||
pump_event_id = "%s" % start.eventId
|
||||
)
|
||||
|
||||
|
||||
def exercise_to_nsentry(self, start, stop=None, time_end=None):
|
||||
if start and stop:
|
||||
reason = "Exercise"
|
||||
if start.exercisechoice == eventtypes.LidAaUserModeChange.ExercisechoiceEnum.Timed:
|
||||
reason = "Exercise (Timed)"
|
||||
|
||||
if stop.exercisestoppedbytimer == eventtypes.LidAaUserModeChange.ExercisestoppedbytimerEnum.TrueVal:
|
||||
reason += " (Stopped by timer)"
|
||||
|
||||
duration_mins = (stop.eventTimestamp - start.eventTimestamp).seconds / 60
|
||||
return NightscoutEntry.activity(
|
||||
created_at=start.eventTimestamp,
|
||||
reason=reason,
|
||||
duration=duration_mins,
|
||||
event_type=EXERCISE_EVENTTYPE,
|
||||
pump_event_id = "%s,%s" % (start.eventId, stop.eventId)
|
||||
)
|
||||
elif start:
|
||||
reason = "Exercise"
|
||||
if start.exercisechoice == eventtypes.LidAaUserModeChange.ExercisechoiceEnum.Timed:
|
||||
reason = "Exercise (Timed)"
|
||||
|
||||
duration_mins = (time_end.eventTimestamp - start.eventTimestamp).seconds / 60
|
||||
return NightscoutEntry.activity(
|
||||
created_at=start.eventTimestamp,
|
||||
reason=reason + " - " + NOT_ENDED,
|
||||
duration=duration_mins,
|
||||
event_type=EXERCISE_EVENTTYPE,
|
||||
pump_event_id = "%s,%s" % (start.eventId, stop.eventId)
|
||||
)
|
||||
|
||||
def process_unended_sleep_stop(self, event, sleep_last_upload):
|
||||
logger.info("ProcessUserMode: Deleting old sleep event treatment before pushing update (delete treatments/%s)" % sleep_last_upload["_id"])
|
||||
if self.pretend:
|
||||
logger.info("ProcessUserMode: Skipping delete in pretend mode")
|
||||
else:
|
||||
self.nightscout.delete_entry('treatments/%s' % sleep_last_upload["_id"])
|
||||
|
||||
duration_mins = (event.eventTimestamp - arrow.get(sleep_last_upload["created_at"])).seconds / 60
|
||||
return NightscoutEntry.activity(
|
||||
created_at=sleep_last_upload["created_at"],
|
||||
reason=sleep_last_upload["reason"].replace(" - %s" % NOT_ENDED, ""),
|
||||
duration=duration_mins,
|
||||
event_type=SLEEP_EVENTTYPE,
|
||||
pump_event_id="%s,%s" % (sleep_last_upload.get("pump_event_id",""), event.eventId)
|
||||
)
|
||||
|
||||
def process_unended_exercise_stop(self, event, exercise_last_upload):
|
||||
logger.info("ProcessUserMode: Deleting old exercise event treatment before pushing update (delete treatments/%s)" % exercise_last_upload["_id"])
|
||||
if self.pretend:
|
||||
logger.info("ProcessUserMode: Skipping delete in pretend mode")
|
||||
else:
|
||||
self.nightscout.delete_entry('treatments/%s' % exercise_last_upload["_id"])
|
||||
|
||||
reason = exercise_last_upload["reason"].replace(" - %s" % NOT_ENDED, "")
|
||||
if event.exercisestoppedbytimer == eventtypes.LidAaUserModeChange.ExercisestoppedbytimerEnum.TrueVal:
|
||||
reason += " (Stopped by timer)"
|
||||
|
||||
duration_mins = (event.eventTimestamp - arrow.get(exercise_last_upload["created_at"])).seconds / 60
|
||||
return NightscoutEntry.activity(
|
||||
created_at=exercise_last_upload["created_at"],
|
||||
reason=reason,
|
||||
duration=duration_mins,
|
||||
event_type=EXERCISE_EVENTTYPE,
|
||||
pump_event_id="%s,%s" % (exercise_last_upload.get("pump_event_id",""), event.eventId)
|
||||
)
|
||||
Reference in New Issue
Block a user