summaryrefslogtreecommitdiff
path: root/dexcom_reader
diff options
context:
space:
mode:
authorBen West <bewest@gmail.com>2014-05-24 16:42:13 -0700
committerBen West <bewest@gmail.com>2014-05-24 16:42:13 -0700
commit5d92368e5b10fb2d01ae25a2730e15cd05a84da6 (patch)
tree6f5264f423e8e5a9ddfd44f3222bc8ad215b6e2f /dexcom_reader
parent6b5ce2584b0fe2da991540a33c71b1f95cd8878a (diff)
prep for installable module
Diffstat (limited to 'dexcom_reader')
-rw-r--r--dexcom_reader/__init__.py0
-rw-r--r--dexcom_reader/constants.py94
-rw-r--r--dexcom_reader/crc16.py37
-rw-r--r--dexcom_reader/database_records.py189
-rw-r--r--dexcom_reader/packetwriter.py49
-rw-r--r--dexcom_reader/readdata.py259
-rw-r--r--dexcom_reader/util.py82
7 files changed, 710 insertions, 0 deletions
diff --git a/dexcom_reader/__init__.py b/dexcom_reader/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dexcom_reader/__init__.py
diff --git a/dexcom_reader/constants.py b/dexcom_reader/constants.py
new file mode 100644
index 0000000..2f60020
--- /dev/null
+++ b/dexcom_reader/constants.py
@@ -0,0 +1,94 @@
1import datetime
2
3
4class Error(Exception):
5 """Base error for dexcom reader."""
6
7
8class CrcError(Error):
9 """Failed to CRC properly."""
10
11
12DEXCOM_G4_USB_VENDOR = 0x22a3
13DEXCOM_G4_USB_PRODUCT = 0x0047
14
15BASE_TIME = datetime.datetime(2009, 1, 1)
16
17NULL = 0
18ACK = 1
19NAK = 2
20INVALID_COMMAND = 3
21INVALID_PARAM = 4
22INCOMPLETE_PACKET_RECEIVED = 5
23RECEIVER_ERROR = 6
24INVALID_MODE = 7
25PING = 10
26READ_FIRMWARE_HEADER = 11
27READ_DATABASE_PARTITION_INFO = 15
28READ_DATABASE_PAGE_RANGE = 16
29READ_DATABASE_PAGES = 17
30READ_DATABASE_PAGE_HEADER = 18
31READ_TRANSMITTER_ID = 25
32WRITE_TRANSMITTER_ID = 26
33READ_LANGUAGE = 27
34WRITE_LANGUAGE = 28
35READ_DISPLAY_TIME_OFFSET = 29
36WRITE_DISPLAY_TIME_OFFSET = 30
37READ_RTC = 31
38RESET_RECEIVER = 32
39READ_BATTERY_LEVEL = 33
40READ_SYSTEM_TIME = 34
41READ_SYSTEM_TIME_OFFSET = 35
42WRITE_SYSTEM_TIME = 36
43READ_GLUCOSE_UNIT = 37
44WRITE_GLUCOSE_UNIT = 38
45READ_BLINDED_MODE = 39
46WRITE_BLINDED_MODE = 40
47READ_CLOCK_MODE = 41
48WRITE_CLOCK_MODE = 42
49READ_DEVICE_MODE = 43
50ERASE_DATABASE = 45
51SHUTDOWN_RECEIVER = 46
52WRITE_PC_PARAMETERS = 47
53READ_BATTERY_STATE = 48
54READ_HARDWARE_BOARD_ID = 49
55READ_FIRMWARE_SETTINGS = 54
56READ_ENABLE_SETUP_WIZARD_FLAG = 55
57READ_SETUP_WIZARD_STATE = 57
58MAX_COMMAND = 59
59MAX_POSSIBLE_COMMAND = 255
60
61EGV_VALUE_MASK = 1023
62EGV_DISPLAY_ONLY_MASK = 32768
63EGV_TREND_ARROW_MASK = 15
64
65BATTERY_STATES = [None, 'CHARGING', 'NOT_CHARGING', 'NTC_FAULT', 'BAD_BATTERY']
66
67RECORD_TYPES = [
68 'MANUFACTURING_DATA', 'FIRMWARE_PARAMETER_DATA', 'PC_SOFTWARE_PARAMETER',
69 'SENSOR_DATA', 'EGV_DATA', 'CAL_SET', 'DEVIATION', 'INSERTION_TIME',
70 'RECEIVER_LOG_DATA', 'RECEIVER_ERROR_DATA', 'METER_DATA', 'USER_EVENT_DATA',
71 'USER_SETTING_DATA', 'MAX_VALUE',
72]
73
74TREND_ARROW_VALUES = [None, 'DOUBLE_UP', 'SINGLE_UP', '45_UP', 'FLAT',
75 '45_DOWN', 'SINGLE_DOWN', 'DOUBLE_DOWN', 'NOT_COMPUTABLE',
76 'OUT_OF_RANGE']
77
78SPECIAL_GLUCOSE_VALUES = {0: None,
79 1: 'SENSOR_NOT_ACTIVE',
80 2: 'MINIMAL_DEVIATION',
81 3: 'NO_ANTENNA',
82 5: 'SENSOR_NOT_CALIBRATED',
83 6: 'COUNTS_DEVIATION',
84 9: 'ABSOLUTE_DEVIATION',
85 10: 'POWER_DEVIATION',
86 12: 'BAD_RF'}
87
88
89LANGUAGES = {
90 0: None,
91 1033: 'ENGLISH',
92}
93
94
diff --git a/dexcom_reader/crc16.py b/dexcom_reader/crc16.py
new file mode 100644
index 0000000..0a55332
--- /dev/null
+++ b/dexcom_reader/crc16.py
@@ -0,0 +1,37 @@
1TABLE = [
2 0, 4129, 8258, 12387, 16516, 20645, 24774, 28903, 33032, 37161, 41290,
3 45419, 49548, 53677, 57806, 61935, 4657, 528, 12915, 8786, 21173, 17044,
4 29431, 25302, 37689, 33560, 45947, 41818, 54205, 50076, 62463, 58334, 9314,
5 13379, 1056, 5121, 25830, 29895, 17572, 21637, 42346, 46411, 34088, 38153,
6 58862, 62927, 50604, 54669, 13907, 9842, 5649, 1584, 30423, 26358, 22165,
7 18100, 46939, 42874, 38681, 34616, 63455, 59390, 55197, 51132, 18628, 22757,
8 26758, 30887, 2112, 6241, 10242, 14371, 51660, 55789, 59790, 63919, 35144,
9 39273, 43274, 47403, 23285, 19156, 31415, 27286, 6769, 2640, 14899, 10770,
10 56317, 52188, 64447, 60318, 39801, 35672, 47931, 43802, 27814, 31879, 19684,
11 23749, 11298, 15363, 3168, 7233, 60846, 64911, 52716, 56781, 44330, 48395,
12 36200, 40265, 32407, 28342, 24277, 20212, 15891, 11826, 7761, 3696, 65439,
13 61374, 57309, 53244, 48923, 44858, 40793, 36728, 37256, 33193, 45514, 41451,
14 53516, 49453, 61774, 57711, 4224, 161, 12482, 8419, 20484, 16421, 28742,
15 24679, 33721, 37784, 41979, 46042, 49981, 54044, 58239, 62302, 689, 4752,
16 8947, 13010, 16949, 21012, 25207, 29270, 46570, 42443, 38312, 34185, 62830,
17 58703, 54572, 50445, 13538, 9411, 5280, 1153, 29798, 25671, 21540, 17413,
18 42971, 47098, 34713, 38840, 59231, 63358, 50973, 55100, 9939, 14066, 1681,
19 5808, 26199, 30326, 17941, 22068, 55628, 51565, 63758, 59695, 39368, 35305,
20 47498, 43435, 22596, 18533, 30726, 26663, 6336, 2273, 14466, 10403, 52093,
21 56156, 60223, 64286, 35833, 39896, 43963, 48026, 19061, 23124, 27191, 31254,
22 2801, 6864, 10931, 14994, 64814, 60687, 56684, 52557, 48554, 44427, 40424,
23 36297, 31782, 27655, 23652, 19525, 15522, 11395, 7392, 3265, 61215, 65342,
24 53085, 57212, 44955, 49082, 36825, 40952, 28183, 32310, 20053, 24180, 11923,
25 16050, 3793, 7920
26]
27
28
29def crc16(buf, start=None, end=None):
30 if start is None:
31 start = 0
32 if end is None:
33 end = len(buf)
34 num = 0
35 for i in range(start, end):
36 num = ((num<<8)&0xff00) ^ TABLE[((num>>8)&0xff)^ord(buf[i])]
37 return num & 0xffff
diff --git a/dexcom_reader/database_records.py b/dexcom_reader/database_records.py
new file mode 100644
index 0000000..bde0c67
--- /dev/null
+++ b/dexcom_reader/database_records.py
@@ -0,0 +1,189 @@
1import crc16
2import constants
3import struct
4import util
5
6
7class BaseDatabaseRecord(object):
8 FORMAT = None
9
10 @classmethod
11 def _CheckFormat(cls):
12 if cls.FORMAT is None or not cls.FORMAT:
13 raise NotImplementedError("Subclasses of %s need to define FORMAT"
14 % cls.__name__)
15
16 @classmethod
17 def _ClassFormat(cls):
18 cls._CheckFormat()
19 return struct.Struct(cls.FORMAT)
20
21 @classmethod
22 def _ClassSize(cls):
23 return cls._ClassFormat().size
24
25 @property
26 def FMT(self):
27 self._CheckFormat()
28 return _ClassFormat()
29
30 @property
31 def SIZE(self):
32 return self._ClassSize()
33
34 @property
35 def crc(self):
36 return self.data[-1]
37
38 def __init__(self, data, raw_data):
39 self.raw_data = raw_data
40 self.data = data
41 self.check_crc()
42
43 def check_crc(self):
44 local_crc = self.calculate_crc()
45 if local_crc != self.crc:
46 raise constants.CrcError('Could not parse %s' % self.__class__.__name__)
47
48 def dump(self):
49 return ''.join('\\x%02x' % ord(c) for c in self.raw_data)
50
51 def calculate_crc(self):
52 return crc16.crc16(self.raw_data[:-2])
53
54 @classmethod
55 def Create(cls, data, record_counter):
56 offset = record_counter * cls._ClassSize()
57 raw_data = data[offset:offset + cls._ClassSize()]
58 unpacked_data = cls._ClassFormat().unpack(raw_data)
59 return cls(unpacked_data, raw_data)
60
61
62class GenericTimestampedRecord(BaseDatabaseRecord):
63 @property
64 def system_time(self):
65 return util.ReceiverTimeToTime(self.data[0])
66
67 @property
68 def display_time(self):
69 return util.ReceiverTimeToTime(self.data[1])
70
71
72class GenericXMLRecord(GenericTimestampedRecord):
73 FORMAT = '<II490sH'
74
75 @property
76 def xmldata(self):
77 data = self.data[2].replace("\x00", "")
78 return data
79
80
81class InsertionRecord(GenericTimestampedRecord):
82 FORMAT = '<3IcH'
83
84 @property
85 def insertion_time(self):
86 return util.ReceiverTimeToTime(self.data[2])
87
88 @property
89 def session_state(self):
90 states = [None, 'REMOVED', 'EXPIRED', 'RESIDUAL_DEVIATION',
91 'COUNTS_DEVIATION', 'SECOND_SESSION', 'OFF_TIME_LOSS',
92 'STARTED', 'BAD_TRANSMITTER', 'MANUFACTURING_MODE']
93 return states[ord(self.data[3])]
94
95 def __repr__(self):
96 return '%s: state=%s' % (self.display_time, self.session_state)
97
98
99class MeterRecord(GenericTimestampedRecord):
100 FORMAT = '<2IHIH'
101
102 @property
103 def meter_glucose(self):
104 return self.data[2]
105
106 @property
107 def meter_time(self):
108 return util.ReceiverTimeToTime(self.data[3])
109
110 def __repr__(self):
111 return '%s: Meter BG:%s' % (self.display_time, self.meter_glucose)
112
113
114class EventRecord(GenericTimestampedRecord):
115 # sys_time,display_time,glucose,meter_time,crc
116 FORMAT = '<2I2c2IH'
117
118 @property
119 def event_type(self):
120 event_types = [None, 'CARBS', 'INSULIN', 'HEALTH', 'EXCERCISE',
121 'MAX_VALUE']
122 return event_types[ord(self.data[2])]
123
124 @property
125 def event_sub_type(self):
126 subtypes = {'HEALTH': [None, 'ILLNESS', 'STRESS', 'HIGH_SYMPTOMS',
127 'LOW_SYMTOMS', 'CYCLE', 'ALCOHOL'],
128 'EXCERCISE': [None, 'LIGHT', 'MEDIUM', 'HEAVY',
129 'MAX_VALUE']}
130 if self.event_type in subtypes:
131 return subtypes[self.event_type][ord(self.data[3])]
132
133 @property
134 def display_time(self):
135 return util.ReceiverTimeToTime(self.data[4])
136
137 @property
138 def event_value(self):
139 value = self.data[5]
140 if self.event_type == 'INSULIN':
141 value = value / 100.0
142 return value
143
144 def __repr__(self):
145 return '%s: event_type=%s sub_type=%s value=%s' % (self.display_time, self.event_type,
146 self.event_sub_type, self.event_value)
147
148
149class EGVRecord(GenericTimestampedRecord):
150 # uint, uint, ushort, byte, ushort
151 # (system_seconds, display_seconds, glucose, trend_arrow, crc)
152 FORMAT = '<2IHcH'
153
154 @property
155 def full_glucose(self):
156 return self.data[2]
157
158 @property
159 def full_trend(self):
160 return self.data[3]
161
162 @property
163 def display_only(self):
164 return bool(self.full_glucose & constants.EGV_DISPLAY_ONLY_MASK)
165
166 @property
167 def glucose(self):
168 return self.full_glucose & constants.EGV_VALUE_MASK
169
170 @property
171 def glucose_special_meaning(self):
172 if self.glucose in constants.SPECIAL_GLUCOSE_VALUES:
173 return constants.SPECIAL_GLUCOSE_VALUES[self.glucose]
174
175 @property
176 def is_special(self):
177 return self.glucose_special_meaning is not None
178
179 @property
180 def trend_arrow(self):
181 arrow_value = ord(self.full_trend) & constants.EGV_TREND_ARROW_MASK
182 return constants.TREND_ARROW_VALUES[arrow_value]
183
184 def __repr__(self):
185 if self.is_special:
186 return '%s: %s' % (self.display_time, self.glucose_special_meaning)
187 else:
188 return '%s: CGM BG:%s (%s) DO:%s' % (self.display_time, self.glucose,
189 self.trend_arrow, self.display_only)
diff --git a/dexcom_reader/packetwriter.py b/dexcom_reader/packetwriter.py
new file mode 100644
index 0000000..6e03342
--- /dev/null
+++ b/dexcom_reader/packetwriter.py
@@ -0,0 +1,49 @@
1import crc16
2import struct
3
4class PacketWriter(object):
5 MAX_PAYLOAD = 1584
6 MIN_LEN = 6
7 MAX_LEN = 1590
8 SOF = 0x01
9 OFFSET_SOF = 0
10 OFFSET_LENGTH = 1
11 OFFSET_CMD = 3
12 OFFSET_PAYLOAD = 4
13
14 def __init__(self):
15 self._packet = None
16
17 def Clear(self):
18 self._packet = None
19
20 def NewSOF(self, v):
21 self._packet[0] = chr(v)
22
23 def PacketString(self):
24 return ''.join(self._packet)
25
26 def AppendCrc(self):
27 self.SetLength()
28 ps = self.PacketString()
29 crc = crc16.crc16(ps, 0, len(ps))
30 for x in struct.pack('H', crc):
31 self._packet.append(x)
32
33 def SetLength(self):
34 self._packet[1] = chr(len(self._packet) + 2)
35
36 def _Add(self, x):
37 try:
38 len(x)
39 for y in x:
40 self._Add(y)
41 except:
42 self._packet.append(x)
43
44 def ComposePacket(self, command, payload=None):
45 assert self._packet is None
46 self._packet = ["\x01", None, "\x00", chr(command)]
47 if payload:
48 self._Add(payload)
49 self.AppendCrc()
diff --git a/dexcom_reader/readdata.py b/dexcom_reader/readdata.py
new file mode 100644
index 0000000..f398407
--- /dev/null
+++ b/dexcom_reader/readdata.py
@@ -0,0 +1,259 @@
1import crc16
2import constants
3import database_records
4import datetime
5import serial
6import sys
7import time
8import packetwriter
9import struct
10import re
11import util
12import xml.etree.ElementTree as ET
13import numpy
14import platform
15
16
17class ReadPacket(object):
18 def __init__(self, command, data):
19 self._command = command
20 self._data = data
21
22 @property
23 def command(self):
24 return self._command
25
26 @property
27 def data(self):
28 return self._data
29
30
31class Dexcom(object):
32 @staticmethod
33 def FindDevice():
34 return util.find_usbserial(constants.DEXCOM_G4_USB_VENDOR,
35 constants.DEXCOM_G4_USB_PRODUCT)
36
37 @classmethod
38 def LocateAndDownload(cls):
39 device = cls.FindDevice()
40 if not device:
41 sys.stderr.write('Could not find Dexcom G4 Receiver!\n')
42 sys.exit(1)
43 else:
44 dex = cls(device)
45 print ('Found %s S/N: %s'
46 % (dex.GetFirmwareHeader().get('ProductName'),
47 dex.ReadManufacturingData().get('SerialNumber')))
48 print 'Transmitter paired: %s' % dex.ReadTransmitterId()
49 print 'Battery Status: %s (%d%%)' % (dex.ReadBatteryState(),
50 dex.ReadBatteryLevel())
51 print 'Record count:'
52 print '- Meter records: %d' % (len(dex.ReadRecords('METER_DATA')))
53 print '- CGM records: %d' % (len(dex.ReadRecords('EGV_DATA')))
54 print ('- CGM commitable records: %d'
55 % (len([not x.display_only for x in dex.ReadRecords('EGV_DATA')])))
56 print '- Event records: %d' % (len(dex.ReadRecords('USER_EVENT_DATA')))
57 print '- Insertion records: %d' % (len(dex.ReadRecords('INSERTION_TIME')))
58
59 def __init__(self, port):
60 self._port_name = port
61 self._port = None
62
63 def Connect(self):
64 if self._port is None:
65 self._port = serial.Serial(port=self._port_name, baudrate=115200)
66
67 def Disconnect(self):
68 if self._port is not None:
69 self._port.close()
70
71 @property
72 def port(self):
73 if self._port is None:
74 self.Connect()
75 return self._port
76
77 def write(self, *args, **kwargs):
78 return self.port.write(*args, **kwargs)
79
80 def read(self, *args, **kwargs):
81 return self.port.read(*args, **kwargs)
82
83 def readpacket(self, timeout=None):
84 total_read = 4
85 initial_read = self.read(total_read)
86 all_data = initial_read
87 if ord(initial_read[0]) == 1:
88 command = initial_read[3]
89 data_number = struct.unpack('<H', initial_read[1:3])[0]
90 if data_number > 6:
91 toread = abs(data_number-6)
92 second_read = self.read(toread)
93 all_data += second_read
94 total_read += toread
95 out = second_read
96 else:
97 out = ''
98 suffix = self.read(2)
99 sent_crc = struct.unpack('<H', suffix)[0]
100 local_crc = crc16.crc16(all_data, 0, total_read)
101 if sent_crc != local_crc:
102 raise constants.CrcError("readpacket Failed CRC check")
103 num1 = total_read + 2
104 return ReadPacket(command, out)
105 else:
106 raise constants.Error('Error reading packet header!')
107
108 def Ping(self):
109 self.WriteCommand(constants.PING)
110 packet = self.readpacket()
111 return ord(packet.command) == constants.ACK
112
113 def WritePacket(self, packet):
114 if not packet:
115 raise constants.Error('Need a packet to send')
116 packetlen = len(packet)
117 if packetlen < 6 or packetlen > 1590:
118 raise constants.Error('Invalid packet length')
119 self.flush()
120 self.write(packet)
121
122 def WriteCommand(self, command_id, *args, **kwargs):
123 p = packetwriter.PacketWriter()
124 p.ComposePacket(command_id, *args, **kwargs)
125 self.WritePacket(p.PacketString())
126
127 def GenericReadCommand(self, command_id):
128 self.WriteCommand(command_id)
129 return self.readpacket()
130
131 def ReadTransmitterId(self):
132 return self.GenericReadCommand(constants.READ_TRANSMITTER_ID).data
133
134 def ReadLanguage(self):
135 lang = self.GenericReadCommand(constants.READ_LANGUAGE).data
136 return constants.LANGUAGES[struct.unpack('H', lang)[0]]
137
138 def ReadBatteryLevel(self):
139 level = self.GenericReadCommand(constants.READ_BATTERY_LEVEL).data
140 return struct.unpack('I', level)[0]
141
142 def ReadBatteryState(self):
143 state = self.GenericReadCommand(constants.READ_BATTERY_STATE).data
144 return constants.BATTERY_STATES[ord(state)]
145
146 def ReadRTC(self):
147 rtc = self.GenericReadCommand(constants.READ_RTC).data
148 return util.ReceiverTimeToTime(struct.unpack('I', rtc)[0])
149
150 def ReadSystemTime(self):
151 rtc = self.GenericReadCommand(constants.READ_SYSTEM_TIME).data
152 return util.ReceiverTimeToTime(struct.unpack('I', rtc)[0])
153
154 def ReadSystemTimeOffset(self):
155 rtc = self.GenericReadCommand(constants.READ_SYSTEM_TIME_OFFSET).data
156 return datetime.timedelta(seconds=struct.unpack('i', rtc)[0])
157
158 def ReadDisplayTimeOffset(self):
159 rtc = self.GenericReadCommand(constants.READ_DISPLAY_TIME_OFFSET).data
160 return datetime.timedelta(seconds=struct.unpack('i', rtc)[0])
161
162 def ReadDisplayTime(self):
163 return self.ReadSystemTime() + self.ReadDisplayTimeOffset()
164
165 def ReadGlucoseUnit(self):
166 UNIT_TYPE = (None, 'mg/dL', 'mmol/L')
167 gu = self.GenericReadCommand(constants.READ_GLUCOSE_UNIT).data
168 return UNIT_TYPE[ord(gu[0])]
169
170 def ReadClockMode(self):
171 CLOCK_MODE = (24, 12)
172 cm = self.GenericReadCommand(constants.READ_CLOCK_MODE).data
173 return CLOCK_MODE[ord(cm[0])]
174
175 def ReadDeviceMode(self):
176 return self.GenericReadCommand(constants.READ_DEVICE_MODE).data
177
178 def ReadManufacturingData(self):
179 data = self.ReadRecords('MANUFACTURING_DATA')[0].xmldata
180 return ET.fromstring(data)
181
182 def flush(self):
183 self.port.flush()
184
185 def clear(self):
186 self.port.flushInput()
187 self.port.flushOutput()
188
189 def GetFirmwareHeader(self):
190 i = self.GenericReadCommand(constants.READ_FIRMWARE_HEADER)
191 return ET.fromstring(i.data)
192
193 def GetFirmwareSettings(self):
194 i = self.GenericReadCommand(constants.READ_FIRMWARE_SETTINGS)
195 return ET.fromstring(i.data)
196
197 def DataPartitions(self):
198 i = self.GenericReadCommand(constants.READ_DATABASE_PARTITION_INFO)
199 return ET.fromstring(i.data)
200
201 def ReadDatabasePageRange(self, record_type):
202 record_type_index = constants.RECORD_TYPES.index(record_type)
203 self.WriteCommand(constants.READ_DATABASE_PAGE_RANGE,
204 chr(record_type_index))
205 packet = self.readpacket()
206 return struct.unpack('II', packet.data)
207
208 def ReadDatabasePage(self, record_type, page):
209 record_type_index = constants.RECORD_TYPES.index(record_type)
210 self.WriteCommand(constants.READ_DATABASE_PAGES,
211 (chr(record_type_index), struct.pack('I', page), chr(1)))
212 packet = self.readpacket()
213 assert ord(packet.command) == 1
214 # first index (uint), numrec (uint), record_type (byte), revision (byte),
215 # page# (uint), r1 (uint), r2 (uint), r3 (uint), ushort (Crc)
216 header_format = '<2I2c4IH'
217 header_data_len = struct.calcsize(header_format)
218 header = struct.unpack_from(header_format, packet.data)
219 header_crc = crc16.crc16(packet.data[:header_data_len-2])
220 assert header_crc == header[-1]
221 assert ord(header[2]) == record_type_index
222 assert header[4] == page
223 packet_data = packet.data[header_data_len:]
224
225 return self.ParsePage(header, packet_data)
226
227 def GenericRecordYielder(self, header, data, record_type):
228 for x in range(header[1]):
229 yield record_type.Create(data, x)
230
231 def ParsePage(self, header, data):
232 record_type = constants.RECORD_TYPES[ord(header[2])]
233 generic_parser_map = {
234 'USER_EVENT_DATA': database_records.EventRecord,
235 'METER_DATA': database_records.MeterRecord,
236 'INSERTION_TIME': database_records.InsertionRecord,
237 'EGV_DATA': database_records.EGVRecord,
238 }
239 xml_parsed = ['PC_SOFTWARE_PARAMETER', 'MANUFACTURING_DATA']
240 if record_type in generic_parser_map:
241 return self.GenericRecordYielder(header, data,
242 generic_parser_map[record_type])
243 elif record_type in xml_parsed:
244 return [database_records.GenericXMLRecord.Create(data, 0)]
245 else:
246 raise NotImplementedError('Parsing of %s has not yet been implemented'
247 % record_type)
248
249 def ReadRecords(self, record_type):
250 records = []
251 assert record_type in constants.RECORD_TYPES
252 page_range = self.ReadDatabasePageRange(record_type)
253 for x in range(page_range[0], page_range[1] or 1):
254 records.extend(self.ReadDatabasePage(record_type, x))
255 return records
256
257
258if __name__ == '__main__':
259 Dexcom.LocateAndDownload()
diff --git a/dexcom_reader/util.py b/dexcom_reader/util.py
new file mode 100644
index 0000000..c719092
--- /dev/null
+++ b/dexcom_reader/util.py
@@ -0,0 +1,82 @@
1import constants
2import datetime
3import os
4import platform
5import plistlib
6import re
7import subprocess
8
9
10def ReceiverTimeToTime(rtime):
11 return constants.BASE_TIME + datetime.timedelta(seconds=rtime)
12
13
14def linux_find_usbserial(vendor, product):
15 DEV_REGEX = re.compile('^tty(USB|ACM)[0-9]+$')
16 for usb_dev_root in os.listdir('/sys/bus/usb/devices'):
17 device_name = os.path.join('/sys/bus/usb/devices', usb_dev_root)
18 if not os.path.exists(os.path.join(device_name, 'idVendor')):
19 continue
20 idv = open(os.path.join(device_name, 'idVendor')).read().strip()
21 if idv != vendor:
22 continue
23 idp = open(os.path.join(device_name, 'idProduct')).read().strip()
24 if idp != product:
25 continue
26 for root, dirs, files in os.walk(device_name):
27 for option in dirs + files:
28 if DEV_REGEX.match(option):
29 return os.path.join('/dev', option)
30
31
32def osx_find_usbserial(vendor, product):
33 def recur(v):
34 if hasattr(v, '__iter__') and 'idVendor' in v and 'idProduct' in v:
35 if v['idVendor'] == vendor and v['idProduct'] == product:
36 tmp = v
37 while True:
38 if 'IODialinDevice' not in tmp and 'IORegistryEntryChildren' in tmp:
39 tmp = tmp['IORegistryEntryChildren']
40 elif 'IODialinDevice' in tmp:
41 return tmp['IODialinDevice']
42 else:
43 break
44
45 if type(v) == list:
46 for x in v:
47 out = recur(x)
48 if out is not None:
49 return out
50 elif type(v) == dict or issubclass(type(v), dict):
51 for x in v.values():
52 out = recur(x)
53 if out is not None:
54 return out
55
56 sp = subprocess.Popen(['/usr/sbin/ioreg', '-k', 'IODialinDevice',
57 '-r', '-t', '-l', '-a', '-x'],
58 stdout=subprocess.PIPE,
59 stdin=subprocess.PIPE, stderr=subprocess.PIPE)
60 stdout, _ = sp.communicate()
61 plist = plistlib.readPlistFromString(stdout)
62 return recur(plist)
63
64
65def find_usbserial(vendor, product):
66 """Find the tty device for a given usbserial devices identifiers.
67
68 Args:
69 vendor: (int) something like 0x0000
70 product: (int) something like 0x0000
71
72 Returns:
73 String, like /dev/ttyACM0 or /dev/tty.usb...
74 """
75 if platform.system() == 'Linux':
76 vendor, product = [('%04x' % (x)).strip() for x in (vendor, product)]
77 return linux_find_usbserial(vendor, product)
78 elif platform.system() == 'Darwin':
79 return osx_find_usbserial(vendor, product)
80 else:
81 raise NotImplementedError('Cannot find serial ports on %s'
82 % platform.system())