upload parsed tconnect bolus, basal, and iob entries to nightscout

This commit is contained in:
James Woglom
2020-09-15 01:59:25 -04:00
parent ef5f549275
commit 60f534d7b7
6 changed files with 227 additions and 90 deletions
-2
View File
@@ -11,5 +11,3 @@ bs4 = "*"
arrow = "*"
lxml = "*"
[requires]
python_version = "3.7"
+6 -4
View File
@@ -58,7 +58,7 @@ class TConnectApi:
if r.status_code != 200:
print("API response:", r.status_code, r.text)
return r.json()
def _parse_date(self, date):
if type(date) == str:
return date
@@ -72,7 +72,7 @@ class TConnectApi:
"startDate": startDate,
"endDate": endDate
})
def _split_empty_sections(self, text):
sections = [[]]
sectionIndex = 0
@@ -84,13 +84,15 @@ class TConnectApi:
sectionIndex += 1
return sections + [None] * (4 - len(sections))
def _csv_to_dict(self, rawdata):
data = []
if not rawdata or len(rawdata) == 0:
return data
headers = rawdata[0].split(",")
for row in csv.reader(rawdata[1:]):
data.append({headers[i]: row[i] for i in range(len(row)) if i < len(headers)})
return data
+173
View File
@@ -0,0 +1,173 @@
#!/usr/bin/env python3
import sys
import datetime
import json
import hashlib
import requests
import arrow
from api import TConnectApi
from parser import TConnectEntry
from nightscout import (
NightscoutEntry,
upload_nightscout,
delete_nightscout,
last_uploaded_nightscout_entry,
last_uploaded_nightscout_activity,
BASAL_EVENTTYPE,
BOLUS_EVENTTYPE,
IOB_ACTIVITYTYPE
)
try:
from secret import TCONNECT_EMAIL, TCONNECT_PASSWORD
except Exception:
print('Unable to import secret.py')
sys.exit(1)
def process_basal_events(data):
suspensionEvents = {}
for s in data["suspensionDeliveryEvents"]:
entry = TConnectEntry.parse_suspension_entry(s)
suspensionEvents[entry["time"]] = entry
basalEvents = []
for b in data["basal"]["tempDeliveryEvents"]:
basalEvents.append(TConnectEntry.parse_basal_entry(b, delivery_type="tempDelivery"))
for b in data["basal"]["algorithmDeliveryEvents"]:
basalEvents.append(TConnectEntry.parse_basal_entry(b, delivery_type="algorithmDelivery"))
for b in data["basal"]["profileDeliveryEvents"]:
basalEvents.append(TConnectEntry.parse_basal_entry(b, delivery_type="profileDelivery"))
basalEvents.sort(key=lambda x: arrow.get(x["time"]))
for i in basalEvents:
print(i)
if i["time"] in suspensionEvents:
i["suspendReason"] = suspensionEvents[i["time"]]["suspendReason"]
return basalEvents
def ns_write_basal_events(basalEvents):
last_upload = last_uploaded_nightscout_entry(BASAL_EVENTTYPE)
last_upload_time = None
if last_upload:
last_upload_time = arrow.get(last_upload["created_at"])
print("Last Nightscout basal upload:", last_upload_time)
for event in basalEvents:
if last_upload_time and arrow.get(event["time"]) <= last_upload_time:
#print("Skipping already uploaded basal event:", event)
continue
entry = NightscoutEntry.basal(
value=event["basal_rate"],
duration_mins=event["duration_mins"],
created_at=event["time"],
reason=event["delivery_type"]
)
print("Processing basal:", event, "entry:", entry)
upload_nightscout(entry)
def process_bolus_events(bolusdata):
bolusEvents = []
for b in bolusdata:
parsed = TConnectEntry.parse_bolus_entry(b)
if parsed["completion"] != "Completed":
print("Skipping non-completed bolus data:", b, "parsed:", parsed)
continue
bolusEvents.append(parsed)
bolusEvents.sort(key=lambda x: arrow.get(x["completion_time"]))
return bolusEvents
def ns_write_bolus_events(bolusEvents):
last_upload = last_uploaded_nightscout_entry(BOLUS_EVENTTYPE)
last_upload_time = None
if last_upload:
last_upload_time = arrow.get(last_upload["created_at"])
print("Last Nightscout bolus upload:", last_upload_time)
for event in bolusEvents:
if last_upload_time and arrow.get(event["completion_time"]) <= last_upload_time:
#print("Skipping already uploaded bolus event:", event)
continue
entry = NightscoutEntry.bolus(
bolus=event["insulin"],
carbs=event["carbs"],
created_at=event["completion_time"],
notes="{}{}".format(event["description"], " (Override)" if event["user_override"] == "1" else "")
)
print("Processing bolus:", event, "entry:", entry)
upload_nightscout(entry)
def process_iob_events(iobdata):
iobEvents = []
for d in iobdata:
iobEvents.append(TConnectEntry.parse_iob_entry(d))
iobEvents.sort(key=lambda x: arrow.get(x["time"]))
return iobEvents
def ns_write_iob_events(iobEvents):
last_upload = last_uploaded_nightscout_activity(IOB_ACTIVITYTYPE)
last_upload_time = None
if last_upload:
last_upload_time = arrow.get(last_upload["created_at"])
print("Last Nightscout iob upload:", last_upload_time)
event = iobEvents[-1]
if last_upload_time and arrow.get(event["time"]) <= last_upload_time:
print("Skipping already uploaded iob event:", event)
return
entry = NightscoutEntry.iob(
iob=event["iob"],
created_at=event["time"]
)
print("Processing iob:", event, "entry:", entry)
upload_nightscout(entry, entity='activity')
# Delete the previous activity
if last_upload and '_id' in last_upload:
print("Deleting old iob entry:", last_upload)
delete_nightscout('activity/{}'.format(last_upload['_id']))
def main():
now = datetime.datetime.now()
tconnect = TConnectApi(TCONNECT_EMAIL, TCONNECT_PASSWORD)
basaldata = tconnect.therapy_timeline(now - datetime.timedelta(days=1), now)
# open("basaldata.json","w").write(json.dumps(data))
# basaldata = json.loads(open("basaldata.json").read())
basalEvents = process_basal_events(basaldata)
ns_write_basal_events(basalEvents)
csvdata = tconnect.therapy_timeline_csv(now - datetime.timedelta(days=1), now)
cgmdata, iobdata, bolusdata = csvdata
print("Most recent cgmdata:", cgmdata[-1])
# open("csvdata.json", "w").write(json.dumps(csvdata))
# cgmdata, iobdata, bolusdata = json.loads(open("csvdata.json").read())
bolusEvents = process_bolus_events(bolusdata)
ns_write_bolus_events(bolusEvents)
iobEvents = process_iob_events(iobdata)
ns_write_iob_events(iobEvents)
if __name__ == '__main__':
main()
+44 -7
View File
@@ -1,6 +1,8 @@
import sys
import requests
import hashlib
import time
import urllib.parse
try:
from secret import NS_URL, NS_SECRET, TIMEZONE_NAME
@@ -8,13 +10,16 @@ except Exception:
print('Unable to import Nightscout secrets from secret.py')
sys.exit(1)
ENTERED_BY = "Tandem Pump (tconnectsync)"
ENTERED_BY = "Pump (tconnectsync)"
BASAL_EVENTTYPE = "Temp Basal"
BOLUS_EVENTTYPE = "Combo Bolus"
IOB_ACTIVITYTYPE = "tconnect_iob"
class NightscoutEntry:
@staticmethod
def basal(value, duration_mins, created_at, reason=""):
return {
"eventType": "Temp Basal",
"eventType": BASAL_EVENTTYPE,
"reason": reason,
"duration": int(round(duration_mins)),
"absolute": float(value),
@@ -23,11 +28,11 @@ class NightscoutEntry:
"insulin": None,
"enteredBy": ENTERED_BY
}
@staticmethod
def bolus(bolus, carbs, created_at, notes=""):
return {
"eventType": "Meal Bolus",
"eventType": BOLUS_EVENTTYPE,
"created_at": created_at,
"carbs": carbs,
"insulin": bolus,
@@ -35,13 +40,45 @@ class NightscoutEntry:
"enteredBy": ENTERED_BY,
}
@staticmethod
def iob(iob, created_at):
return {
"activityType": IOB_ACTIVITYTYPE,
"iob": iob,
"created_at": created_at,
"enteredBy": ENTERED_BY
}
def upload_nightscout(ns_format):
upload = requests.post(NS_URL + 'api/v1/treatments?api_secret=' + NS_SECRET, json=ns_format, headers={
def upload_nightscout(ns_format, entity='treatments'):
upload = requests.post(NS_URL + 'api/v1/' + entity + '?api_secret=' + NS_SECRET, json=ns_format, headers={
'Accept': 'application/json',
'Content-Type': 'application/json',
'api-secret': hashlib.sha1(NS_SECRET.encode()).hexdigest()
})
print("Nightscout upload status:", upload.status_code, upload.text)
def delete_nightscout(entity):
upload = requests.delete(NS_URL + 'api/v1/' + entity + '?api_secret=' + NS_SECRET, json={}, headers={
'Accept': 'application/json',
'Content-Type': 'application/json',
'api-secret': hashlib.sha1(NS_SECRET.encode()).hexdigest()
})
print("Nightscout delete status:", upload.status_code, upload.text)
def last_uploaded_nightscout_entry(eventType):
latest = requests.get(NS_URL + 'api/v1/treatments?count=1&find[enteredBy]=' + urllib.parse.quote(ENTERED_BY) + '&find[eventType]=' + urllib.parse.quote(eventType) + '&ts=' + str(time.time()), headers={
'api-secret': hashlib.sha1(NS_SECRET.encode()).hexdigest()
})
j = latest.json()
if j and len(j) > 0:
return j[0]
return None
def last_uploaded_nightscout_activity(activityType):
latest = requests.get(NS_URL + 'api/v1/activity?find[enteredBy]=' + urllib.parse.quote(ENTERED_BY) + '&find[activityType]=' + urllib.parse.quote(activityType) + '&ts=' + str(time.time()), headers={
'api-secret': hashlib.sha1(NS_SECRET.encode()).hexdigest()
})
j = latest.json()
if j and len(j) > 0:
return j[0]
return None
+4 -4
View File
@@ -33,7 +33,7 @@ class TConnectEntry:
"duration_mins": duration_mins,
"basal_rate": basal_rate,
}
@staticmethod
def parse_suspension_entry(data):
time = TConnectEntry._epoch_parse(data["x"])
@@ -42,7 +42,7 @@ class TConnectEntry:
"continuation": data["continuation"],
"suspendReason": data["suspendReason"],
}
@staticmethod
def _datetime_parse(date):
return arrow.get(date, tzinfo=TIMEZONE_NAME)
@@ -55,7 +55,7 @@ class TConnectEntry:
"reading": data["Readings (CGM / BGM)"],
"reading_type": data["Description"],
}
@staticmethod
def parse_iob_entry(data):
# EventDateTime is stored in the user's timezone.
@@ -64,7 +64,7 @@ class TConnectEntry:
"iob": data["IOB"],
"event_id": data["EventID"],
}
@staticmethod
def parse_bolus_entry(data):
# RequestDateTime and CompletionDateTime are stored in the user's timezone.
-73
View File
@@ -1,73 +0,0 @@
#!/usr/bin/env python3
import sys
import datetime
import json
import hashlib
import requests
import arrow
from api import TConnectApi
from parser import TConnectEntry
from nightscout import NightscoutEntry, upload_nightscout
try:
from secret import TCONNECT_EMAIL, TCONNECT_PASSWORD
except Exception:
print('Unable to import secret.py')
sys.exit(1)
def process_basal_events(data):
suspensionEvents = {}
for s in data["suspensionDeliveryEvents"]:
entry = TConnectEntry.parse_suspension_entry(s)
suspensionEvents[entry["time"]] = entry
basalEvents = []
for b in data["basal"]["tempDeliveryEvents"]:
basalEvents.append(TConnectEntry.parse_basal_entry(b, delivery_type="tempDelivery"))
for b in data["basal"]["algorithmDeliveryEvents"]:
basalEvents.append(TConnectEntry.parse_basal_entry(b, delivery_type="algorithmDelivery"))
for b in data["basal"]["profileDeliveryEvents"]:
basalEvents.append(TConnectEntry.parse_basal_entry(b, delivery_type="profileDelivery"))
basalEvents.sort(key=lambda x: arrow.get(x["time"]))
for i in basalEvents:
print(i)
if i["time"] in suspensionEvents:
i["suspendReason"] = suspensionEvents[i["time"]]["suspendReason"]
return basalEvents
def main():
now = datetime.datetime.now()
# tconnect = TConnectApi(TCONNECT_EMAIL, TCONNECT_PASSWORD)
# data = tconnect.therapy_timeline(now - datetime.timedelta(days=1), now)
# open("basaldata.json","w").write(json.dumps(data))
data = json.loads(open("basaldata.json").read())
basalEvents = process_basal_events(data)
#csvdata = tconnect.therapy_timeline_csv()
#open("csvdata.json", "w").write(json.dumps(csvdata))
# csvdata = json.loads(open("csvdata.json").read())
# for b in csvdata[1]:
# print(TConnectEntry.parse_iob_entry(b))
if __name__ == '__main__':
main()