mirror of
https://github.com/bckelley/tconnectsync.git
synced 2026-08-24 03:34:12 -05:00
Type-check the full tconnectsync package, not just process_alarm
Remove the single-file opt-in in setup.cfg ([mypy] files) so mypy checks the entire tconnectsync package, and fix everything that surfaced. Config: - setup.cfg: mypy `files` now points at the whole `tconnectsync` package. Event processors (were typed as BaseEvent, which lacks the per-event fields like seqNum and the event-specific attributes): - Type each *_to_nsentry / helper with the concrete event type(s) it handles (or a Union of them), matching the existing process_alarm.py pattern. - process_basal_suspension, process_basal_resume, process_cgm_start_join_stop: filter out None before appending to ns_entries, matching process_basal / process_cgm_alert. Previously a None from a non-matching event would have been appended and passed to upload_entry(). - Add explicit `return None` fall-throughs where a helper annotated to return a value could implicitly return None. - process_cgm_reading: give each sensor's GlucosevaluestatusEnum its own local variable so the enum types don't clash. process.py: - Add an EventProcessor Protocol and annotate event_classes with it, so the instantiated handlers are typed instead of `object`. - Rename the updater-loop variable so it no longer collides with the processor-loop variable's type. Other: - api/tandemsource.py: narrow the JWKS key to RSAPublicKey before jwt.decode, and use arrow.get() (not time.time()) when forcing token expiry so the attribute type stays consistent. - api/common.py: split_days_range returns Arrow tuples, not str tuples. - parser/nightscout.py: sort profile segments by the typed startTime rather than an untyped dict value (same resulting order). - domain/tandemsource/pump_settings.py: PumpSettings inherits DataClassJsonMixin so from_dict is visible to the type checker. - domain/tandemsource/event_class.py: ignore the set/Enum __hash__ clash. - nightscout.py: explicit return None when a ConnectionError is swallowed. No runtime behavior change; full test suite (461 passed, 1 skipped) still green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaQXTakxAKEfeeys5cv3en
This commit is contained in:
@@ -50,6 +50,6 @@ console_scripts =
|
|||||||
|
|
||||||
[mypy]
|
[mypy]
|
||||||
files =
|
files =
|
||||||
tconnectsync/sync/tandemsource/process_alarm.py
|
tconnectsync
|
||||||
follow_imports = silent
|
follow_imports = silent
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ def days_between(start, end) -> int:
|
|||||||
return diff.days
|
return diff.days
|
||||||
|
|
||||||
# both inclusive
|
# both inclusive
|
||||||
def split_days_range(start_a, end_a, days: int = 5) -> List[Tuple[str, str]]:
|
def split_days_range(start_a, end_a, days: int = 5) -> List[Tuple[arrow.Arrow, arrow.Arrow]]:
|
||||||
ranges = []
|
ranges = []
|
||||||
start = arrow.get(start_a)
|
start = arrow.get(start_a)
|
||||||
end = arrow.get(end_a)
|
end = arrow.get(end_a)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from requests_oidc.plugins import OSCachedPlugin
|
|||||||
from requests_oidc.utils import ServerDetails
|
from requests_oidc.utils import ServerDetails
|
||||||
from requests_oauthlib import OAuth2Session
|
from requests_oauthlib import OAuth2Session
|
||||||
from jwt.algorithms import RSAAlgorithm
|
from jwt.algorithms import RSAAlgorithm
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
||||||
|
|
||||||
|
|
||||||
from ..util import timeago, cap_length
|
from ..util import timeago, cap_length
|
||||||
@@ -391,6 +392,10 @@ class TandemSourceApi:
|
|||||||
key = public_keys.get(kid)
|
key = public_keys.get(kid)
|
||||||
if not key:
|
if not key:
|
||||||
raise ApiException(0, 'Public key not found for JWT: %s' % kid)
|
raise ApiException(0, 'Public key not found for JWT: %s' % kid)
|
||||||
|
# A JWKS endpoint publishes public keys; from_jwk() is typed as possibly
|
||||||
|
# returning a private key, so narrow it before passing to jwt.decode().
|
||||||
|
if not isinstance(key, RSAPublicKey):
|
||||||
|
raise ApiException(0, 'JWK is not an RSA public key for JWT: %s' % kid)
|
||||||
|
|
||||||
audience = self.TDC_OIDC_CLIENT_ID
|
audience = self.TDC_OIDC_CLIENT_ID
|
||||||
issuer = self.TDC_OIDC_ISSUER
|
issuer = self.TDC_OIDC_ISSUER
|
||||||
@@ -575,7 +580,7 @@ class TandemSourceApi:
|
|||||||
# Trigger automatic re-login, and try again once
|
# Trigger automatic re-login, and try again once
|
||||||
if e.status_code == 401:
|
if e.status_code == 401:
|
||||||
logger.info("Performing automatic re-login after HTTP 401 for TandemSourceApi")
|
logger.info("Performing automatic re-login after HTTP 401 for TandemSourceApi")
|
||||||
self.accessTokenExpiresAt = time.time()
|
self.accessTokenExpiresAt = arrow.get()
|
||||||
self.login(self._email, self._password)
|
self.login(self._email, self._password)
|
||||||
|
|
||||||
return self.get(endpoint, query, tries=tries+1)
|
return self.get(endpoint, query, tries=tries+1)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from enum import Enum
|
|||||||
|
|
||||||
from ...eventparser import events
|
from ...eventparser import events
|
||||||
|
|
||||||
class EventClass(set, Enum):
|
class EventClass(set, Enum): # type: ignore[misc] # set/Enum both define __hash__; the combination works at runtime
|
||||||
# LidBasalDelivery = every 5min entry
|
# LidBasalDelivery = every 5min entry
|
||||||
# LidBasalRateChange = only when basal rate changes
|
# LidBasalRateChange = only when basal rate changes
|
||||||
BASAL = {events.LidBasalDelivery} # , LidBasalRateChange
|
BASAL = {events.LidBasalDelivery} # , LidBasalRateChange
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from dataclasses_json import dataclass_json
|
from dataclasses_json import dataclass_json, DataClassJsonMixin
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
# These dataclasses model the `settings.details` blob from the Tandem Source
|
# These dataclasses model the `settings.details` blob from the Tandem Source
|
||||||
@@ -52,6 +52,6 @@ class PumpCgmSettings:
|
|||||||
|
|
||||||
@dataclass_json
|
@dataclass_json
|
||||||
@dataclass
|
@dataclass
|
||||||
class PumpSettings:
|
class PumpSettings(DataClassJsonMixin):
|
||||||
profiles: PumpProfiles
|
profiles: PumpProfiles
|
||||||
cgmSettings: PumpCgmSettings
|
cgmSettings: PumpCgmSettings
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ class NightscoutApi:
|
|||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as e:
|
||||||
if self.ignore_conn_errors:
|
if self.ignore_conn_errors:
|
||||||
logger.warn('Ignoring ConnectionError because ignore_conn_errors=true', e)
|
logger.warn('Ignoring ConnectionError because ignore_conn_errors=true', e)
|
||||||
|
return None
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@@ -106,6 +107,7 @@ class NightscoutApi:
|
|||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as e:
|
||||||
if self.ignore_conn_errors:
|
if self.ignore_conn_errors:
|
||||||
logger.warn('Ignoring ConnectionError because ignore_conn_errors=true', e)
|
logger.warn('Ignoring ConnectionError because ignore_conn_errors=true', e)
|
||||||
|
return None
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@@ -125,6 +127,7 @@ class NightscoutApi:
|
|||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as e:
|
||||||
if self.ignore_conn_errors:
|
if self.ignore_conn_errors:
|
||||||
logger.warn('Ignoring ConnectionError because ignore_conn_errors=true', e)
|
logger.warn('Ignoring ConnectionError because ignore_conn_errors=true', e)
|
||||||
|
return None
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@@ -144,6 +147,7 @@ class NightscoutApi:
|
|||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as e:
|
||||||
if self.ignore_conn_errors:
|
if self.ignore_conn_errors:
|
||||||
logger.warn('Ignoring ConnectionError because ignore_conn_errors=true', e)
|
logger.warn('Ignoring ConnectionError because ignore_conn_errors=true', e)
|
||||||
|
return None
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|||||||
@@ -215,32 +215,40 @@ class NightscoutEntry:
|
|||||||
return {
|
return {
|
||||||
# insulin duration in hours; Nightscout JS bug requires all top-level fields to be strings
|
# insulin duration in hours; Nightscout JS bug requires all top-level fields to be strings
|
||||||
"dia": "%s" % (profile.insulinDuration / 60),
|
"dia": "%s" % (profile.insulinDuration / 60),
|
||||||
"carbratio": list(sorted([
|
# Sort by the typed segment.startTime (monotonic with timeAsSeconds)
|
||||||
|
# so the sort key is a well-typed int rather than an untyped dict value.
|
||||||
|
"carbratio": [
|
||||||
{
|
{
|
||||||
"time": minutes_to_ns_time(segment.startTime),
|
"time": minutes_to_ns_time(segment.startTime),
|
||||||
"timeAsSeconds": segment.startTime * 60,
|
"timeAsSeconds": segment.startTime * 60,
|
||||||
"value": segment.carbRatio / 1000 # milliunits->units
|
"value": segment.carbRatio / 1000 # milliunits->units
|
||||||
} for segment in profile.tDependentSegs if not segment.skip
|
} for segment in sorted(
|
||||||
], key=lambda x: x["timeAsSeconds"])),
|
(s for s in profile.tDependentSegs if not s.skip),
|
||||||
|
key=lambda s: s.startTime)
|
||||||
|
],
|
||||||
|
|
||||||
"carbs_hr": NIGHTSCOUT_PROFILE_CARBS_HR_VALUE,
|
"carbs_hr": NIGHTSCOUT_PROFILE_CARBS_HR_VALUE,
|
||||||
"delay": NIGHTSCOUT_PROFILE_DELAY_VALUE,
|
"delay": NIGHTSCOUT_PROFILE_DELAY_VALUE,
|
||||||
|
|
||||||
"sens": list(sorted([ # Correction factor / isf
|
"sens": [ # Correction factor / isf
|
||||||
{
|
{
|
||||||
"time": minutes_to_ns_time(segment.startTime),
|
"time": minutes_to_ns_time(segment.startTime),
|
||||||
"timeAsSeconds": segment.startTime * 60,
|
"timeAsSeconds": segment.startTime * 60,
|
||||||
"value": segment.isf
|
"value": segment.isf
|
||||||
} for segment in profile.tDependentSegs if not segment.skip
|
} for segment in sorted(
|
||||||
], key=lambda x: x["timeAsSeconds"])),
|
(s for s in profile.tDependentSegs if not s.skip),
|
||||||
|
key=lambda s: s.startTime)
|
||||||
|
],
|
||||||
|
|
||||||
"basal": list(sorted([
|
"basal": [
|
||||||
{
|
{
|
||||||
"time": minutes_to_ns_time(segment.startTime),
|
"time": minutes_to_ns_time(segment.startTime),
|
||||||
"timeAsSeconds": segment.startTime * 60,
|
"timeAsSeconds": segment.startTime * 60,
|
||||||
"value": segment.basalRate / 1000 # milliunits->units
|
"value": segment.basalRate / 1000 # milliunits->units
|
||||||
} for segment in profile.tDependentSegs
|
} for segment in sorted(
|
||||||
], key=lambda x: x["timeAsSeconds"])),
|
profile.tDependentSegs,
|
||||||
|
key=lambda s: s.startTime)
|
||||||
|
],
|
||||||
|
|
||||||
"target_low": [
|
"target_low": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,12 +3,19 @@ import collections
|
|||||||
import arrow
|
import arrow
|
||||||
|
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import List, Optional, Tuple, TYPE_CHECKING
|
from typing import Dict, Iterable, List, Optional, Protocol, Tuple, Type, TYPE_CHECKING
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...api import TConnectApi
|
from ...api import TConnectApi
|
||||||
from ...nightscout import NightscoutApi
|
from ...nightscout import NightscoutApi
|
||||||
from ...api.tandemsource import BffPump
|
from ...api.tandemsource import BffPump
|
||||||
|
|
||||||
|
class EventProcessor(Protocol):
|
||||||
|
"""Structural interface implemented by every Process* event handler."""
|
||||||
|
def __init__(self, tconnect: "TConnectApi", nightscout: "NightscoutApi", tconnect_device_id: str, pretend: bool, features: List[str]) -> None: ...
|
||||||
|
def enabled(self) -> bool: ...
|
||||||
|
def process(self, events: Iterable, time_start: arrow.Arrow, time_end: arrow.Arrow) -> List[dict]: ...
|
||||||
|
def write(self, ns_entries: List[dict]) -> int: ...
|
||||||
|
|
||||||
from ...features import DEVICE_STATUS, DEFAULT_FEATURES
|
from ...features import DEVICE_STATUS, DEFAULT_FEATURES
|
||||||
from ...eventparser import events as eventtypes
|
from ...eventparser import events as eventtypes
|
||||||
from ...domain.tandemsource.event_class import EventClass
|
from ...domain.tandemsource.event_class import EventClass
|
||||||
@@ -37,7 +44,7 @@ class ProcessTimeRange:
|
|||||||
self.secret = secret
|
self.secret = secret
|
||||||
self.features = features
|
self.features = features
|
||||||
|
|
||||||
event_classes = {
|
event_classes: Dict[str, Type[EventProcessor]] = {
|
||||||
EventClass.BASAL.name: ProcessBasal,
|
EventClass.BASAL.name: ProcessBasal,
|
||||||
EventClass.BASAL_SUSPENSION.name: ProcessBasalSuspension,
|
EventClass.BASAL_SUSPENSION.name: ProcessBasalSuspension,
|
||||||
EventClass.BASAL_RESUME.name: ProcessBasalResume,
|
EventClass.BASAL_RESUME.name: ProcessBasalResume,
|
||||||
@@ -93,7 +100,10 @@ class ProcessTimeRange:
|
|||||||
# Ensure time_end is timezone-aware for comparison
|
# Ensure time_end is timezone-aware for comparison
|
||||||
time_end_aware = arrow.get(time_end)
|
time_end_aware = arrow.get(time_end)
|
||||||
capped_time_end = min(events_last_time, time_end_aware) if events_last_time else time_end_aware
|
capped_time_end = min(events_last_time, time_end_aware) if events_last_time else time_end_aware
|
||||||
ns_entries = c.process(events, events_first_time, capped_time_end)
|
# events_first_time is populated whenever for_eventclass has entries
|
||||||
|
# (i.e. at least one event was seen); fall back to time_start otherwise.
|
||||||
|
time_start_for_events = events_first_time if events_first_time else time_start
|
||||||
|
ns_entries = c.process(events, time_start_for_events, capped_time_end)
|
||||||
w = c.write(ns_entries)
|
w = c.write(ns_entries)
|
||||||
if w:
|
if w:
|
||||||
processed_count += w
|
processed_count += w
|
||||||
@@ -101,10 +111,10 @@ class ProcessTimeRange:
|
|||||||
logger.info("Skipping %s, is not enabled from features %s" % (clazz, self.features))
|
logger.info("Skipping %s, is not enabled from features %s" % (clazz, self.features))
|
||||||
|
|
||||||
for updater_class in self.updater_classes:
|
for updater_class in self.updater_classes:
|
||||||
c = updater_class(self.tconnect, self.nightscout, self.tconnect_device_id, self.pretend, self.features)
|
updater = updater_class(self.tconnect, self.nightscout, self.tconnect_device_id, self.pretend, self.features)
|
||||||
if c.enabled():
|
if updater.enabled():
|
||||||
logger.info("%s is enabled from features %s" % (updater_class.__name__, self.features))
|
logger.info("%s is enabled from features %s" % (updater_class.__name__, self.features))
|
||||||
done = c.update(self.pretend)
|
done = updater.update(self.pretend)
|
||||||
logger.info("%s completed with update required: %s" % (updater_class.__name__, done))
|
logger.info("%s completed with update required: %s" % (updater_class.__name__, done))
|
||||||
else:
|
else:
|
||||||
logger.info("Skipping %s, is not enabled from features %s" % (updater_class.__name__, self.features))
|
logger.info("Skipping %s, is not enabled from features %s" % (updater_class.__name__, self.features))
|
||||||
|
|||||||
@@ -15,14 +15,15 @@ from ...parser.nightscout import (
|
|||||||
NightscoutEntry
|
NightscoutEntry
|
||||||
)
|
)
|
||||||
|
|
||||||
from typing import Iterable, List, Optional, TYPE_CHECKING
|
from typing import Iterable, List, Optional, Union, TYPE_CHECKING
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...api import TConnectApi
|
from ...api import TConnectApi
|
||||||
from ...nightscout import NightscoutApi
|
from ...nightscout import NightscoutApi
|
||||||
from ...eventparser.raw_event import BaseEvent
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
BasalEvent = Union[eventtypes.LidBasalRateChange, eventtypes.LidBasalDelivery]
|
||||||
|
|
||||||
class ProcessBasal:
|
class ProcessBasal:
|
||||||
def __init__(self, tconnect: "TConnectApi", nightscout: "NightscoutApi", tconnect_device_id: str, pretend: bool, features: List[str] = DEFAULT_FEATURES) -> None:
|
def __init__(self, tconnect: "TConnectApi", nightscout: "NightscoutApi", tconnect_device_id: str, pretend: bool, features: List[str] = DEFAULT_FEATURES) -> None:
|
||||||
self.tconnect = tconnect
|
self.tconnect = tconnect
|
||||||
@@ -81,7 +82,7 @@ class ProcessBasal:
|
|||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
def basal_to_nsentry(self, start: arrow.Arrow, duration: datetime.timedelta, event: "BaseEvent") -> Optional[dict]:
|
def basal_to_nsentry(self, start: arrow.Arrow, duration: datetime.timedelta, event: BasalEvent) -> Optional[dict]:
|
||||||
if type(event) == eventtypes.LidBasalRateChange:
|
if type(event) == eventtypes.LidBasalRateChange:
|
||||||
value = insulin_float_round(event.commandedBasalRate)
|
value = insulin_float_round(event.commandedBasalRate)
|
||||||
if IGNORE_ZERO_UNIT_BASAL and value < 0.01:
|
if IGNORE_ZERO_UNIT_BASAL and value < 0.01:
|
||||||
@@ -106,3 +107,5 @@ class ProcessBasal:
|
|||||||
reason = ', '.join(bitmask_to_list(event.commandedRateSource)),
|
reason = ', '.join(bitmask_to_list(event.commandedRateSource)),
|
||||||
pump_event_id = "%s" % event.seqNum
|
pump_event_id = "%s" % event.seqNum
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from typing import Iterable, List, Optional, TYPE_CHECKING
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...api import TConnectApi
|
from ...api import TConnectApi
|
||||||
from ...nightscout import NightscoutApi
|
from ...nightscout import NightscoutApi
|
||||||
from ...eventparser.raw_event import BaseEvent
|
|
||||||
|
|
||||||
from ...features import DEFAULT_FEATURES
|
from ...features import DEFAULT_FEATURES
|
||||||
from ... import features
|
from ... import features
|
||||||
@@ -46,7 +45,9 @@ class ProcessBasalResume:
|
|||||||
logger.info("Skipping BasalResume event not after last upload time: %s (time range: %s - %s)" % (event, time_start, time_end))
|
logger.info("Skipping BasalResume event not after last upload time: %s (time range: %s - %s)" % (event, time_start, time_end))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ns_entries.append(self.resume_to_nsentry(event))
|
ns = self.resume_to_nsentry(event)
|
||||||
|
if ns:
|
||||||
|
ns_entries.append(ns)
|
||||||
|
|
||||||
|
|
||||||
return ns_entries
|
return ns_entries
|
||||||
@@ -64,9 +65,11 @@ class ProcessBasalResume:
|
|||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
def resume_to_nsentry(self, event: "BaseEvent") -> Optional[dict]:
|
def resume_to_nsentry(self, event: eventtypes.LidPumpingResumed) -> Optional[dict]:
|
||||||
if type(event) == eventtypes.LidPumpingResumed:
|
if type(event) == eventtypes.LidPumpingResumed:
|
||||||
return NightscoutEntry.basalresume(
|
return NightscoutEntry.basalresume(
|
||||||
created_at = event.eventTimestamp.format(),
|
created_at = event.eventTimestamp.format(),
|
||||||
pump_event_id = "%s" % event.seqNum
|
pump_event_id = "%s" % event.seqNum
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from typing import Iterable, List, Optional, TYPE_CHECKING
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...api import TConnectApi
|
from ...api import TConnectApi
|
||||||
from ...nightscout import NightscoutApi
|
from ...nightscout import NightscoutApi
|
||||||
from ...eventparser.raw_event import BaseEvent
|
|
||||||
|
|
||||||
from ...features import DEFAULT_FEATURES
|
from ...features import DEFAULT_FEATURES
|
||||||
from ... import features
|
from ... import features
|
||||||
@@ -46,7 +45,9 @@ class ProcessBasalSuspension:
|
|||||||
logger.info("Skipping basalsuspension event not after last upload time: %s (time range: %s - %s)" % (event, time_start, time_end))
|
logger.info("Skipping basalsuspension event not after last upload time: %s (time range: %s - %s)" % (event, time_start, time_end))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ns_entries.append(self.suspension_to_nsentry(event))
|
ns = self.suspension_to_nsentry(event)
|
||||||
|
if ns:
|
||||||
|
ns_entries.append(ns)
|
||||||
|
|
||||||
|
|
||||||
return ns_entries
|
return ns_entries
|
||||||
@@ -64,10 +65,12 @@ class ProcessBasalSuspension:
|
|||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
def suspension_to_nsentry(self, event: "BaseEvent") -> Optional[dict]:
|
def suspension_to_nsentry(self, event: eventtypes.LidPumpingSuspended) -> Optional[dict]:
|
||||||
if type(event) == eventtypes.LidPumpingSuspended:
|
if type(event) == eventtypes.LidPumpingSuspended:
|
||||||
return NightscoutEntry.basalsuspension(
|
return NightscoutEntry.basalsuspension(
|
||||||
created_at = event.eventTimestamp.format(),
|
created_at = event.eventTimestamp.format(),
|
||||||
reason = ', '.join(bitmask_to_list(event.suspendReason)),
|
reason = ', '.join(bitmask_to_list(event.suspendReason)),
|
||||||
pump_event_id = "%s" % event.seqNum
|
pump_event_id = "%s" % event.seqNum
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ from typing import Iterable, List, Optional, TYPE_CHECKING
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...api import TConnectApi
|
from ...api import TConnectApi
|
||||||
from ...nightscout import NightscoutApi
|
from ...nightscout import NightscoutApi
|
||||||
from ...eventparser.raw_event import BaseEvent
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -41,7 +40,7 @@ class ProcessBolus:
|
|||||||
logger.info("Last Nightscout bolus upload: %s" % last_upload_time)
|
logger.info("Last Nightscout bolus upload: %s" % last_upload_time)
|
||||||
|
|
||||||
# Correlate a bolus's request/completion messages by bolusid.
|
# Correlate a bolus's request/completion messages by bolusid.
|
||||||
bolusEventsForId = {}
|
bolusEventsForId: dict = {}
|
||||||
for event in sorted(events, key=lambda x: x.eventTimestamp):
|
for event in sorted(events, key=lambda x: x.eventTimestamp):
|
||||||
bolusEventsForId.setdefault(event.bolusId, {})[type(event)] = event
|
bolusEventsForId.setdefault(event.bolusId, {})[type(event)] = event
|
||||||
|
|
||||||
@@ -90,7 +89,7 @@ class ProcessBolus:
|
|||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
def bolus_to_nsentry(self, bolusCompleted: "BaseEvent", bolusRequested1: "BaseEvent", bolusRequested2: "BaseEvent", bolusRequested3: "BaseEvent") -> Optional[dict]:
|
def bolus_to_nsentry(self, bolusCompleted: eventtypes.LidBolusCompleted, bolusRequested1: Optional[eventtypes.LidBolusRequestedMsg1], bolusRequested2: Optional[eventtypes.LidBolusRequestedMsg2], bolusRequested3: Optional[eventtypes.LidBolusRequestedMsg3]) -> dict:
|
||||||
suffixes = []
|
suffixes = []
|
||||||
if bolusRequested2 and bolusRequested2.userOverride == eventtypes.LidBolusRequestedMsg2.UseroverrideEnum.Yes:
|
if bolusRequested2 and bolusRequested2.userOverride == eventtypes.LidBolusRequestedMsg2.UseroverrideEnum.Yes:
|
||||||
suffixes.append('(Override)')
|
suffixes.append('(Override)')
|
||||||
@@ -119,7 +118,7 @@ class ProcessBolus:
|
|||||||
pump_event_id = ",".join(seq_nums)
|
pump_event_id = ",".join(seq_nums)
|
||||||
)
|
)
|
||||||
|
|
||||||
def bolex_to_nsentry(self, bolexCompleted: "BaseEvent") -> Optional[dict]:
|
def bolex_to_nsentry(self, bolexCompleted: eventtypes.LidBolexCompleted) -> dict:
|
||||||
# The extended portion of a combo bolus, added as its own treatment at
|
# The extended portion of a combo bolus, added as its own treatment at
|
||||||
# the time it finished delivering. Insulin only; carbs/bg belong to the
|
# the time it finished delivering. Insulin only; carbs/bg belong to the
|
||||||
# initial LidBolusCompleted entry and must not be double-counted here.
|
# initial LidBolusCompleted entry and must not be double-counted here.
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ from ...parser.nightscout import (
|
|||||||
NightscoutEntry
|
NightscoutEntry
|
||||||
)
|
)
|
||||||
|
|
||||||
from typing import Iterable, List, Optional, TYPE_CHECKING
|
from typing import Iterable, List, TYPE_CHECKING
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...api import TConnectApi
|
from ...api import TConnectApi
|
||||||
from ...nightscout import NightscoutApi
|
from ...nightscout import NightscoutApi
|
||||||
from ...eventparser.raw_event import BaseEvent
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -84,7 +83,7 @@ class ProcessCartridge:
|
|||||||
|
|
||||||
return count
|
return count
|
||||||
|
|
||||||
def cart_to_nsentry(self, cartFilled: "BaseEvent") -> Optional[dict]:
|
def cart_to_nsentry(self, cartFilled: eventtypes.LidCartridgeFilled) -> dict:
|
||||||
# insulinVolume is populated on t:slim X2 / Mobi; v2Volume is a legacy fallback.
|
# insulinVolume is populated on t:slim X2 / Mobi; v2Volume is a legacy fallback.
|
||||||
volume = cartFilled.insulinVolume or cartFilled.v2Volume
|
volume = cartFilled.insulinVolume or cartFilled.v2Volume
|
||||||
return NightscoutEntry.sitechange(
|
return NightscoutEntry.sitechange(
|
||||||
@@ -93,7 +92,7 @@ class ProcessCartridge:
|
|||||||
pump_event_id = "%s" % cartFilled.seqNum
|
pump_event_id = "%s" % cartFilled.seqNum
|
||||||
)
|
)
|
||||||
|
|
||||||
def cannula_to_nsentry(self, cannulaFilled: "BaseEvent") -> Optional[dict]:
|
def cannula_to_nsentry(self, cannulaFilled: eventtypes.LidCannulaFilled) -> dict:
|
||||||
# primeSize is fractional (e.g. 0.3u); format with one decimal, not %d.
|
# primeSize is fractional (e.g. 0.3u); format with one decimal, not %d.
|
||||||
primed = cannulaFilled.primeSize if cannulaFilled.primeSize and cannulaFilled.primeSize > 0 else None
|
primed = cannulaFilled.primeSize if cannulaFilled.primeSize and cannulaFilled.primeSize > 0 else None
|
||||||
return NightscoutEntry.sitechange(
|
return NightscoutEntry.sitechange(
|
||||||
@@ -102,7 +101,7 @@ class ProcessCartridge:
|
|||||||
pump_event_id = "%s" % cannulaFilled.seqNum
|
pump_event_id = "%s" % cannulaFilled.seqNum
|
||||||
)
|
)
|
||||||
|
|
||||||
def tubing_to_nsentry(self, tubingFilled: "BaseEvent") -> Optional[dict]:
|
def tubing_to_nsentry(self, tubingFilled: eventtypes.LidTubingFilled) -> dict:
|
||||||
# primeSize is -1 (sentinel, "not recorded") on real tubing fills; only show a real prime volume.
|
# primeSize is -1 (sentinel, "not recorded") on real tubing fills; only show a real prime volume.
|
||||||
primed = tubingFilled.primeSize if tubingFilled.primeSize and tubingFilled.primeSize > 0 else None
|
primed = tubingFilled.primeSize if tubingFilled.primeSize and tubingFilled.primeSize > 0 else None
|
||||||
return NightscoutEntry.sitechange(
|
return NightscoutEntry.sitechange(
|
||||||
|
|||||||
@@ -12,14 +12,20 @@ from ...parser.nightscout import (
|
|||||||
NightscoutEntry
|
NightscoutEntry
|
||||||
)
|
)
|
||||||
|
|
||||||
from typing import Iterable, List, Optional, TYPE_CHECKING
|
from typing import Iterable, List, Optional, Union, TYPE_CHECKING
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...api import TConnectApi
|
from ...api import TConnectApi
|
||||||
from ...nightscout import NightscoutApi
|
from ...nightscout import NightscoutApi
|
||||||
from ...eventparser.raw_event import BaseEvent
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# The three CGM alert event types all expose dalertId / dalertIdRaw / seqNum.
|
||||||
|
CgmAlertEvent = Union[
|
||||||
|
eventtypes.LidCgmAlertActivated,
|
||||||
|
eventtypes.LidCgmAlertActivatedDex,
|
||||||
|
eventtypes.LidCgmAlertActivatedFsl2,
|
||||||
|
]
|
||||||
|
|
||||||
class ProcessCGMAlert:
|
class ProcessCGMAlert:
|
||||||
def __init__(self, tconnect: "TConnectApi", nightscout: "NightscoutApi", tconnect_device_id: str, pretend: bool, features: List[str] = DEFAULT_FEATURES) -> None:
|
def __init__(self, tconnect: "TConnectApi", nightscout: "NightscoutApi", tconnect_device_id: str, pretend: bool, features: List[str] = DEFAULT_FEATURES) -> None:
|
||||||
self.tconnect = tconnect
|
self.tconnect = tconnect
|
||||||
@@ -70,7 +76,7 @@ class ProcessCGMAlert:
|
|||||||
|
|
||||||
return count
|
return count
|
||||||
|
|
||||||
def alert_to_nsentry(self, alert: "BaseEvent") -> Optional[dict]:
|
def alert_to_nsentry(self, alert: CgmAlertEvent) -> Optional[dict]:
|
||||||
# FSL3 alert codes are defined in eventparser/static_dicts.py:CGM_ALERTS_DICT
|
# FSL3 alert codes are defined in eventparser/static_dicts.py:CGM_ALERTS_DICT
|
||||||
# Alert code meanings are documented in comments there.
|
# Alert code meanings are documented in comments there.
|
||||||
if not alert.dalertId:
|
if not alert.dalertId:
|
||||||
@@ -98,3 +104,5 @@ class ProcessCGMAlert:
|
|||||||
reason = ("Libre CGM Alert (%s)" % alert.dalertId.name) if alert.dalertId else "Libre CGM Alert (Unknown)",
|
reason = ("Libre CGM Alert (%s)" % alert.dalertId.name) if alert.dalertId else "Libre CGM Alert (Unknown)",
|
||||||
pump_event_id = "%s" % alert.seqNum
|
pump_event_id = "%s" % alert.seqNum
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from typing import Iterable, List, Optional, Union, TYPE_CHECKING
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...api import TConnectApi
|
from ...api import TConnectApi
|
||||||
from ...nightscout import NightscoutApi
|
from ...nightscout import NightscoutApi
|
||||||
from ...eventparser.raw_event import BaseEvent
|
|
||||||
|
|
||||||
# The four CGM-reading event types share the glucoseValueStatus /
|
# The four CGM-reading event types share the glucoseValueStatus /
|
||||||
# currentGlucoseDisplayValue fields determine_glucose_value() reads.
|
# currentGlucoseDisplayValue fields determine_glucose_value() reads.
|
||||||
@@ -52,22 +51,22 @@ def determine_glucose_value(event: CgmReadingEvent) -> int:
|
|||||||
status = event.glucoseValueStatus
|
status = event.glucoseValueStatus
|
||||||
|
|
||||||
if isinstance(event, eventtypes.LidCgmDataG7):
|
if isinstance(event, eventtypes.LidCgmDataG7):
|
||||||
e = eventtypes.LidCgmDataG7.GlucosevaluestatusEnum
|
g7 = eventtypes.LidCgmDataG7.GlucosevaluestatusEnum
|
||||||
return _resolve_glucose_value(display_value, status,
|
return _resolve_glucose_value(display_value, status,
|
||||||
precise=e.PreciseValue, high=e.SpecialHigh, low=e.SpecialLow)
|
precise=g7.PreciseValue, high=g7.SpecialHigh, low=g7.SpecialLow)
|
||||||
if isinstance(event, eventtypes.LidCgmDataGxb):
|
if isinstance(event, eventtypes.LidCgmDataGxb):
|
||||||
e = eventtypes.LidCgmDataGxb.GlucosevaluestatusEnum
|
gxb = eventtypes.LidCgmDataGxb.GlucosevaluestatusEnum
|
||||||
return _resolve_glucose_value(display_value, status,
|
return _resolve_glucose_value(display_value, status,
|
||||||
precise=e.CurrentglucosedisplayvalueContainsTheGlucoseReading,
|
precise=gxb.CurrentglucosedisplayvalueContainsTheGlucoseReading,
|
||||||
high=e.TheGlucoseReadingIsHigh, low=e.TheGlucoseReadingIsLow)
|
high=gxb.TheGlucoseReadingIsHigh, low=gxb.TheGlucoseReadingIsLow)
|
||||||
if isinstance(event, eventtypes.LidCgmDataFsl3):
|
if isinstance(event, eventtypes.LidCgmDataFsl3):
|
||||||
e = eventtypes.LidCgmDataFsl3.GlucosevaluestatusEnum
|
fsl3 = eventtypes.LidCgmDataFsl3.GlucosevaluestatusEnum
|
||||||
return _resolve_glucose_value(display_value, status,
|
return _resolve_glucose_value(display_value, status,
|
||||||
precise=e.PreciseValue, high=e.SpecialHigh, low=e.SpecialLow)
|
precise=fsl3.PreciseValue, high=fsl3.SpecialHigh, low=fsl3.SpecialLow)
|
||||||
if isinstance(event, eventtypes.LidCgmDataFsl2):
|
if isinstance(event, eventtypes.LidCgmDataFsl2):
|
||||||
e = eventtypes.LidCgmDataFsl2.GlucosevaluestatusEnum
|
fsl2 = eventtypes.LidCgmDataFsl2.GlucosevaluestatusEnum
|
||||||
return _resolve_glucose_value(display_value, status,
|
return _resolve_glucose_value(display_value, status,
|
||||||
precise=e.PreciseValue, high=e.SpecialHigh, low=e.SpecialLow)
|
precise=fsl2.PreciseValue, high=fsl2.SpecialHigh, low=fsl2.SpecialLow)
|
||||||
|
|
||||||
return display_value
|
return display_value
|
||||||
|
|
||||||
@@ -120,12 +119,12 @@ class ProcessCGMReading:
|
|||||||
|
|
||||||
return count
|
return count
|
||||||
|
|
||||||
def timestamp_for(self, event: "BaseEvent") -> arrow.Arrow:
|
def timestamp_for(self, event: CgmReadingEvent) -> arrow.Arrow:
|
||||||
# For backfills the time the event was added to the pump's event store
|
# 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
|
# might not be the time it actually occurred, so we use the egvTimestamp
|
||||||
return arrow.get(TANDEM_EPOCH + event.egvTimeStamp, tzinfo='UTC').replace(tzinfo=self.timezone)
|
return arrow.get(TANDEM_EPOCH + event.egvTimeStamp, tzinfo='UTC').replace(tzinfo=self.timezone)
|
||||||
|
|
||||||
def to_nsentry(self, event: "BaseEvent") -> Optional[dict]:
|
def to_nsentry(self, event: CgmReadingEvent) -> dict:
|
||||||
return NightscoutEntry.entry(
|
return NightscoutEntry.entry(
|
||||||
sgv = determine_glucose_value(event),
|
sgv = determine_glucose_value(event),
|
||||||
created_at = self.timestamp_for(event).format(),
|
created_at = self.timestamp_for(event).format(),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import arrow
|
|||||||
|
|
||||||
from ...features import DEFAULT_FEATURES
|
from ...features import DEFAULT_FEATURES
|
||||||
from ... import features
|
from ... import features
|
||||||
|
from ...eventparser import events as eventtypes
|
||||||
from ...domain.tandemsource.event_class import EventClass
|
from ...domain.tandemsource.event_class import EventClass
|
||||||
from ...nightscout import format_datetime
|
from ...nightscout import format_datetime
|
||||||
from ...parser.nightscout import (
|
from ...parser.nightscout import (
|
||||||
@@ -12,14 +13,28 @@ from ...parser.nightscout import (
|
|||||||
NightscoutEntry
|
NightscoutEntry
|
||||||
)
|
)
|
||||||
|
|
||||||
from typing import Iterable, List, Optional, TYPE_CHECKING
|
from typing import Iterable, List, Optional, Union, TYPE_CHECKING
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...api import TConnectApi
|
from ...api import TConnectApi
|
||||||
from ...nightscout import NightscoutApi
|
from ...nightscout import NightscoutApi
|
||||||
from ...eventparser.raw_event import BaseEvent
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# The CGM session start/join/stop event types (see EventClass._CGM_START /
|
||||||
|
# _CGM_JOIN / _CGM_STOP); all expose seqNum and eventTimestamp.
|
||||||
|
CgmSessionEvent = Union[
|
||||||
|
eventtypes.LidCgmStartSessionGx,
|
||||||
|
eventtypes.LidCgmStartSessionFsl2,
|
||||||
|
eventtypes.LidCgmJoinSessionGx,
|
||||||
|
eventtypes.LidCgmJoinSessionG7,
|
||||||
|
eventtypes.LidCgmJoinSessionFsl2,
|
||||||
|
eventtypes.LidCgmJoinSessionFsl3,
|
||||||
|
eventtypes.LidCgmStopSessionGx,
|
||||||
|
eventtypes.LidCgmStopSessionG7,
|
||||||
|
eventtypes.LidCgmStopSessionFsl2,
|
||||||
|
eventtypes.LidCgmStopSessionFsl3,
|
||||||
|
]
|
||||||
|
|
||||||
class ProcessCGMStartJoinStop:
|
class ProcessCGMStartJoinStop:
|
||||||
def __init__(self, tconnect: "TConnectApi", nightscout: "NightscoutApi", tconnect_device_id: str, pretend: bool, features: List[str] = DEFAULT_FEATURES) -> None:
|
def __init__(self, tconnect: "TConnectApi", nightscout: "NightscoutApi", tconnect_device_id: str, pretend: bool, features: List[str] = DEFAULT_FEATURES) -> None:
|
||||||
self.tconnect = tconnect
|
self.tconnect = tconnect
|
||||||
@@ -63,7 +78,9 @@ class ProcessCGMStartJoinStop:
|
|||||||
|
|
||||||
ns_entries = []
|
ns_entries = []
|
||||||
for event in allEvents:
|
for event in allEvents:
|
||||||
ns_entries.append(self.to_nsentry(event))
|
ns = self.to_nsentry(event)
|
||||||
|
if ns:
|
||||||
|
ns_entries.append(ns)
|
||||||
|
|
||||||
return ns_entries
|
return ns_entries
|
||||||
|
|
||||||
@@ -80,7 +97,7 @@ class ProcessCGMStartJoinStop:
|
|||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
def to_nsentry(self, event: "BaseEvent") -> Optional[dict]:
|
def to_nsentry(self, event: CgmSessionEvent) -> Optional[dict]:
|
||||||
if type(event) in EventClass._CGM_START:
|
if type(event) in EventClass._CGM_START:
|
||||||
return NightscoutEntry.cgm_start(
|
return NightscoutEntry.cgm_start(
|
||||||
created_at = format_datetime(event.eventTimestamp),
|
created_at = format_datetime(event.eventTimestamp),
|
||||||
@@ -99,3 +116,5 @@ class ProcessCGMStartJoinStop:
|
|||||||
reason = "CGM Session Stopped",
|
reason = "CGM Session Stopped",
|
||||||
pump_event_id = "%s" % event.seqNum
|
pump_event_id = "%s" % event.seqNum
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ from typing import Iterable, List, Optional, TYPE_CHECKING
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...api import TConnectApi
|
from ...api import TConnectApi
|
||||||
from ...nightscout import NightscoutApi
|
from ...nightscout import NightscoutApi
|
||||||
from ...eventparser.raw_event import BaseEvent
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -63,7 +62,7 @@ class ProcessDeviceStatus:
|
|||||||
return []
|
return []
|
||||||
return [entry]
|
return [entry]
|
||||||
|
|
||||||
def daily_basal_to_nsentry(self, event: "BaseEvent") -> Optional[dict]:
|
def daily_basal_to_nsentry(self, event: eventtypes.LidDailyBasal) -> Optional[dict]:
|
||||||
# NOTE: the pump-logs endpoint does not emit event 81 (LID_DAILY_BASAL)
|
# NOTE: the pump-logs endpoint does not emit event 81 (LID_DAILY_BASAL)
|
||||||
# for either t:slim X2 or Mobi (verified against live accounts), and no
|
# for either t:slim X2 or Mobi (verified against live accounts), and no
|
||||||
# other returned event carries battery data. DEVICE_STATUS therefore
|
# other returned event carries battery data. DEVICE_STATUS therefore
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from typing import Iterable, List, Optional, TYPE_CHECKING
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...api import TConnectApi
|
from ...api import TConnectApi
|
||||||
from ...nightscout import NightscoutApi
|
from ...nightscout import NightscoutApi
|
||||||
from ...eventparser.raw_event import BaseEvent
|
|
||||||
|
|
||||||
from ...features import DEFAULT_FEATURES
|
from ...features import DEFAULT_FEATURES
|
||||||
from ... import features
|
from ... import features
|
||||||
@@ -90,7 +89,7 @@ class ProcessUserMode:
|
|||||||
processed_sleep.append((start_sleep, event))
|
processed_sleep.append((start_sleep, event))
|
||||||
start_sleep = None
|
start_sleep = None
|
||||||
else:
|
else:
|
||||||
if sleep_not_ended:
|
if sleep_not_ended and sleep_last_upload:
|
||||||
logger.info("ProcessUserMode: Found StopSleep without StartSleep, with incomplete sleep event in nightscout: %s NS: %s" % (event, sleep_last_upload))
|
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))
|
ns_entries.append(self.process_unended_sleep_stop(event, sleep_last_upload))
|
||||||
else:
|
else:
|
||||||
@@ -102,7 +101,7 @@ class ProcessUserMode:
|
|||||||
processed_exercise.append((start_exercise, event))
|
processed_exercise.append((start_exercise, event))
|
||||||
start_exercise = None
|
start_exercise = None
|
||||||
else:
|
else:
|
||||||
if exercise_not_ended:
|
if exercise_not_ended and exercise_last_upload:
|
||||||
logger.info("ProcessUserMode: Found StopExercise without StartExercise, with incomplete exercise event in nightscout: %s NS: %s" % (event, exercise_last_upload))
|
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))
|
ns_entries.append(self.process_unended_exercise_stop(event, exercise_last_upload))
|
||||||
else:
|
else:
|
||||||
@@ -118,10 +117,14 @@ class ProcessUserMode:
|
|||||||
logger.info("ProcessUserMode: exercise is active")
|
logger.info("ProcessUserMode: exercise is active")
|
||||||
|
|
||||||
for items in processed_sleep:
|
for items in processed_sleep:
|
||||||
ns_entries.append(self.sleep_to_nsentry(start=items[0], stop=items[1], time_end=time_end))
|
ns = self.sleep_to_nsentry(start=items[0], stop=items[1], time_end=time_end)
|
||||||
|
if ns:
|
||||||
|
ns_entries.append(ns)
|
||||||
|
|
||||||
for items in processed_exercise:
|
for items in processed_exercise:
|
||||||
ns_entries.append(self.exercise_to_nsentry(start=items[0], stop=items[1], time_end=time_end))
|
ns = self.exercise_to_nsentry(start=items[0], stop=items[1], time_end=time_end)
|
||||||
|
if ns:
|
||||||
|
ns_entries.append(ns)
|
||||||
|
|
||||||
return ns_entries
|
return ns_entries
|
||||||
|
|
||||||
@@ -137,19 +140,19 @@ class ProcessUserMode:
|
|||||||
|
|
||||||
return count
|
return count
|
||||||
|
|
||||||
def is_start_sleep(self, event: "BaseEvent") -> bool:
|
def is_start_sleep(self, event: eventtypes.LidAaUserModeChange) -> bool:
|
||||||
return event.requestedAction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StartSleep
|
return event.requestedAction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StartSleep
|
||||||
def is_stop_sleep(self, event: "BaseEvent") -> bool:
|
def is_stop_sleep(self, event: eventtypes.LidAaUserModeChange) -> bool:
|
||||||
return event.requestedAction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StopSleep or \
|
return event.requestedAction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StopSleep or \
|
||||||
event.requestedAction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StopAll
|
event.requestedAction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StopAll
|
||||||
def is_start_exercise(self, event: "BaseEvent") -> bool:
|
def is_start_exercise(self, event: eventtypes.LidAaUserModeChange) -> bool:
|
||||||
return event.requestedAction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StartExercise
|
return event.requestedAction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StartExercise
|
||||||
def is_stop_exercise(self, event: "BaseEvent") -> bool:
|
def is_stop_exercise(self, event: eventtypes.LidAaUserModeChange) -> bool:
|
||||||
return event.requestedAction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StopExercise or \
|
return event.requestedAction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StopExercise or \
|
||||||
event.requestedAction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StopAll
|
event.requestedAction == eventtypes.LidAaUserModeChange.RequestedactionEnum.StopAll
|
||||||
|
|
||||||
|
|
||||||
def sleep_to_nsentry(self, start: "BaseEvent", stop: Optional["BaseEvent"] = None, time_end: Optional[arrow.Arrow] = None) -> Optional[dict]:
|
def sleep_to_nsentry(self, start: eventtypes.LidAaUserModeChange, stop: Optional[eventtypes.LidAaUserModeChange] = None, time_end: Optional[arrow.Arrow] = None) -> Optional[dict]:
|
||||||
if start and stop:
|
if start and stop:
|
||||||
reason = None
|
reason = None
|
||||||
if start.sleepStartedByGui == eventtypes.LidAaUserModeChange.SleepstartedbyguiEnum.TrueVal:
|
if start.sleepStartedByGui == eventtypes.LidAaUserModeChange.SleepstartedbyguiEnum.TrueVal:
|
||||||
@@ -181,8 +184,10 @@ class ProcessUserMode:
|
|||||||
pump_event_id = "%s" % start.seqNum
|
pump_event_id = "%s" % start.seqNum
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def exercise_to_nsentry(self, start: "BaseEvent", stop: Optional["BaseEvent"] = None, time_end: Optional[arrow.Arrow] = None) -> Optional[dict]:
|
|
||||||
|
def exercise_to_nsentry(self, start: eventtypes.LidAaUserModeChange, stop: Optional[eventtypes.LidAaUserModeChange] = None, time_end: Optional[arrow.Arrow] = None) -> Optional[dict]:
|
||||||
if start and stop:
|
if start and stop:
|
||||||
reason = "Exercise"
|
reason = "Exercise"
|
||||||
if start.exerciseChoice == eventtypes.LidAaUserModeChange.ExercisechoiceEnum.Timed:
|
if start.exerciseChoice == eventtypes.LidAaUserModeChange.ExercisechoiceEnum.Timed:
|
||||||
@@ -213,7 +218,9 @@ class ProcessUserMode:
|
|||||||
pump_event_id = "%s" % start.seqNum
|
pump_event_id = "%s" % start.seqNum
|
||||||
)
|
)
|
||||||
|
|
||||||
def process_unended_sleep_stop(self, event: "BaseEvent", sleep_last_upload: dict) -> dict:
|
return None
|
||||||
|
|
||||||
|
def process_unended_sleep_stop(self, event: eventtypes.LidAaUserModeChange, sleep_last_upload: dict) -> dict:
|
||||||
logger.info("ProcessUserMode: Deleting old sleep event treatment before pushing update (delete treatments/%s)" % sleep_last_upload["_id"])
|
logger.info("ProcessUserMode: Deleting old sleep event treatment before pushing update (delete treatments/%s)" % sleep_last_upload["_id"])
|
||||||
if self.pretend:
|
if self.pretend:
|
||||||
logger.info("ProcessUserMode: Skipping delete in pretend mode")
|
logger.info("ProcessUserMode: Skipping delete in pretend mode")
|
||||||
@@ -229,7 +236,7 @@ class ProcessUserMode:
|
|||||||
pump_event_id="%s,%s" % (sleep_last_upload.get("pump_event_id",""), event.seqNum)
|
pump_event_id="%s,%s" % (sleep_last_upload.get("pump_event_id",""), event.seqNum)
|
||||||
)
|
)
|
||||||
|
|
||||||
def process_unended_exercise_stop(self, event: "BaseEvent", exercise_last_upload: dict) -> dict:
|
def process_unended_exercise_stop(self, event: eventtypes.LidAaUserModeChange, exercise_last_upload: dict) -> dict:
|
||||||
logger.info("ProcessUserMode: Deleting old exercise event treatment before pushing update (delete treatments/%s)" % exercise_last_upload["_id"])
|
logger.info("ProcessUserMode: Deleting old exercise event treatment before pushing update (delete treatments/%s)" % exercise_last_upload["_id"])
|
||||||
if self.pretend:
|
if self.pretend:
|
||||||
logger.info("ProcessUserMode: Skipping delete in pretend mode")
|
logger.info("ProcessUserMode: Skipping delete in pretend mode")
|
||||||
|
|||||||
Reference in New Issue
Block a user