From 5b79e4eb51a6140c3747ee135af36b4c2c285484 Mon Sep 17 00:00:00 2001 From: Andrew Cady Date: Thu, 26 Jan 2017 11:53:35 +0000 Subject: dexcom_dumper: improved JSON output; new option --hours --- dexcom_reader/dexcom_dumper.py | 108 +++++++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 32 deletions(-) diff --git a/dexcom_reader/dexcom_dumper.py b/dexcom_reader/dexcom_dumper.py index e279433..c531dfc 100644 --- a/dexcom_reader/dexcom_dumper.py +++ b/dexcom_reader/dexcom_dumper.py @@ -3,14 +3,16 @@ import readdata import sys import json import requests +from pytz import UTC from sys import stdout, stderr from datetime import timedelta, datetime from time import sleep +from itertools import islice, takewhile +from database_records import GenericTimestampedRecord from optparse import OptionParser G5_IS_DEFAULT = True -DEFAULT_PAGE_COUNT = 2 parser = OptionParser() parser.add_option("--g4", action="store_false", dest="g5", default=G5_IS_DEFAULT, help="use Dexcom G4 instead of Dexcom G5") @@ -18,8 +20,10 @@ parser.add_option("--g5", action="store_true", dest="g5", default=G5_IS_DEFAULT parser.add_option("-a", "--all", action="store_const", dest="command", const="dump_everything", help="dump all available records") parser.add_option("-p", "--poll", action="store_const", dest="command", const="poll", help="poll for latest CGM record") +parser.add_option("--test", action="store_const", dest="command", const="test", help="test") -parser.add_option("-n", type="int", dest="num_records", default=DEFAULT_PAGE_COUNT, help="number of pages of CGM records to display") +parser.add_option("--hours", type="int", dest="hours", default=None, help="display N most recent hours of CGM records") +parser.add_option("-n", type="int", dest="num_records", default=None, help="number of CGM records to display") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="verbosity (currently for debugging)") parser.add_option("-H", "--human", action="store_true", dest="human", help="print human-readable times") parser.add_option("-j", "--json", action="store_true", dest="json", help="print JSON output") @@ -33,6 +37,9 @@ HUMAN = options.human JSON = options.json HOST = options.host +if command is 'dump_cgm' and options.num_records is None and not options.hours: + options.hours = 2 + def get_dexcom_reader(): if options.g5: dd = readdata.DexcomG5.FindDevice() @@ -43,25 +50,30 @@ def get_dexcom_reader(): dr = get_dexcom_reader() -def dump_everything(): - -# record_types = ['METER_DATA', 'INSERTION_TIME', 'USER_EVENT_DATA', 'CAL_SET', 'SENSOR_DATA'] - +def parseable_record_types(): unparseable = ['FIRMWARE_PARAMETER_DATA', 'RECEIVER_LOG_DATA', 'USER_SETTING_DATA', 'MAX_VALUE'] parsed_to_xml = ['MANUFACTURING_DATA', 'PC_SOFTWARE_PARAMETER'] skip = unparseable + parsed_to_xml - record_types = filter(lambda v: not v in skip, constants.RECORD_TYPES) + return filter(lambda v: not v in skip, constants.RECORD_TYPES) - for t in record_types: +def choose_range(rs): + if options.hours: + now = dr.ReadSystemTime() + when = now - timedelta(hours=options.hours) + return takewhile(lambda r: r.system_time > when, rs) + else: + return islice(rs, options.num_records) + +def dump_everything(): + for t in parseable_record_types(): print t + ":" - for r in dr.ReadRecords(t): - print r + for r in choose_range(dr.iter_records(t)): + print toJSON(r) if JSON else r def dump_cgm(): - cgm_records = dr.ReadRecords('EGV_DATA', options.num_records) - for cr in cgm_records: + for cr in reversed(list(choose_range(dr.iter_records('EGV_DATA')))): if not cr.display_only: - print cr + print toJSON(cr) if JSON else cr def recent(t): now = dr.ReadSystemTime() @@ -74,7 +86,7 @@ def print_verbose(s): def read_recent_egv_data(): try: - r = dr.ReadRecords('EGV_DATA', options.num_records)[-1] + r = dr.ReadRecords('EGV_DATA', 1)[-1] if recent(r) and not r.is_special and not r.display_only: return r else: @@ -119,31 +131,25 @@ def POST(path, json_str): resp = requests.post(HOST + path, data=json_str, headers={'Content-type': 'application/json'}) + print_verbose(resp.text) + resp.raise_for_status() return True except: + print_verbose(sys.exc_info()) return False -def strftime_JSON(t): - return t.strftime('%FT%T.0Z'); - def send_ping(now): if HOST: - POST('/ping', json.dumps(strftime_JSON(now))) + POST('/ping', toJSON(now)) def print_cgm_bg(now, r): - if HOST or JSON: - json_str = json.dumps({ - 'bgeEventTime': strftime_JSON(r.system_time), - 'bgeGlucose': r.glucose, - 'bgeTrendArrow': r.trend_arrow.replace('45_', 'DIAGONAL_', 1), - 'bgeDisplayOnly': r.display_only - }) - if HOST: - POST('/bgevent', json_str) - send_ping(now) - return - if JSON: - print json_str + if HOST: + r.trend_arrow.replace('45_', 'DIAGONAL_', 1) + POST('/bgevent', toJSON(r)) + send_ping(now) + return + elif JSON: + print toJSON(r) else: print '%s: %s %s' % (format_times(now, r.system_time), r.glucose, r.trend_arrow) stdout.flush() @@ -168,7 +174,45 @@ def poll(): if (next_reading > 0): sleep_verbose(next_reading) +def since(when, rectype='EGV_DATA'): + return reversed(list(takewhile(lambda r: r.system_time > when, dr.iter_records(rectype)))) + +def test(): + now = dr.ReadSystemTime() + POST('/ping', toJSON(now)) + + for t in parseable_record_types(): + rs = dr.ReadRecords(t, 1) + if not rs: + continue + r=rs[-1] + print toJSON([t,r]) + +class JSON_Time(json.JSONEncoder): + def default(self, o): + if isinstance(o, datetime): + if o.tzinfo is None: + return o.replace(tzinfo=UTC).isoformat() + else: + return o.isoformat() + + return json.JSONEncoder.default(self, o) + +class JSON_CGM(JSON_Time): + def default(self, o): + if isinstance(o, GenericTimestampedRecord): + op={} + for k in o.BASE_FIELDS + o.FIELDS: + op[k] = getattr(o, k) + return op + + return JSON_Time.default(self, o) + +def toJSON(o): + return json.dumps(o, cls=JSON_CGM) + {"dump_everything": dump_everything, "dump_cgm": dump_cgm, - "poll": poll + "poll": poll, + "test": test, }[command]() -- cgit v1.2.3