summaryrefslogtreecommitdiff
path: root/dexcom_reader/database_records.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/database_records.py
parent6b5ce2584b0fe2da991540a33c71b1f95cd8878a (diff)
prep for installable module
Diffstat (limited to 'dexcom_reader/database_records.py')
-rw-r--r--dexcom_reader/database_records.py189
1 files changed, 189 insertions, 0 deletions
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)