def main(): dev = MockWrite10() dev.opcodes = sbc s = SCSI(dev) s.blocksize = 512 data = bytearray(27 * 512) w = s.write10(1024, 27, data) cdb = w.cdb assert cdb[0] == s.device.opcodes.WRITE_10.value assert cdb[1] == 0 assert scsi_ba_to_int(cdb[2:6]) == 1024 assert cdb[6] == 0 assert scsi_ba_to_int(cdb[7:9]) == 27 assert cdb[9] == 0 cdb = w.unmarshall_cdb(cdb) assert cdb['opcode'] == s.device.opcodes.WRITE_10.value assert cdb['wrprotect'] == 0 assert cdb['dpo'] == 0 assert cdb['fua'] == 0 assert cdb['lba'] == 1024 assert cdb['group'] == 0 assert cdb['tl'] == 27 d = Write10.unmarshall_cdb(Write10.marshall_cdb(cdb)) assert d == cdb w = s.write10(65536, 27, data, wrprotect=2, dpo=1, fua=1, group=19) cdb = w.cdb assert cdb[0] == s.device.opcodes.WRITE_10.value assert cdb[1] == 0x58 assert scsi_ba_to_int(cdb[2:6]) == 65536 assert cdb[6] == 0x13 assert scsi_ba_to_int(cdb[7:9]) == 27 assert cdb[9] == 0 cdb = w.unmarshall_cdb(cdb) assert cdb['opcode'] == s.device.opcodes.WRITE_10.value assert cdb['wrprotect'] == 2 assert cdb['dpo'] == 1 assert cdb['fua'] == 1 assert cdb['lba'] == 65536 assert cdb['group'] == 19 assert cdb['tl'] == 27 d = Write10.unmarshall_cdb(Write10.marshall_cdb(cdb)) assert d == cdb
class Device(object): def __init__(self, device): if not device: raise exceptions.CommandLineError( '--device parameter is required, should point to the disk ' 'device representing the meter.') self.device_name_ = device self.scsi_device_ = SCSIDevice(device, readwrite=True) self.scsi_ = SCSI(self.scsi_device_) self.scsi_.blocksize = _REGISTER_SIZE def connect(self): inq = self.scsi_.inquiry() logging.debug('Device connected: %r', inq.result) vendor = inq.result['t10_vendor_identification'][:32] if vendor != b'LifeScan': raise exceptions.ConnectionFailed( 'Device %s is not a LifeScan glucometer.' % self.device_name_) def disconnect(self): return def _send_request(self, lba, request_format, request_obj, response_format): """Send a request to the meter, and read its response. Args: lba: (int) the address of the block register to use, known valid addresses are 3, 4 and 5. request_format: a construct format identifier of the request to send request_obj: the object to format with the provided identifier response_format: a construct format identifier to parse the returned message with. Returns: The Container object parsed from the response received by the meter. Raises: lifescan.MalformedCommand if Construct fails to build the request or parse the response. """ try: request = request_format.build(request_obj) request_raw = _PACKET.build( {'data': { 'value': { 'message': request, } }}) logging.debug('Request sent: %s', binascii.hexlify(request_raw)) self.scsi_.write10(lba, 1, request_raw) response_raw = self.scsi_.read10(lba, 1) logging.debug('Response received: %s', binascii.hexlify(response_raw.datain)) response_pkt = _PACKET.parse(response_raw.datain).data logging.debug('Response packet: %r', response_pkt) response = response_format.parse(response_pkt.value.message) logging.debug('Response parsed: %r', response) return response except construct.ConstructError as e: raise lifescan.MalformedCommand(str(e)) def _query_string(self, selector): response = self._send_request(3, _QUERY_REQUEST, {'selector': selector}, _QUERY_RESPONSE) # Unfortunately the CString implementation in construct does not support # multi-byte encodings, so we need to discard the terminating null byte # ourself. return response.value[:-1] def get_meter_info(self): return common.MeterInfo( 'OneTouch %s glucometer' % self._query_string('model'), serial_number=self.get_serial_number(), version_info=('Software version: ' + self.get_version(), ), native_unit=self.get_glucose_unit()) def get_serial_number(self): return self._query_string('serial') def get_version(self): return self._query_string('software') def get_datetime(self): response = self._send_request(3, _READ_RTC_REQUEST, None, _READ_RTC_RESPONSE) return response.timestamp def set_datetime(self, date=datetime.datetime.now()): self._send_request(3, _WRITE_RTC_REQUEST, {'timestamp': date}, lifescan_binary_protocol.COMMAND_SUCCESS) # The device does not return the new datetime, so confirm by calling # READ RTC again. return self.get_datetime() def zero_log(self): self._send_request(3, _MEMORY_ERASE_REQUEST, None, lifescan_binary_protocol.COMMAND_SUCCESS) def get_glucose_unit(self): response = self._send_request(4, _READ_PARAMETER_REQUEST, {'selector': 'unit'}, _READ_UNIT_RESPONSE) return response.unit def _get_reading_count(self): response = self._send_request(3, _READ_RECORD_COUNT_REQUEST, None, _READ_RECORD_COUNT_RESPONSE) return response.count def _get_reading(self, record_id): response = self._send_request(3, _READ_RECORD_REQUEST, {'record_id': record_id}, _READ_RECORD_RESPONSE) return common.GlucoseReading(response.timestamp, float(response.value), meal=response.meal) def get_readings(self): record_count = self._get_reading_count() for record_id in range(record_count): yield self._get_reading(record_id)
class Device(object): def __init__(self, device): if not device: raise exceptions.CommandLineError( '--device parameter is required, should point to the disk device ' 'representing the meter.') self.device_name_ = device self.scsi_device_ = SCSIDevice(device, readwrite=True) self.scsi_ = SCSI(self.scsi_device_) self.scsi_.blocksize = _REGISTER_SIZE def _send_message(self, cmd, lba): """Send a request to the meter, and read its response. Args: cmd: (bytes) the raw command to send the device, without preamble or checksum. lba: (int) the address of the block register to use, known valid addresses are 3, 4 and 5. Returns: (bytes) The raw response from the meter. No preamble or coda is present, and the checksum has already been validated. """ self.scsi_.write10(lba, 1, _encode_message(cmd)) response = self.scsi_.read10(lba, 1) # TODO: validate that the response is valid. return _extract_message(response.datain) def connect(self): inq = self.scsi_.inquiry() vendor = inq.result['t10_vendor_identification'][:32] if vendor != b'LifeScan': raise exceptions.ConnectionFailed( 'Device %s is not a LifeScan glucometer.' % self.device_name_) def disconnect(self): return def get_meter_info(self): return common.MeterInfo( 'OneTouch %s glucometer' % self._query_string(_QUERY_KEY_MODEL), serial_number=self.get_serial_number(), version_info=( 'Software version: ' + self.get_version(),), native_unit=self.get_glucose_unit()) def _query_string(self, query_key): response = self._send_message(_QUERY_REQUEST + query_key, 3) if response[0:2] != b'\x04\06': raise lifescan.MalformedCommand( 'invalid response, expected 04 06, received %02x %02x' % ( response[0], response[1])) # Strings are encoded in wide characters (LE), but they should # only contain ASCII characters. Note that the string is # null-terminated, so the last character should be dropped. return response[2:].decode('utf-16-le')[:-1] def _read_parameter(self, parameter_key): response = self._send_message( _READ_PARAMETER_REQUEST + parameter_key, 4) if response[0:2] != b'\x03\x06': raise lifescan.MalformedCommand( 'invalid response, expected 03 06, received %02x %02x' % ( response[0], response[1])) return response[2:] def get_serial_number(self): return self._query_string(_QUERY_KEY_SERIAL) def get_version(self): return self._query_string(_QUERY_KEY_SOFTWARE) def get_datetime(self): response = self._send_message(_READ_RTC_REQUEST, 3) if response[0:2] != b'\x04\06': raise lifescan.MalformedCommand( 'invalid response, expected 04 06, received %02x %02x' % ( response[0], response[1])) (timestamp,) = _STRUCT_TIMESTAMP.unpack(response[2:]) return _convert_timestamp(timestamp) def set_datetime(self, date=datetime.datetime.now()): epoch = datetime.datetime.utcfromtimestamp(_EPOCH_BASE) delta = date - epoch timestamp = int(delta.total_seconds()) timestamp_bytes = _STRUCT_TIMESTAMP.pack(timestamp) response = self._send_message(_WRITE_RTC_REQUEST + timestamp_bytes, 3) if response[0:2] != b'\x04\06': raise lifescan.MalformedCommand( 'invalid response, expected 04 06, received %02x %02x' % ( response[0], response[1])) # The device does not return the new datetime, so confirm by # calling READ RTC again. return self.get_datetime() def zero_log(self): response = self._send_message(_MEMORY_ERASE_REQUEST, 3) if response[0:2] != b'\x04\06': raise lifescan.MalformedCommand( 'invalid response, expected 04 06, received %02x %02x' % ( response[0], response[1])) def _get_reading_count(self): response = self._send_message(_READ_RECORD_COUNT_REQUEST, 3) if response[0:2] != b'\x04\06': raise lifescan.MalformedCommand( 'invalid response, expected 04 06, received %02x %02x' % ( response[0], response[1])) (record_count,) = _STRUCT_RECORDID.unpack(response[2:]) return record_count def get_glucose_unit(self): unit_value = self._read_parameter(_PARAMETER_KEY_UNIT) if unit_value == b'\x00\x00\x00\x00': return common.UNIT_MGDL elif unit_value == b'\x01\x00\x00\x00': return common.UNIT_MMOLL else: raise exceptions.InvalidGlucoseUnit('%r' % unit_value) def _get_reading(self, record_number): request = (_READ_RECORD_REQUEST_PREFIX + _STRUCT_RECORDID.pack(record_number) + _READ_RECORD_REQUEST_SUFFIX) response = self._send_message(request, 3) if response[0:2] != b'\x04\06': raise lifescan.MalformedCommand( 'invalid response, expected 04 06, received %02x %02x' % ( response[0], response[1])) (unused_const1, unused_const2, unused_counter, unused_const3, unused_counter2, timestamp, value, unused_flags, unused_const4, unused_const5) = _STRUCT_RECORD.unpack(response) return common.Reading(_convert_timestamp(timestamp), float(value)) def get_readings(self): record_count = self._get_reading_count() for record_number in range(record_count): yield self._get_reading(record_number)
class Device: def __init__(self, device): if not device: raise exceptions.CommandLineError( '--device parameter is required, should point to the disk ' 'device representing the meter.') self.device_name_ = device self.scsi_device_ = SCSIDevice(device, readwrite=True) self.scsi_ = SCSI(self.scsi_device_) self.scsi_.blocksize = _REGISTER_SIZE def connect(self): inq = self.scsi_.inquiry() logging.debug('Device connected: %r', inq.result) vendor = inq.result['t10_vendor_identification'][:32] if vendor != b'LifeScan': raise exceptions.ConnectionFailed( 'Device %s is not a LifeScan glucometer.' % self.device_name_) def disconnect(self): # pylint: disable=no-self-use return def _send_request(self, lba, request_format, request_obj, response_format): """Send a request to the meter, and read its response. Args: lba: (int) the address of the block register to use, known valid addresses are 3, 4 and 5. request_format: a construct format identifier of the request to send request_obj: the object to format with the provided identifier response_format: a construct format identifier to parse the returned message with. Returns: The Container object parsed from the response received by the meter. Raises: lifescan.MalformedCommand if Construct fails to build the request or parse the response. """ try: request = request_format.build(request_obj) request_raw = _PACKET.build({'data': {'value': { 'message': request, }}}) logging.debug( 'Request sent: %s', binascii.hexlify(request_raw)) self.scsi_.write10(lba, 1, request_raw) response_raw = self.scsi_.read10(lba, 1) logging.debug( 'Response received: %s', binascii.hexlify(response_raw.datain)) response_pkt = _PACKET.parse(response_raw.datain).data logging.debug('Response packet: %r', response_pkt) response = response_format.parse(response_pkt.value.message) logging.debug('Response parsed: %r', response) return response except construct.ConstructError as e: raise lifescan.MalformedCommand(str(e)) def _query_string(self, selector): response = self._send_request( 3, _QUERY_REQUEST, {'selector': selector}, _QUERY_RESPONSE) return response.value def get_meter_info(self): return common.MeterInfo( 'OneTouch %s glucometer' % self._query_string('model'), serial_number=self.get_serial_number(), version_info=( 'Software version: ' + self.get_version(),), native_unit=self.get_glucose_unit()) def get_serial_number(self): return self._query_string('serial') def get_version(self): return self._query_string('software') def get_datetime(self): response = self._send_request( 3, _READ_RTC_REQUEST, None, _READ_RTC_RESPONSE) return response.timestamp def set_datetime(self, date=datetime.datetime.now()): self._send_request( 3, _WRITE_RTC_REQUEST, {'timestamp': date}, _COMMAND_SUCCESS) # The device does not return the new datetime, so confirm by calling # READ RTC again. return self.get_datetime() def zero_log(self): self._send_request( 3, _MEMORY_ERASE_REQUEST, None, _COMMAND_SUCCESS) def get_glucose_unit(self): response = self._send_request( 4, _READ_PARAMETER_REQUEST, {'selector': 'unit'}, _READ_UNIT_RESPONSE) return response.unit def _get_reading_count(self): response = self._send_request( 3, _READ_RECORD_COUNT_REQUEST, None, _READ_RECORD_COUNT_RESPONSE) return response.count def _get_reading(self, record_id): response = self._send_request( 3, _READ_RECORD_REQUEST, {'record_id': record_id}, _READ_RECORD_RESPONSE) return common.GlucoseReading( response.timestamp, float(response.value), meal=response.meal) def get_readings(self): record_count = self._get_reading_count() for record_id in range(record_count): yield self._get_reading(record_id)
class Device(driver.GlucometerDevice): def __init__(self, device: Optional[str]) -> None: if not device: raise exceptions.CommandLineError( "--device parameter is required, should point to the disk " "device representing the meter.") super().__init__(device) self.device_name_ = device self.scsi_device_ = SCSIDevice(device, readwrite=True) self.scsi_ = SCSI(self.scsi_device_) self.scsi_.blocksize = _REGISTER_SIZE def connect(self) -> None: inq = self.scsi_.inquiry() logging.debug("Device connected: %r", inq.result) vendor = inq.result["t10_vendor_identification"][:32] if vendor != b"LifeScan": raise exceptions.ConnectionFailed( f"Device {self.device_name_} is not a LifeScan glucometer.") def disconnect(self) -> None: # pylint: disable=no-self-use return def _send_request( self, lba: int, request_format: construct.Struct, request_obj: Optional[Dict[str, Any]], response_format: construct.Struct, ) -> construct.Container: """Send a request to the meter, and read its response. Args: lba: the address of the block register to use, known valid addresses are 3, 4 and 5. request_format: a construct format identifier of the request to send request_obj: the object to format with the provided identifier response_format: a construct format identifier to parse the returned message with. Returns: The Container object parsed from the response received by the meter. Raises: lifescan.MalformedCommand if Construct fails to build the request or parse the response. """ try: request = request_format.build(request_obj) request_raw = _PACKET.build( {"data": { "value": { "message": request } }}) logging.debug("Request sent: %s", binascii.hexlify(request_raw)) self.scsi_.write10(lba, 1, request_raw) response_raw = self.scsi_.read10(lba, 1) logging.debug("Response received: %s", binascii.hexlify(response_raw.datain)) response_pkt = _PACKET.parse(response_raw.datain).data logging.debug("Response packet: %r", response_pkt) response = response_format.parse(response_pkt.value.message) logging.debug("Response parsed: %r", response) return response except construct.ConstructError as e: raise lifescan.MalformedCommand(str(e)) def _query_string(self, selector: str) -> str: response = self._send_request(3, _QUERY_REQUEST, {"selector": selector}, _QUERY_RESPONSE) return response.value def get_meter_info(self) -> common.MeterInfo: model = self._query_string("model") return common.MeterInfo( f"OneTouch {model} glucometer", serial_number=self.get_serial_number(), version_info=(f"Software version: {self.get_version()}", ), native_unit=self.get_glucose_unit(), ) def get_serial_number(self) -> str: return self._query_string("serial") def get_version(self) -> str: return self._query_string("software") def get_datetime(self) -> datetime.datetime: response = self._send_request(3, _READ_RTC_REQUEST, None, _READ_RTC_RESPONSE) return response.timestamp def _set_device_datetime(self, date: datetime.datetime) -> datetime.datetime: self._send_request(3, _WRITE_RTC_REQUEST, {"timestamp": date}, _COMMAND_SUCCESS) # The device does not return the new datetime, so confirm by calling # READ RTC again. return self.get_datetime() def zero_log(self) -> None: self._send_request(3, _MEMORY_ERASE_REQUEST, None, _COMMAND_SUCCESS) def get_glucose_unit(self) -> common.Unit: response = self._send_request(4, _READ_PARAMETER_REQUEST, {"selector": "unit"}, _READ_UNIT_RESPONSE) return response.unit def _get_reading_count(self) -> int: response = self._send_request(3, _READ_RECORD_COUNT_REQUEST, None, _READ_RECORD_COUNT_RESPONSE) return response.count def _get_reading(self, record_id: int) -> common.GlucoseReading: response = self._send_request(3, _READ_RECORD_REQUEST, {"record_id": record_id}, _READ_RECORD_RESPONSE) return common.GlucoseReading(response.timestamp, float(response.value), meal=response.meal) def get_readings(self) -> Generator[common.AnyReading, None, None]: record_count = self._get_reading_count() for record_id in range(record_count): yield self._get_reading(record_id)