diff options
Diffstat (limited to 'dexcom_reader/readdata.py')
-rw-r--r-- | dexcom_reader/readdata.py | 259 |
1 files changed, 259 insertions, 0 deletions
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 @@ | |||
1 | import crc16 | ||
2 | import constants | ||
3 | import database_records | ||
4 | import datetime | ||
5 | import serial | ||
6 | import sys | ||
7 | import time | ||
8 | import packetwriter | ||
9 | import struct | ||
10 | import re | ||
11 | import util | ||
12 | import xml.etree.ElementTree as ET | ||
13 | import numpy | ||
14 | import platform | ||
15 | |||
16 | |||
17 | class 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 | |||
31 | class 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 | |||
258 | if __name__ == '__main__': | ||
259 | Dexcom.LocateAndDownload() | ||