diff options
author | Diego Elio Pettenò <flameeyes@flameeyes.eu> | 2018-12-12 23:16:57 +0100 |
---|---|---|
committer | Diego Elio Pettenò <flameeyes@flameeyes.eu> | 2018-12-12 23:16:57 +0100 |
commit | b1b2d814c0bdc883096bb8a23d397d5510189f9e (patch) | |
tree | 5e92df9bcad097d127c55be2240f47e05dff433e /glucometerutils/drivers/fsoptium.py | |
parent | hiddevice: split handle_ and hidapi_handle_. (diff) | |
download | glucometerutils-b1b2d814c0bdc883096bb8a23d397d5510189f9e.tar glucometerutils-b1b2d814c0bdc883096bb8a23d397d5510189f9e.tar.gz glucometerutils-b1b2d814c0bdc883096bb8a23d397d5510189f9e.tar.bz2 glucometerutils-b1b2d814c0bdc883096bb8a23d397d5510189f9e.tar.lz glucometerutils-b1b2d814c0bdc883096bb8a23d397d5510189f9e.tar.xz glucometerutils-b1b2d814c0bdc883096bb8a23d397d5510189f9e.tar.zst glucometerutils-b1b2d814c0bdc883096bb8a23d397d5510189f9e.zip |
Diffstat (limited to 'glucometerutils/drivers/fsoptium.py')
-rw-r--r-- | glucometerutils/drivers/fsoptium.py | 368 |
1 files changed, 184 insertions, 184 deletions
diff --git a/glucometerutils/drivers/fsoptium.py b/glucometerutils/drivers/fsoptium.py index 1e1a319..395494c 100644 --- a/glucometerutils/drivers/fsoptium.py +++ b/glucometerutils/drivers/fsoptium.py @@ -65,214 +65,214 @@ _MONTH_MATCHES = { def _parse_clock(datestr): - """Convert the date/time string used by the the device into a datetime. + """Convert the date/time string used by the the device into a datetime. - Args: - datestr: a string as returned by the device during information handling. - """ - match = _CLOCK_RE.match(datestr) - if not match: - raise exceptions.InvalidResponse(datestr) - - # int() parses numbers in decimal, so we don't have to worry about '08' - day = int(match.group('day')) - month = _MONTH_MATCHES[match.group('month')] - year = int(match.group('year')) - - hour, minute, second = map(match.group('time').split(':'), int) - - return datetime.datetime(year, month, day, hour, minute, second) - - -class Device(serial.SerialDevice): - BAUDRATE = 19200 - DEFAULT_CABLE_ID = '1a61:3420' - - def _send_command(self, command): - cmd_bytes = bytes('$%s\r\n' % command, 'ascii') - logging.debug('Sending command: %r', cmd_bytes) - - self.serial_.write(cmd_bytes) - self.serial_.flush() - - response = self.serial_.readlines() - - logging.debug('Received response: %r', response) - - # We always want to decode the output, and remove stray \r\n. Any failure in - # decoding means the output is invalid anyway. - decoded_response = [line.decode('ascii').rstrip('\r\n') - for line in response] - return decoded_response - - def connect(self): - self._send_command('xmem') # ignore output this time - self._fetch_device_information() - - def disconnect(self): - return - - def _fetch_device_information(self): - data = self._send_command('colq') - - for line in data: - parsed_line = line.split('\t') - - if parsed_line[0] == 'S/N:': - self.device_serialno_ = parsed_line[1] - elif parsed_line[0] == 'Ver:': - self.device_version_ = parsed_line[1] - if parsed_line[2] == 'MMOL': - self.device_glucose_unit_ = common.Unit.MMOL_L - else: # I only have a mmol/l device, so I can't be sure. - self.device_glucose_unit_ = common.Unit.MG_DL - # There are more entries: Clock, Market, ROM and Usage, but we don't care - # for those here. - elif parsed_line[0] == 'CMD OK': - return - - # I have not figured out why this happens, but sometimes it's echoing back - # the commands and not replying to them. - raise exceptions.ConnectionFailed() - - def get_meter_info(self): - """Fetch and parses the device information. - - Returns: - A common.MeterInfo object. - """ - return common.MeterInfo( - 'Freestyle Optium glucometer', - serial_number=self.get_serial_number(), - version_info=( - 'Software version: ' + self.get_version(),), - native_unit=self.get_glucose_unit()) - - def get_version(self): - """Returns an identifier of the firmware version of the glucometer. - - Returns: - The software version returned by the glucometer, such as "0.22" - """ - return self.device_version_ - - def get_serial_number(self): - """Retrieve the serial number of the device. - - Returns: - A string representing the serial number of the device. - """ - return self.device_serialno_ - - def get_glucose_unit(self): - """Returns a constant representing the unit displayed by the meter. - - Returns: - common.Unit.MG_DL: if the glucometer displays in mg/dL - common.Unit.MMOL_L: if the glucometer displays in mmol/L - """ - return self.device_glucose_unit_ - - def get_datetime(self): - """Returns the current date and time for the glucometer. - - Returns: - A datetime object built according to the returned response. + Args: + datestr: a string as returned by the device during information handling. """ - data = self._send_command('colq') + match = _CLOCK_RE.match(datestr) + if not match: + raise exceptions.InvalidResponse(datestr) - for line in data: - if not line.startswith('Clock:'): - continue + # int() parses numbers in decimal, so we don't have to worry about '08' + day = int(match.group('day')) + month = _MONTH_MATCHES[match.group('month')] + year = int(match.group('year')) - return _parse_clock(line) + hour, minute, second = map(match.group('time').split(':'), int) - raise exceptions.InvalidResponse('\n'.join(data)) + return datetime.datetime(year, month, day, hour, minute, second) - def set_datetime(self, date=datetime.datetime.now()): - """Sets the date and time of the glucometer. - Args: - date: The value to set the date/time of the glucometer to. If none is - given, the current date and time of the computer is used. +class Device(serial.SerialDevice): + BAUDRATE = 19200 + DEFAULT_CABLE_ID = '1a61:3420' - Returns: - A datetime object built according to the returned response. - """ - data = self._send_command(date.strftime('tim,%m,%d,%y,%H,%M')) + def _send_command(self, command): + cmd_bytes = bytes('$%s\r\n' % command, 'ascii') + logging.debug('Sending command: %r', cmd_bytes) - parsed_data = ''.join(data) - if parsed_data != 'CMD OK': - raise exceptions.InvalidResponse(parsed_data) + self.serial_.write(cmd_bytes) + self.serial_.flush() - return self.get_datetime() + response = self.serial_.readlines() - def zero_log(self): - """Zeros out the data log of the device. + logging.debug('Received response: %r', response) - This function will clear the memory of the device deleting all the readings - in an irrecoverable way. - """ - raise NotImplementedError + # We always want to decode the output, and remove stray \r\n. Any failure in + # decoding means the output is invalid anyway. + decoded_response = [line.decode('ascii').rstrip('\r\n') + for line in response] + return decoded_response - def get_readings(self): - """Iterates over the reading values stored in the glucometer. + def connect(self): + self._send_command('xmem') # ignore output this time + self._fetch_device_information() - Args: - unit: The glucose unit to use for the output. + def disconnect(self): + return - Yields: - A tuple (date, value) of the readings in the glucometer. The value is a - floating point in the unit specified; if no unit is specified, the default - unit in the glucometer will be used. + def _fetch_device_information(self): + data = self._send_command('colq') + + for line in data: + parsed_line = line.split('\t') - Raises: - exceptions.InvalidResponse: if the response does not match what expected. - """ - data = self._send_command('xmem') + if parsed_line[0] == 'S/N:': + self.device_serialno_ = parsed_line[1] + elif parsed_line[0] == 'Ver:': + self.device_version_ = parsed_line[1] + if parsed_line[2] == 'MMOL': + self.device_glucose_unit_ = common.Unit.MMOL_L + else: # I only have a mmol/l device, so I can't be sure. + self.device_glucose_unit_ = common.Unit.MG_DL + # There are more entries: Clock, Market, ROM and Usage, but we don't care + # for those here. + elif parsed_line[0] == 'CMD OK': + return + + # I have not figured out why this happens, but sometimes it's echoing back + # the commands and not replying to them. + raise exceptions.ConnectionFailed() + + def get_meter_info(self): + """Fetch and parses the device information. + + Returns: + A common.MeterInfo object. + """ + return common.MeterInfo( + 'Freestyle Optium glucometer', + serial_number=self.get_serial_number(), + version_info=( + 'Software version: ' + self.get_version(),), + native_unit=self.get_glucose_unit()) + + def get_version(self): + """Returns an identifier of the firmware version of the glucometer. + + Returns: + The software version returned by the glucometer, such as "0.22" + """ + return self.device_version_ + + def get_serial_number(self): + """Retrieve the serial number of the device. + + Returns: + A string representing the serial number of the device. + """ + return self.device_serialno_ + + def get_glucose_unit(self): + """Returns a constant representing the unit displayed by the meter. + + Returns: + common.Unit.MG_DL: if the glucometer displays in mg/dL + common.Unit.MMOL_L: if the glucometer displays in mmol/L + """ + return self.device_glucose_unit_ + + def get_datetime(self): + """Returns the current date and time for the glucometer. + + Returns: + A datetime object built according to the returned response. + """ + data = self._send_command('colq') + + for line in data: + if not line.startswith('Clock:'): + continue + + return _parse_clock(line) + + raise exceptions.InvalidResponse('\n'.join(data)) + + def set_datetime(self, date=datetime.datetime.now()): + """Sets the date and time of the glucometer. + + Args: + date: The value to set the date/time of the glucometer to. If none is + given, the current date and time of the computer is used. + + Returns: + A datetime object built according to the returned response. + """ + data = self._send_command(date.strftime('tim,%m,%d,%y,%H,%M')) + + parsed_data = ''.join(data) + if parsed_data != 'CMD OK': + raise exceptions.InvalidResponse(parsed_data) + + return self.get_datetime() + + def zero_log(self): + """Zeros out the data log of the device. + + This function will clear the memory of the device deleting all the readings + in an irrecoverable way. + """ + raise NotImplementedError + + def get_readings(self): + """Iterates over the reading values stored in the glucometer. + + Args: + unit: The glucose unit to use for the output. + + Yields: + A tuple (date, value) of the readings in the glucometer. The value is a + floating point in the unit specified; if no unit is specified, the default + unit in the glucometer will be used. + + Raises: + exceptions.InvalidResponse: if the response does not match what expected. + """ + data = self._send_command('xmem') - # The first line is empty, the second is the serial number, the third the - # version, the fourth the current time, and the fifth the record count.. The - # last line has a checksum and the end. - count = int(data[4]) - if count != (len(data) - 6): - raise exceptions.InvalidResponse('\n'.join(data)) + # The first line is empty, the second is the serial number, the third the + # version, the fourth the current time, and the fifth the record count.. The + # last line has a checksum and the end. + count = int(data[4]) + if count != (len(data) - 6): + raise exceptions.InvalidResponse('\n'.join(data)) - # Extract the checksum from the last line. - checksum_match = _CHECKSUM_RE.match(data[-1]) - if not checksum_match: - raise exceptions.InvalidResponse('\n'.join(data)) + # Extract the checksum from the last line. + checksum_match = _CHECKSUM_RE.match(data[-1]) + if not checksum_match: + raise exceptions.InvalidResponse('\n'.join(data)) - expected_checksum = int(checksum_match.group('checksum'), 16) - # exclude the last line in the checksum calculation, as that's the checksum - # itself. The final \r\n is added separately. - calculated_checksum = sum(ord(c) for c in '\r\n'.join(data[:-1])) + 0xd + 0xa + expected_checksum = int(checksum_match.group('checksum'), 16) + # exclude the last line in the checksum calculation, as that's the checksum + # itself. The final \r\n is added separately. + calculated_checksum = sum(ord(c) for c in '\r\n'.join(data[:-1])) + 0xd + 0xa - if expected_checksum != calculated_checksum: - raise exceptions.InvalidChecksum(expected_checksum, calculated_checksum) + if expected_checksum != calculated_checksum: + raise exceptions.InvalidChecksum(expected_checksum, calculated_checksum) - for line in data[5:-1]: - match = _READING_RE.match(line) - if not match: - raise exceptions.InvalidResponse(line) + for line in data[5:-1]: + match = _READING_RE.match(line) + if not match: + raise exceptions.InvalidResponse(line) - if match.group('type') != 'G': - logging.warning('Non-glucose readings are not supported, ignoring.') - continue + if match.group('type') != 'G': + logging.warning('Non-glucose readings are not supported, ignoring.') + continue - if match.group('reading') == 'HI ': - value = float("inf") - else: - value = float(match.group('reading')) + if match.group('reading') == 'HI ': + value = float("inf") + else: + value = float(match.group('reading')) - day = int(match.group('day')) - month = _MONTH_MATCHES[match.group('month')] - year = int(match.group('year')) + day = int(match.group('day')) + month = _MONTH_MATCHES[match.group('month')] + year = int(match.group('year')) - hour, minute = map(int, match.group('time').split(':')) + hour, minute = map(int, match.group('time').split(':')) - timestamp = datetime.datetime(year, month, day, hour, minute) + timestamp = datetime.datetime(year, month, day, hour, minute) - # The reading, if present, is always in mg/dL even if the glucometer is - # set to mmol/L. - yield common.GlucoseReading(timestamp, value) + # The reading, if present, is always in mg/dL even if the glucometer is + # set to mmol/L. + yield common.GlucoseReading(timestamp, value) |