summaryrefslogtreecommitdiff
path: root/dexcom_reader/readdata.py
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/readdata.py
parent6b5ce2584b0fe2da991540a33c71b1f95cd8878a (diff)
prep for installable module
Diffstat (limited to 'dexcom_reader/readdata.py')
-rw-r--r--dexcom_reader/readdata.py259
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 @@
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()