summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen West <bewest@gmail.com>2016-05-28 19:31:45 -0700
committerBen West <bewest@gmail.com>2016-05-28 19:31:45 -0700
commit676a61177cf157d0d3a736e1141e52c1bf328f15 (patch)
treef0ee27234e21a54291e07356d6801e7cc16fe150
parent12751d803cde318b75f848940c99889719129acd (diff)
parent0193c69e36cfb079a5cefd56518215c4cef103ec (diff)
Merge pull request #3 from openaps/dev
stub out next release: 0.1.0-dev area
-rw-r--r--dexcom_reader/database_records.py112
-rw-r--r--dexcom_reader/readdata.py28
-rw-r--r--etc/udev/rules.d/80-dexcom.rules18
-rw-r--r--setup.py6
4 files changed, 156 insertions, 8 deletions
diff --git a/dexcom_reader/database_records.py b/dexcom_reader/database_records.py
index 8d471f4..9274d24 100644
--- a/dexcom_reader/database_records.py
+++ b/dexcom_reader/database_records.py
@@ -103,21 +103,117 @@ class InsertionRecord(GenericTimestampedRecord):
103 def session_state(self): 103 def session_state(self):
104 states = [None, 'REMOVED', 'EXPIRED', 'RESIDUAL_DEVIATION', 104 states = [None, 'REMOVED', 'EXPIRED', 'RESIDUAL_DEVIATION',
105 'COUNTS_DEVIATION', 'SECOND_SESSION', 'OFF_TIME_LOSS', 105 'COUNTS_DEVIATION', 'SECOND_SESSION', 'OFF_TIME_LOSS',
106 'STARTED', 'BAD_TRANSMITTER', 'MANUFACTURING_MODE'] 106 'STARTED', 'BAD_TRANSMITTER', 'MANUFACTURING_MODE',
107 'UNKNOWN1', 'UNKNOWN2', 'UNKNOWN3', 'UNKNOWN4', 'UNKNOWN5',
108 'UNKNOWN6', 'UNKNOWN7', 'UNKNOWN8']
107 return states[ord(self.data[3])] 109 return states[ord(self.data[3])]
108 110
109 def __repr__(self): 111 def __repr__(self):
110 return '%s: state=%s' % (self.display_time, self.session_state) 112 return '%s: state=%s' % (self.display_time, self.session_state)
111 113
114class G5InsertionRecord (InsertionRecord):
115 FORMAT = '<3Ic10BH'
112 116
113class Calibration(GenericTimestampedRecord): 117class Calibration(GenericTimestampedRecord):
118 FORMAT = '<2Iddd3cdb'
119 # CAL_FORMAT = '<2Iddd3cdb'
120 FIELDS = [ 'slope', 'intercept', 'scale', 'decay', 'numsub', 'raw' ]
114 @property 121 @property
115 def raw(self): 122 def raw (self):
116 return binascii.hexlify(bytearray(self.data)) 123 return binascii.hexlify(self.raw_data)
124 @property
125 def slope (self):
126 return self.data[2]
127 @property
128 def intercept (self):
129 return self.data[3]
130 @property
131 def scale (self):
132 return self.data[4]
133 @property
134 def decay (self):
135 return self.data[8]
136 @property
137 def numsub (self):
138 return int(self.data[9])
117 139
118 def __repr__(self): 140 def __repr__(self):
119 return '%s: CAL SET:%s' % (self.display_time, self.raw) 141 return '%s: CAL SET:%s' % (self.display_time, self.raw)
120 142
143 LEGACY_SIZE = 148
144 REV_2_SIZE = 249
145 @classmethod
146 def _ClassSize(cls):
147
148 return cls.REV_2_SIZE
149
150 @classmethod
151 def Create(cls, data, record_counter):
152 offset = record_counter * cls._ClassSize()
153 cal_size = struct.calcsize(cls.FORMAT)
154 raw_data = data[offset:offset + cls._ClassSize()]
155
156 cal_data = data[offset:offset + cal_size]
157 unpacked_data = cls._ClassFormat().unpack(cal_data)
158 return cls(unpacked_data, raw_data)
159
160 def __init__ (self, data, raw_data):
161 self.page_data = raw_data
162 self.raw_data = raw_data
163 self.data = data
164 subsize = struct.calcsize(SubCal.FORMAT)
165 offset = self.numsub * subsize
166 calsize = struct.calcsize(self.FORMAT)
167 caldata = raw_data[:calsize]
168 subdata = raw_data[calsize:calsize + offset]
169 crcdata = raw_data[calsize+offset:calsize+offset+2]
170
171 subcals = [ ]
172 for i in xrange(self.numsub):
173 offset = i * subsize
174 raw_sub = subdata[offset:offset+subsize]
175 sub = SubCal(raw_sub, self.data[1])
176 subcals.append(sub)
177
178 self.subcals = subcals
179
180 self.check_crc()
181 def to_dict (self):
182 res = super(Calibration, self).to_dict( )
183 res['subrecords'] = [ sub.to_dict( ) for sub in self.subcals ]
184 return res
185 @property
186 def crc(self):
187 return struct.unpack('H', self.raw_data[-2:])[0]
188
189class LegacyCalibration (Calibration):
190 @classmethod
191 def _ClassSize(cls):
192
193 return cls.LEGACY_SIZE
194
195
196class SubCal (GenericTimestampedRecord):
197 FORMAT = '<IIIIc'
198 BASE_FIELDS = [ ]
199 FIELDS = [ 'entered', 'meter', 'sensor', 'applied', ]
200 def __init__ (self, raw_data, displayOffset=None):
201 self.raw_data = raw_data
202 self.data = self._ClassFormat().unpack(raw_data)
203 self.displayOffset = displayOffset
204 @property
205 def entered (self):
206 return util.ReceiverTimeToTime(self.data[0])
207 @property
208 def meter (self):
209 return int(self.data[1])
210 @property
211 def sensor (self):
212 return int(self.data[2])
213 @property
214 def applied (self):
215 return util.ReceiverTimeToTime(self.data[3])
216
121class MeterRecord(GenericTimestampedRecord): 217class MeterRecord(GenericTimestampedRecord):
122 FORMAT = '<2IHIH' 218 FORMAT = '<2IHIH'
123 FIELDS = ['meter_glucose', 'meter_time' ] 219 FIELDS = ['meter_glucose', 'meter_time' ]
@@ -133,6 +229,8 @@ class MeterRecord(GenericTimestampedRecord):
133 def __repr__(self): 229 def __repr__(self):
134 return '%s: Meter BG:%s' % (self.display_time, self.meter_glucose) 230 return '%s: Meter BG:%s' % (self.display_time, self.meter_glucose)
135 231
232class G5MeterRecord (MeterRecord):
233 FORMAT = '<2IHI5BH'
136 234
137class EventRecord(GenericTimestampedRecord): 235class EventRecord(GenericTimestampedRecord):
138 # sys_time,display_time,glucose,meter_time,crc 236 # sys_time,display_time,glucose,meter_time,crc
@@ -230,3 +328,11 @@ class EGVRecord(GenericTimestampedRecord):
230 else: 328 else:
231 return '%s: CGM BG:%s (%s) DO:%s' % (self.display_time, self.glucose, 329 return '%s: CGM BG:%s (%s) DO:%s' % (self.display_time, self.glucose,
232 self.trend_arrow, self.display_only) 330 self.trend_arrow, self.display_only)
331
332class G5EGVRecord (EGVRecord):
333 FORMAT = '<2IHBBBBBBBBBcBH'
334 @property
335 def full_trend(self):
336 return self.data[12]
337
338
diff --git a/dexcom_reader/readdata.py b/dexcom_reader/readdata.py
index cc02de3..0f20e71 100644
--- a/dexcom_reader/readdata.py
+++ b/dexcom_reader/readdata.py
@@ -251,7 +251,7 @@ class Dexcom(object):
251 assert ord(packet.command) == 1 251 assert ord(packet.command) == 1
252 # first index (uint), numrec (uint), record_type (byte), revision (byte), 252 # first index (uint), numrec (uint), record_type (byte), revision (byte),
253 # page# (uint), r1 (uint), r2 (uint), r3 (uint), ushort (Crc) 253 # page# (uint), r1 (uint), r2 (uint), r3 (uint), ushort (Crc)
254 header_format = '<2I2c4IH' 254 header_format = '<2IcB4IH'
255 header_data_len = struct.calcsize(header_format) 255 header_data_len = struct.calcsize(header_format)
256 header = struct.unpack_from(header_format, packet.data) 256 header = struct.unpack_from(header_format, packet.data)
257 header_crc = crc16.crc16(packet.data[:header_data_len-2]) 257 header_crc = crc16.crc16(packet.data[:header_data_len-2])
@@ -266,15 +266,21 @@ class Dexcom(object):
266 for x in xrange(header[1]): 266 for x in xrange(header[1]):
267 yield record_type.Create(data, x) 267 yield record_type.Create(data, x)
268 268
269 def ParsePage(self, header, data): 269 PARSER_MAP = {
270 record_type = constants.RECORD_TYPES[ord(header[2])]
271 generic_parser_map = {
272 'USER_EVENT_DATA': database_records.EventRecord, 270 'USER_EVENT_DATA': database_records.EventRecord,
273 'METER_DATA': database_records.MeterRecord, 271 'METER_DATA': database_records.MeterRecord,
272 'CAL_SET': database_records.Calibration,
273 # 'CAL_SET': database_records.Calibration,
274 'INSERTION_TIME': database_records.InsertionRecord, 274 'INSERTION_TIME': database_records.InsertionRecord,
275 'EGV_DATA': database_records.EGVRecord, 275 'EGV_DATA': database_records.EGVRecord,
276 'SENSOR_DATA': database_records.SensorRecord, 276 'SENSOR_DATA': database_records.SensorRecord,
277 } 277 }
278 def ParsePage(self, header, data):
279 record_type = constants.RECORD_TYPES[ord(header[2])]
280 revision = int(header[3])
281 generic_parser_map = self.PARSER_MAP
282 if revision < 2 and record_type == 'CAL_SET':
283 generic_parser_map.update(CAL_SET=database_records.LegacyCalibration)
278 xml_parsed = ['PC_SOFTWARE_PARAMETER', 'MANUFACTURING_DATA'] 284 xml_parsed = ['PC_SOFTWARE_PARAMETER', 'MANUFACTURING_DATA']
279 if record_type in generic_parser_map: 285 if record_type in generic_parser_map:
280 return self.GenericRecordYielder(header, data, 286 return self.GenericRecordYielder(header, data,
@@ -308,6 +314,20 @@ class Dexcom(object):
308 records.extend(self.ReadDatabasePage(record_type, x)) 314 records.extend(self.ReadDatabasePage(record_type, x))
309 return records 315 return records
310 316
317class DexcomG5 (Dexcom):
318 PARSER_MAP = {
319 'USER_EVENT_DATA': database_records.EventRecord,
320 'METER_DATA': database_records.G5MeterRecord,
321 'CAL_SET': database_records.Calibration,
322 'INSERTION_TIME': database_records.G5InsertionRecord,
323 'EGV_DATA': database_records.G5EGVRecord,
324 'SENSOR_DATA': database_records.SensorRecord,
325 }
326
327def GetDevice (port, G5=False):
328 if G5:
329 return DexcomG5(port)
330 return Dexcom(port)
311 331
312if __name__ == '__main__': 332if __name__ == '__main__':
313 Dexcom.LocateAndDownload() 333 Dexcom.LocateAndDownload()
diff --git a/etc/udev/rules.d/80-dexcom.rules b/etc/udev/rules.d/80-dexcom.rules
new file mode 100644
index 0000000..0c95f52
--- /dev/null
+++ b/etc/udev/rules.d/80-dexcom.rules
@@ -0,0 +1,18 @@
1
2
3
4# udev can only match one entry at a time, but this triggers additional add entries.
5# 22a3:0047
6# 22a3 0047
7ATTRS{idVendor}=="22a3", ATTRS{idProduct}=="0047", \
8 ENV{ID_MM_DEVICE_IGNORE}="1", \
9 MODE="0664", GROUP="plugdev"
10
11
12# create a symlink to the ttyUSB device created automatically above
13#ACTION=="add", \
14# SUBSYSTEM=="tty", \
15# ATTRS{idVendor}=="22a3", ATTRS{idProduct}=="0047", \
16# SYMLINK+="ttyUSB.Dexcom%n"
17
18
diff --git a/setup.py b/setup.py
index 3bad349..da90585 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@ def readme():
10 return f.read() 10 return f.read()
11 11
12setup(name='dexcom_reader', 12setup(name='dexcom_reader',
13 version='0.0.8', # http://semver.org/ 13 version='0.1.0-dev', # http://semver.org/
14 description='Audit, and inspect data from Dexcom G4.', 14 description='Audit, and inspect data from Dexcom G4.',
15 long_description=readme(), 15 long_description=readme(),
16 author="Will Nowak", 16 author="Will Nowak",
@@ -34,6 +34,10 @@ setup(name='dexcom_reader',
34 package_data={ 34 package_data={
35 'dexcom_reader': ['etc/udev/rules.d/*'] 35 'dexcom_reader': ['etc/udev/rules.d/*']
36 }, 36 },
37 data_files = [
38 ('/etc/udev/rules.d/80-dexcom.rules', ['etc/udev/80-dexcom.rules'] ),
39
40 ],
37 zip_safe=False, 41 zip_safe=False,
38 include_package_data=True 42 include_package_data=True
39) 43)