diff options
author | Andrew Cady <d@jerkface.net> | 2017-01-26 11:53:35 +0000 |
---|---|---|
committer | Andrew Cady <d@jerkface.net> | 2017-01-26 13:04:13 +0000 |
commit | 5b79e4eb51a6140c3747ee135af36b4c2c285484 (patch) | |
tree | fccc294b3bb13e1d3daec7050cfe989e3be469a7 | |
parent | 896265b0b523014ea4b4105e3fe87bdc234e8b62 (diff) |
dexcom_dumper: improved JSON output; new option --hours
-rw-r--r-- | dexcom_reader/dexcom_dumper.py | 108 |
1 files 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 | |||
3 | import sys | 3 | import sys |
4 | import json | 4 | import json |
5 | import requests | 5 | import requests |
6 | from pytz import UTC | ||
6 | from sys import stdout, stderr | 7 | from sys import stdout, stderr |
7 | from datetime import timedelta, datetime | 8 | from datetime import timedelta, datetime |
8 | from time import sleep | 9 | from time import sleep |
10 | from itertools import islice, takewhile | ||
11 | from database_records import GenericTimestampedRecord | ||
9 | 12 | ||
10 | from optparse import OptionParser | 13 | from optparse import OptionParser |
11 | 14 | ||
12 | G5_IS_DEFAULT = True | 15 | G5_IS_DEFAULT = True |
13 | DEFAULT_PAGE_COUNT = 2 | ||
14 | 16 | ||
15 | parser = OptionParser() | 17 | parser = OptionParser() |
16 | parser.add_option("--g4", action="store_false", dest="g5", default=G5_IS_DEFAULT, help="use Dexcom G4 instead of Dexcom G5") | 18 | 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 | |||
18 | 20 | ||
19 | parser.add_option("-a", "--all", action="store_const", dest="command", const="dump_everything", help="dump all available records") | 21 | parser.add_option("-a", "--all", action="store_const", dest="command", const="dump_everything", help="dump all available records") |
20 | parser.add_option("-p", "--poll", action="store_const", dest="command", const="poll", help="poll for latest CGM record") | 22 | parser.add_option("-p", "--poll", action="store_const", dest="command", const="poll", help="poll for latest CGM record") |
23 | parser.add_option("--test", action="store_const", dest="command", const="test", help="test") | ||
21 | 24 | ||
22 | parser.add_option("-n", type="int", dest="num_records", default=DEFAULT_PAGE_COUNT, help="number of pages of CGM records to display") | 25 | parser.add_option("--hours", type="int", dest="hours", default=None, help="display N most recent hours of CGM records") |
26 | parser.add_option("-n", type="int", dest="num_records", default=None, help="number of CGM records to display") | ||
23 | parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="verbosity (currently for debugging)") | 27 | parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="verbosity (currently for debugging)") |
24 | parser.add_option("-H", "--human", action="store_true", dest="human", help="print human-readable times") | 28 | parser.add_option("-H", "--human", action="store_true", dest="human", help="print human-readable times") |
25 | parser.add_option("-j", "--json", action="store_true", dest="json", help="print JSON output") | 29 | parser.add_option("-j", "--json", action="store_true", dest="json", help="print JSON output") |
@@ -33,6 +37,9 @@ HUMAN = options.human | |||
33 | JSON = options.json | 37 | JSON = options.json |
34 | HOST = options.host | 38 | HOST = options.host |
35 | 39 | ||
40 | if command is 'dump_cgm' and options.num_records is None and not options.hours: | ||
41 | options.hours = 2 | ||
42 | |||
36 | def get_dexcom_reader(): | 43 | def get_dexcom_reader(): |
37 | if options.g5: | 44 | if options.g5: |
38 | dd = readdata.DexcomG5.FindDevice() | 45 | dd = readdata.DexcomG5.FindDevice() |
@@ -43,25 +50,30 @@ def get_dexcom_reader(): | |||
43 | 50 | ||
44 | dr = get_dexcom_reader() | 51 | dr = get_dexcom_reader() |
45 | 52 | ||
46 | def dump_everything(): | 53 | def parseable_record_types(): |
47 | |||
48 | # record_types = ['METER_DATA', 'INSERTION_TIME', 'USER_EVENT_DATA', 'CAL_SET', 'SENSOR_DATA'] | ||
49 | |||
50 | unparseable = ['FIRMWARE_PARAMETER_DATA', 'RECEIVER_LOG_DATA', 'USER_SETTING_DATA', 'MAX_VALUE'] | 54 | unparseable = ['FIRMWARE_PARAMETER_DATA', 'RECEIVER_LOG_DATA', 'USER_SETTING_DATA', 'MAX_VALUE'] |
51 | parsed_to_xml = ['MANUFACTURING_DATA', 'PC_SOFTWARE_PARAMETER'] | 55 | parsed_to_xml = ['MANUFACTURING_DATA', 'PC_SOFTWARE_PARAMETER'] |
52 | skip = unparseable + parsed_to_xml | 56 | skip = unparseable + parsed_to_xml |
53 | record_types = filter(lambda v: not v in skip, constants.RECORD_TYPES) | 57 | return filter(lambda v: not v in skip, constants.RECORD_TYPES) |
54 | 58 | ||
55 | for t in record_types: | 59 | def choose_range(rs): |
60 | if options.hours: | ||
61 | now = dr.ReadSystemTime() | ||
62 | when = now - timedelta(hours=options.hours) | ||
63 | return takewhile(lambda r: r.system_time > when, rs) | ||
64 | else: | ||
65 | return islice(rs, options.num_records) | ||
66 | |||
67 | def dump_everything(): | ||
68 | for t in parseable_record_types(): | ||
56 | print t + ":" | 69 | print t + ":" |
57 | for r in dr.ReadRecords(t): | 70 | for r in choose_range(dr.iter_records(t)): |
58 | print r | 71 | print toJSON(r) if JSON else r |
59 | 72 | ||
60 | def dump_cgm(): | 73 | def dump_cgm(): |
61 | cgm_records = dr.ReadRecords('EGV_DATA', options.num_records) | 74 | for cr in reversed(list(choose_range(dr.iter_records('EGV_DATA')))): |
62 | for cr in cgm_records: | ||
63 | if not cr.display_only: | 75 | if not cr.display_only: |
64 | print cr | 76 | print toJSON(cr) if JSON else cr |
65 | 77 | ||
66 | def recent(t): | 78 | def recent(t): |
67 | now = dr.ReadSystemTime() | 79 | now = dr.ReadSystemTime() |
@@ -74,7 +86,7 @@ def print_verbose(s): | |||
74 | 86 | ||
75 | def read_recent_egv_data(): | 87 | def read_recent_egv_data(): |
76 | try: | 88 | try: |
77 | r = dr.ReadRecords('EGV_DATA', options.num_records)[-1] | 89 | r = dr.ReadRecords('EGV_DATA', 1)[-1] |
78 | if recent(r) and not r.is_special and not r.display_only: | 90 | if recent(r) and not r.is_special and not r.display_only: |
79 | return r | 91 | return r |
80 | else: | 92 | else: |
@@ -119,31 +131,25 @@ def POST(path, json_str): | |||
119 | resp = requests.post(HOST + path, data=json_str, | 131 | resp = requests.post(HOST + path, data=json_str, |
120 | headers={'Content-type': | 132 | headers={'Content-type': |
121 | 'application/json'}) | 133 | 'application/json'}) |
134 | print_verbose(resp.text) | ||
135 | resp.raise_for_status() | ||
122 | return True | 136 | return True |
123 | except: | 137 | except: |
138 | print_verbose(sys.exc_info()) | ||
124 | return False | 139 | return False |
125 | 140 | ||
126 | def strftime_JSON(t): | ||
127 | return t.strftime('%FT%T.0Z'); | ||
128 | |||
129 | def send_ping(now): | 141 | def send_ping(now): |
130 | if HOST: | 142 | if HOST: |
131 | POST('/ping', json.dumps(strftime_JSON(now))) | 143 | POST('/ping', toJSON(now)) |
132 | 144 | ||
133 | def print_cgm_bg(now, r): | 145 | def print_cgm_bg(now, r): |
134 | if HOST or JSON: | 146 | if HOST: |
135 | json_str = json.dumps({ | 147 | r.trend_arrow.replace('45_', 'DIAGONAL_', 1) |
136 | 'bgeEventTime': strftime_JSON(r.system_time), | 148 | POST('/bgevent', toJSON(r)) |
137 | 'bgeGlucose': r.glucose, | 149 | send_ping(now) |
138 | 'bgeTrendArrow': r.trend_arrow.replace('45_', 'DIAGONAL_', 1), | 150 | return |
139 | 'bgeDisplayOnly': r.display_only | 151 | elif JSON: |
140 | }) | 152 | print toJSON(r) |
141 | if HOST: | ||
142 | POST('/bgevent', json_str) | ||
143 | send_ping(now) | ||
144 | return | ||
145 | if JSON: | ||
146 | print json_str | ||
147 | else: | 153 | else: |
148 | print '%s: %s %s' % (format_times(now, r.system_time), r.glucose, r.trend_arrow) | 154 | print '%s: %s %s' % (format_times(now, r.system_time), r.glucose, r.trend_arrow) |
149 | stdout.flush() | 155 | stdout.flush() |
@@ -168,7 +174,45 @@ def poll(): | |||
168 | if (next_reading > 0): | 174 | if (next_reading > 0): |
169 | sleep_verbose(next_reading) | 175 | sleep_verbose(next_reading) |
170 | 176 | ||
177 | def since(when, rectype='EGV_DATA'): | ||
178 | return reversed(list(takewhile(lambda r: r.system_time > when, dr.iter_records(rectype)))) | ||
179 | |||
180 | def test(): | ||
181 | now = dr.ReadSystemTime() | ||
182 | POST('/ping', toJSON(now)) | ||
183 | |||
184 | for t in parseable_record_types(): | ||
185 | rs = dr.ReadRecords(t, 1) | ||
186 | if not rs: | ||
187 | continue | ||
188 | r=rs[-1] | ||
189 | print toJSON([t,r]) | ||
190 | |||
191 | class JSON_Time(json.JSONEncoder): | ||
192 | def default(self, o): | ||
193 | if isinstance(o, datetime): | ||
194 | if o.tzinfo is None: | ||
195 | return o.replace(tzinfo=UTC).isoformat() | ||
196 | else: | ||
197 | return o.isoformat() | ||
198 | |||
199 | return json.JSONEncoder.default(self, o) | ||
200 | |||
201 | class JSON_CGM(JSON_Time): | ||
202 | def default(self, o): | ||
203 | if isinstance(o, GenericTimestampedRecord): | ||
204 | op={} | ||
205 | for k in o.BASE_FIELDS + o.FIELDS: | ||
206 | op[k] = getattr(o, k) | ||
207 | return op | ||
208 | |||
209 | return JSON_Time.default(self, o) | ||
210 | |||
211 | def toJSON(o): | ||
212 | return json.dumps(o, cls=JSON_CGM) | ||
213 | |||
171 | {"dump_everything": dump_everything, | 214 | {"dump_everything": dump_everything, |
172 | "dump_cgm": dump_cgm, | 215 | "dump_cgm": dump_cgm, |
173 | "poll": poll | 216 | "poll": poll, |
217 | "test": test, | ||
174 | }[command]() | 218 | }[command]() |