diff --git a/README.md b/README.md index 1adade0..f21156c 100644 --- a/README.md +++ b/README.md @@ -390,9 +390,9 @@ You can use one of the same `run.sh` files referenced above, but remove the `--a This application utilizes three separate Tandem APIs for obtaining t:connect data, referenced here by the identifying part of their URLs: -* [**controliq**](https://github.com/jwoglom/tconnectsync/blob/master/tconnectsync/api/controliq.py) - Contains Control:IQ related data, namely a timeline of all Basal events uploaded by the pump, separated by type (temp basals, algorithmically-updated basals, or profile-updated basals). +* [**controliq**](https://github.com/jwoglom/tconnectsync/blob/master/tconnectsync/api/controliq.py) - Contains Control:IQ related data, namely a timeline of all Basal events uploaded by the pump, separated by type (temp basals, algorithmically-updated basals, or profile-updated basals). Additionally includes CGM and Bolus data. * [**android**](https://github.com/jwoglom/tconnectsync/blob/master/tconnectsync/api/android.py) - Used internally by the t:connect Android app, these API endpoints were discovered by reverse-engineering the Android app. Most of the API endpoints are used for uploading pump data, and tconnectsync uses one endpoint which returns the most recent event ID uploaded by the pump, so we know when more data has been uploaded. -* [**tconnectws2**](https://github.com/jwoglom/tconnectsync/blob/master/tconnectsync/api/ws2.py) - More legacy than the others, this seems to power the bulk of the main t:connect website. It is used to retrieve a CSV export of non-ControlIQ basal data, as well as bolus and IOB data. (I haven't found any mentions of bolus or IOB data in the Control:IQ-specific API.) +* [**tconnectws2**](https://github.com/jwoglom/tconnectsync/blob/master/tconnectsync/api/ws2.py) - More legacy than the others, this seems to power the bulk of the main t:connect website. It is used as a last resort due to severe performance issues with this API (see https://github.com/jwoglom/tconnectsync/issues/43). We can use it to retrieve a CSV export of non-ControlIQ basal data, as well as bolus and IOB data. It is only used for bolus data as a fallback, and for pump-reported IOB data if requested. Full tracking of pump events also uses a limited version of this API. I have only tested tconnectsync with a Tandem pump set in the US Eastern timezone. Tandem's (to us, undocumented) APIs are [a bit loose with timezones](https://github.com/jwoglom/tconnectsync/blob/master/tconnectsync/parser.py#L15), so please let me know if you notice any timezone-related bugs. ## Backfilling t:connect Data @@ -406,3 +406,15 @@ python3 main.py --start-date 2020-01-01 --end-date 2020-03-01 In order to bulk-import a lot of data, you may need to use shorter intervals, and invoke tconnectsync multiple times. Tandem's API endpoints occasionally return invalid data if you request too large of a data window which causes tconnectsync to error out mid-way through. One oddity when backfilling data is that the Control:IQ specific API endpoints return errors if they are queried before you updated your pump to utilize Control:IQ. This is [partially worked around in tconnectsync's code](https://github.com/jwoglom/tconnectsync/blob/d841c3811aeff3671d941a7d3ff4b80cce6a219e/main.py#L238), but you might need to update the logic if you did not switch to a Control:IQ enabled pump immediately after launch. + +## t:connect API Testing + +To test t:connect API endpoints in a Python shell, you can do something like the following: + +```python +import tconnectsync +tconnectsync.util.cli.enable_logging() +api = tconnectsync.util.cli.get_api() +# Make API calls, e.g. +therapy_timeline = api.controliq.therapy_timeline('2022-08-01', '2022-08-10') +``` \ No newline at end of file diff --git a/tconnectsync/api/controliq.py b/tconnectsync/api/controliq.py index a23f2a3..194eb8b 100644 --- a/tconnectsync/api/controliq.py +++ b/tconnectsync/api/controliq.py @@ -17,6 +17,7 @@ class ControlIQApi: userGuid = None accessToken = None accessTokenExpiresAt = None + tconnect_software_ver = None def __init__(self, email, password): self.login(email, password) @@ -48,7 +49,8 @@ class ControlIQApi: def _build_login_data(self, email, password, soup): try: - version = soup.select_one("#footer_version").text + version = soup.select_one("#footer_version").text.strip() + self.tconnect_software_ver = version logger.info("Reported tconnect software version: %s" % version) except Exception: logger.warn("Unable to find tconnect software version") @@ -132,10 +134,7 @@ class ControlIQApi: startDate = parse_date(start) endDate = parse_date(end) - return self.get('tconnect/controliq/api/summary/users/%s' % (self.userGuid), { - "startDate": startDate, - "endDate": endDate - }) + return self.get('tconnect/controliq/api/summary/users/%s?startDate=%s&endDate=%s' % (self.userGuid, startDate, endDate), {}) """ Returns active account features, including the date when ControlIQ was enabled. diff --git a/tconnectsync/check.py b/tconnectsync/check.py index 3ee4cf2..f1213f5 100644 --- a/tconnectsync/check.py +++ b/tconnectsync/check.py @@ -21,7 +21,7 @@ Attempts to authenticate with each t:connect API, and returns the output of a sample API call from each. Also attempts to connect to the Nightscout API. """ -def check_login(tconnect, time_start, time_end, verbose=False, sanitize=False): +def check_login(tconnect, time_start, time_end, verbose=False, sanitize=True): errors = 0 loglines = [] @@ -76,6 +76,7 @@ def check_login(tconnect, time_start, time_end, verbose=False, sanitize=False): try: summary = tconnect.controliq.dashboard_summary(time_start, time_end) debug("ControlIQ dashboard summary: \n%s" % pformat(summary)) + log("tconnect_software_ver: %s" % tconnect.controliq.tconnect_software_ver) except Exception as e: log("Error occurred querying ControlIQ API for dashboard_summary:") log(e) @@ -128,7 +129,7 @@ def check_login(tconnect, time_start, time_end, verbose=False, sanitize=False): log("Last therapy_timeline_csv reading: \n%s" % pformat(ttcsv["readingData"][-1])) lastReadingTime = TConnectEntry._datetime_parse(ttcsv["readingData"][-1]['EventDateTime']) except Exception as e: - log("Error occurred querying WS2 therapy_timeline_csv:") + log("Error occurred querying WS2 therapy_timeline_csv. This is okay so long as you are not using the PUMP_EVENTS or IOB sync features.") log(e) errors += 1 diff --git a/tconnectsync/secret.py b/tconnectsync/secret.py index b9a00f2..dccb8c4 100644 --- a/tconnectsync/secret.py +++ b/tconnectsync/secret.py @@ -31,7 +31,7 @@ def get_bool(name, default): TCONNECT_EMAIL = get('TCONNECT_EMAIL', 'email@email.com') TCONNECT_PASSWORD = get('TCONNECT_PASSWORD', 'password') -PUMP_SERIAL_NUMBER = get_number('PUMP_SERIAL_NUMBER', '11111111') +PUMP_SERIAL_NUMBER = int(get_number('PUMP_SERIAL_NUMBER', '11111111')) NS_URL = get('NS_URL', 'https://yournightscouturl/') NS_SECRET = get('NS_SECRET', 'apisecret') diff --git a/tconnectsync/util/__init__.py b/tconnectsync/util/__init__.py index f60db25..4f2f8b8 100644 --- a/tconnectsync/util/__init__.py +++ b/tconnectsync/util/__init__.py @@ -1,5 +1,8 @@ import arrow +from . import cli +from . import constants + def timeago(timestamp): seconds = (arrow.get() - arrow.get(timestamp)).total_seconds() fmt = '%s ago' if seconds >= 0 else 'in %s' diff --git a/tconnectsync/util/cli.py b/tconnectsync/util/cli.py new file mode 100644 index 0000000..57708ce --- /dev/null +++ b/tconnectsync/util/cli.py @@ -0,0 +1,20 @@ +import logging + +""" +Enables logging at the specified level for all loggers +inside the tconnectsync package, and sets up a basicConfig +to print those log messages to stderr. +""" +def enable_logging(level=logging.DEBUG): + logging.basicConfig() + for logger in logging.root.manager.loggerDict: + if logger.startswith('tconnectsync'): + logging.getLogger(logger).setLevel(level) + +""" +Returns a TConnectApi object with default secret parameters. +""" +def get_api(): + from ..api import TConnectApi + from ..secret import TCONNECT_EMAIL, TCONNECT_PASSWORD + return TConnectApi(TCONNECT_EMAIL, TCONNECT_PASSWORD) \ No newline at end of file