mirror of
https://github.com/jwoglom/tconnectsync.git
synced 2026-08-24 10:14:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a028cdf3fe | ||
|
|
093b42ff94 | ||
|
|
00023c6aba | ||
|
|
1ed3d35965 | ||
|
|
9493b6546f | ||
|
|
24a635ca21 | ||
|
|
80824906d9 | ||
|
|
e5259a7981 | ||
|
|
650fb60988 | ||
|
|
2b367d45f3 | ||
|
|
dd2dcd17bd | ||
|
|
98b7edc904 | ||
|
|
e82d311fd6 | ||
|
|
9a2a4ca771 | ||
|
|
54ba2ed7df | ||
|
|
2892eb8ace | ||
|
|
1d432982ad | ||
|
|
891c2e2c10 | ||
|
|
7dc479c84b | ||
|
|
0417fb9fd8 | ||
|
|
b80d52662b | ||
|
|
51ac469f00 | ||
|
|
81b13f236a | ||
|
|
d57b3872f1 | ||
|
|
0609a46ed2 | ||
|
|
e63d725c2b | ||
|
|
fec964e767 | ||
|
|
366182f65b | ||
|
|
d9d09dddb3 | ||
|
|
04643255fe | ||
|
|
c2f7070f3b | ||
|
|
9c35f850b9 | ||
|
|
a7383b0796 | ||
|
|
4f52927d2f | ||
|
|
5b938c010d | ||
|
|
3e50c7018d | ||
|
|
32f3639eb0 | ||
|
|
9a7cb1bdfd | ||
|
|
ead7ab883b | ||
|
|
ff395ffa3e | ||
|
|
8f2ad586fd | ||
|
|
828cccaf10 | ||
|
|
ae26102df2 | ||
|
|
152c263fe0 | ||
|
|
38c933a257 | ||
|
|
d507e47e7a | ||
|
|
8c2f41c2d5 | ||
|
|
018c6d2228 | ||
|
|
32ba49dc8a | ||
|
|
a5cbc5d612 | ||
|
|
ef515021cc | ||
|
|
4af54bf7cb |
+2
-2
@@ -5,9 +5,9 @@ coverage:
|
||||
project:
|
||||
default: false
|
||||
tconnectsync:
|
||||
paths:
|
||||
paths:
|
||||
- "tconnectsync/"
|
||||
target: '75%'
|
||||
target: '60%'
|
||||
threshold: '5%'
|
||||
tests:
|
||||
paths:
|
||||
|
||||
@@ -28,6 +28,6 @@ jobs:
|
||||
--outdir dist/
|
||||
.
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@master
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
@@ -18,57 +18,60 @@ jobs:
|
||||
python-version: ['3.8', '3.9', '3.10', '3.11']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install flake8 pytest pipenv
|
||||
pipenv install --system
|
||||
- name: Run pipenv check
|
||||
run: |
|
||||
# DDoS attacks in wheel and setuptools packages, not relevant
|
||||
# root certificate store, not relevant
|
||||
pipenv check \
|
||||
--ignore 51499 \
|
||||
--ignore 52495 \
|
||||
--ignore 52365 \
|
||||
--ignore 59956 \
|
||||
--ignore 58755 \
|
||||
--ignore 67895 \
|
||||
--ignore 61893 \
|
||||
--ignore 61601 \
|
||||
--ignore 62044 \
|
||||
--ignore 67599 \
|
||||
--ignore 72083 \
|
||||
--ignore 71064 \
|
||||
--ignore 71608 \
|
||||
--ignore 72236
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pytest
|
||||
- name: Check codecov configuration
|
||||
run: |
|
||||
curl -X POST --data-binary @.codecov.yml https://codecov.io/validate
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install flake8 pytest pipenv
|
||||
pipenv install --system
|
||||
- name: Run pipenv check
|
||||
run: |
|
||||
# DDoS attacks in wheel and setuptools packages, not relevant
|
||||
# root certificate store, not relevant
|
||||
pipenv check \
|
||||
--ignore 51499 \
|
||||
--ignore 52495 \
|
||||
--ignore 52365 \
|
||||
--ignore 59956 \
|
||||
--ignore 58755 \
|
||||
--ignore 67895 \
|
||||
--ignore 61893 \
|
||||
--ignore 61601 \
|
||||
--ignore 62044 \
|
||||
--ignore 67599 \
|
||||
--ignore 72083 \
|
||||
--ignore 71064 \
|
||||
--ignore 71608 \
|
||||
--ignore 72236
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
- name: Run tconnectsync --help
|
||||
run: |
|
||||
tconnectsync --help
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pytest
|
||||
- name: Check codecov configuration
|
||||
run: |
|
||||
curl -X POST --data-binary @.codecov.yml https://codecov.io/validate
|
||||
|
||||
if [[ "$(curl -s -o /dev/null -w "%{http_code}" -X POST --data-binary @.codecov.yml https://codecov.io/validate)" != "200" ]]; then
|
||||
echo Error parsing codecov file
|
||||
exit 1
|
||||
fi
|
||||
- name: Generate Coverage Report
|
||||
run: |
|
||||
pip install coverage
|
||||
coverage run -m unittest
|
||||
- name: Upload Coverage to Codecov
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
fail_ci_if_error: false
|
||||
if [[ "$(curl -s -o /dev/null -w "%{http_code}" -X POST --data-binary @.codecov.yml https://codecov.io/validate)" != "200" ]]; then
|
||||
echo Error parsing codecov file
|
||||
exit 1
|
||||
fi
|
||||
- name: Generate Coverage Report
|
||||
run: |
|
||||
pip install coverage
|
||||
coverage run -m unittest
|
||||
- name: Upload Coverage to Codecov
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
fail_ci_if_error: false
|
||||
|
||||
@@ -17,12 +17,14 @@ pysocks = "*"
|
||||
urllib3 = "==1.26.6"
|
||||
requests = {extras = ["socks"], version = "==2.31.0"}
|
||||
requests-oidc = "*"
|
||||
pyjwt = "*"
|
||||
PyJWT = "==2.8.0"
|
||||
cryptography = "*"
|
||||
dataclasses-json = "*"
|
||||
cffi = ">=1.15.1"
|
||||
typing-extensions = "*"
|
||||
|
||||
[scripts]
|
||||
tconnectsync = "python3 main.py"
|
||||
test = "python3 -m unittest discover -vv"
|
||||
build_events = "bash -c 'cd tconnectsync/eventparser && python3 build_events.py > events.py'"
|
||||
lint = "bash -c 'flake8 . --count --select=E9,F63,F7,F82 && flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 && echo PASS'"
|
||||
Generated
+6
-5
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "74ba9aa4c7c93f3533b986f868b63bea54995dbd8d37bde4745bc9023de66dc7"
|
||||
"sha256": "1d28c5104c37e390b88704d1453eec15e5c6df241ca85f82a88973f8aee2d1d1"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@@ -471,12 +471,12 @@
|
||||
},
|
||||
"pyjwt": {
|
||||
"hashes": [
|
||||
"sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850",
|
||||
"sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"
|
||||
"sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de",
|
||||
"sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==2.9.0"
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.8.0"
|
||||
},
|
||||
"pypng": {
|
||||
"hashes": [
|
||||
@@ -590,6 +590,7 @@
|
||||
"sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d",
|
||||
"sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==4.12.2"
|
||||
},
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 50%
|
||||
threshold: null
|
||||
@@ -1,9 +1,9 @@
|
||||
[metadata]
|
||||
name = tconnectsync
|
||||
version = 2.0.2
|
||||
version = 2.1.8
|
||||
author = James Woglom
|
||||
author_email = j@wogloms.net
|
||||
description = Syncs Tandem t:connect pump data to Nightscout for the t:slim X2
|
||||
description = Syncs Tandem Source (formerly t:connect) insulin pump data to Nightscout for the t:slim X2
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://github.com/jwoglom/tconnectsync
|
||||
@@ -25,6 +25,16 @@ install_requires =
|
||||
arrow
|
||||
lxml
|
||||
python-dotenv
|
||||
requests-mock
|
||||
pysocks
|
||||
urllib3
|
||||
requests
|
||||
requests-oidc
|
||||
PyJWT
|
||||
cryptography
|
||||
dataclasses-json
|
||||
cffi
|
||||
typing-extensions
|
||||
|
||||
[options.packages.find]
|
||||
where = .
|
||||
|
||||
@@ -4,6 +4,12 @@ import arrow
|
||||
import argparse
|
||||
import logging
|
||||
import pkg_resources
|
||||
import typing
|
||||
|
||||
# Required for cryptography lib in python 3.7
|
||||
if sys.version_info < (3, 8):
|
||||
import typing_extensions
|
||||
typing.Protocol = typing_extensions.Protocol
|
||||
|
||||
from .api import TConnectApi
|
||||
from .process import process_time_range
|
||||
@@ -114,6 +120,8 @@ def main(*args, **kwargs):
|
||||
sys.exit(u.process(tconnect, nightscout, time_start, time_end, args.pretend, features=args.features))
|
||||
else:
|
||||
tconnectDevice = TandemSourceChooseDevice(secret, tconnect).choose()
|
||||
added = TandemSourceProcessTimeRange(tconnect, nightscout, tconnectDevice, pretend=args.pretend, features=args.features).process(time_start, time_end)
|
||||
sys.exit(added)
|
||||
added, last_event_id = TandemSourceProcessTimeRange(tconnect, nightscout, tconnectDevice, pretend=args.pretend, features=args.features).process(time_start, time_end)
|
||||
|
||||
# return exit code 0 if processed events
|
||||
sys.exit(0 if added>0 else 1)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ from jwt.algorithms import RSAAlgorithm
|
||||
from ..util import timeago, cap_length
|
||||
from .common import parse_ymd_date, base_headers, base_session, ApiException, ApiLoginException
|
||||
from ..secret import CACHE_CREDENTIALS, CACHE_CREDENTIALS_PATH
|
||||
from ..eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -357,22 +358,42 @@ class TandemSourceApi:
|
||||
def pump_event_metadata(self):
|
||||
return self.get('api/reports/reportsfacade/%s/pumpeventmetadata' % (self.pumperId), {})
|
||||
|
||||
DEFAULT_EVENT_IDS = [229,5,28,4,26,99,279,3,16,59,21,55,20,280,64,65,66,61,33,371,171,369,460,172,370,461,372,399,256,213,406,394,212,404,214,405,447,313,60,14,6,90,230,140,12,11,53,13,63,203,307,191]
|
||||
|
||||
"""
|
||||
Returns raw unparsed string for pump events
|
||||
tconnect_device_id is "tconnectDeviceId" from pump_event_metadata()
|
||||
"""
|
||||
def pump_events_raw(self, tconnect_device_id, min_date=None, max_date=None):
|
||||
def pump_events_raw(self, tconnect_device_id, min_date=None, max_date=None, event_ids_filter=DEFAULT_EVENT_IDS):
|
||||
minDate = parse_ymd_date(min_date)
|
||||
maxDate = parse_ymd_date(max_date)
|
||||
logger.debug(f'pump_events_raw({minDate}, {maxDate})')
|
||||
logger.debug(f'pump_events_raw({tconnect_device_id}, {minDate}, {maxDate})')
|
||||
|
||||
# 229,5,28,4,26,99,279,3,16,59,21,55,20,280,64,65,66,61,33,371,171,369,460,172,370,461,372,399,256,213,406,394,212,404,214,405,447,313,60,14,6,90,230,140,12,11,53,13,63,203,307,191
|
||||
eventIdsFilter = '229%2C5%2C28%2C4%2C26%2C99%2C279%2C3%2C16%2C59%2C21%2C55%2C20%2C280%2C64%2C65%2C66%2C61%2C33%2C371%2C171%2C369%2C460%2C172%2C370%2C461%2C372%2C399%2C256%2C213%2C406%2C394%2C212%2C404%2C214%2C405%2C447%2C313%2C60%2C14%2C6%2C90%2C230%2C140%2C12%2C11%2C53%2C13%2C63%2C203%2C307%2C191'
|
||||
return self.get('api/reports/reportsfacade/pumpevents/%s/%s?minDate=%s&maxDate=%s&eventIds=%s' % (
|
||||
# default: 229,5,28,4,26,99,279,3,16,59,21,55,20,280,64,65,66,61,33,371,171,369,460,172,370,461,372,399,256,213,406,394,212,404,214,405,447,313,60,14,6,90,230,140,12,11,53,13,63,203,307,191
|
||||
eventIdsFilter = '%2C'.join(map(str, event_ids_filter)) if event_ids_filter else None
|
||||
return self.get('api/reports/reportsfacade/pumpevents/%s/%s?minDate=%s&maxDate=%s%s' % (
|
||||
self.pumperId,
|
||||
tconnect_device_id,
|
||||
minDate,
|
||||
maxDate,
|
||||
eventIdsFilter
|
||||
'&eventIds=%s' % eventIdsFilter if eventIdsFilter else ''
|
||||
), {})
|
||||
|
||||
"""
|
||||
Fetch and decode pump events using eventparser.
|
||||
Default of fetch_all_events=False will filter to the same eventids used in the Tandem Source backend.
|
||||
If fetch_all_events=True, then all event types from the history log will be returned.
|
||||
"""
|
||||
def pump_events(self, tconnect_device_id, min_date=None, max_date=None, fetch_all_event_types=False):
|
||||
pump_events_raw = self.pump_events_raw(
|
||||
tconnect_device_id,
|
||||
min_date,
|
||||
max_date,
|
||||
event_ids_filter=None if fetch_all_event_types else self.DEFAULT_EVENT_IDS
|
||||
)
|
||||
|
||||
pump_events_decoded = decode_raw_events(pump_events_raw)
|
||||
logger.info(f"Read {len(pump_events_decoded)} bytes (est. {len(pump_events_decoded)/EVENT_LEN} events)")
|
||||
return Events(pump_events_decoded)
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ class PumpProfile:
|
||||
carbEntry: int # 1 / 0
|
||||
maxBolus: int # milliunits
|
||||
|
||||
def __post_init__(self):
|
||||
self.tDependentSegs = [i for i in self.tDependentSegs if not i.skip]
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class PumpProfiles:
|
||||
|
||||
@@ -3,18 +3,12 @@ import struct
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, IntFlag
|
||||
from .raw_event import RawEvent
|
||||
from .raw_event import RawEvent, BaseEvent
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
EVENT_LEN = 26
|
||||
|
||||
@dataclass
|
||||
class BaseEvent:
|
||||
@staticmethod
|
||||
def build(raw):
|
||||
raise NotImplemented
|
||||
|
||||
'''
|
||||
|
||||
TYPE_TO_STRUCT = {
|
||||
@@ -67,9 +61,13 @@ class {name}(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
'''
|
||||
|
||||
def firstLower(text):
|
||||
@@ -85,13 +83,13 @@ def eventNameFormat(text):
|
||||
def fieldNameFormat(text):
|
||||
if not text or all([i.isupper() for i in text]):
|
||||
return text
|
||||
return firstLower(text.replace('_', ' ').title().replace(' ', ''))
|
||||
return firstLower(text.replace('_', ' ').title().replace(' ', '')).replace('raw', 'Raw')
|
||||
|
||||
|
||||
def build_fields(event_def):
|
||||
ret = []
|
||||
for name, field in event_def["data"].items():
|
||||
suffix = 'Raw' if "transform" in field else ''
|
||||
suffix = 'Raw' if "transform" in field and name[-3:] != 'Raw' else ''
|
||||
f = f'{fieldNameFormat(name)}{suffix}: {TYPE_TO_PYOBJ[field["type"]]}'
|
||||
if "uom" in field:
|
||||
f += ' # ' + field['uom']
|
||||
@@ -107,7 +105,7 @@ def build_decode(event_def):
|
||||
p1 = f'{fieldNameFormat(name)}, = {unpack_command_for(field)}'
|
||||
p1s.append(p1)
|
||||
|
||||
suffix = 'Raw' if "transform" in field else ''
|
||||
suffix = 'Raw' if "transform" in field and name[-3:] != 'Raw' else ''
|
||||
|
||||
p2 = f'{fieldNameFormat(name)}{suffix} = {fieldNameFormat(name)},'
|
||||
p2s.append(p2)
|
||||
@@ -160,13 +158,21 @@ def build_events_map(events):
|
||||
|
||||
if __name__ == '__main__':
|
||||
import json
|
||||
merged_events = {}
|
||||
|
||||
output = f'{header}'
|
||||
with open("events.json", "r") as f:
|
||||
j = json.loads(f.read())
|
||||
for event_id, event_def in j["events"].items():
|
||||
output += build_event(event_id, event_def)
|
||||
merged_events.update(j["events"])
|
||||
|
||||
with open("custom_events.json", "r") as f:
|
||||
j = json.loads(f.read())
|
||||
merged_events.update(j["events"])
|
||||
|
||||
for event_id, event_def in merged_events.items():
|
||||
output += build_event(event_id, event_def)
|
||||
|
||||
output += build_events_map(merged_events)
|
||||
|
||||
output += build_events_map(j["events"])
|
||||
|
||||
print(output)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"events": {
|
||||
"81": {
|
||||
"name": "LID_DAILY_BASAL",
|
||||
"data": {
|
||||
"dailyTotalBasal": {
|
||||
"type": "float32",
|
||||
"offset": 0,
|
||||
"uom": "units"
|
||||
},
|
||||
"lastBasalRate": {
|
||||
"type": "float32",
|
||||
"offset": 4,
|
||||
"uom": "units/hour"
|
||||
},
|
||||
"iob": {
|
||||
"type": "float32",
|
||||
"offset": 8,
|
||||
"uom": "units"
|
||||
},
|
||||
"batteryChargePercentMSBRaw": {
|
||||
"type": "uint8",
|
||||
"offset": 12
|
||||
},
|
||||
"batteryChargePercentLSBRaw": {
|
||||
"type": "uint8",
|
||||
"offset": 13,
|
||||
"transform": [["battery_charge_percent", ""]]
|
||||
},
|
||||
"batteryLipoMilliVolts": {
|
||||
"type": "uint16",
|
||||
"offset": 14
|
||||
}
|
||||
}
|
||||
},
|
||||
"48": {
|
||||
"name": "LID_CARBS_ENTERED",
|
||||
"data": {
|
||||
"carbs": {
|
||||
"type": "float32",
|
||||
"offset": 0,
|
||||
"uom": "carbs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"36": {
|
||||
"name": "LID_USB_CONNECTED",
|
||||
"data": {
|
||||
"negotiatedCurrent": {
|
||||
"type": "float32",
|
||||
"offset": 0,
|
||||
"uom": "mA"
|
||||
}
|
||||
}
|
||||
},
|
||||
"37": {
|
||||
"name": "LID_USB_DISCONNECTED",
|
||||
"data": {
|
||||
"negotiatedCurrent": {
|
||||
"type": "float32",
|
||||
"offset": 0,
|
||||
"uom": "mA"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,12 @@ import struct
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, IntFlag
|
||||
from .raw_event import RawEvent
|
||||
from .raw_event import RawEvent, BaseEvent
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
EVENT_LEN = 26
|
||||
|
||||
@dataclass
|
||||
class BaseEvent:
|
||||
@staticmethod
|
||||
def build(raw):
|
||||
raise NotImplemented
|
||||
|
||||
UINT8 = '>B'
|
||||
INT8 = '>b'
|
||||
UINT16 = '>H'
|
||||
@@ -87,9 +81,13 @@ class LidBasalRateChange(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidAlertActivated(BaseEvent):
|
||||
@@ -263,9 +261,13 @@ class LidAlertActivated(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidAlarmActivated(BaseEvent):
|
||||
@@ -441,9 +443,13 @@ class LidAlarmActivated(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidMalfunctionActivated(BaseEvent):
|
||||
@@ -478,9 +484,13 @@ class LidMalfunctionActivated(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidPumpingSuspended(BaseEvent):
|
||||
@@ -536,9 +546,13 @@ class LidPumpingSuspended(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidPumpingResumed(BaseEvent):
|
||||
@@ -567,9 +581,13 @@ class LidPumpingResumed(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidTimeChanged(BaseEvent):
|
||||
@@ -580,20 +598,20 @@ class LidTimeChanged(BaseEvent):
|
||||
raw: RawEvent
|
||||
timeprior: int # ms
|
||||
timeafter: int # ms
|
||||
rawrtctime: int # ms
|
||||
Rawrtctime: int # ms
|
||||
|
||||
|
||||
@staticmethod
|
||||
def build(raw):
|
||||
timeprior, = struct.unpack_from(UINT32, raw[:EVENT_LEN], 10)
|
||||
timeafter, = struct.unpack_from(UINT32, raw[:EVENT_LEN], 14)
|
||||
rawrtctime, = struct.unpack_from(UINT32, raw[:EVENT_LEN], 18)
|
||||
Rawrtctime, = struct.unpack_from(UINT32, raw[:EVENT_LEN], 18)
|
||||
|
||||
return LidTimeChanged(
|
||||
raw = RawEvent.build(raw),
|
||||
timeprior = timeprior,
|
||||
timeafter = timeafter,
|
||||
rawrtctime = rawrtctime,
|
||||
Rawrtctime = Rawrtctime,
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -601,9 +619,13 @@ class LidTimeChanged(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidDateChanged(BaseEvent):
|
||||
@@ -614,20 +636,20 @@ class LidDateChanged(BaseEvent):
|
||||
raw: RawEvent
|
||||
dateprior: int # day
|
||||
dateafter: int # day
|
||||
rawrtctime: int # ms
|
||||
Rawrtctime: int # ms
|
||||
|
||||
|
||||
@staticmethod
|
||||
def build(raw):
|
||||
dateprior, = struct.unpack_from(UINT32, raw[:EVENT_LEN], 10)
|
||||
dateafter, = struct.unpack_from(UINT32, raw[:EVENT_LEN], 14)
|
||||
rawrtctime, = struct.unpack_from(UINT32, raw[:EVENT_LEN], 18)
|
||||
Rawrtctime, = struct.unpack_from(UINT32, raw[:EVENT_LEN], 18)
|
||||
|
||||
return LidDateChanged(
|
||||
raw = RawEvent.build(raw),
|
||||
dateprior = dateprior,
|
||||
dateafter = dateafter,
|
||||
rawrtctime = rawrtctime,
|
||||
Rawrtctime = Rawrtctime,
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -635,9 +657,13 @@ class LidDateChanged(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidBgReadingTaken(BaseEvent):
|
||||
@@ -755,9 +781,13 @@ class LidBgReadingTaken(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidBolusCompleted(BaseEvent):
|
||||
@@ -823,9 +853,13 @@ class LidBolusCompleted(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidBolexCompleted(BaseEvent):
|
||||
@@ -891,9 +925,13 @@ class LidBolexCompleted(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidAlertCleared(BaseEvent):
|
||||
@@ -1061,9 +1099,13 @@ class LidAlertCleared(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidAlarmCleared(BaseEvent):
|
||||
@@ -1230,9 +1272,13 @@ class LidAlarmCleared(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCartridgeFilled(BaseEvent):
|
||||
@@ -1261,9 +1307,13 @@ class LidCartridgeFilled(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidShelfMode(BaseEvent):
|
||||
@@ -1304,9 +1354,13 @@ class LidShelfMode(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidBolusActivated(BaseEvent):
|
||||
@@ -1358,9 +1412,13 @@ class LidBolusActivated(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidBolexActivated(BaseEvent):
|
||||
@@ -1412,9 +1470,13 @@ class LidBolexActivated(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidDataLogCorruption(BaseEvent):
|
||||
@@ -1443,9 +1505,13 @@ class LidDataLogCorruption(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCannulaFilled(BaseEvent):
|
||||
@@ -1495,9 +1561,13 @@ class LidCannulaFilled(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidTubingFilled(BaseEvent):
|
||||
@@ -1550,9 +1620,13 @@ class LidTubingFilled(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidBolusRequestedMsg1(BaseEvent):
|
||||
@@ -1639,9 +1713,13 @@ class LidBolusRequestedMsg1(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidBolusRequestedMsg2(BaseEvent):
|
||||
@@ -1774,9 +1852,13 @@ class LidBolusRequestedMsg2(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidBolusRequestedMsg3(BaseEvent):
|
||||
@@ -1811,9 +1893,13 @@ class LidBolusRequestedMsg3(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidNewDay(BaseEvent):
|
||||
@@ -1845,9 +1931,13 @@ class LidNewDay(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidArmInit(BaseEvent):
|
||||
@@ -1882,9 +1972,13 @@ class LidArmInit(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidPlgsPeriodic(BaseEvent):
|
||||
@@ -2057,9 +2151,13 @@ class LidPlgsPeriodic(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmAlertActivated(BaseEvent):
|
||||
@@ -2123,9 +2221,13 @@ class LidCgmAlertActivated(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmAlertCleared(BaseEvent):
|
||||
@@ -2180,9 +2282,13 @@ class LidCgmAlertCleared(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidVersionInfo(BaseEvent):
|
||||
@@ -2217,9 +2323,13 @@ class LidVersionInfo(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidUpdateStatus(BaseEvent):
|
||||
@@ -2280,9 +2390,13 @@ class LidUpdateStatus(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmStartSessionGx(BaseEvent):
|
||||
@@ -2314,9 +2428,13 @@ class LidCgmStartSessionGx(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmJoinSessionGx(BaseEvent):
|
||||
@@ -2390,9 +2508,13 @@ class LidCgmJoinSessionGx(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmStopSessionGx(BaseEvent):
|
||||
@@ -2469,9 +2591,13 @@ class LidCgmStopSessionGx(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidAaUserModeChange(BaseEvent):
|
||||
@@ -2688,9 +2814,13 @@ class LidAaUserModeChange(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidAaPcmChange(BaseEvent):
|
||||
@@ -2867,9 +2997,13 @@ class LidAaPcmChange(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmDataGxb(BaseEvent):
|
||||
@@ -2998,9 +3132,13 @@ class LidCgmDataGxb(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidBasalDelivery(BaseEvent):
|
||||
@@ -3061,9 +3199,13 @@ class LidBasalDelivery(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidBolusDelivery(BaseEvent):
|
||||
@@ -3189,9 +3331,13 @@ class LidBolusDelivery(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidVersionsA(BaseEvent):
|
||||
@@ -3226,9 +3372,13 @@ class LidVersionsA(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidAaDailyStatus(BaseEvent):
|
||||
@@ -3323,9 +3473,13 @@ class LidAaDailyStatus(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmAlertActivatedDex(BaseEvent):
|
||||
@@ -3412,9 +3566,13 @@ class LidCgmAlertActivatedDex(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmAlertClearedDex(BaseEvent):
|
||||
@@ -3492,9 +3650,13 @@ class LidCgmAlertClearedDex(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmAlertAckDex(BaseEvent):
|
||||
@@ -3593,9 +3755,13 @@ class LidCgmAlertAckDex(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmDataFsl2(BaseEvent):
|
||||
@@ -3758,9 +3924,13 @@ class LidCgmDataFsl2(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmJoinSessionG7(BaseEvent):
|
||||
@@ -3789,9 +3959,13 @@ class LidCgmJoinSessionG7(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmDataG7(BaseEvent):
|
||||
@@ -3960,9 +4134,13 @@ class LidCgmDataG7(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmStartSessionFsl2(BaseEvent):
|
||||
@@ -3991,9 +4169,13 @@ class LidCgmStartSessionFsl2(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmStopSessionFsl2(BaseEvent):
|
||||
@@ -4028,9 +4210,13 @@ class LidCgmStopSessionFsl2(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmJoinSessionFsl2(BaseEvent):
|
||||
@@ -4065,9 +4251,13 @@ class LidCgmJoinSessionFsl2(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmStopSessionG7(BaseEvent):
|
||||
@@ -4108,9 +4298,13 @@ class LidCgmStopSessionG7(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmAlertActivatedFsl2(BaseEvent):
|
||||
@@ -4195,9 +4389,13 @@ class LidCgmAlertActivatedFsl2(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCgmAlertClearedFsl2(BaseEvent):
|
||||
@@ -4273,9 +4471,159 @@ class LidCgmAlertClearedFsl2(BaseEvent):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidDailyBasal(BaseEvent):
|
||||
"""81: LID_DAILY_BASAL"""
|
||||
ID = 81
|
||||
NAME = "LID_DAILY_BASAL"
|
||||
|
||||
raw: RawEvent
|
||||
dailytotalbasal: float # units
|
||||
lastbasalrate: float # units/hour
|
||||
iob: float # units
|
||||
batterychargepercentmsbRaw: int
|
||||
batterychargepercentlsbRaw: int
|
||||
batterylipomillivolts: int
|
||||
|
||||
@property
|
||||
def batteryChargePercent(self):
|
||||
return (256*(self.batterychargepercentmsbRaw%2)+self.batterychargepercentlsbRaw)/512
|
||||
|
||||
@staticmethod
|
||||
def build(raw):
|
||||
dailytotalbasal, = struct.unpack_from(FLOAT32, raw[:EVENT_LEN], 10)
|
||||
lastbasalrate, = struct.unpack_from(FLOAT32, raw[:EVENT_LEN], 14)
|
||||
iob, = struct.unpack_from(FLOAT32, raw[:EVENT_LEN], 18)
|
||||
batterychargepercentmsbRaw, = struct.unpack_from(UINT8, raw[:EVENT_LEN], 22)
|
||||
batterychargepercentlsbRaw, = struct.unpack_from(UINT8, raw[:EVENT_LEN], 23)
|
||||
batterylipomillivolts, = struct.unpack_from(UINT16, raw[:EVENT_LEN], 24)
|
||||
|
||||
return LidDailyBasal(
|
||||
raw = RawEvent.build(raw),
|
||||
dailytotalbasal = dailytotalbasal,
|
||||
lastbasalrate = lastbasalrate,
|
||||
iob = iob,
|
||||
batterychargepercentmsbRaw = batterychargepercentmsbRaw,
|
||||
batterychargepercentlsbRaw = batterychargepercentlsbRaw,
|
||||
batterylipomillivolts = batterylipomillivolts,
|
||||
)
|
||||
|
||||
@property
|
||||
def eventTimestamp(self):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidCarbsEntered(BaseEvent):
|
||||
"""48: LID_CARBS_ENTERED"""
|
||||
ID = 48
|
||||
NAME = "LID_CARBS_ENTERED"
|
||||
|
||||
raw: RawEvent
|
||||
carbs: float # carbs
|
||||
|
||||
|
||||
@staticmethod
|
||||
def build(raw):
|
||||
carbs, = struct.unpack_from(FLOAT32, raw[:EVENT_LEN], 10)
|
||||
|
||||
return LidCarbsEntered(
|
||||
raw = RawEvent.build(raw),
|
||||
carbs = carbs,
|
||||
)
|
||||
|
||||
@property
|
||||
def eventTimestamp(self):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidUsbConnected(BaseEvent):
|
||||
"""36: LID_USB_CONNECTED"""
|
||||
ID = 36
|
||||
NAME = "LID_USB_CONNECTED"
|
||||
|
||||
raw: RawEvent
|
||||
negotiatedcurrent: float # mA
|
||||
|
||||
|
||||
@staticmethod
|
||||
def build(raw):
|
||||
negotiatedcurrent, = struct.unpack_from(FLOAT32, raw[:EVENT_LEN], 10)
|
||||
|
||||
return LidUsbConnected(
|
||||
raw = RawEvent.build(raw),
|
||||
negotiatedcurrent = negotiatedcurrent,
|
||||
)
|
||||
|
||||
@property
|
||||
def eventTimestamp(self):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
|
||||
@dataclass
|
||||
class LidUsbDisconnected(BaseEvent):
|
||||
"""37: LID_USB_DISCONNECTED"""
|
||||
ID = 37
|
||||
NAME = "LID_USB_DISCONNECTED"
|
||||
|
||||
raw: RawEvent
|
||||
negotiatedcurrent: float # mA
|
||||
|
||||
|
||||
@staticmethod
|
||||
def build(raw):
|
||||
negotiatedcurrent, = struct.unpack_from(FLOAT32, raw[:EVENT_LEN], 10)
|
||||
|
||||
return LidUsbDisconnected(
|
||||
raw = RawEvent.build(raw),
|
||||
negotiatedcurrent = negotiatedcurrent,
|
||||
)
|
||||
|
||||
@property
|
||||
def eventTimestamp(self):
|
||||
return self.raw.timestamp
|
||||
|
||||
@property
|
||||
def seqNum(self):
|
||||
return self.raw.seqNum
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.ID
|
||||
|
||||
EVENT_IDS = {
|
||||
3: LidBasalRateChange,
|
||||
4: LidAlertActivated,
|
||||
@@ -4329,6 +4677,10 @@ EVENT_IDS = {
|
||||
447: LidCgmStopSessionG7,
|
||||
460: LidCgmAlertActivatedFsl2,
|
||||
461: LidCgmAlertClearedFsl2,
|
||||
81: LidDailyBasal,
|
||||
48: LidCarbsEntered,
|
||||
36: LidUsbConnected,
|
||||
37: LidUsbDisconnected,
|
||||
}
|
||||
|
||||
EVENT_NAMES = {
|
||||
@@ -4384,5 +4736,9 @@ EVENT_NAMES = {
|
||||
"LID_CGM_STOP_SESSION_G7": LidCgmStopSessionG7,
|
||||
"LID_CGM_ALERT_ACTIVATED_FSL2": LidCgmAlertActivatedFsl2,
|
||||
"LID_CGM_ALERT_CLEARED_FSL2": LidCgmAlertClearedFsl2,
|
||||
"LID_DAILY_BASAL": LidDailyBasal,
|
||||
"LID_CARBS_ENTERED": LidCarbsEntered,
|
||||
"LID_USB_CONNECTED": LidUsbConnected,
|
||||
"LID_USB_DISCONNECTED": LidUsbDisconnected,
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,13 @@ from .events import EVENT_IDS
|
||||
from .utils import batched
|
||||
|
||||
|
||||
Event = lambda x: EVENT_IDS[RawEvent.build(x).id].build(x)
|
||||
def Event(x):
|
||||
raw_event = RawEvent.build(x)
|
||||
if not raw_event.id in EVENT_IDS:
|
||||
return raw_event
|
||||
|
||||
return EVENT_IDS[raw_event.id].build(x)
|
||||
|
||||
Events = lambda x: (Event(bytearray(e)) for e in batched(x, EVENT_LEN))
|
||||
|
||||
def decode_raw_events(raw):
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import struct
|
||||
import arrow
|
||||
|
||||
from ..secret import TIMEZONE_NAME
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
EVENT_LEN = 26
|
||||
@@ -8,6 +10,21 @@ UINT16 = '>H'
|
||||
UINT32 = '>I'
|
||||
TANDEM_EPOCH = 1199145600
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseEvent:
|
||||
@staticmethod
|
||||
def build(raw):
|
||||
raise NotImplemented
|
||||
|
||||
@property
|
||||
def eventTimestamp(self):
|
||||
raise NotImplemented
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
raise NotImplemented
|
||||
|
||||
@dataclass
|
||||
class RawEvent:
|
||||
source: int
|
||||
@@ -29,7 +46,21 @@ class RawEvent:
|
||||
seqNum = seqNum,
|
||||
raw = raw
|
||||
)
|
||||
|
||||
|
||||
@property
|
||||
def timestamp(self):
|
||||
return arrow.get(TANDEM_EPOCH + self.timestampRaw)
|
||||
# Event timestamps do not have TZ data attached to them when parsed,
|
||||
# but represent the user's time zone setting. So we keep the time
|
||||
# referenced on them, but force the timezone to what the user
|
||||
# requests via the TZ secret.
|
||||
return arrow.get(TANDEM_EPOCH + self.timestampRaw, tzinfo='UTC').replace(tzinfo=TIMEZONE_NAME)
|
||||
|
||||
@property
|
||||
def eventId(self):
|
||||
return self.id
|
||||
|
||||
@property
|
||||
def eventTimestamp(self):
|
||||
return self.timestamp
|
||||
|
||||
|
||||
|
||||
@@ -111,11 +111,22 @@ def transform_ratio(event_def, name, name_fmt, field, tx):
|
||||
|
||||
return out
|
||||
|
||||
def transform_battery_charge_percent(event_def, name, name_fmt, field, tx):
|
||||
out = []
|
||||
out += [
|
||||
'@property',
|
||||
f'def batteryChargePercent(self):',
|
||||
f' return (256*(self.batterychargepercentmsbRaw%2)+self.batterychargepercentlsbRaw)/512',
|
||||
''
|
||||
]
|
||||
|
||||
return out
|
||||
|
||||
|
||||
TRANSFORMS = {
|
||||
'enum': transform_enum,
|
||||
'dictionary': transform_dictionary,
|
||||
'bitmask': transform_bitmask,
|
||||
'ratio': transform_ratio
|
||||
'ratio': transform_ratio,
|
||||
'battery_charge_percent': transform_battery_charge_percent
|
||||
}
|
||||
@@ -10,6 +10,7 @@ PUMP_EVENTS = "PUMP_EVENTS"
|
||||
PUMP_EVENTS_BASAL_SUSPENSION = "PUMP_EVENTS_BASAL_SUSPENSION"
|
||||
PROFILES = "PROFILES"
|
||||
CGM_ALERTS = "CGM_ALERTS"
|
||||
DEVICE_STATUS = "DEVICE_STATUS"
|
||||
|
||||
DEFAULT_FEATURES = [
|
||||
BASAL,
|
||||
@@ -27,6 +28,7 @@ ALL_FEATURES = [
|
||||
PROFILES,
|
||||
CGM,
|
||||
CGM_ALERTS,
|
||||
DEVICE_STATUS,
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -154,6 +154,33 @@ class NightscoutApi:
|
||||
else:
|
||||
raise e
|
||||
|
||||
def last_uploaded_devicestatus(self, time_start=None, time_end=None):
|
||||
def internal(t_to_space):
|
||||
dateFilter = time_range('created_at', time_start, time_end, t_to_space=t_to_space)
|
||||
latest = requests.get(urljoin(self.url, 'api/v1/devicestatus?find[device]=' + urllib.parse.quote(ENTERED_BY) + dateFilter + '&ts=' + str(time.time())), headers={
|
||||
'api-secret': hashlib.sha1(self.secret.encode()).hexdigest()
|
||||
}, verify=self.verify)
|
||||
if latest.status_code != 200:
|
||||
raise ApiException(latest.status_code, "Nightscout devicestatus %s response: %s" % (latest.status_code, latest.text))
|
||||
|
||||
j = latest.json()
|
||||
if j and len(j) > 0:
|
||||
return j[0]
|
||||
return None
|
||||
|
||||
try:
|
||||
ret = internal(False)
|
||||
if ret is None and (time_start or time_end):
|
||||
ret = internal(True)
|
||||
if ret is not None:
|
||||
logger.warning("devicestatus time_start=%s time_end=%s only returned data when timestamps contained a space" % (time_start, time_end))
|
||||
return ret
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
if self.ignore_conn_errors:
|
||||
logger.warn('Ignoring ConnectionError because ignore_conn_errors=true', e)
|
||||
else:
|
||||
raise e
|
||||
|
||||
"""
|
||||
Returns general status information about the Nightscout server.
|
||||
"""
|
||||
|
||||
@@ -194,6 +194,20 @@ class NightscoutEntry:
|
||||
"pump_event_id": pump_event_id
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def devicestatus(created_at, batteryVoltage, batteryString, pump_event_id=""):
|
||||
return {
|
||||
"device": ENTERED_BY,
|
||||
"created_at": created_at,
|
||||
"pump": {
|
||||
"battery": {
|
||||
"voltage": float(batteryVoltage),
|
||||
"string": batteryString
|
||||
},
|
||||
},
|
||||
"pump_event_id": pump_event_id
|
||||
}
|
||||
|
||||
# Tandem-scraped profile to Nightscout profile store entry
|
||||
@staticmethod
|
||||
def profile_store(profile: Profile, device_settings: DeviceSettings) -> dict:
|
||||
@@ -251,30 +265,33 @@ class NightscoutEntry:
|
||||
return {
|
||||
# insulin duration in hours; Nightscout JS bug requires all top-level fields to be strings
|
||||
"dia": "%s" % (profile.insulinDuration / 60),
|
||||
"carbratio": [
|
||||
"carbratio": list(sorted([
|
||||
{
|
||||
"time": minutes_to_ns_time(segment.startTime),
|
||||
"timeAsSeconds": segment.startTime * 60,
|
||||
"value": segment.carbRatio / 1000 # milliunits->units
|
||||
} for segment in profile.tDependentSegs
|
||||
],
|
||||
} for segment in profile.tDependentSegs if not segment.skip
|
||||
], key=lambda x: x["timeAsSeconds"])),
|
||||
|
||||
"carbs_hr": NIGHTSCOUT_PROFILE_CARBS_HR_VALUE,
|
||||
"delay": NIGHTSCOUT_PROFILE_DELAY_VALUE,
|
||||
"sens": [ # Correction factor / isf
|
||||
|
||||
"sens": list(sorted([ # Correction factor / isf
|
||||
{
|
||||
"time": minutes_to_ns_time(segment.startTime),
|
||||
"timeAsSeconds": segment.startTime * 60,
|
||||
"value": segment.isf
|
||||
} for segment in profile.tDependentSegs
|
||||
],
|
||||
"basal": [
|
||||
} for segment in profile.tDependentSegs if not segment.skip
|
||||
], key=lambda x: x["timeAsSeconds"])),
|
||||
|
||||
"basal": list(sorted([
|
||||
{
|
||||
"time": minutes_to_ns_time(segment.startTime),
|
||||
"timeAsSeconds": segment.startTime * 60,
|
||||
"value": segment.basalRate / 1000 # milliunits->units
|
||||
} for segment in profile.tDependentSegs
|
||||
],
|
||||
], key=lambda x: x["timeAsSeconds"])),
|
||||
|
||||
"target_low": [
|
||||
{
|
||||
"time": "00:00",
|
||||
|
||||
@@ -54,6 +54,7 @@ if not get('NS_SECRET') and get('API_SECRET'):
|
||||
NS_SKIP_TLS_VERIFY = get_bool('NS_SKIP_TLS_VERIFY', 'false')
|
||||
NS_IGNORE_CONN_ERRORS = get_bool('NS_IGNORE_CONN_ERRORS', 'false')
|
||||
|
||||
# This should be the timezone your pump is set to.
|
||||
TIMEZONE_NAME = get('TIMEZONE_NAME', 'America/New_York')
|
||||
|
||||
if not get('TIMEZONE_NAME') and get('TZ'):
|
||||
@@ -75,9 +76,13 @@ AUTOUPDATE_MAX_LOOP_INVOCATIONS = get_number('AUTOUPDATE_MAX_LOOP_INVOCATIONS',
|
||||
|
||||
NIGHTSCOUT_PROFILE_UPLOAD_MODE = get_one_of('NIGHTSCOUT_PROFILE_UPLOAD_MODE', 'add', ['add', 'replace'])
|
||||
|
||||
# When set, all possible history log event types are fetched from Tandem Source
|
||||
FETCH_ALL_EVENT_TYPES = get_bool('FETCH_ALL_EVENT_TYPES', 'false')
|
||||
|
||||
# Default Nightscout profile segment fields which aren't stored by Tandem
|
||||
NIGHTSCOUT_PROFILE_CARBS_HR_VALUE = get('NIGHTSCOUT_PROFILE_CARBS_HR_VALUE', '20')
|
||||
NIGHTSCOUT_PROFILE_DELAY_VALUE = get('NIGHTSCOUT_PROFILE_DELAY_VALUE', '20')
|
||||
IGNORE_ZERO_UNIT_BASAL = get_bool('IGNORE_ZERO_UNIT_BASAL', 'false')
|
||||
|
||||
ENABLE_TESTING_MODES = get_bool('ENABLE_TESTING_MODES', 'false')
|
||||
SKIP_NS_LAST_UPLOADED_CHECK = get_bool('SKIP_NS_LAST_UPLOADED_CHECK', 'false')
|
||||
|
||||
@@ -18,6 +18,7 @@ class TandemSourceAutoupdate:
|
||||
self.last_max_date_with_events = None
|
||||
self.last_event_time = 0
|
||||
self.last_attempt_time = 0
|
||||
self.last_event_seqnum = None
|
||||
self.time_diffs_between_attempts = []
|
||||
self.time_diffs_between_updates = []
|
||||
|
||||
@@ -41,26 +42,30 @@ class TandemSourceAutoupdate:
|
||||
|
||||
tconnectDevice = ChooseDevice(self.secret, tconnect).choose()
|
||||
|
||||
cur_max_date_with_events = arrow.get(tconnectDevice['maxDateWithEvents'])
|
||||
if not self.last_max_date_with_events or cur_max_date_with_events > self.cur_max_date_with_events:
|
||||
event_seqnum = None
|
||||
cur_max_date_with_events = arrow.get(tconnectDevice['maxDateWithEvents']).float_timestamp
|
||||
if not self.last_max_date_with_events or cur_max_date_with_events > self.last_max_date_with_events:
|
||||
logger.info('New reported tandemsource data. (cur_max_date: %s last_max_date: %s)' % (cur_max_date_with_events, self.last_max_date_with_events))
|
||||
|
||||
if pretend:
|
||||
logger.info('Would update now if not in pretend mode')
|
||||
else:
|
||||
added = ProcessTimeRange(tconnect, nightscout, tconnectDevice, pretend, features=features).process(time_start, time_end)
|
||||
added, event_seqnum = ProcessTimeRange(tconnect, nightscout, tconnectDevice, pretend, self.secret, features=features).process(time_start, time_end)
|
||||
logger.info('Added %d items from ProcessTimeRange' % added)
|
||||
self.last_successful_process_time_range = now
|
||||
|
||||
# Track the time it took to find a new event between runs,
|
||||
# but skip this calculation the first process cycle (since
|
||||
# we don't know at what exact point the event index changed)
|
||||
if self.last_event_index:
|
||||
if self.last_event_seqnum:
|
||||
self.time_diffs_between_updates.append(now - self.last_max_date_with_events)
|
||||
logger.debug('Updating tracking of time since last update: %s' % self.time_diffs_between_updates)
|
||||
|
||||
# Mark the last event index uploaded from the pump and timestamp
|
||||
if event_seqnum:
|
||||
self.last_event_seqnum = event_seqnum
|
||||
self.last_event_time = now
|
||||
self.last_max_date_with_events = cur_max_date_with_events
|
||||
self.last_event_time = now
|
||||
self.last_attempt_time = now
|
||||
self.time_diffs_between_attempts = []
|
||||
else:
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
from .choose_device import ChooseDevice
|
||||
from .process import ProcessTimeRange
|
||||
from ...api import TConnectApi
|
||||
from ... import secret
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def fetch_oneshot(username, password, time_start=None, time_end=None):
|
||||
tconnect = TConnectApi(username, password)
|
||||
if not time_start and not time_end:
|
||||
time_end = datetime.datetime.now()
|
||||
time_start = time_end - datetime.timedelta(days=1)
|
||||
|
||||
tconnectDevice = ChooseDevice(secret, tconnect).choose()
|
||||
return tconnect.tandemsource.pump_events(tconnectDevice['tconnectDeviceId'], time_start, time_end, fetch_all_event_types=secret.FETCH_ALL_EVENT_TYPES)
|
||||
@@ -0,0 +1,17 @@
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from .choose_device import ChooseDevice
|
||||
from .process import ProcessTimeRange
|
||||
from ... import secret
|
||||
|
||||
import datetime
|
||||
|
||||
def run_oneshot(tconnect, nightscout, pretend=False, features=DEFAULT_FEATURES, secret_arg=None, time_start=None, time_end=None):
|
||||
if not time_start and not time_end:
|
||||
time_end = datetime.datetime.now()
|
||||
time_start = time_end - datetime.timedelta(days=1)
|
||||
|
||||
if not secret_arg:
|
||||
secret_arg = secret
|
||||
|
||||
tconnectDevice = ChooseDevice(secret_arg, tconnect).choose()
|
||||
return ProcessTimeRange(tconnect, nightscout, tconnectDevice, pretend, secret_arg, features).process(time_start, time_end)
|
||||
@@ -2,7 +2,6 @@ import logging
|
||||
import collections
|
||||
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
from ...eventparser import events as eventtypes
|
||||
from ...domain.tandemsource.event_class import EventClass
|
||||
from .process_basal import ProcessBasal
|
||||
@@ -20,12 +19,13 @@ from .update_profiles import UpdateProfiles
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ProcessTimeRange:
|
||||
def __init__(self, tconnect, nightscout, tconnectDevice, pretend, features=DEFAULT_FEATURES):
|
||||
def __init__(self, tconnect, nightscout, tconnectDevice, pretend, secret, features=DEFAULT_FEATURES):
|
||||
self.tconnect = tconnect
|
||||
self.nightscout = nightscout
|
||||
self.tconnect_device_id = tconnectDevice['tconnectDeviceId']
|
||||
self.max_date_with_events = tconnectDevice['maxDateWithEvents']
|
||||
self.pretend = pretend
|
||||
self.secret = secret
|
||||
self.features = features
|
||||
|
||||
event_classes = {
|
||||
@@ -47,22 +47,22 @@ class ProcessTimeRange:
|
||||
|
||||
def process(self, time_start, time_end):
|
||||
logger.info(f"ProcessTimeRange time_start={time_start} time_end={time_end} tconnect_device_id={self.tconnect_device_id} features={self.features}")
|
||||
|
||||
pump_events_raw = self.tconnect.tandemsource.pump_events_raw(self.tconnect_device_id, time_start, time_end)
|
||||
pump_events_decoded = decode_raw_events(pump_events_raw)
|
||||
logger.info(f"Read {len(pump_events_decoded)} bytes (est. {len(pump_events_decoded)/EVENT_LEN} events)")
|
||||
events = Events(pump_events_decoded)
|
||||
events = self.tconnect.tandemsource.pump_events(self.tconnect_device_id, time_start, time_end, fetch_all_event_types=self.secret.FETCH_ALL_EVENT_TYPES)
|
||||
|
||||
events_first_time = None
|
||||
events_last_time = None
|
||||
last_event_seqnum = None
|
||||
for_eventclass = collections.defaultdict(list)
|
||||
for event in events:
|
||||
if not events_first_time:
|
||||
events_first_time = event.eventTimestamp
|
||||
if not events_last_time:
|
||||
events_last_time = event.eventTimestamp
|
||||
if not last_event_seqnum:
|
||||
last_event_seqnum = event.seqNum
|
||||
events_first_time = min(events_first_time, event.eventTimestamp)
|
||||
events_last_time = max(events_last_time, event.eventTimestamp)
|
||||
last_event_seqnum = max(event.seqNum, last_event_seqnum)
|
||||
|
||||
clazz = EventClass.for_event(event)
|
||||
if clazz:
|
||||
@@ -78,7 +78,9 @@ class ProcessTimeRange:
|
||||
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)
|
||||
w = c.write(ns_entries)
|
||||
if w:
|
||||
processed_count += w
|
||||
else:
|
||||
logger.info("Skipping %s, is not enabled from features %s" % (clazz, self.features))
|
||||
|
||||
@@ -91,6 +93,6 @@ class ProcessTimeRange:
|
||||
else:
|
||||
logger.info("Skipping %s, is not enabled from features %s" % (updater_class.__name__, self.features))
|
||||
|
||||
logger.info("Processed %d events" % processed_count)
|
||||
return processed_count
|
||||
logger.info("Processed %d events. Last event ID seen: %d" % (processed_count if processed_count else 0, last_event_seqnum if last_event_seqnum else -1))
|
||||
return processed_count, last_event_seqnum
|
||||
|
||||
|
||||
@@ -35,9 +35,12 @@ class ProcessAlarm:
|
||||
|
||||
ns_entries = []
|
||||
for event in sorted(events, key=lambda x: x.eventTimestamp):
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) < last_upload_time:
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) <= last_upload_time:
|
||||
if self.pretend:
|
||||
logger.info("Skipping Alarm event before last upload time: %s (time range: %s - %s)" % (event, time_start, time_end))
|
||||
logger.info("Skipping Alarm event not after last upload time: %s (time range: %s - %s)" % (event, time_start, time_end))
|
||||
continue
|
||||
|
||||
if self.skip_event(event):
|
||||
continue
|
||||
|
||||
ns_entries.append(self.alarm_to_nsentry(event))
|
||||
@@ -45,6 +48,12 @@ class ProcessAlarm:
|
||||
|
||||
return ns_entries
|
||||
|
||||
def skip_event(self, event):
|
||||
return event.alarmid in (
|
||||
eventtypes.LidAlarmActivated.AlarmidEnum.ResumePumpAlarm,
|
||||
eventtypes.LidAlarmActivated.AlarmidEnum.ResumePumpAlarm2
|
||||
)
|
||||
|
||||
def write(self, ns_entries):
|
||||
count = 0
|
||||
for entry in ns_entries:
|
||||
@@ -63,11 +72,11 @@ class ProcessAlarm:
|
||||
return NightscoutEntry.alarm(
|
||||
created_at = event.eventTimestamp.format(),
|
||||
reason = "%s" % event.alarmid.name,
|
||||
pump_event_id = "%s" % event.eventId
|
||||
pump_event_id = "%s" % event.seqNum
|
||||
)
|
||||
elif type(event) == eventtypes.LidMalfunctionActivated:
|
||||
return NightscoutEntry.alarm(
|
||||
created_at = event.eventTimestamp.format(),
|
||||
reason = "Malfunction",
|
||||
pump_event_id = "%s" % event.eventId
|
||||
pump_event_id = "%s" % event.seqNum
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import arrow
|
||||
|
||||
from ...secret import IGNORE_ZERO_UNIT_BASAL
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from ... import features
|
||||
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
@@ -36,13 +37,17 @@ class ProcessBasal:
|
||||
|
||||
with_duration = []
|
||||
for event in sorted(events, key=lambda x: x.eventTimestamp):
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) < last_upload_time:
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) <= last_upload_time:
|
||||
if self.pretend:
|
||||
logger.info("Skipping basal event before last upload time: %s (time range: %s - %s)" % (event, time_start, time_end))
|
||||
logger.info("Skipping basal event not after last upload time: %s (time range: %s - %s)" % (event, time_start, time_end))
|
||||
continue
|
||||
|
||||
with_duration.append([event.eventTimestamp, None, event])
|
||||
|
||||
if not with_duration:
|
||||
logger.info("No basal events found to process")
|
||||
return []
|
||||
|
||||
for i in range(len(with_duration)-1):
|
||||
with_duration[i][1] = with_duration[i+1][0] - with_duration[i][0]
|
||||
|
||||
@@ -50,7 +55,9 @@ class ProcessBasal:
|
||||
|
||||
ns_entries = []
|
||||
for item in with_duration:
|
||||
ns_entries.append(self.basal_to_nsentry(*item))
|
||||
ns = self.basal_to_nsentry(*item)
|
||||
if ns:
|
||||
ns_entries.append(ns)
|
||||
|
||||
return ns_entries
|
||||
|
||||
@@ -69,18 +76,26 @@ class ProcessBasal:
|
||||
|
||||
def basal_to_nsentry(self, start, duration, event):
|
||||
if type(event) == eventtypes.LidBasalRateChange:
|
||||
value = insulin_float_round(event.commandedbasalrate)
|
||||
if IGNORE_ZERO_UNIT_BASAL and value < 0.01:
|
||||
logger.info("Ignoring basal entry with %.2f unit basal because IGNORE_ZERO_UNIT_BASAL=true: %s" % (value, event))
|
||||
return None
|
||||
return NightscoutEntry.basal(
|
||||
value = insulin_float_round(event.commandedbasalrate),
|
||||
value = value,
|
||||
duration_mins = duration.seconds / 60,
|
||||
created_at = start.format(),
|
||||
reason = ', '.join(bitmask_to_list(event.changetype)),
|
||||
pump_event_id = "%s" % event.eventId
|
||||
pump_event_id = "%s" % event.seqNum
|
||||
)
|
||||
if type(event) == eventtypes.LidBasalDelivery:
|
||||
value = insulin_milliunits_to_real(event.commandedRate)
|
||||
if IGNORE_ZERO_UNIT_BASAL and value < 0.01:
|
||||
logger.info("Ignoring basal entry with %.2f unit basal because IGNORE_ZERO_UNIT_BASAL=true: %s" % (value, event))
|
||||
return None
|
||||
return NightscoutEntry.basal(
|
||||
value = insulin_milliunits_to_real(event.commandedRate),
|
||||
value = value,
|
||||
duration_mins = duration.seconds / 60,
|
||||
created_at = start.format(),
|
||||
reason = ', '.join(bitmask_to_list(event.commandedRateSource)),
|
||||
pump_event_id = "%s" % event.eventId
|
||||
pump_event_id = "%s" % event.seqNum
|
||||
)
|
||||
|
||||
@@ -35,9 +35,9 @@ class ProcessBasalResume:
|
||||
|
||||
ns_entries = []
|
||||
for event in sorted(events, key=lambda x: x.eventTimestamp):
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) < last_upload_time:
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) <= last_upload_time:
|
||||
if self.pretend:
|
||||
logger.info("Skipping BasalResume event before 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
|
||||
|
||||
ns_entries.append(self.resume_to_nsentry(event))
|
||||
@@ -62,5 +62,5 @@ class ProcessBasalResume:
|
||||
if type(event) == eventtypes.LidPumpingResumed:
|
||||
return NightscoutEntry.basalresume(
|
||||
created_at = event.eventTimestamp.format(),
|
||||
pump_event_id = "%s" % event.eventId
|
||||
pump_event_id = "%s" % event.seqNum
|
||||
)
|
||||
|
||||
@@ -35,9 +35,9 @@ class ProcessBasalSuspension:
|
||||
|
||||
ns_entries = []
|
||||
for event in sorted(events, key=lambda x: x.eventTimestamp):
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) < last_upload_time:
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) <= last_upload_time:
|
||||
if self.pretend:
|
||||
logger.info("Skipping basalsuspension event before 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
|
||||
|
||||
ns_entries.append(self.suspension_to_nsentry(event))
|
||||
@@ -63,5 +63,5 @@ class ProcessBasalSuspension:
|
||||
return NightscoutEntry.basalsuspension(
|
||||
created_at = event.eventTimestamp.format(),
|
||||
reason = ', '.join(bitmask_to_list(event.suspendreason)),
|
||||
pump_event_id = "%s" % event.eventId
|
||||
pump_event_id = "%s" % event.seqNum
|
||||
)
|
||||
|
||||
@@ -44,9 +44,9 @@ class ProcessBolus:
|
||||
bolusEventsForId[event.bolusid][type(event)] = event
|
||||
|
||||
if type(event) == eventtypes.LidBolusCompleted:
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) < last_upload_time:
|
||||
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))
|
||||
logger.info("Skipping bolusCompletedEvent not after last upload time: %s (time range: %s - %s)" % (event, time_start, time_end))
|
||||
continue
|
||||
|
||||
bolusCompletedEvents.append(event)
|
||||
@@ -91,10 +91,10 @@ class ProcessBolus:
|
||||
|
||||
suffix = (' ' + (' '.join(suffixes))) if suffixes else ''
|
||||
|
||||
event_ids = []
|
||||
seq_nums = []
|
||||
for e in [bolusCompleted, bolusRequested1, bolusRequested2, bolusRequested3]:
|
||||
if e:
|
||||
event_ids.append(str(e.eventId))
|
||||
seq_nums.append(str(e.seqNum))
|
||||
|
||||
notes = ''
|
||||
if bolusRequested2 and str(bolusRequested2.optionsRaw) in eventtypes.LidBolusRequestedMsg2.OptionsMap:
|
||||
@@ -107,6 +107,6 @@ class ProcessBolus:
|
||||
created_at = bolusCompleted.eventTimestamp.format(),
|
||||
notes = notes + suffix,
|
||||
bg = bolusRequested1.BG if bolusRequested1 and bolusRequested1.BG > 0 else None,
|
||||
pump_event_id = ",".join(event_ids)
|
||||
pump_event_id = ",".join(seq_nums)
|
||||
)
|
||||
|
||||
|
||||
@@ -37,9 +37,9 @@ class ProcessCartridge:
|
||||
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 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))
|
||||
logger.info("Skipping %s not after last upload time: %s (time range: %s - %s)" % (type(event), event, time_start, time_end))
|
||||
continue
|
||||
|
||||
if type(event) == eventtypes.LidCartridgeFilled:
|
||||
@@ -82,19 +82,19 @@ class ProcessCartridge:
|
||||
return NightscoutEntry.sitechange(
|
||||
created_at = cartFilled.eventTimestamp.format(),
|
||||
reason = "Cartridge Filled" + (" (%du filled)" % round(cartFilled.v2Volume) if cartFilled.v2Volume else ""),
|
||||
pump_event_id = "%s" % cartFilled.eventId
|
||||
pump_event_id = "%s" % cartFilled.seqNum
|
||||
)
|
||||
|
||||
def cannula_to_nsentry(self, cannulaFilled):
|
||||
return NightscoutEntry.sitechange(
|
||||
created_at = cannulaFilled.eventTimestamp.format(),
|
||||
reason = "Cannula Filled" + (" (%du primed)" % round(cannulaFilled.primesize, 2) if cannulaFilled.primesize else ""),
|
||||
pump_event_id = "%s" % cannulaFilled.eventId
|
||||
pump_event_id = "%s" % cannulaFilled.seqNum
|
||||
)
|
||||
|
||||
def tubing_to_nsentry(self, tubingFilled):
|
||||
return NightscoutEntry.sitechange(
|
||||
created_at = tubingFilled.eventTimestamp.format(),
|
||||
reason = "Tubing Filled" + (" (%du primed)" % round(tubingFilled.primesize) if tubingFilled.primesize else ""),
|
||||
pump_event_id = "%s" % tubingFilled.eventId
|
||||
pump_event_id = "%s" % tubingFilled.seqNum
|
||||
)
|
||||
|
||||
@@ -35,9 +35,9 @@ class ProcessCGMAlert:
|
||||
|
||||
alertEvents = []
|
||||
for event in sorted(events, key=lambda x: x.eventTimestamp):
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) < last_upload_time:
|
||||
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))
|
||||
logger.info("Skipping %s not after last upload time: %s (time range: %s - %s)" % (type(event), event, time_start, time_end))
|
||||
continue
|
||||
|
||||
alertEvents.append(event)
|
||||
@@ -73,7 +73,7 @@ class ProcessCGMAlert:
|
||||
return NightscoutEntry.cgm_alert(
|
||||
created_at = alert.eventTimestamp.format(),
|
||||
reason = ("CGM Alert (%s)" % alert.dalertid.name) if alert.dalertid else "CGM Alert (Unknown)",
|
||||
pump_event_id = "%s" % alert.eventId
|
||||
pump_event_id = "%s" % alert.seqNum
|
||||
)
|
||||
elif type(alert) == eventtypes.LidCgmAlertActivatedDex:
|
||||
if alert.dalertid == eventtypes.LidCgmAlertActivatedDex.DalertidEnum.CgmOutOfRange:
|
||||
@@ -82,11 +82,11 @@ class ProcessCGMAlert:
|
||||
return NightscoutEntry.cgm_alert(
|
||||
created_at = alert.eventTimestamp.format(),
|
||||
reason = ("Dexcom CGM Alert (%s)" % alert.dalertid.name) if alert.dalertid else "Dexcom CGM Alert (Unknown)",
|
||||
pump_event_id = "%s" % alert.eventId
|
||||
pump_event_id = "%s" % alert.seqNum
|
||||
)
|
||||
elif type(alert) == eventtypes.LidCgmAlertActivatedFsl2:
|
||||
return NightscoutEntry.cgm_alert(
|
||||
created_at = alert.eventTimestamp.format(),
|
||||
reason = ("Libre CGM Alert (%s)" % alert.dalertid.name) if alert.dalertid else "Libre CGM Alert (Unknown)",
|
||||
pump_event_id = "%s" % alert.eventId
|
||||
pump_event_id = "%s" % alert.seqNum
|
||||
)
|
||||
|
||||
@@ -38,9 +38,9 @@ class ProcessCGMReading:
|
||||
|
||||
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 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))
|
||||
logger.info("ProcessCGMReading: Skipping %s not after last upload time: %s (time range: %s - %s)" % (type(event), event, time_start, time_end))
|
||||
continue
|
||||
|
||||
readings.append(event)
|
||||
@@ -72,5 +72,5 @@ class ProcessCGMReading:
|
||||
return NightscoutEntry.entry(
|
||||
sgv = event.currentglucosedisplayvalue,
|
||||
created_at = self.timestamp_for(event).format(),
|
||||
pump_event_id = "%s" % event.eventId,
|
||||
pump_event_id = "%s" % event.seqNum,
|
||||
)
|
||||
|
||||
@@ -48,9 +48,9 @@ class ProcessCGMStartJoinStop:
|
||||
|
||||
allEvents = []
|
||||
for event in sorted(events, key=lambda x: x.eventTimestamp):
|
||||
if last_upload_time and arrow.get(event.eventTimestamp) < last_upload_time:
|
||||
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))
|
||||
logger.info("ProcessCGMStartJoinStop: Skipping %s not after last upload time: %s (time range: %s - %s)" % (type(event), event, time_start, time_end))
|
||||
continue
|
||||
|
||||
allEvents.append(event)
|
||||
@@ -80,17 +80,17 @@ class ProcessCGMStartJoinStop:
|
||||
return NightscoutEntry.cgm_start(
|
||||
created_at = event.eventTimestamp.format(),
|
||||
reason = "CGM Session Started",
|
||||
pump_event_id = "%s" % event.eventId
|
||||
pump_event_id = "%s" % event.seqNum
|
||||
)
|
||||
elif type(event) in EventClass._CGM_JOIN:
|
||||
return NightscoutEntry.cgm_join(
|
||||
created_at = event.eventTimestamp.format(),
|
||||
reason = "CGM Session Joined",
|
||||
pump_event_id = "%s" % event.eventId
|
||||
pump_event_id = "%s" % event.seqNum
|
||||
)
|
||||
elif type(event) in EventClass._CGM_STOP:
|
||||
return NightscoutEntry.cgm_stop(
|
||||
created_at = event.eventTimestamp.format(),
|
||||
reason = "CGM Session Stopped",
|
||||
pump_event_id = "%s" % event.eventId
|
||||
pump_event_id = "%s" % event.seqNum
|
||||
)
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
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
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ProcessDeviceStatus:
|
||||
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.DEVICE_STATUS in self.features
|
||||
|
||||
def process(self, events, time_start, time_end):
|
||||
logger.debug("ProcessDeviceStatus: querying for last uploaded devicestatus")
|
||||
last_upload = self.nightscout.last_uploaded_devicestatus(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("ProcessDeviceStatus: Last Nightscout devicestatus upload: %s" % last_upload_time)
|
||||
|
||||
|
||||
last_daily_basal_event = None
|
||||
for event in sorted(events, key=lambda x: x.raw.timestamp):
|
||||
if last_upload_time and event.raw.timestamp <= last_upload_time:
|
||||
if self.pretend:
|
||||
logger.info("ProcessDeviceStatus: Skipping %s not after last upload time: %s (time range: %s - %s)" % (type(event), event, time_start, time_end))
|
||||
continue
|
||||
|
||||
if isinstance(event, eventtypes.LidDailyBasal):
|
||||
last_daily_basal_event = event
|
||||
|
||||
|
||||
if not last_daily_basal_event:
|
||||
logger.info("ProcessDeviceStatus: No last_daily_basal_event found for add (time range: %s - %s)" % (time_start, time_end))
|
||||
return []
|
||||
|
||||
|
||||
ns_entries = []
|
||||
ns_entries.append(self.daily_basal_to_nsentry(last_daily_basal_event))
|
||||
return ns_entries
|
||||
|
||||
def daily_basal_to_nsentry(self, event):
|
||||
return NightscoutEntry.devicestatus(
|
||||
created_at=event.eventTimestamp.format(),
|
||||
batteryVoltage=(float(event.batterylipomillivolts or 0)/1000),
|
||||
batteryString="%.0f%s" % (100*event.batteryChargePercent, '%'),
|
||||
pump_event_id = "%s" % event.seqNum
|
||||
)
|
||||
|
||||
|
||||
def write(self, ns_entries):
|
||||
count = 0
|
||||
for entry in ns_entries:
|
||||
if self.pretend:
|
||||
logger.info("Would upload devicestatus to Nightscout: %s" % entry)
|
||||
else:
|
||||
logger.info("Uploading devicestatus to Nightscout: %s" % entry)
|
||||
self.nightscout.upload_entry(entry, entity='devicestatus')
|
||||
count += 1
|
||||
|
||||
return count
|
||||
@@ -72,9 +72,9 @@ class ProcessUserMode:
|
||||
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 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))
|
||||
logger.info("ProcessUserMode: Skipping usermode event not after last upload time: %s (time range: %s - %s)" % (event, time_start, time_end))
|
||||
continue
|
||||
|
||||
if self.is_start_sleep(event):
|
||||
@@ -157,7 +157,7 @@ class ProcessUserMode:
|
||||
reason=reason,
|
||||
duration=duration_mins,
|
||||
event_type=SLEEP_EVENTTYPE,
|
||||
pump_event_id = "%s,%s" % (start.eventId, stop.eventId)
|
||||
pump_event_id = "%s,%s" % (start.seqNum, stop.seqNum)
|
||||
)
|
||||
elif start:
|
||||
reason = None
|
||||
@@ -172,7 +172,7 @@ class ProcessUserMode:
|
||||
reason=reason + " - " + NOT_ENDED if reason else NOT_ENDED,
|
||||
duration=duration_mins,
|
||||
event_type=SLEEP_EVENTTYPE,
|
||||
pump_event_id = "%s" % start.eventId
|
||||
pump_event_id = "%s" % start.seqNum
|
||||
)
|
||||
|
||||
|
||||
@@ -191,20 +191,20 @@ class ProcessUserMode:
|
||||
reason=reason,
|
||||
duration=duration_mins,
|
||||
event_type=EXERCISE_EVENTTYPE,
|
||||
pump_event_id = "%s,%s" % (start.eventId, stop.eventId)
|
||||
pump_event_id = "%s,%s" % (start.seqNum, stop.seqNum)
|
||||
)
|
||||
elif start:
|
||||
reason = "Exercise"
|
||||
if start.exercisechoice == eventtypes.LidAaUserModeChange.ExercisechoiceEnum.Timed:
|
||||
reason = "Exercise (Timed)"
|
||||
|
||||
duration_mins = (time_end.eventTimestamp - start.eventTimestamp).seconds / 60
|
||||
duration_mins = (time_end - start.eventTimestamp).seconds / 60
|
||||
return NightscoutEntry.activity(
|
||||
created_at=start.eventTimestamp.format(),
|
||||
reason=reason + " - " + NOT_ENDED,
|
||||
duration=duration_mins,
|
||||
event_type=EXERCISE_EVENTTYPE,
|
||||
pump_event_id = "%s,%s" % (start.eventId, stop.eventId)
|
||||
pump_event_id = "%s" % start.seqNum
|
||||
)
|
||||
|
||||
def process_unended_sleep_stop(self, event, sleep_last_upload):
|
||||
@@ -220,7 +220,7 @@ class ProcessUserMode:
|
||||
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)
|
||||
pump_event_id="%s,%s" % (sleep_last_upload.get("pump_event_id",""), event.seqNum)
|
||||
)
|
||||
|
||||
def process_unended_exercise_stop(self, event, exercise_last_upload):
|
||||
@@ -240,5 +240,5 @@ class ProcessUserMode:
|
||||
reason=reason,
|
||||
duration=duration_mins,
|
||||
event_type=EXERCISE_EVENTTYPE,
|
||||
pump_event_id="%s,%s" % (exercise_last_upload.get("pump_event_id",""), event.eventId)
|
||||
pump_event_id="%s,%s" % (exercise_last_upload.get("pump_event_id",""), event.seqNum)
|
||||
)
|
||||
@@ -8,7 +8,7 @@ from ...features import DEFAULT_FEATURES
|
||||
from ... import features
|
||||
from ...domain.tandemsource.pump_settings import PumpSettings
|
||||
from ...parser.nightscout import (
|
||||
NightscoutEntry
|
||||
NightscoutEntry, ENTERED_BY
|
||||
)
|
||||
from ...secret import NIGHTSCOUT_PROFILE_UPLOAD_MODE
|
||||
|
||||
@@ -53,6 +53,8 @@ class UpdateProfiles:
|
||||
if ns_profile_obj is None:
|
||||
ns_profile_obj = {}
|
||||
|
||||
logger.info("Current Nightscout profile was authored by: %s" % (ns_profile_obj.get('enteredBy')))
|
||||
|
||||
diff, ns_profile_new = self.compare_profiles(pump_settings, ns_profile_obj)
|
||||
if not diff:
|
||||
logger.info("Pump and Nightscout profiles up to date")
|
||||
@@ -145,6 +147,7 @@ class UpdateProfiles:
|
||||
return False, ns_profile_obj
|
||||
|
||||
logger.info("New Nightscout profile object: %s", new_ns_profile)
|
||||
new_ns_profile['enteredBy'] = ENTERED_BY
|
||||
return True, new_ns_profile
|
||||
|
||||
def nightscout_profiles_identical(self, configured: dict, translated: dict) -> bool:
|
||||
|
||||
@@ -26,6 +26,9 @@ class NightscoutApi(tconnectsync.nightscout.NightscoutApi):
|
||||
def last_uploaded_activity(self, activityType):
|
||||
raise NotImplementedError
|
||||
|
||||
def last_uploaded_devicestatus(self, time_start=None, time_end=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def api_status(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
import arrow
|
||||
|
||||
from tconnectsync.sync.tandemsource.process_alarm import ProcessAlarm
|
||||
from tconnectsync.eventparser import events as eventtypes
|
||||
from tconnectsync.eventparser.generic import Event
|
||||
|
||||
from ...api.fake import TConnectApi
|
||||
from ...nightscout_fake import NightscoutApi
|
||||
|
||||
class TestProcessAlarm(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
self.tconnect = TConnectApi()
|
||||
self.nightscout = NightscoutApi()
|
||||
self.tconnect_device_id = 'abcdef'
|
||||
self.process = ProcessAlarm(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False)
|
||||
|
||||
def test_resume_alarm_ignored(self):
|
||||
self.nightscout.last_uploaded_entry = lambda *args, **kwargs: None
|
||||
|
||||
events = [
|
||||
# 2024-11-11 07:24:29-05:00
|
||||
Event(b'\x00\x05\x1f\xb8.\xad\x00\x0e\x91\xee\x00\x00\x00\x12\x00\x00 w\x01N\x0b\x16\x00\x00\x00\x00')
|
||||
]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidAlarmActivated)
|
||||
self.assertEqual(events[0].alarmid, eventtypes.LidAlarmActivated.AlarmidEnum.ResumePumpAlarm)
|
||||
|
||||
p = self.process.process(events, time_start=None, time_end=None)
|
||||
|
||||
self.assertEqual(len(p), 0)
|
||||
|
||||
|
||||
def test_empty_cartridge_alarm(self):
|
||||
self.nightscout.last_uploaded_entry = lambda *args, **kwargs: None
|
||||
|
||||
events = [
|
||||
# 2024-11-17 08:44:17-05:00
|
||||
Event(b'\x00\x05\x1f\xc0*a\x00\x0e\xf5\x90\x00\x00\x00\x08\x00\x00 1\x00\x00\x00gA\x1a\x1e\x84')
|
||||
]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidAlarmActivated)
|
||||
self.assertEqual(events[0].alarmid, eventtypes.LidAlarmActivated.AlarmidEnum.EmptyCartridgeAlarm)
|
||||
|
||||
p = self.process.process(events, time_start=None, time_end=None)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertDictEqual(p[0], {
|
||||
'eventType': 'Alarm',
|
||||
'created_at': '2024-11-17 08:44:17-05:00',
|
||||
'enteredBy': 'Pump (tconnectsync)',
|
||||
'notes': 'EmptyCartridgeAlarm',
|
||||
'reason': 'EmptyCartridgeAlarm',
|
||||
'pump_event_id': '980368'
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
|
||||
from tconnectsync.sync.tandemsource.process_device_status import ProcessDeviceStatus
|
||||
from tconnectsync.eventparser import events as eventtypes
|
||||
from tconnectsync.eventparser.generic import Event
|
||||
|
||||
from ...api.fake import TConnectApi
|
||||
from ...nightscout_fake import NightscoutApi
|
||||
|
||||
class TestProcessDeviceStatus(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
self.tconnect = TConnectApi()
|
||||
self.nightscout = NightscoutApi()
|
||||
self.tconnect_device_id = 'abcdef'
|
||||
self.process = ProcessDeviceStatus(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False)
|
||||
|
||||
def test_single_event_no_last_uploaded(self):
|
||||
self.nightscout.last_uploaded_devicestatus = lambda *args, **kwargs: None
|
||||
|
||||
events = [
|
||||
Event(b'\x00Q\x1f\xd6\x14g\x00\x0f\xf7\xa4A\xb2\xd3\xe2?L\xcc\xcd@~\xdeb\x0e\xf67\x00')
|
||||
]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidDailyBasal)
|
||||
self.assertEqual(events[0].batterychargepercentmsbRaw, 14)
|
||||
self.assertEqual(events[0].batterychargepercentlsbRaw, 246)
|
||||
self.assertEqual(events[0].batterylipomillivolts, 14080)
|
||||
|
||||
p = self.process.process(events, time_start=None, time_end=None)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertDictEqual(p[0], {
|
||||
'created_at': '2024-12-03 23:40:23-05:00',
|
||||
'device': 'Pump (tconnectsync)',
|
||||
'pump': {
|
||||
'battery': {
|
||||
'string': '48%',
|
||||
'voltage': 14.08
|
||||
}
|
||||
},
|
||||
'pump_event_id': '1046436'
|
||||
})
|
||||
|
||||
def test_single_event_already_uploaded(self):
|
||||
self.nightscout.last_uploaded_devicestatus = lambda *args, **kwargs: {'created_at': '2024-12-03 23:40:23-05:00'}
|
||||
|
||||
events = [
|
||||
Event(b'\x00Q\x1f\xd6\x14g\x00\x0f\xf7\xa4A\xb2\xd3\xe2?L\xcc\xcd@~\xdeb\x0e\xf67\x00')
|
||||
]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidDailyBasal)
|
||||
self.assertEqual(events[0].batterychargepercentmsbRaw, 14)
|
||||
self.assertEqual(events[0].batterychargepercentlsbRaw, 246)
|
||||
self.assertEqual(events[0].batterylipomillivolts, 14080)
|
||||
|
||||
p = self.process.process(events, time_start=None, time_end=None)
|
||||
|
||||
self.assertEqual(len(p), 0)
|
||||
|
||||
|
||||
def test_multiple_event_with_last_uploaded(self):
|
||||
self.nightscout.last_uploaded_devicestatus = lambda *args, **kwargs: {'created_at': '2024-12-03 23:40:23-05:00'}
|
||||
|
||||
events = [
|
||||
Event(b'\x00Q\x1f\xd6\x14g\x00\x0f\xf7\xa4A\xb2\xd3\xe2?L\xcc\xcd@~\xdeb\x0e\xf67\x00'),
|
||||
Event(b'\x00Q\x1f\xd6<?\x00\x0f\xf9[@\r\xcd{?\x9b\xa5\xe3?\xe3\x9a;\x0e\xf36\x00')
|
||||
]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidDailyBasal)
|
||||
self.assertEqual(events[0].raw.timestampRaw, 534123623) # 2024-12-03 23:40:23-05:00
|
||||
self.assertEqual(events[0].batterychargepercentmsbRaw, 14)
|
||||
self.assertEqual(events[0].batterychargepercentlsbRaw, 246)
|
||||
self.assertEqual(events[0].batterylipomillivolts, 14080)
|
||||
|
||||
self.assertEqual(type(events[1]), eventtypes.LidDailyBasal)
|
||||
self.assertEqual(events[1].raw.timestampRaw, 534133823) # 2024-12-04 02:30:23-05:00
|
||||
self.assertEqual(events[1].batterychargepercentmsbRaw, 14)
|
||||
self.assertEqual(events[1].batterychargepercentlsbRaw, 243)
|
||||
self.assertEqual(events[1].batterylipomillivolts, 13824)
|
||||
|
||||
p = self.process.process(events, time_start=None, time_end=None)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertDictEqual(p[0], {
|
||||
'created_at': '2024-12-04 02:30:23-05:00',
|
||||
'device': 'Pump (tconnectsync)',
|
||||
'pump': {
|
||||
'battery': {
|
||||
'string': '47%',
|
||||
'voltage': 13.824
|
||||
}
|
||||
},
|
||||
'pump_event_id': '1046875'
|
||||
})
|
||||
|
||||
def test_multiple_event_only_latest_applied(self):
|
||||
self.nightscout.last_uploaded_devicestatus = lambda *args, **kwargs: {'created_at': '2024-12-03 23:40:23-05:00'}
|
||||
|
||||
events = [
|
||||
Event(b'\x00Q\x1f\xd6\x14g\x00\x0f\xf7\xa4A\xb2\xd3\xe2?L\xcc\xcd@~\xdeb\x0e\xf67\x00'),
|
||||
Event(b'\x00Q\x1f\xd6<?\x00\x0f\xf9[@\r\xcd{?\x9b\xa5\xe3?\xe3\x9a;\x0e\xf36\x00'),
|
||||
Event(b'\x00Q\x1f\xd6k\x1f\x00\x0f\xfc>A\x0e\xa3\x80?\x9a~\xfa@\x11z6\x0e\xee5\x00')
|
||||
]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidDailyBasal)
|
||||
self.assertEqual(events[0].raw.timestampRaw, 534123623) # 2024-12-03 23:40:23-05:00
|
||||
self.assertEqual(events[0].batterychargepercentmsbRaw, 14)
|
||||
self.assertEqual(events[0].batterychargepercentlsbRaw, 246)
|
||||
self.assertEqual(events[0].batterylipomillivolts, 14080)
|
||||
|
||||
self.assertEqual(type(events[1]), eventtypes.LidDailyBasal)
|
||||
self.assertEqual(events[1].raw.timestampRaw, 534133823) # 2024-12-04 02:30:23-05:00
|
||||
self.assertEqual(events[1].batterychargepercentmsbRaw, 14)
|
||||
self.assertEqual(events[1].batterychargepercentlsbRaw, 243)
|
||||
self.assertEqual(events[1].batterylipomillivolts, 13824)
|
||||
|
||||
self.assertEqual(type(events[2]), eventtypes.LidDailyBasal)
|
||||
self.assertEqual(events[2].raw.timestampRaw, 534145823) # 2024-12-04 05:50:23-05:00
|
||||
self.assertEqual(events[2].batterychargepercentmsbRaw, 14)
|
||||
self.assertEqual(events[2].batterychargepercentlsbRaw, 238)
|
||||
self.assertEqual(events[2].batterylipomillivolts, 13568)
|
||||
|
||||
p = self.process.process(events, time_start=None, time_end=None)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertDictEqual(p[0], {
|
||||
'created_at': '2024-12-04 05:50:23-05:00',
|
||||
'device': 'Pump (tconnectsync)',
|
||||
'pump': {
|
||||
'battery': {
|
||||
'string': '46%',
|
||||
'voltage': 13.568
|
||||
}
|
||||
},
|
||||
'pump_event_id': '1047614'
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
import arrow
|
||||
|
||||
from tconnectsync.sync.tandemsource.process_user_mode import ProcessUserMode
|
||||
from tconnectsync.eventparser import events as eventtypes
|
||||
from tconnectsync.eventparser.generic import Event
|
||||
|
||||
from ...api.fake import TConnectApi
|
||||
from ...nightscout_fake import NightscoutApi
|
||||
|
||||
class TestProcessUserModeSleep(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
self.tconnect = TConnectApi()
|
||||
self.nightscout = NightscoutApi()
|
||||
self.tconnect_device_id = 'abcdef'
|
||||
self.process = ProcessUserMode(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False)
|
||||
|
||||
def test_single_start_sleep_active(self):
|
||||
self.nightscout.last_uploaded_entry = lambda *args, **kwargs: None
|
||||
|
||||
events = [
|
||||
# 2024-12-04 23:00:23-05:00 - sleep start
|
||||
Event(b'\x00\xe5\x1f\xd7\\\x87\x00\x10\t\xaa\x00\x01\x00\x01\x00\x00\x01\x00\x00\xf0\x01\x01\x00\x00\x00\x00')
|
||||
]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidAaUserModeChange)
|
||||
self.assertEqual(events[0].previoususermode, eventtypes.LidAaUserModeChange.PrevioususermodeEnum.Normal)
|
||||
self.assertEqual(events[0].currentusermode, eventtypes.LidAaUserModeChange.CurrentusermodeEnum.Sleeping)
|
||||
self.assertEqual(events[0].requestedaction, eventtypes.LidAaUserModeChange.RequestedactionEnum.StartSleep)
|
||||
|
||||
time_end = arrow.get('2024-12-04T23:00:30-05:00')
|
||||
p = self.process.process(events, time_start=None, time_end=time_end)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertDictEqual(p[0], {
|
||||
"eventType": 'Sleep',
|
||||
"reason": 'Sleep (Scheduled) - Not Ended',
|
||||
"notes": 'Sleep (Scheduled) - Not Ended',
|
||||
"duration": 0.11666666666666667,
|
||||
"created_at": '2024-12-04 23:00:23-05:00',
|
||||
"enteredBy": "Pump (tconnectsync)",
|
||||
"pump_event_id": '1051050'
|
||||
})
|
||||
self.assertEqual(self.nightscout.deleted_entries, [])
|
||||
|
||||
def test_single_stop_sleep_no_last_uploaded(self):
|
||||
self.nightscout.last_uploaded_entry = lambda *args, **kwargs: None
|
||||
|
||||
events = [
|
||||
# 2024-12-04 09:01:23-05:00 - sleep end
|
||||
Event(b'\x00\xe5\x1f\xd6\x97\xe3\x00\x0f\xfe\xb0\x00\x02\x01\x00\x00\x00\x00\x00\x00\xf0\x01\x01\x00\x00\x00\x00')
|
||||
]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidAaUserModeChange)
|
||||
self.assertEqual(events[0].previoususermode, eventtypes.LidAaUserModeChange.PrevioususermodeEnum.Sleeping)
|
||||
self.assertEqual(events[0].currentusermode, eventtypes.LidAaUserModeChange.CurrentusermodeEnum.Normal)
|
||||
self.assertEqual(events[0].requestedaction, eventtypes.LidAaUserModeChange.RequestedactionEnum.StopSleep)
|
||||
|
||||
time_end = arrow.get('2024-12-04T23:00:30-05:00')
|
||||
p = self.process.process(events, time_start=None, time_end=time_end)
|
||||
|
||||
self.assertEqual(len(p), 0)
|
||||
self.assertEqual(self.nightscout.deleted_entries, [])
|
||||
|
||||
|
||||
def test_start_and_stop_sleep(self):
|
||||
self.nightscout.last_uploaded_entry = lambda *args, **kwargs: None
|
||||
|
||||
events = [
|
||||
# 2024-12-04 23:00:23-05:00 - sleep start
|
||||
Event(b'\x00\xe5\x1f\xd7\\\x87\x00\x10\t\xaa\x00\x01\x00\x01\x00\x00\x01\x00\x00\xf0\x01\x01\x00\x00\x00\x00'),
|
||||
# 2024-12-05 09:01:23-05:00 - sleep end
|
||||
Event(b'\x00\xe5\x1f\xd7\xe9c\x00\x10\x10\xf8\x00\x02\x01\x00\x00\x00\x00\x00\x00\xf0\x01\x01\x00\x00\x00\x00')
|
||||
]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidAaUserModeChange)
|
||||
self.assertEqual(events[0].previoususermode, eventtypes.LidAaUserModeChange.PrevioususermodeEnum.Normal)
|
||||
self.assertEqual(events[0].currentusermode, eventtypes.LidAaUserModeChange.CurrentusermodeEnum.Sleeping)
|
||||
self.assertEqual(events[0].requestedaction, eventtypes.LidAaUserModeChange.RequestedactionEnum.StartSleep)
|
||||
|
||||
|
||||
self.assertEqual(type(events[1]), eventtypes.LidAaUserModeChange)
|
||||
self.assertEqual(events[1].previoususermode, eventtypes.LidAaUserModeChange.PrevioususermodeEnum.Sleeping)
|
||||
self.assertEqual(events[1].currentusermode, eventtypes.LidAaUserModeChange.CurrentusermodeEnum.Normal)
|
||||
self.assertEqual(events[1].requestedaction, eventtypes.LidAaUserModeChange.RequestedactionEnum.StopSleep)
|
||||
|
||||
time_end = arrow.get('2024-12-05T10:00:00-05:00')
|
||||
p = self.process.process(events, time_start=None, time_end=time_end)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertDictEqual(p[0], {
|
||||
"eventType": 'Sleep',
|
||||
"reason": 'Sleep (Scheduled)',
|
||||
"notes": 'Sleep (Scheduled)',
|
||||
"duration": 601.0,
|
||||
"created_at": '2024-12-04 23:00:23-05:00',
|
||||
"enteredBy": "Pump (tconnectsync)",
|
||||
"pump_event_id": '1051050,1052920'
|
||||
})
|
||||
self.assertEqual(self.nightscout.deleted_entries, [])
|
||||
|
||||
def test_stop_with_partial_last_uploaded(self):
|
||||
self.nightscout.last_uploaded_entry = lambda eventType, **kwargs: {
|
||||
'Sleep': {
|
||||
"eventType": 'Sleep',
|
||||
"reason": 'Sleep (Scheduled) - Not Ended',
|
||||
"notes": 'Sleep (Scheduled) - Not Ended',
|
||||
"duration": 0.11666666666666667,
|
||||
"created_at": '2024-12-04 23:00:23-05:00',
|
||||
"enteredBy": "Pump (tconnectsync)",
|
||||
"pump_event_id": '1051050',
|
||||
"_id": "id_to_delete"
|
||||
}
|
||||
}.get(eventType)
|
||||
|
||||
events = [
|
||||
# 2024-12-05 09:01:23-05:00 - sleep end
|
||||
Event(b'\x00\xe5\x1f\xd7\xe9c\x00\x10\x10\xf8\x00\x02\x01\x00\x00\x00\x00\x00\x00\xf0\x01\x01\x00\x00\x00\x00')
|
||||
]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidAaUserModeChange)
|
||||
self.assertEqual(events[0].previoususermode, eventtypes.LidAaUserModeChange.PrevioususermodeEnum.Sleeping)
|
||||
self.assertEqual(events[0].currentusermode, eventtypes.LidAaUserModeChange.CurrentusermodeEnum.Normal)
|
||||
self.assertEqual(events[0].requestedaction, eventtypes.LidAaUserModeChange.RequestedactionEnum.StopSleep)
|
||||
|
||||
time_end = arrow.get('2024-12-05T10:00:00-05:00')
|
||||
p = self.process.process(events, time_start=None, time_end=time_end)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertDictEqual(p[0], {
|
||||
"eventType": 'Sleep',
|
||||
"reason": 'Sleep (Scheduled)',
|
||||
"notes": 'Sleep (Scheduled)',
|
||||
"duration": 601.0,
|
||||
"created_at": '2024-12-04 23:00:23-05:00',
|
||||
"enteredBy": "Pump (tconnectsync)",
|
||||
"pump_event_id": '1051050,1052920'
|
||||
})
|
||||
self.assertEqual(self.nightscout.deleted_entries, ['treatments/id_to_delete'])
|
||||
|
||||
|
||||
class TestProcessUserModeExercise(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
self.tconnect = TConnectApi()
|
||||
self.nightscout = NightscoutApi()
|
||||
self.tconnect_device_id = 'abcdef'
|
||||
self.process = ProcessUserMode(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False)
|
||||
|
||||
def test_single_start_exercise_active(self):
|
||||
self.nightscout.last_uploaded_entry = lambda *args, **kwargs: None
|
||||
|
||||
events = [
|
||||
# 2024-12-04 23:00:23-05:00 - exercise start
|
||||
Event(b'\x00\xe5\x1f\xd7\\\x87\x00\x10\t\xaa\x00\x03\x00\x02\x00\x00\x01\x00\x00\xf0\x00\x01\x00\x00\x00\x00'),
|
||||
]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidAaUserModeChange)
|
||||
self.assertEqual(events[0].previoususermode, eventtypes.LidAaUserModeChange.PrevioususermodeEnum.Normal)
|
||||
self.assertEqual(events[0].currentusermode, eventtypes.LidAaUserModeChange.CurrentusermodeEnum.Exercising)
|
||||
self.assertEqual(events[0].requestedaction, eventtypes.LidAaUserModeChange.RequestedactionEnum.StartExercise)
|
||||
|
||||
time_end = arrow.get('2024-12-04T23:00:30-05:00')
|
||||
p = self.process.process(events, time_start=None, time_end=time_end)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertDictEqual(p[0], {
|
||||
"eventType": 'Exercise',
|
||||
"reason": 'Exercise - Not Ended',
|
||||
"notes": 'Exercise - Not Ended',
|
||||
"duration": 0.11666666666666667,
|
||||
"created_at": '2024-12-04 23:00:23-05:00',
|
||||
"enteredBy": "Pump (tconnectsync)",
|
||||
"pump_event_id": '1051050'
|
||||
})
|
||||
self.assertEqual(self.nightscout.deleted_entries, [])
|
||||
|
||||
def test_single_stop_exercise_no_last_uploaded(self):
|
||||
self.nightscout.last_uploaded_entry = lambda *args, **kwargs: None
|
||||
|
||||
events = [
|
||||
# 2024-12-04 09:01:23-05:00 - exercise end
|
||||
Event(b'\x00\xe5\x1f\xd6\x97\xe3\x00\x0f\xfe\xb0\x00\x04\x02\x00\x00\x00\x00\x00\x00\xf0\x01\x03\x00\x00\x00\x00')
|
||||
]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidAaUserModeChange)
|
||||
self.assertEqual(events[0].previoususermode, eventtypes.LidAaUserModeChange.PrevioususermodeEnum.Exercising)
|
||||
self.assertEqual(events[0].currentusermode, eventtypes.LidAaUserModeChange.CurrentusermodeEnum.Normal)
|
||||
self.assertEqual(events[0].requestedaction, eventtypes.LidAaUserModeChange.RequestedactionEnum.StopExercise)
|
||||
|
||||
time_end = arrow.get('2024-12-04T23:00:30-05:00')
|
||||
p = self.process.process(events, time_start=None, time_end=time_end)
|
||||
|
||||
self.assertEqual(len(p), 0)
|
||||
self.assertEqual(self.nightscout.deleted_entries, [])
|
||||
|
||||
|
||||
def test_start_and_stop_exercise(self):
|
||||
self.nightscout.last_uploaded_entry = lambda *args, **kwargs: None
|
||||
|
||||
events = [
|
||||
# 2024-12-04 23:00:23-05:00 - exercise start
|
||||
Event(b'\x00\xe5\x1f\xd7\\\x87\x00\x10\t\xaa\x00\x03\x00\x02\x00\x00\x01\x00\x00\xf0\x00\x01\x00\x00\x00\x00'),
|
||||
# 2024-12-05 09:01:23-05:00 - exercise end
|
||||
Event(b'\x00\xe5\x1f\xd7\xe9c\x00\x10\x10\xf8\x00\x04\x02\x00\x00\x00\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00')
|
||||
]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidAaUserModeChange)
|
||||
self.assertEqual(events[0].previoususermode, eventtypes.LidAaUserModeChange.PrevioususermodeEnum.Normal)
|
||||
self.assertEqual(events[0].currentusermode, eventtypes.LidAaUserModeChange.CurrentusermodeEnum.Exercising)
|
||||
self.assertEqual(events[0].requestedaction, eventtypes.LidAaUserModeChange.RequestedactionEnum.StartExercise)
|
||||
|
||||
|
||||
self.assertEqual(type(events[1]), eventtypes.LidAaUserModeChange)
|
||||
self.assertEqual(events[1].previoususermode, eventtypes.LidAaUserModeChange.PrevioususermodeEnum.Exercising)
|
||||
self.assertEqual(events[1].currentusermode, eventtypes.LidAaUserModeChange.CurrentusermodeEnum.Normal)
|
||||
self.assertEqual(events[1].requestedaction, eventtypes.LidAaUserModeChange.RequestedactionEnum.StopExercise)
|
||||
|
||||
time_end = arrow.get('2024-12-05T10:00:00-05:00')
|
||||
p = self.process.process(events, time_start=None, time_end=time_end)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertDictEqual(p[0], {
|
||||
"eventType": 'Exercise',
|
||||
"reason": 'Exercise',
|
||||
"notes": 'Exercise',
|
||||
"duration": 601.0,
|
||||
"created_at": '2024-12-04 23:00:23-05:00',
|
||||
"enteredBy": "Pump (tconnectsync)",
|
||||
"pump_event_id": '1051050,1052920'
|
||||
})
|
||||
self.assertEqual(self.nightscout.deleted_entries, [])
|
||||
|
||||
def test_stop_with_partial_last_uploaded(self):
|
||||
self.nightscout.last_uploaded_entry = lambda eventType, **kwargs: {
|
||||
'Exercise': {
|
||||
"eventType": 'Exercise',
|
||||
"reason": 'Exercise - Not Ended',
|
||||
"notes": 'Exercise - Not Ended',
|
||||
"duration": 0.11666666666666667,
|
||||
"created_at": '2024-12-04 23:00:23-05:00',
|
||||
"enteredBy": "Pump (tconnectsync)",
|
||||
"pump_event_id": '1051050',
|
||||
"_id": 'id_to_delete'
|
||||
}
|
||||
}.get(eventType)
|
||||
|
||||
events = [
|
||||
# 2024-12-05 09:01:23-05:00 - exercise end
|
||||
Event(b'\x00\xe5\x1f\xd7\xe9c\x00\x10\x10\xf8\x00\x04\x02\x00\x00\x00\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00')
|
||||
]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidAaUserModeChange)
|
||||
self.assertEqual(events[0].previoususermode, eventtypes.LidAaUserModeChange.PrevioususermodeEnum.Exercising)
|
||||
self.assertEqual(events[0].currentusermode, eventtypes.LidAaUserModeChange.CurrentusermodeEnum.Normal)
|
||||
self.assertEqual(events[0].requestedaction, eventtypes.LidAaUserModeChange.RequestedactionEnum.StopExercise)
|
||||
|
||||
time_end = arrow.get('2024-12-05T10:00:00-05:00')
|
||||
p = self.process.process(events, time_start=None, time_end=time_end)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertDictEqual(p[0], {
|
||||
"eventType": 'Exercise',
|
||||
"reason": 'Exercise',
|
||||
"notes": 'Exercise',
|
||||
"duration": 601.0,
|
||||
"created_at": '2024-12-04 23:00:23-05:00',
|
||||
"enteredBy": "Pump (tconnectsync)",
|
||||
"pump_event_id": '1051050,1052920'
|
||||
})
|
||||
self.assertEqual(self.nightscout.deleted_entries, ['treatments/id_to_delete'])
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user