diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..60aa96b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +group: travis_latest +language: python +cache: pip +matrix: + allow_failures: + - python: 2.7 + include: + - python: 2.7 + - python: 3.5 + dist: xenial # required for Debian Squeeze + #- python: 3.6 + - python: 3.7 + dist: xenial # required for Python >= 3.7 (travis-ci/travis-ci#9069) +install: + # - pip install -r requirements.txt + - pip install flake8 +before_script: + # stop the build if there are Python syntax errors or undefined names + - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + - flake8 . --count --ignore=E203 --exit-zero --max-complexity=10 --max-line-length=127 --statistics +script: + - true # add other tests here +notifications: + on_success: change + on_failure: change # `always` will be the setting once code changes slow down diff --git a/dexcom_reader/constants.py b/dexcom_reader/constants.py index 421c190..46b3630 100644 --- a/dexcom_reader/constants.py +++ b/dexcom_reader/constants.py @@ -2,15 +2,15 @@ class Error(Exception): - """Base error for dexcom reader.""" + """Base error for dexcom reader.""" class CrcError(Error): - """Failed to CRC properly.""" + """Failed to CRC properly.""" -DEXCOM_G4_USB_VENDOR = 0x22a3 -DEXCOM_G4_USB_PRODUCT = 0x0047 +DEXCOM_USB_VENDOR = 0x22A3 +DEXCOM_USB_PRODUCT = 0x0047 BASE_TIME = datetime.datetime(2009, 1, 1) @@ -64,33 +64,52 @@ class CrcError(Error): EGV_DISPLAY_ONLY_MASK = 32768 EGV_TREND_ARROW_MASK = 15 -BATTERY_STATES = [None, 'CHARGING', 'NOT_CHARGING', 'NTC_FAULT', 'BAD_BATTERY'] +BATTERY_STATES = [None, "CHARGING", "NOT_CHARGING", "NTC_FAULT", "BAD_BATTERY"] RECORD_TYPES = [ - 'MANUFACTURING_DATA', 'FIRMWARE_PARAMETER_DATA', 'PC_SOFTWARE_PARAMETER', - 'SENSOR_DATA', 'EGV_DATA', 'CAL_SET', 'DEVIATION', 'INSERTION_TIME', - 'RECEIVER_LOG_DATA', 'RECEIVER_ERROR_DATA', 'METER_DATA', 'USER_EVENT_DATA', - 'USER_SETTING_DATA', 'MAX_VALUE', + "MANUFACTURING_DATA", + "FIRMWARE_PARAMETER_DATA", + "PC_SOFTWARE_PARAMETER", + "SENSOR_DATA", + "EGV_DATA", + "CAL_SET", + "DEVIATION", + "INSERTION_TIME", + "RECEIVER_LOG_DATA", + "RECEIVER_ERROR_DATA", + "METER_DATA", + "USER_EVENT_DATA", + "USER_SETTING_DATA", + "MAX_VALUE", ] -TREND_ARROW_VALUES = [None, 'DOUBLE_UP', 'SINGLE_UP', '45_UP', 'FLAT', - '45_DOWN', 'SINGLE_DOWN', 'DOUBLE_DOWN', 'NOT_COMPUTABLE', - 'OUT_OF_RANGE'] +TREND_ARROW_VALUES = [ + None, + "DOUBLE_UP", + "SINGLE_UP", + "45_UP", + "FLAT", + "45_DOWN", + "SINGLE_DOWN", + "DOUBLE_DOWN", + "NOT_COMPUTABLE", + "OUT_OF_RANGE", +] -SPECIAL_GLUCOSE_VALUES = {0: None, - 1: 'SENSOR_NOT_ACTIVE', - 2: 'MINIMAL_DEVIATION', - 3: 'NO_ANTENNA', - 5: 'SENSOR_NOT_CALIBRATED', - 6: 'COUNTS_DEVIATION', - 9: 'ABSOLUTE_DEVIATION', - 10: 'POWER_DEVIATION', - 12: 'BAD_RF'} +SPECIAL_GLUCOSE_VALUES = { + 0: None, + 1: "SENSOR_NOT_ACTIVE", + 2: "MINIMAL_DEVIATION", + 3: "NO_ANTENNA", + 5: "SENSOR_NOT_CALIBRATED", + 6: "COUNTS_DEVIATION", + 9: "ABSOLUTE_DEVIATION", + 10: "POWER_DEVIATION", + 12: "BAD_RF", +} LANGUAGES = { - 0: None, - 1033: 'ENGLISH', + 0: None, + 1033: "ENGLISH", } - - diff --git a/dexcom_reader/crc16.py b/dexcom_reader/crc16.py index 0a55332..a3b4f8e 100644 --- a/dexcom_reader/crc16.py +++ b/dexcom_reader/crc16.py @@ -1,37 +1,39 @@ +# fmt: off TABLE = [ - 0, 4129, 8258, 12387, 16516, 20645, 24774, 28903, 33032, 37161, 41290, - 45419, 49548, 53677, 57806, 61935, 4657, 528, 12915, 8786, 21173, 17044, - 29431, 25302, 37689, 33560, 45947, 41818, 54205, 50076, 62463, 58334, 9314, - 13379, 1056, 5121, 25830, 29895, 17572, 21637, 42346, 46411, 34088, 38153, - 58862, 62927, 50604, 54669, 13907, 9842, 5649, 1584, 30423, 26358, 22165, - 18100, 46939, 42874, 38681, 34616, 63455, 59390, 55197, 51132, 18628, 22757, - 26758, 30887, 2112, 6241, 10242, 14371, 51660, 55789, 59790, 63919, 35144, - 39273, 43274, 47403, 23285, 19156, 31415, 27286, 6769, 2640, 14899, 10770, - 56317, 52188, 64447, 60318, 39801, 35672, 47931, 43802, 27814, 31879, 19684, - 23749, 11298, 15363, 3168, 7233, 60846, 64911, 52716, 56781, 44330, 48395, - 36200, 40265, 32407, 28342, 24277, 20212, 15891, 11826, 7761, 3696, 65439, - 61374, 57309, 53244, 48923, 44858, 40793, 36728, 37256, 33193, 45514, 41451, - 53516, 49453, 61774, 57711, 4224, 161, 12482, 8419, 20484, 16421, 28742, - 24679, 33721, 37784, 41979, 46042, 49981, 54044, 58239, 62302, 689, 4752, - 8947, 13010, 16949, 21012, 25207, 29270, 46570, 42443, 38312, 34185, 62830, - 58703, 54572, 50445, 13538, 9411, 5280, 1153, 29798, 25671, 21540, 17413, - 42971, 47098, 34713, 38840, 59231, 63358, 50973, 55100, 9939, 14066, 1681, - 5808, 26199, 30326, 17941, 22068, 55628, 51565, 63758, 59695, 39368, 35305, - 47498, 43435, 22596, 18533, 30726, 26663, 6336, 2273, 14466, 10403, 52093, - 56156, 60223, 64286, 35833, 39896, 43963, 48026, 19061, 23124, 27191, 31254, - 2801, 6864, 10931, 14994, 64814, 60687, 56684, 52557, 48554, 44427, 40424, - 36297, 31782, 27655, 23652, 19525, 15522, 11395, 7392, 3265, 61215, 65342, - 53085, 57212, 44955, 49082, 36825, 40952, 28183, 32310, 20053, 24180, 11923, - 16050, 3793, 7920 + 0, 4129, 8258, 12387, 16516, 20645, 24774, 28903, 33032, 37161, 41290, + 45419, 49548, 53677, 57806, 61935, 4657, 528, 12915, 8786, 21173, 17044, + 29431, 25302, 37689, 33560, 45947, 41818, 54205, 50076, 62463, 58334, 9314, + 13379, 1056, 5121, 25830, 29895, 17572, 21637, 42346, 46411, 34088, 38153, + 58862, 62927, 50604, 54669, 13907, 9842, 5649, 1584, 30423, 26358, 22165, + 18100, 46939, 42874, 38681, 34616, 63455, 59390, 55197, 51132, 18628, 22757, + 26758, 30887, 2112, 6241, 10242, 14371, 51660, 55789, 59790, 63919, 35144, + 39273, 43274, 47403, 23285, 19156, 31415, 27286, 6769, 2640, 14899, 10770, + 56317, 52188, 64447, 60318, 39801, 35672, 47931, 43802, 27814, 31879, 19684, + 23749, 11298, 15363, 3168, 7233, 60846, 64911, 52716, 56781, 44330, 48395, + 36200, 40265, 32407, 28342, 24277, 20212, 15891, 11826, 7761, 3696, 65439, + 61374, 57309, 53244, 48923, 44858, 40793, 36728, 37256, 33193, 45514, 41451, + 53516, 49453, 61774, 57711, 4224, 161, 12482, 8419, 20484, 16421, 28742, + 24679, 33721, 37784, 41979, 46042, 49981, 54044, 58239, 62302, 689, 4752, + 8947, 13010, 16949, 21012, 25207, 29270, 46570, 42443, 38312, 34185, 62830, + 58703, 54572, 50445, 13538, 9411, 5280, 1153, 29798, 25671, 21540, 17413, + 42971, 47098, 34713, 38840, 59231, 63358, 50973, 55100, 9939, 14066, 1681, + 5808, 26199, 30326, 17941, 22068, 55628, 51565, 63758, 59695, 39368, 35305, + 47498, 43435, 22596, 18533, 30726, 26663, 6336, 2273, 14466, 10403, 52093, + 56156, 60223, 64286, 35833, 39896, 43963, 48026, 19061, 23124, 27191, 31254, + 2801, 6864, 10931, 14994, 64814, 60687, 56684, 52557, 48554, 44427, 40424, + 36297, 31782, 27655, 23652, 19525, 15522, 11395, 7392, 3265, 61215, 65342, + 53085, 57212, 44955, 49082, 36825, 40952, 28183, 32310, 20053, 24180, 11923, + 16050, 3793, 7920 ] +# fmt: on def crc16(buf, start=None, end=None): - if start is None: - start = 0 - if end is None: - end = len(buf) - num = 0 - for i in range(start, end): - num = ((num<<8)&0xff00) ^ TABLE[((num>>8)&0xff)^ord(buf[i])] - return num & 0xffff + if start is None: + start = 0 + if end is None: + end = len(buf) + num = 0 + for i in range(start, end): + num = ((num << 8) & 0xFF00) ^ TABLE[((num >> 8) & 0xFF) ^ ord(buf[i])] + return num & 0xFFFF diff --git a/dexcom_reader/database_records.py b/dexcom_reader/database_records.py index 9274d24..4239550 100644 --- a/dexcom_reader/database_records.py +++ b/dexcom_reader/database_records.py @@ -1,338 +1,394 @@ -import crc16 -import constants -import struct -import util import binascii +import struct + +from . import constants, crc16, util -class BaseDatabaseRecord(object): - FORMAT = None +class BaseDatabaseRecord: + FORMAT = None - @classmethod - def _CheckFormat(cls): - if cls.FORMAT is None or not cls.FORMAT: - raise NotImplementedError("Subclasses of %s need to define FORMAT" - % cls.__name__) + @classmethod + def _CheckFormat(cls): + if cls.FORMAT is None or not cls.FORMAT: + raise NotImplementedError( + "Subclasses of %s need to define FORMAT" % cls.__name__ + ) - @classmethod - def _ClassFormat(cls): - cls._CheckFormat() - return struct.Struct(cls.FORMAT) + @classmethod + def _ClassFormat(cls): + cls._CheckFormat() + return struct.Struct(cls.FORMAT) - @classmethod - def _ClassSize(cls): - return cls._ClassFormat().size + @classmethod + def _ClassSize(cls): + return cls._ClassFormat().size - @property - def FMT(self): - self._CheckFormat() - return _ClassFormat() + @property + def FMT(self): + self._CheckFormat() + return self._ClassFormat() - @property - def SIZE(self): - return self._ClassSize() + @property + def SIZE(self): + return self._ClassSize() - @property - def crc(self): - return self.data[-1] + @property + def crc(self): + return self.data[-1] - def __init__(self, data, raw_data): - self.raw_data = raw_data - self.data = data - self.check_crc() + def __init__(self, data, raw_data): + self.raw_data = raw_data + self.data = data + self.check_crc() - def check_crc(self): - local_crc = self.calculate_crc() - if local_crc != self.crc: - raise constants.CrcError('Could not parse %s' % self.__class__.__name__) + def check_crc(self): + local_crc = self.calculate_crc() + if local_crc != self.crc: + raise constants.CrcError("Could not parse %s" % self.__class__.__name__) - def dump(self): - return ''.join('\\x%02x' % ord(c) for c in self.raw_data) + def dump(self): + return "".join("\\x%02x" % ord(c) for c in self.raw_data) - def calculate_crc(self): - return crc16.crc16(self.raw_data[:-2]) + def calculate_crc(self): + return crc16.crc16(self.raw_data[:-2]) - @classmethod - def Create(cls, data, record_counter): - offset = record_counter * cls._ClassSize() - raw_data = data[offset:offset + cls._ClassSize()] - unpacked_data = cls._ClassFormat().unpack(raw_data) - return cls(unpacked_data, raw_data) + @classmethod + def Create(cls, data, record_counter): + offset = record_counter * cls._ClassSize() + raw_data = data[offset : offset + cls._ClassSize()] + unpacked_data = cls._ClassFormat().unpack(raw_data) + return cls(unpacked_data, raw_data) class GenericTimestampedRecord(BaseDatabaseRecord): - FIELDS = [ ] - BASE_FIELDS = [ 'system_time', 'display_time' ] - @property - def system_time(self): - return util.ReceiverTimeToTime(self.data[0]) + FIELDS = [] + BASE_FIELDS = ["system_time", "display_time"] - @property - def display_time(self): - return util.ReceiverTimeToTime(self.data[1]) + @property + def system_time(self): + return util.ReceiverTimeToTime(self.data[0]) + @property + def display_time(self): + return util.ReceiverTimeToTime(self.data[1]) + + def to_dict(self): + d = dict() + for k in self.BASE_FIELDS + self.FIELDS: + d[k] = getattr(self, k) + if callable(getattr(d[k], "isoformat", None)): + d[k] = d[k].isoformat() + return d - def to_dict (self): - d = dict( ) - for k in self.BASE_FIELDS + self.FIELDS: - d[k] = getattr(self, k) - if callable(getattr(d[k], 'isoformat', None)): - d[k] = d[k].isoformat( ) - return d class GenericXMLRecord(GenericTimestampedRecord): - FORMAT = ' 6: - toread = abs(data_number-6) - second_read = self.read(toread) - all_data += second_read - total_read += toread - out = second_read - else: - out = '' - suffix = self.read(2) - sent_crc = struct.unpack(' 1590: - raise constants.Error('Invalid packet length') - self.flush() - self.write(packet) - - def WriteCommand(self, command_id, *args, **kwargs): - p = packetwriter.PacketWriter() - p.ComposePacket(command_id, *args, **kwargs) - self.WritePacket(p.PacketString()) - - def GenericReadCommand(self, command_id): - self.WriteCommand(command_id) - return self.readpacket() - - def ReadTransmitterId(self): - return self.GenericReadCommand(constants.READ_TRANSMITTER_ID).data - - def ReadLanguage(self): - lang = self.GenericReadCommand(constants.READ_LANGUAGE).data - return constants.LANGUAGES[struct.unpack('H', lang)[0]] - - def ReadBatteryLevel(self): - level = self.GenericReadCommand(constants.READ_BATTERY_LEVEL).data - return struct.unpack('I', level)[0] - - def ReadBatteryState(self): - state = self.GenericReadCommand(constants.READ_BATTERY_STATE).data - return constants.BATTERY_STATES[ord(state)] - - def ReadRTC(self): - rtc = self.GenericReadCommand(constants.READ_RTC).data - return util.ReceiverTimeToTime(struct.unpack('I', rtc)[0]) - - def ReadSystemTime(self): - rtc = self.GenericReadCommand(constants.READ_SYSTEM_TIME).data - return util.ReceiverTimeToTime(struct.unpack('I', rtc)[0]) - - def ReadSystemTimeOffset(self): - raw = self.GenericReadCommand(constants.READ_SYSTEM_TIME_OFFSET).data - return datetime.timedelta(seconds=struct.unpack('i', raw)[0]) - - def ReadDisplayTimeOffset(self): - raw = self.GenericReadCommand(constants.READ_DISPLAY_TIME_OFFSET).data - return datetime.timedelta(seconds=struct.unpack('i', raw)[0]) - - def WriteDisplayTimeOffset(self, offset=None): - payload = struct.pack('i', offset) - self.WriteCommand(constants.WRITE_DISPLAY_TIME_OFFSET, payload) - packet = self.readpacket() - return dict(ACK=ord(packet.command) == constants.ACK) - - - def ReadDisplayTime(self): - return self.ReadSystemTime() + self.ReadDisplayTimeOffset() - - def ReadGlucoseUnit(self): - UNIT_TYPE = (None, 'mg/dL', 'mmol/L') - gu = self.GenericReadCommand(constants.READ_GLUCOSE_UNIT).data - return UNIT_TYPE[ord(gu[0])] - - def ReadClockMode(self): - CLOCK_MODE = (24, 12) - cm = self.GenericReadCommand(constants.READ_CLOCK_MODE).data - return CLOCK_MODE[ord(cm[0])] - - def ReadDeviceMode(self): - # ??? - return self.GenericReadCommand(constants.READ_DEVICE_MODE).data - - def ReadBlindedMode(self): - MODES = { 0: False } - raw = self.GenericReadCommand(constants.READ_BLINDED_MODE).data - mode = MODES.get(bytearray(raw)[0], True) - return mode - - def ReadHardwareBoardId(self): - return self.GenericReadCommand(constants.READ_HARDWARE_BOARD_ID).data - - def ReadEnableSetupWizardFlag (self): - # ??? - return self.GenericReadCommand(constants.READ_ENABLE_SETUP_WIZARD_FLAG).data - - def ReadSetupWizardState (self): - # ??? - return self.GenericReadCommand(constants.READ_SETUP_WIZARD_STATE).data - - def WriteChargerCurrentSetting (self, status): - MAP = ( 'Off', 'Power100mA', 'Power500mA', 'PowerMax', 'PowerSuspended' ) - payload = str(bytearray([MAP.index(status)])) - self.WriteCommand(constants.WRITE_CHARGER_CURRENT_SETTING, payload) - packet = self.readpacket() - raw = bytearray(packet.data) - return dict(ACK=ord(packet.command) == constants.ACK, raw=list(raw)) - - def ReadChargerCurrentSetting (self): - MAP = ( 'Off', 'Power100mA', 'Power500mA', 'PowerMax', 'PowerSuspended' ) - raw = bytearray(self.GenericReadCommand(constants.READ_CHARGER_CURRENT_SETTING).data) - return MAP[raw[0]] - - - def ReadManufacturingData(self): - data = self.ReadRecords('MANUFACTURING_DATA')[0].xmldata - return ET.fromstring(data) - - def flush(self): - self.port.flush() - - def clear(self): - self.port.flushInput() - self.port.flushOutput() - - def GetFirmwareHeader(self): - i = self.GenericReadCommand(constants.READ_FIRMWARE_HEADER) - return ET.fromstring(i.data) - - def GetFirmwareSettings(self): - i = self.GenericReadCommand(constants.READ_FIRMWARE_SETTINGS) - return ET.fromstring(i.data) - - def DataPartitions(self): - i = self.GenericReadCommand(constants.READ_DATABASE_PARTITION_INFO) - return ET.fromstring(i.data) - - def ReadDatabasePageRange(self, record_type): - record_type_index = constants.RECORD_TYPES.index(record_type) - self.WriteCommand(constants.READ_DATABASE_PAGE_RANGE, - chr(record_type_index)) - packet = self.readpacket() - return struct.unpack('II', packet.data) - - def ReadDatabasePage(self, record_type, page): - record_type_index = constants.RECORD_TYPES.index(record_type) - self.WriteCommand(constants.READ_DATABASE_PAGES, - (chr(record_type_index), struct.pack('I', page), chr(1))) - packet = self.readpacket() - assert ord(packet.command) == 1 - # first index (uint), numrec (uint), record_type (byte), revision (byte), - # page# (uint), r1 (uint), r2 (uint), r3 (uint), ushort (Crc) - header_format = '<2IcB4IH' - header_data_len = struct.calcsize(header_format) - header = struct.unpack_from(header_format, packet.data) - header_crc = crc16.crc16(packet.data[:header_data_len-2]) - assert header_crc == header[-1] - assert ord(header[2]) == record_type_index - assert header[4] == page - packet_data = packet.data[header_data_len:] - - return self.ParsePage(header, packet_data) - - def GenericRecordYielder(self, header, data, record_type): - for x in xrange(header[1]): - yield record_type.Create(data, x) - - PARSER_MAP = { - 'USER_EVENT_DATA': database_records.EventRecord, - 'METER_DATA': database_records.MeterRecord, - 'CAL_SET': database_records.Calibration, - # 'CAL_SET': database_records.Calibration, - 'INSERTION_TIME': database_records.InsertionRecord, - 'EGV_DATA': database_records.EGVRecord, - 'SENSOR_DATA': database_records.SensorRecord, +import sys +from xml.etree import ElementTree as ET + +import serial + +from . import constants, crc16, database_records, packetwriter, util + + +class ReadPacket: + def __init__(self, command, data): + self._command = command + self._data = data + + @property + def command(self): + return self._command + + @property + def data(self): + return self._data + + +class Dexcom: + @staticmethod + def FindDevice(): + return util.find_usbserial( + constants.DEXCOM_USB_VENDOR, constants.DEXCOM_USB_PRODUCT + ) + + @classmethod + def LocateAndDownload(cls): + device = cls.FindDevice() + if not device: + sys.stderr.write("Could not find Dexcom Receiver!\n") + sys.exit(1) + else: + dex = cls(device) + print( + "Found %s S/N: %s" + % ( + dex.GetFirmwareHeader().get("ProductName"), + dex.ReadManufacturingData().get("SerialNumber"), + ) + ) + print("Transmitter paired: %s" % dex.ReadTransmitterId()) + print( + "Battery Status: %s (%d%%)" + % (dex.ReadBatteryState(), dex.ReadBatteryLevel()) + ) + print("Record count:") + print("- Meter records: %d" % (len(dex.ReadRecords("METER_DATA")))) + print("- CGM records: %d" % (len(dex.ReadRecords("EGV_DATA")))) + print( + "- CGM commitable records: %d" + % (len([not x.display_only for x in dex.ReadRecords("EGV_DATA")])) + ) + print("- Event records: %d" % (len(dex.ReadRecords("USER_EVENT_DATA")))) + print("- Insertion records: %d" % (len(dex.ReadRecords("INSERTION_TIME")))) + + def __init__(self, port): + self._port_name = port + self._port = None + + def Connect(self): + if self._port is None: + self._port = serial.Serial(port=self._port_name, baudrate=115200) + + def Disconnect(self): + if self._port is not None: + self._port.close() + + @property + def port(self): + if self._port is None: + self.Connect() + return self._port + + def write(self, *args, **kwargs): + return self.port.write(*args, **kwargs) + + def read(self, *args, **kwargs): + return self.port.read(*args, **kwargs) + + def readpacket(self, timeout=None): + total_read = 4 + initial_read = self.read(total_read) + all_data = initial_read + if ord(initial_read[0]) == 1: + command = initial_read[3] + data_number = struct.unpack(" 6: + toread = abs(data_number - 6) + second_read = self.read(toread) + all_data += second_read + total_read += toread + out = second_read + else: + out = "" + suffix = self.read(2) + sent_crc = struct.unpack(" 1590: + raise constants.Error("Invalid packet length") + self.flush() + self.write(packet) + + def WriteCommand(self, command_id, *args, **kwargs): + p = packetwriter.PacketWriter() + p.ComposePacket(command_id, *args, **kwargs) + self.WritePacket(p.PacketString()) + + def GenericReadCommand(self, command_id): + self.WriteCommand(command_id) + return self.readpacket() + + def ReadTransmitterId(self): + return self.GenericReadCommand(constants.READ_TRANSMITTER_ID).data + + def ReadLanguage(self): + lang = self.GenericReadCommand(constants.READ_LANGUAGE).data + return constants.LANGUAGES[struct.unpack("H", lang)[0]] + + def ReadBatteryLevel(self): + level = self.GenericReadCommand(constants.READ_BATTERY_LEVEL).data + return struct.unpack("I", level)[0] + + def ReadBatteryState(self): + state = self.GenericReadCommand(constants.READ_BATTERY_STATE).data + return constants.BATTERY_STATES[ord(state)] + + def ReadRTC(self): + rtc = self.GenericReadCommand(constants.READ_RTC).data + return util.ReceiverTimeToTime(struct.unpack("I", rtc)[0]) + + def ReadSystemTime(self): + rtc = self.GenericReadCommand(constants.READ_SYSTEM_TIME).data + return util.ReceiverTimeToTime(struct.unpack("I", rtc)[0]) + + def ReadSystemTimeOffset(self): + raw = self.GenericReadCommand(constants.READ_SYSTEM_TIME_OFFSET).data + return datetime.timedelta(seconds=struct.unpack("i", raw)[0]) + + def ReadDisplayTimeOffset(self): + raw = self.GenericReadCommand(constants.READ_DISPLAY_TIME_OFFSET).data + return datetime.timedelta(seconds=struct.unpack("i", raw)[0]) + + def WriteDisplayTimeOffset(self, offset=None): + payload = struct.pack("i", offset) + self.WriteCommand(constants.WRITE_DISPLAY_TIME_OFFSET, payload) + packet = self.readpacket() + return dict(ACK=ord(packet.command) == constants.ACK) + + def ReadDisplayTime(self): + return self.ReadSystemTime() + self.ReadDisplayTimeOffset() + + def ReadGlucoseUnit(self): + UNIT_TYPE = (None, "mg/dL", "mmol/L") + gu = self.GenericReadCommand(constants.READ_GLUCOSE_UNIT).data + return UNIT_TYPE[ord(gu[0])] + + def ReadClockMode(self): + CLOCK_MODE = (24, 12) + cm = self.GenericReadCommand(constants.READ_CLOCK_MODE).data + return CLOCK_MODE[ord(cm[0])] + + def ReadDeviceMode(self): + # ??? + return self.GenericReadCommand(constants.READ_DEVICE_MODE).data + + def ReadBlindedMode(self): + MODES = {0: False} + raw = self.GenericReadCommand(constants.READ_BLINDED_MODE).data + mode = MODES.get(bytearray(raw)[0], True) + return mode + + def ReadHardwareBoardId(self): + return self.GenericReadCommand(constants.READ_HARDWARE_BOARD_ID).data + + def ReadEnableSetupWizardFlag(self): + # ??? + return self.GenericReadCommand(constants.READ_ENABLE_SETUP_WIZARD_FLAG).data + + def ReadSetupWizardState(self): + # ??? + return self.GenericReadCommand(constants.READ_SETUP_WIZARD_STATE).data + + def WriteChargerCurrentSetting(self, status): + MAP = ("Off", "Power100mA", "Power500mA", "PowerMax", "PowerSuspended") + payload = str(bytearray([MAP.index(status)])) + self.WriteCommand(constants.WRITE_CHARGER_CURRENT_SETTING, payload) + packet = self.readpacket() + raw = bytearray(packet.data) + return dict(ACK=ord(packet.command) == constants.ACK, raw=list(raw)) + + def ReadChargerCurrentSetting(self): + MAP = ("Off", "Power100mA", "Power500mA", "PowerMax", "PowerSuspended") + raw = bytearray( + self.GenericReadCommand(constants.READ_CHARGER_CURRENT_SETTING).data + ) + return MAP[raw[0]] + + def ReadManufacturingData(self): + data = self.ReadRecords("MANUFACTURING_DATA")[0].xmldata + return ET.fromstring(data) + + def flush(self): + self.port.flush() + + def clear(self): + self.port.flushInput() + self.port.flushOutput() + + def GetFirmwareHeader(self): + i = self.GenericReadCommand(constants.READ_FIRMWARE_HEADER) + return ET.fromstring(i.data) + + def GetFirmwareSettings(self): + i = self.GenericReadCommand(constants.READ_FIRMWARE_SETTINGS) + return ET.fromstring(i.data) + + def DataPartitions(self): + i = self.GenericReadCommand(constants.READ_DATABASE_PARTITION_INFO) + return ET.fromstring(i.data) + + def ReadDatabasePageRange(self, record_type): + record_type_index = constants.RECORD_TYPES.index(record_type) + self.WriteCommand(constants.READ_DATABASE_PAGE_RANGE, chr(record_type_index)) + packet = self.readpacket() + return struct.unpack("II", packet.data) + + def ReadDatabasePage(self, record_type, page): + record_type_index = constants.RECORD_TYPES.index(record_type) + self.WriteCommand( + constants.READ_DATABASE_PAGES, + (chr(record_type_index), struct.pack("I", page), chr(1)), + ) + packet = self.readpacket() + assert ord(packet.command) == 1 + # first index (uint), numrec (uint), record_type (byte), revision (byte), + # page# (uint), r1 (uint), r2 (uint), r3 (uint), ushort (Crc) + header_format = "<2IcB4IH" + header_data_len = struct.calcsize(header_format) + header = struct.unpack_from(header_format, packet.data) + header_crc = crc16.crc16(packet.data[: header_data_len - 2]) + assert header_crc == header[-1] + assert ord(header[2]) == record_type_index + assert header[4] == page + packet_data = packet.data[header_data_len:] + + return self.ParsePage(header, packet_data) + + def GenericRecordYielder(self, header, data, record_type): + for x in range(header[1]): + yield record_type.Create(data, x) + + PARSER_MAP = { + "USER_EVENT_DATA": database_records.EventRecord, + "METER_DATA": database_records.MeterRecord, + "CAL_SET": database_records.Calibration, + "INSERTION_TIME": database_records.InsertionRecord, + "EGV_DATA": database_records.EGVRecord, + "SENSOR_DATA": database_records.SensorRecord, } - def ParsePage(self, header, data): - record_type = constants.RECORD_TYPES[ord(header[2])] - revision = int(header[3]) - generic_parser_map = self.PARSER_MAP - if revision < 2 and record_type == 'CAL_SET': - generic_parser_map.update(CAL_SET=database_records.LegacyCalibration) - xml_parsed = ['PC_SOFTWARE_PARAMETER', 'MANUFACTURING_DATA'] - if record_type in generic_parser_map: - return self.GenericRecordYielder(header, data, - generic_parser_map[record_type]) - elif record_type in xml_parsed: - return [database_records.GenericXMLRecord.Create(data, 0)] - else: - raise NotImplementedError('Parsing of %s has not yet been implemented' - % record_type) - - def iter_records (self, record_type): - assert record_type in constants.RECORD_TYPES - page_range = self.ReadDatabasePageRange(record_type) - start, end = page_range - if start != end or not end: - end += 1 - for x in reversed(xrange(start, end)): - records = list(self.ReadDatabasePage(record_type, x)) - records.reverse( ) - for record in records: - yield record - - def ReadRecords(self, record_type): - records = [] - assert record_type in constants.RECORD_TYPES - page_range = self.ReadDatabasePageRange(record_type) - start, end = page_range - if start != end or not end: - end += 1 - for x in range(start, end): - records.extend(self.ReadDatabasePage(record_type, x)) - return records - -class DexcomG5 (Dexcom): - PARSER_MAP = { - 'USER_EVENT_DATA': database_records.EventRecord, - 'METER_DATA': database_records.G5MeterRecord, - 'CAL_SET': database_records.Calibration, - 'INSERTION_TIME': database_records.G5InsertionRecord, - 'EGV_DATA': database_records.G5EGVRecord, - 'SENSOR_DATA': database_records.SensorRecord, + + def ParsePage(self, header, data): + record_type = constants.RECORD_TYPES[ord(header[2])] + revision = int(header[3]) + generic_parser_map = self.PARSER_MAP + if revision > 4 and record_type == "EGV_DATA": + generic_parser_map.update(EGV_DATA=database_records.G6EGVRecord) + if revision > 1 and record_type == "INSERTION_TIME": + generic_parser_map.update(INSERTION_TIME=database_records.G5InsertionRecord) + if revision > 2 and record_type == "METER_DATA": + generic_parser_map.update(METER_DATA=database_records.G5MeterRecord) + if revision < 2 and record_type == "CAL_SET": + generic_parser_map.update(CAL_SET=database_records.LegacyCalibration) + xml_parsed = ["PC_SOFTWARE_PARAMETER", "MANUFACTURING_DATA"] + if record_type in generic_parser_map: + return self.GenericRecordYielder( + header, data, generic_parser_map[record_type] + ) + elif record_type in xml_parsed: + return [database_records.GenericXMLRecord.Create(data, 0)] + else: + raise NotImplementedError( + "Parsing of %s has not yet been implemented" % record_type + ) + + def iter_records(self, record_type): + assert record_type in constants.RECORD_TYPES + page_range = self.ReadDatabasePageRange(record_type) + start, end = page_range + if start != end or not end: + end += 1 + for x in reversed(range(start, end)): + records = list(self.ReadDatabasePage(record_type, x)) + records.reverse() + yield from records + + def ReadRecords(self, record_type): + records = [] + assert record_type in constants.RECORD_TYPES + page_range = self.ReadDatabasePageRange(record_type) + start, end = page_range + if start != end or not end: + end += 1 + for x in range(start, end): + records.extend(self.ReadDatabasePage(record_type, x)) + return records + + +class DexcomG5(Dexcom): + PARSER_MAP = { + "USER_EVENT_DATA": database_records.EventRecord, + "METER_DATA": database_records.G5MeterRecord, + "CAL_SET": database_records.Calibration, + "INSERTION_TIME": database_records.G5InsertionRecord, + "EGV_DATA": database_records.G5EGVRecord, + "SENSOR_DATA": database_records.SensorRecord, + } + + +class DexcomG6(Dexcom): + PARSER_MAP = { + "USER_EVENT_DATA": database_records.EventRecord, + "METER_DATA": database_records.G5MeterRecord, + "CAL_SET": database_records.Calibration, + "INSERTION_TIME": database_records.G5InsertionRecord, + "EGV_DATA": database_records.G6EGVRecord, + "SENSOR_DATA": database_records.SensorRecord, } -def GetDevice (port, G5=False): - if G5: - return DexcomG5(port) - return Dexcom(port) -if __name__ == '__main__': - Dexcom.LocateAndDownload() +def GetDevice(port, G5=False, G6=False): + if G5: + return DexcomG5(port) + if G6: + return DexcomG6(port) + return Dexcom(port) + + +if __name__ == "__main__": + Dexcom.LocateAndDownload() diff --git a/dexcom_reader/record_test.py b/dexcom_reader/record_test.py index eb82358..ce50783 100644 --- a/dexcom_reader/record_test.py +++ b/dexcom_reader/record_test.py @@ -1,14 +1,14 @@ -import readdata +from . import readdata dd = readdata.Dexcom.FindDevice() dr = readdata.Dexcom(dd) -meter_records = dr.ReadRecords('METER_DATA') -print 'First Meter Record = ' -print meter_records[0] -print 'Last Meter Record =' -print meter_records[-1] -insertion_records = dr.ReadRecords('INSERTION_TIME') -print 'First Insertion Record = ' -print insertion_records[0] -print 'Last Insertion Record = ' -print insertion_records[-1] +meter_records = dr.ReadRecords("METER_DATA") +print("First Meter Record = ") +print(meter_records[0]) +print("Last Meter Record =") +print(meter_records[-1]) +insertion_records = dr.ReadRecords("INSERTION_TIME") +print("First Insertion Record = ") +print(insertion_records[0]) +print("Last Insertion Record = ") +print(insertion_records[-1]) diff --git a/dexcom_reader/util.py b/dexcom_reader/util.py index c719092..3911364 100644 --- a/dexcom_reader/util.py +++ b/dexcom_reader/util.py @@ -1,4 +1,3 @@ -import constants import datetime import os import platform @@ -6,77 +5,80 @@ import re import subprocess +from . import constants + def ReceiverTimeToTime(rtime): - return constants.BASE_TIME + datetime.timedelta(seconds=rtime) + return constants.BASE_TIME + datetime.timedelta(seconds=rtime) def linux_find_usbserial(vendor, product): - DEV_REGEX = re.compile('^tty(USB|ACM)[0-9]+$') - for usb_dev_root in os.listdir('/sys/bus/usb/devices'): - device_name = os.path.join('/sys/bus/usb/devices', usb_dev_root) - if not os.path.exists(os.path.join(device_name, 'idVendor')): - continue - idv = open(os.path.join(device_name, 'idVendor')).read().strip() - if idv != vendor: - continue - idp = open(os.path.join(device_name, 'idProduct')).read().strip() - if idp != product: - continue - for root, dirs, files in os.walk(device_name): - for option in dirs + files: - if DEV_REGEX.match(option): - return os.path.join('/dev', option) + DEV_REGEX = re.compile("^tty(USB|ACM)[0-9]+$") + for usb_dev_root in os.listdir("/sys/bus/usb/devices"): + device_name = os.path.join("/sys/bus/usb/devices", usb_dev_root) + if not os.path.exists(os.path.join(device_name, "idVendor")): + continue + idv = open(os.path.join(device_name, "idVendor")).read().strip() + if idv != vendor: + continue + idp = open(os.path.join(device_name, "idProduct")).read().strip() + if idp != product: + continue + for root, dirs, files in os.walk(device_name): + for option in dirs + files: + if DEV_REGEX.match(option): + return os.path.join("/dev", option) -def osx_find_usbserial(vendor, product): - def recur(v): - if hasattr(v, '__iter__') and 'idVendor' in v and 'idProduct' in v: - if v['idVendor'] == vendor and v['idProduct'] == product: - tmp = v - while True: - if 'IODialinDevice' not in tmp and 'IORegistryEntryChildren' in tmp: - tmp = tmp['IORegistryEntryChildren'] - elif 'IODialinDevice' in tmp: - return tmp['IODialinDevice'] - else: - break +def osx_find_usbserial(vendor, product): # noqa: C901 + def recur(v): + if hasattr(v, "__iter__") and "idVendor" in v and "idProduct" in v: + if v["idVendor"] == vendor and v["idProduct"] == product: + tmp = v + while True: + if "IODialinDevice" not in tmp and "IORegistryEntryChildren" in tmp: + tmp = tmp["IORegistryEntryChildren"] + elif "IODialinDevice" in tmp: + return tmp["IODialinDevice"] + else: + break - if type(v) == list: - for x in v: - out = recur(x) - if out is not None: - return out - elif type(v) == dict or issubclass(type(v), dict): - for x in v.values(): - out = recur(x) - if out is not None: - return out + if isinstance(v, list): + for x in v: + out = recur(x) + if out is not None: + return out + elif isinstance(v, dict) or issubclass(type(v), dict): + for x in list(v.values()): + out = recur(x) + if out is not None: + return out - sp = subprocess.Popen(['/usr/sbin/ioreg', '-k', 'IODialinDevice', - '-r', '-t', '-l', '-a', '-x'], - stdout=subprocess.PIPE, - stdin=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, _ = sp.communicate() - plist = plistlib.readPlistFromString(stdout) - return recur(plist) + sp = subprocess.Popen( + ["/usr/sbin/ioreg", "-k", "IODialinDevice", "-r", "-t", "-l", "-a", "-x"], + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + stdout, _ = sp.communicate() + plist = plistlib.readPlistFromString(stdout) + return recur(plist) def find_usbserial(vendor, product): - """Find the tty device for a given usbserial devices identifiers. + """Find the tty device for a given usbserial devices identifiers. - Args: - vendor: (int) something like 0x0000 - product: (int) something like 0x0000 + Args: + vendor: (int) something like 0x0000 + product: (int) something like 0x0000 - Returns: - String, like /dev/ttyACM0 or /dev/tty.usb... - """ - if platform.system() == 'Linux': - vendor, product = [('%04x' % (x)).strip() for x in (vendor, product)] - return linux_find_usbserial(vendor, product) - elif platform.system() == 'Darwin': - return osx_find_usbserial(vendor, product) - else: - raise NotImplementedError('Cannot find serial ports on %s' - % platform.system()) + Returns: + String, like /dev/ttyACM0 or /dev/tty.usb... + """ + if platform.system() == "Linux": + vendor, product = [("%04x" % (x)).strip() for x in (vendor, product)] + return linux_find_usbserial(vendor, product) + elif platform.system() == "Darwin": + return osx_find_usbserial(vendor, product) + else: + raise NotImplementedError("Cannot find serial ports on %s" % platform.system()) diff --git a/ez_setup.py b/ez_setup.py index 837ef3f..ac38b45 100644 --- a/ez_setup.py +++ b/ez_setup.py @@ -13,15 +13,14 @@ This file can also be run as a script to install or upgrade setuptools. """ +import optparse import os +import platform import shutil +import subprocess import sys -import tempfile import tarfile -import optparse -import subprocess -import platform - +import tempfile from distutils import log try: @@ -32,23 +31,30 @@ DEFAULT_VERSION = "1.0" DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" + def _python_cmd(*args): args = (sys.executable,) + args return subprocess.call(args) == 0 + def _check_call_py24(cmd, *args, **kwargs): res = subprocess.call(cmd, *args, **kwargs) + class CalledProcessError(Exception): pass + if not res == 0: msg = "Command '%s' return non-zero exit status %d" % (cmd, res) raise CalledProcessError(msg) -vars(subprocess).setdefault('check_call', _check_call_py24) + + +vars(subprocess).setdefault("check_call", _check_call_py24) + def _install(tarball, install_args=()): # extracting the tarball tmpdir = tempfile.mkdtemp() - log.warn('Extracting in %s', tmpdir) + log.warn("Extracting in %s", tmpdir) old_wd = os.getcwd() try: os.chdir(tmpdir) @@ -59,13 +65,13 @@ def _install(tarball, install_args=()): # going in the directory subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) os.chdir(subdir) - log.warn('Now working in %s', subdir) + log.warn("Now working in %s", subdir) # installing - log.warn('Installing Setuptools') - if not _python_cmd('setup.py', 'install', *install_args): - log.warn('Something went wrong during the installation.') - log.warn('See the error message above.') + log.warn("Installing Setuptools") + if not _python_cmd("setup.py", "install", *install_args): + log.warn("Something went wrong during the installation.") + log.warn("See the error message above.") # exitcode will be 2 return 2 finally: @@ -76,7 +82,7 @@ def _install(tarball, install_args=()): def _build_egg(egg, tarball, to_dir): # extracting the tarball tmpdir = tempfile.mkdtemp() - log.warn('Extracting in %s', tmpdir) + log.warn("Extracting in %s", tmpdir) old_wd = os.getcwd() try: os.chdir(tmpdir) @@ -87,11 +93,11 @@ def _build_egg(egg, tarball, to_dir): # going in the directory subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) os.chdir(subdir) - log.warn('Now working in %s', subdir) + log.warn("Now working in %s", subdir) # building an egg - log.warn('Building a Setuptools egg in %s', to_dir) - _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + log.warn("Building a Setuptools egg in %s", to_dir) + _python_cmd("setup.py", "-q", "bdist_egg", "--dist-dir", to_dir) finally: os.chdir(old_wd) @@ -99,33 +105,39 @@ def _build_egg(egg, tarball, to_dir): # returning the result log.warn(egg) if not os.path.exists(egg): - raise IOError('Could not build the egg.') + raise OSError("Could not build the egg.") def _do_download(version, download_base, to_dir, download_delay): - egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' - % (version, sys.version_info[0], sys.version_info[1])) + egg = os.path.join( + to_dir, + "setuptools-%s-py%d.%d.egg" + % (version, sys.version_info[0], sys.version_info[1]), + ) if not os.path.exists(egg): - tarball = download_setuptools(version, download_base, - to_dir, download_delay) + tarball = download_setuptools(version, download_base, to_dir, download_delay) _build_egg(egg, tarball, to_dir) sys.path.insert(0, egg) # Remove previously-imported pkg_resources if present (see # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details). - if 'pkg_resources' in sys.modules: - del sys.modules['pkg_resources'] + if "pkg_resources" in sys.modules: + del sys.modules["pkg_resources"] import setuptools + setuptools.bootstrap_install_from = egg -def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, download_delay=15): +def use_setuptools( + version=DEFAULT_VERSION, + download_base=DEFAULT_URL, + to_dir=os.curdir, + download_delay=15, +): # making sure we use the absolute path to_dir = os.path.abspath(to_dir) - was_imported = 'pkg_resources' in sys.modules or \ - 'setuptools' in sys.modules + was_imported = "pkg_resources" in sys.modules or "setuptools" in sys.modules try: import pkg_resources except ImportError: @@ -137,19 +149,19 @@ def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, e = sys.exc_info()[1] if was_imported: sys.stderr.write( - "The required version of setuptools (>=%s) is not available,\n" - "and can't be installed while this script is running. Please\n" - "install a more recent version first, using\n" - "'easy_install -U setuptools'." - "\n\n(Currently using %r)\n" % (version, e.args[0])) + "The required version of setuptools (>=%s) is not available,\n" + "and can't be installed while this script is running. Please\n" + "install a more recent version first, using\n" + "'easy_install -U setuptools'." + "\n\n(Currently using %r)\n" % (version, e.args[0]) + ) sys.exit(2) else: - del pkg_resources, sys.modules['pkg_resources'] # reload ok - return _do_download(version, download_base, to_dir, - download_delay) + del pkg_resources, sys.modules["pkg_resources"] # reload ok + return _do_download(version, download_base, to_dir, download_delay) except pkg_resources.DistributionNotFound: - return _do_download(version, download_base, to_dir, - download_delay) + return _do_download(version, download_base, to_dir, download_delay) + def download_file_powershell(url, target): """ @@ -158,61 +170,70 @@ def download_file_powershell(url, target): """ target = os.path.abspath(target) cmd = [ - 'powershell', - '-Command', + "powershell", + "-Command", "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" % vars(), ] subprocess.check_call(cmd) + def has_powershell(): - if platform.system() != 'Windows': + if platform.system() != "Windows": return False - cmd = ['powershell', '-Command', 'echo test'] - devnull = open(os.path.devnull, 'wb') + cmd = ["powershell", "-Command", "echo test"] + devnull = open(os.path.devnull, "wb") try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) - except: + except: # noqa: E722 return False finally: devnull.close() return True + download_file_powershell.viable = has_powershell + def download_file_curl(http://github.com/url,%20target): - cmd = ['curl', url, '--silent', '--output', target] + cmd = ["curl", url, "--silent", "--output", target] subprocess.check_call(cmd) + def has_curl(): - cmd = ['curl', '--version'] - devnull = open(os.path.devnull, 'wb') + cmd = ["curl", "--version"] + devnull = open(os.path.devnull, "wb") try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) - except: + except: # noqa: E722 return False finally: devnull.close() return True + download_file_curl.viable = has_curl + def download_file_wget(url, target): - cmd = ['wget', url, '--quiet', '--output-document', target] + cmd = ["wget", url, "--quiet", "--output-document", target] subprocess.check_call(cmd) + def has_wget(): - cmd = ['wget', '--version'] - devnull = open(os.path.devnull, 'wb') + cmd = ["wget", "--version"] + devnull = open(os.path.devnull, "wb") try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) - except: + except: # noqa: E722 return False finally: devnull.close() return True + download_file_wget.viable = has_wget + def download_file_insecure(url, target): """ Use Python to download the file, even though it cannot authenticate the @@ -221,7 +242,7 @@ def download_file_insecure(url, target): try: from urllib.request import urlopen except ImportError: - from urllib2 import urlopen + from urllib.request import urlopen src = dst = None try: src = urlopen(url) @@ -236,8 +257,10 @@ def download_file_insecure(url, target): if dst: dst.close() + download_file_insecure.viable = lambda: True + def get_best_downloader(): downloaders = [ download_file_powershell, @@ -250,8 +273,10 @@ def get_best_downloader(): if dl.viable(): return dl -def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, delay=15): + +def download_setuptools( + version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay=15 +): """Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available @@ -282,6 +307,7 @@ def _extractall(self, path=".", members=None): import copy import operator from tarfile import ExtractError + directories = [] if members is None: @@ -297,12 +323,14 @@ def _extractall(self, path=".", members=None): # Reverse sort directories. if sys.version_info < (2, 4): + def sorter(dir1, dir2): - return cmp(dir1.name, dir2.name) + return cmp(dir1.name, dir2.name) # noqa: F821 + directories.sort(sorter) directories.reverse() else: - directories.sort(key=operator.attrgetter('name'), reverse=True) + directories.sort(key=operator.attrgetter("name"), reverse=True) # Set correct owner, mtime and filemode on directories. for tarinfo in directories: @@ -328,30 +356,40 @@ def _build_install_args(options): if sys.version_info < (2, 6): log.warn("--user requires Python 2.6 or later") raise SystemExit(1) - install_args.append('--user') + install_args.append("--user") return install_args + def _parse_args(): """ Parse the command line for options """ parser = optparse.OptionParser() parser.add_option( - '--user', dest='user_install', action='http://github.com/store_true', default=False, - help='install in user site package (requires Python 2.6 or later)') + "--user", + dest="user_install", + action="http://github.com/store_true", + default=False, + help="install in user site package (requires Python 2.6 or later)", + ) parser.add_option( - '--download-base', dest='download_base', metavar="URL", + "--download-base", + dest="download_base", + metavar="URL", default=DEFAULT_URL, - help='alternative URL from where to download the setuptools package') + help="alternative URL from where to download the setuptools package", + ) options, args = parser.parse_args() # positional arguments are ignored return options + def main(version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" options = _parse_args() tarball = download_setuptools(download_base=options.download_base) return _install(tarball, _build_install_args(options)) -if __name__ == '__main__': + +if __name__ == "__main__": sys.exit(main()) diff --git a/setup.py b/setup.py index ccd42b5..a563548 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,16 @@ #!/usr/bin/python +from setuptools import find_packages, setup -from setuptools import setup, find_packages -import platform - -import dexcom_reader def readme(): with open("README.md") as f: return f.read() -setup(name='dexcom_reader', - version='0.1.10', # http://semver.org/ - description='Audit, and inspect data from Dexcom G4.', + +setup( + name="dexcom_reader", + version="0.2.0", # http://semver.org/ + description="Audit, and inspect data from Dexcom G4, G5, and G6 receivers.", long_description=readme(), author="Will Nowak", # I'm just maintaining the package, @compbrain authored it. @@ -19,29 +18,26 @@ def readme(): maintainer="Ben West", maintainer_email="bewest+dexcom_reader@gmail.com", url="https://github.com/openaps/dexcom_reader", - packages=find_packages( ), - install_requires = [ - 'pyserial' + packages=find_packages(), + install_requires=["pyserial"], + classifiers=[ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Scientific/Engineering", + "Topic :: Software Development :: Libraries", ], - classifiers = [ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Intended Audience :: Science/Research', - 'Programming Language :: Python', - 'Topic :: Scientific/Engineering', - 'Topic :: Software Development :: Libraries' - ], - package_data={ - 'dexcom_reader': ['etc/udev/rules.d/*'] - }, - data_files = [ - # installing to system locations breaks things for virtualenv based - # environments. - # ('/etc/udev/rules.d', ['dexcom_reader/etc/udev/rules.d/80-dexcom.rules'] ), - + package_data={"dexcom_reader": ["etc/udev/rules.d/*"]}, + data_files=[ + # installing to system locations breaks things for virtualenv based + # environments. + # ('/etc/udev/rules.d', ['dexcom_reader/etc/udev/rules.d/80-dexcom.rules'] ), ], zip_safe=False, - include_package_data=True + include_package_data=True, ) #####