Compare commits

...
9 Commits
12 changed files with 135 additions and 64 deletions
+2 -1
View File
@@ -30,7 +30,8 @@ jobs:
pipenv install --system
- name: Run pipenv check
run: |
pipenv check
pipenv check \
--ignore 51499 # DDoS attack in wheel package, which is unsupported in python 3.7
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
Generated
+39 -39
View File
@@ -16,18 +16,18 @@
"default": {
"arrow": {
"hashes": [
"sha256:05caf1fd3d9a11a1135b2b6f09887421153b94558e5ef4d090b567b47173ac2b",
"sha256:d622c46ca681b5b3e3574fcb60a04e5cc81b9625112d5fb2b44220c36c892177"
"sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1",
"sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"
],
"index": "pypi",
"version": "==1.2.2"
"version": "==1.2.3"
},
"beautifulsoup4": {
"hashes": [
"sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30",
"sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"
],
"markers": "python_full_version >= '3.6.0'",
"markers": "python_version >= '3.6'",
"version": "==4.11.1"
},
"bs4": {
@@ -39,27 +39,27 @@
},
"certifi": {
"hashes": [
"sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d",
"sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
"sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14",
"sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"
],
"markers": "python_full_version >= '3.6.0'",
"version": "==2022.6.15"
"markers": "python_version >= '3.6'",
"version": "==2022.9.24"
},
"charset-normalizer": {
"hashes": [
"sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5",
"sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
],
"markers": "python_full_version >= '3.6.0'",
"version": "==2.1.0"
"markers": "python_version >= '3.6'",
"version": "==2.1.1"
},
"idna": {
"hashes": [
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
"sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
],
"markers": "python_version >= '3.5'",
"version": "==3.3"
"version": "==3.4"
},
"lxml": {
"hashes": [
@@ -156,11 +156,11 @@
},
"python-dotenv": {
"hashes": [
"sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f",
"sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"
"sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5",
"sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"
],
"index": "pypi",
"version": "==0.20.0"
"version": "==0.21.0"
},
"requests": {
"hashes": [
@@ -172,11 +172,11 @@
},
"requests-mock": {
"hashes": [
"sha256:0a2d38a117c08bb78939ec163522976ad59a6b7fdd82b709e23bb98004a44970",
"sha256:8d72abe54546c1fc9696fa1516672f1031d72a55a1d66c85184f972a24ba0eba"
"sha256:2fdbb637ad17ee15c06f33d31169e71bf9fe2bdb7bc9da26185be0dd8d842699",
"sha256:59c9c32419a9fb1ae83ec242d98e889c45bd7d7a65d48375cc243ec08441658b"
],
"index": "pypi",
"version": "==1.9.3"
"version": "==1.10.0"
},
"six": {
"hashes": [
@@ -191,20 +191,20 @@
"sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759",
"sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"
],
"markers": "python_full_version >= '3.6.0'",
"markers": "python_version >= '3.6'",
"version": "==2.3.2.post1"
},
"tconnectsync": {
"path": ".",
"version": "==0.7.1"
"version": "==0.8.6"
},
"urllib3": {
"hashes": [
"sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec",
"sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6"
"sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc",
"sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'",
"version": "==1.26.10"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.26.13"
}
},
"develop": {
@@ -217,11 +217,11 @@
},
"jedi": {
"hashes": [
"sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d",
"sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"
"sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e",
"sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"
],
"markers": "python_version >= '3.6'",
"version": "==0.18.1"
"version": "==0.18.2"
},
"parso": {
"hashes": [
@@ -233,27 +233,27 @@
},
"prompt-toolkit": {
"hashes": [
"sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0",
"sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289"
"sha256:535c29c31216c77302877d5120aef6c94ff573748a5b5ca5b1b1f76f5e700c73",
"sha256:ced598b222f6f4029c0800cefaa6a17373fb580cd093223003475ce32805c35b"
],
"markers": "python_full_version >= '3.6.2'",
"version": "==3.0.30"
"version": "==3.0.33"
},
"ptpython": {
"hashes": [
"sha256:99636899ab0e4d026e2ecc9368269114f387b4bb5411e57f072b0bde724d9f99",
"sha256:eafd4ced27ca5dc370881d4358d1ab5041b32d88d31af8e3c24167fe4af64ed6"
"sha256:1ec28ba37959e8e6c4880fcf4748ad5c61341d4c1bfd24a38a4d5b97af1f475a",
"sha256:a57b9952eac4a12540a4dfb4ccd4a243403eceb27b0d13245b5e4144c8731d32"
],
"index": "pypi",
"version": "==3.0.20"
"version": "==3.0.21"
},
"pygments": {
"hashes": [
"sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb",
"sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"
"sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1",
"sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"
],
"markers": "python_version >= '3.6'",
"version": "==2.12.0"
"version": "==2.13.0"
},
"wcwidth": {
"hashes": [
+1 -1
View File
@@ -1,6 +1,6 @@
[metadata]
name = tconnectsync
version = 0.8.5
version = 0.8.9
author = James Woglom
author_email = j@wogloms.net
description = Syncs Tandem t:connect pump data to Nightscout for the t:slim X2
+3 -3
View File
@@ -55,10 +55,10 @@ class ControlIQApi:
self.tconnect_software_ver = version
logger.info("Reported tconnect software version: %s" % version)
if version != self.LAST_CONFIRMED_SOFTWARE_VERSION:
logger.warn("Newer API version than last confirmed working. Saw %s and expected %s" % (version, self.LAST_CONFIRMED_SOFTWARE_VERSION))
logger.warn("If you experience any issues, please report them to https://github.com/jwoglom/tconnectsync")
logger.warning("Newer API version than last confirmed working. Saw %s and expected %s" % (version, self.LAST_CONFIRMED_SOFTWARE_VERSION))
logger.warning("If you experience any issues, please report them to https://github.com/jwoglom/tconnectsync")
except Exception:
logger.warn("Unable to find tconnect software version")
logger.warning("Unable to find tconnect software version")
pass
return {
"__LASTFOCUS": "",
+3 -3
View File
@@ -76,7 +76,7 @@ class Autoupdate:
logger.error("Exiting with error code due to AUTOUPDATE_RESTART_ON_FAILURE")
return 1
else:
logger.warn(AutoupdateFailureWarning(("%s: An event index change was recorded, but no new data was found via the API. " % datetime.datetime.now()) +
logger.warning(AutoupdateFailureWarning(("%s: An event index change was recorded, but no new data was found via the API. " % datetime.datetime.now()) +
"The %s was %d minutes ago. Resetting TConnectApi to attempt to solve this problem." %
("last processed event" if self.last_successful_process_time_range else "start of autoupdate", (now - last_action_or_start)//60)))
@@ -143,7 +143,7 @@ class Autoupdate:
# uploaded, so
if len(self.time_diffs_between_attempts) >= 3:
# The pump hasn't sent us data that, based on previous cadence, we were expecting
logger.warn(AutoupdateNoIndexChangeWarning("Sleeping %d seconds after unexpected no index change based on previous cadence. (New data might be delayed.)" %
logger.warning(AutoupdateNoIndexChangeWarning("Sleeping %d seconds after unexpected no index change based on previous cadence. (New data might be delayed.)" %
int(self.secret.AUTOUPDATE_UNEXPECTED_NO_INDEX_SLEEP_SECONDS)))
logger.debug("Last event time: %s, time diffs between attempts: %s" % (self.last_event_time, self.time_diffs_between_attempts))
@@ -204,4 +204,4 @@ class AutoupdateNoNewDataDetectedError(AutoupdateError):
pass
class AutoupdateNoIndexChangeWarning(AutoupdateWarning):
pass
pass
+49
View File
@@ -429,3 +429,52 @@ class BolusTherapyEvent(TherapyEvent):
'type': 'BG',
'uploadId': 0}
"""
class BasalTherapyEvent(TherapyEvent):
"""
{
'basalRate': {
'duration': 0,
'percent': 0,
'value': 0.0
},
'displayInHistory': 0,
'eventDateTime': '2022-12-02T00:00:00',
'note': {
'id': 0,
'indexId': '16403',
'eventTypeId': 90,
'sourceRecordId': 0,
'eventId': 0,
'active': False
},
'noteDate': {},
'requestDateTime': '0001-01-01T00:00:00',
'type': 'Basal',
'description': 'NDE',
'sourceRecId': xxx,
'eventTypeId': 0,
'indexId': 0,
'uploadId': 0,
'interactive': 1,
'tempRateId': 0,
'tempRateCompleted': 0,
'tempRateActivated': 0
}
"""
basalRateValue = None
basalRatePercent = None
basalRateDuration = None
eventTime = None
@classmethod
def parse(_, json):
self = CGMTherapyEvent()
TherapyEvent.parse(self, json)
if 'basalRate' in json:
self.basalRateValue = json['basalRate']['value']
self.basalRatePercent = json['basalRate']['percent']
self.basalRateDuration = json['basalRate']['duration']
self.eventTime = json['eventDateTime']
return self
+11 -2
View File
@@ -77,9 +77,18 @@ class NightscoutApi:
return j[0]
return None
ret = internal(False)
ret = None
try:
ret = internal(False)
except ApiException as e:
logger.warning("last_uploaded_entry with no t_to_space: %s", e)
ret = None
if ret is None and (time_start or time_end):
ret = internal(True)
try:
ret = internal(True)
except ApiException as e:
logger.warning("last_uploaded_entry with t_to_space: %s", e)
ret = None
if ret is not None:
logger.warning("last_uploaded_entry with eventType=%s time_start=%s time_end=%s only returned data when timestamps contained a space" % (eventType, time_start, time_end))
return ret
+6 -2
View File
@@ -1,4 +1,4 @@
from tconnectsync.domain.therapy_event import BolusTherapyEvent, CGMTherapyEvent, BGTherapyEvent
from tconnectsync.domain.therapy_event import BolusTherapyEvent, CGMTherapyEvent, BGTherapyEvent, BasalTherapyEvent
from tconnectsync.parser.tconnect import TConnectEntry
import logging
@@ -9,6 +9,7 @@ def split_therapy_events(ciqTherapyEvents):
bolusEvents = []
cgmEvents = []
bgEvents = []
basalEvents = []
for e in ciqTherapyEvents['event']:
event = TConnectEntry.parse_therapy_event(e)
if isinstance(event, BolusTherapyEvent):
@@ -17,7 +18,10 @@ def split_therapy_events(ciqTherapyEvents):
cgmEvents.append(event)
elif isinstance(event, BGTherapyEvent):
bgEvents.append(event)
elif isinstance(event, BasalTherapyEvent):
basalEvents.append(event)
logger.debug("split_therapy_events: %d bolus, %d CGM, %d BG" % (len(bolusEvents), len(cgmEvents), len(bgEvents)))
logger.debug("split_therapy_events: %d bolus, %d CGM, %d BG, %d basal" % (len(bolusEvents), len(cgmEvents), len(bgEvents), len(basalEvents)))
# TODO: BG events (CGM Calibration) values are not currently returned from ciq_therapy_events.py
return bolusEvents, cgmEvents
+3 -1
View File
@@ -3,7 +3,7 @@ import sys
import arrow
from tconnectsync.domain.bolus import Bolus
from tconnectsync.domain.therapy_event import BolusTherapyEvent, CGMTherapyEvent, BGTherapyEvent
from tconnectsync.domain.therapy_event import BolusTherapyEvent, CGMTherapyEvent, BGTherapyEvent, BasalTherapyEvent
try:
from ..secret import TIMEZONE_NAME
@@ -202,6 +202,8 @@ class TConnectEntry:
return CGMTherapyEvent.parse(data)
elif data["type"] == "BG":
return BGTherapyEvent.parse(data)
elif data["type"] == "Basal":
return BasalTherapyEvent.parse(data)
raise UnknownTherapyEventException(data)
+10 -10
View File
@@ -86,19 +86,19 @@ def process_time_range(tconnect, nightscout, time_start, time_end, pretend, feat
ciqFallingBack or \
BOLUS_BG in features or \
IOB in features:
logger.warn("Downloading t:connect CSV data")
logger.warning("Downloading t:connect CSV data")
if bolusFallingBack:
logger.warn("Falling back on WS2 CSV data source because BOLUS is an enabled feature and CIQ bolus data was empty!!")
logger.warning("Falling back on WS2 CSV data source because BOLUS is an enabled feature and CIQ bolus data was empty!!")
if ciqFallingBack:
logger.warn("Falling back on WS2 CSV data source because CGM is an enabled feature and CIQ cgm data was empty!!")
logger.warning("Falling back on WS2 CSV data source because CGM is an enabled feature and CIQ cgm data was empty!!")
if BOLUS_BG in features:
logger.warn("Falling back on WS2 CSV data source because BOLUS_BG is an enabled feature. " +
logger.warning("Falling back on WS2 CSV data source because BOLUS_BG is an enabled feature. " +
"Please consider disabling this feature to improve synchronization reliability.")
if IOB in features:
logger.warn("Falling back on WS2 CSV data source because IOB is an enabled feature. " +
logger.warning("Falling back on WS2 CSV data source because IOB is an enabled feature. " +
"Please consider disabling this feature to improve synchronization reliability.")
logger.warn("<!!> The WS2 data source is unreliable and may prevent timely synchronization")
logger.warning("<!!> The WS2 data source is unreliable and may prevent timely synchronization")
csvdata = tconnect.ws2.therapy_timeline_csv(time_start, time_end)
csvReadingData = csvdata["readingData"]
@@ -147,8 +147,8 @@ def process_time_range(tconnect, nightscout, time_start, time_end, pretend, feat
pumpEvents = process_ciq_activity_events(ciqTherapyTimelineData)
logger.debug("CIQ activity events: %s" % pumpEvents)
logger.warn("Using WS2 data source for basalsuspension because PUMP_EVENTS is an enabled feature")
logger.warn("<!!> The WS2 data source is unreliable and may prevent timely synchronization")
logger.warning("Using WS2 data source for basalsuspension because PUMP_EVENTS is an enabled feature")
logger.warning("<!!> The WS2 data source is unreliable and may prevent timely synchronization")
ws2BasalSuspension = tconnect.ws2.basalsuspension(time_start, time_end)
bsPumpEvents = process_basalsuspension_events(ws2BasalSuspension)
@@ -167,7 +167,7 @@ def process_time_range(tconnect, nightscout, time_start, time_end, pretend, feat
bolusEvents = process_bolus_events(ciqBolusData, source="ciq")
if csvBolusData and not bolusEvents:
logger.warn("Falling back on non-CIQ csvBolusData")
logger.warning("Falling back on non-CIQ csvBolusData")
bolusEvents = process_bolus_events(csvBolusData, source="csv")
logger.debug("ciq bolusEvents: %s" % bolusEvents)
@@ -184,4 +184,4 @@ def process_time_range(tconnect, nightscout, time_start, time_end, pretend, feat
logger.debug("Finished writing iob events")
logger.info("Wrote %d events to Nightscout this process cycle" % added)
return added
return added
+6
View File
@@ -34,6 +34,12 @@ def process_bolus_events(bolusdata, cgmEvents=None, source=""):
else:
logger.warning("Skipping non-completed %s bolus data (was a bolus in progress?): %s parsed: %s" % (source, b, parsed))
continue
if parsed.is_extended_bolus:
if not parsed.bolex_start_time and not parsed.start_time:
logger.warning("Skipping non-completed %s extended bolus data with no start time: %s parsed: %s" % (source, b, parsed))
elif not parsed.bolex_start_time and parsed.start_time:
logger.warning("Setting bolex_start_time to start_time for non-completed %s extended bolus: %s parsed: %s" % (source, b, parsed))
parsed.bolex_start_time = parsed.start_time
if parsed.bg and cgmEvents:
requested_at = parsed.request_time if not parsed.extended_bolus else parsed.bolex_start_time
+2 -2
View File
@@ -24,9 +24,9 @@ def build_mock_logger():
logger.error(*args, **kwargs)
def fake_warn(*args, **kwargs):
logger.warn(*args, **kwargs)
logger.warning(*args, **kwargs)
with patch("tconnectsync.autoupdate.logger.error") as mock_error, patch("tconnectsync.autoupdate.logger.warn") as mock_warn:
with patch("tconnectsync.autoupdate.logger.error") as mock_error, patch("tconnectsync.autoupdate.logger.warning") as mock_warn:
mock_error.side_effect = fake_error
mock_warn.side_effect = fake_warn
yield (mock_error, mock_warn)