Beispiel #1
0
def find_device(device, debug):
    if device is None:
        devices = glob.glob('/dev/sg[0-9]*')
    else:
        devices = [device]
    for device in devices:
        try:
            if debug:
                print("Trying", device, ' ', end='')
            sd = SCSIDevice(device)
            s = SCSI(sd)
            i = s.inquiry().result
            vendor = i['t10_vendor_identification'].decode(
                "latin1", "backslashreplace").strip()
            product = i['product_identification'].decode(
                "latin1", "backslashreplace").strip()
            if debug:
                print(vendor, product)
            if vendor == 'YULIN' and product == 'PROGRAMMER':
                print("Device", device)
                return sd
        except Exception as e:
            print("Exception", traceback.format_exc())
            pass
    raise BaseException("Cannot find JQ6500 (YULIN PROGRAMMER) device")
Beispiel #2
0
def main():
    i = 1
    page_code = 0
    evpd = 0
    while i < len(sys.argv):
        if sys.argv[i] == '--help':
            return usage()
        if sys.argv[i] == '-p':
            del sys.argv[i]
            page_code = int(sys.argv[i], 16)
            evpd = 1
            del sys.argv[i]
            continue
        i += 1

    if len(sys.argv) < 2:
        return usage()

    device = sys.argv[1]

    sd = SCSIDevice(device)
    s = SCSI(sd)

    i = s.testunitready()

    if not evpd:
        inquiry_standard(s)
        return

    if page_code == INQUIRY.VPD.SUPPORTED_VPD_PAGES:
        inquiry_supported_vpd_pages(s)
        return

    if page_code == INQUIRY.VPD.BLOCK_LIMITS:
        inquiry_block_limits(s)
        return

    if page_code == INQUIRY.VPD.BLOCK_DEVICE_CHARACTERISTICS:
        inquiry_block_dev_char(s)
        return

    if page_code == INQUIRY.VPD.LOGICAL_BLOCK_PROVISIONING:
        inquiry_logical_block_prov(s)
        return

    if page_code == INQUIRY.VPD.UNIT_SERIAL_NUMBER:
        inquiry_unit_serial_number(s)
        return

    if page_code == INQUIRY.VPD.DEVICE_IDENTIFICATION:
        inquiry_device_identification(s)
        return

    print 'No pretty print for this page, page_code=0x%02x' % page_code
    print '=============================================\n'
    i = s.inquiry(evpd=1, page_code=page_code).result
    for k, v in i.iteritems():
        print '%s - %s' % (k, v)
Beispiel #3
0
def main():
    i = 1
    page_code = 0
    evpd = 0
    while i < len(sys.argv):
        if sys.argv[i] == '--help':
            return usage()
        if sys.argv[i] == '-p':
            del sys.argv[i]
            page_code = int(sys.argv[i], 16)
            evpd = 1
            del sys.argv[i]
            continue
        i += 1

    if len(sys.argv) < 2:
        return usage()

    device = sys.argv[1]

    sd = SCSIDevice(device)
    s = SCSI(sd)

    i = s.testunitready()

    if not evpd:
        inquiry_standard(s)
        return

    if page_code == INQUIRY.VPD.SUPPORTED_VPD_PAGES:
        inquiry_supported_vpd_pages(s)
        return

    if page_code == INQUIRY.VPD.BLOCK_LIMITS:
        inquiry_block_limits(s)
        return

    if page_code == INQUIRY.VPD.BLOCK_DEVICE_CHARACTERISTICS:
        inquiry_block_dev_char(s)
        return

    if page_code == INQUIRY.VPD.LOGICAL_BLOCK_PROVISIONING:
        inquiry_logical_block_prov(s)
        return

    if page_code == INQUIRY.VPD.UNIT_SERIAL_NUMBER:
        inquiry_unit_serial_number(s)
        return

    if page_code == INQUIRY.VPD.DEVICE_IDENTIFICATION:
        inquiry_device_identification(s)
        return

    print 'No pretty print for this page, page_code=0x%02x' % page_code
    print '=============================================\n'
    i = s.inquiry(evpd=1, page_code=page_code).result
    for k, v in i.iteritems():
        print '%s - %s' % (k, v)
Beispiel #4
0
def main():
    device = ''
    for i in range(len(sys.argv)):
        if sys.argv[i] == '-f':
            del sys.argv[i]
            device = sys.argv[i]
            del sys.argv[i]
            break

    if not device:
        usage()
        exit(1)

    scsi = SCSI(SCSIDevice(device))
    i = scsi.inquiry().result
    if i['peripheral_device_type'] != INQUIRY.DEVICE_TYPE.MEDIA_CHANGER_DEVICE:
        print '%s is not a MediaChanger device' % device
        exit(1)

    eaa = scsi.modesense6(page_code=MODESENSE6.PAGE_CODE.ELEMENT_ADDRESS_ASSIGNMENT).result['mode_pages'][0]

    # get the data transfer elements
    dte = scsi.readelementstatus(
        start=eaa['first_data_transfer_element_address'],
        num=eaa['num_data_transfer_elements'],
        element_type=READELEMENTSTATUS.ELEMENT_TYPE.DATA_TRANSFER,
        voltag=1, curdata=1, dvcid=1,
        alloclen=16384).result['element_status_pages'][0]['element_descriptors']

    # get all the storage elements
    se = scsi.readelementstatus(
        start=eaa['first_storage_element_address'],
        num=eaa['num_storage_elements'],
        element_type=READELEMENTSTATUS.ELEMENT_TYPE.STORAGE,
        voltag=1, curdata=1, dvcid=1,
        alloclen=16384).result['element_status_pages'][0]['element_descriptors']

    # get all the medium transport elements
    mte = scsi.readelementstatus(
        start=eaa['first_medium_transport_element_address'],
        num=eaa['num_medium_transport_elements'],
        element_type=READELEMENTSTATUS.ELEMENT_TYPE.MEDIUM_TRANSPORT,
        voltag=1, curdata=1, dvcid=1,
        alloclen=16384).result['element_status_pages'][0]['element_descriptors']

    if sys.argv[1] == 'status':
        return status(scsi, dte, se)

    if sys.argv[1] == 'load':
        return load(scsi, mte, dte, se, int(sys.argv[2]), int(sys.argv[3]))

    if sys.argv[1] == 'unload':
        return unload(scsi, mte, dte, se, int(sys.argv[2]), int(sys.argv[3]))

    usage()
    exit(1)
Beispiel #5
0
def main():
    device = ''
    for i in range(len(sys.argv)):
        if sys.argv[i] == '-f':
            del sys.argv[i]
            device = sys.argv[i]
            del sys.argv[i]
            break

    if not device:
        usage()
        exit(1)

    scsi = SCSI(init_device(device))
    i = scsi.inquiry().result
    if i['peripheral_device_type'] != INQUIRY.DEVICE_TYPE.MEDIA_CHANGER_DEVICE:
        print('%s is not a MediaChanger device' % device)
        exit(1)

    eaa = scsi.modesense6(page_code=MODESENSE6.PAGE_CODE.ELEMENT_ADDRESS_ASSIGNMENT).result['mode_pages'][0]

    # get the data transfer elements
    dte = scsi.readelementstatus(
        start=eaa['first_data_transfer_element_address'],
        num=eaa['num_data_transfer_elements'],
        element_type=READELEMENTSTATUS.ELEMENT_TYPE.DATA_TRANSFER,
        voltag=1, curdata=1, dvcid=1,
        alloclen=16384).result['element_status_pages'][0]['element_descriptors']

    # get all the storage elements
    se = scsi.readelementstatus(
        start=eaa['first_storage_element_address'],
        num=eaa['num_storage_elements'],
        element_type=READELEMENTSTATUS.ELEMENT_TYPE.STORAGE,
        voltag=1, curdata=1, dvcid=1,
        alloclen=16384).result['element_status_pages'][0]['element_descriptors']

    # get all the medium transport elements
    mte = scsi.readelementstatus(
        start=eaa['first_medium_transport_element_address'],
        num=eaa['num_medium_transport_elements'],
        element_type=READELEMENTSTATUS.ELEMENT_TYPE.MEDIUM_TRANSPORT,
        voltag=1, curdata=1, dvcid=1,
        alloclen=16384).result['element_status_pages'][0]['element_descriptors']

    if sys.argv[1] == 'status':
        return status(scsi, dte, se)

    if sys.argv[1] == 'load':
        return load(scsi, mte, dte, se, int(sys.argv[2]), int(sys.argv[3]))

    if sys.argv[1] == 'unload':
        return unload(scsi, mte, dte, se, int(sys.argv[2]), int(sys.argv[3]))

    usage()
    exit(1)
def main():
    dev = MockInquiry()
    dev.opcodes = spc
    s = SCSI(dev)

    # cdb for standard page request
    i = s.inquiry(alloclen=128)
    cdb = i.cdb
    assert cdb[0] == s.device.opcodes.INQUIRY.value
    assert cdb[1:3] == bytearray(2)
    assert scsi_ba_to_int(cdb[3:5]) == 128
    assert cdb[5] == 0
    cdb = i.unmarshall_cdb(cdb)
    assert cdb['opcode'] == s.device.opcodes.INQUIRY.value
    assert cdb['evpd'] == 0
    assert cdb['page_code'] == 0
    assert cdb['alloc_len'] == 128

    d = Inquiry.unmarshall_cdb(Inquiry.marshall_cdb(cdb))
    assert d == cdb

    # supported vpd pages
    i = s.inquiry(evpd=1, page_code=0x88, alloclen=300)
    cdb = i.cdb
    assert cdb[0] == s.device.opcodes.INQUIRY.value
    assert cdb[1] == 0x01
    assert cdb[2] == 0x88
    assert scsi_ba_to_int(cdb[3:5]) == 300
    assert cdb[5] == 0
    cdb = i.unmarshall_cdb(cdb)
    assert cdb['opcode'] == s.device.opcodes.INQUIRY.value
    assert cdb['evpd'] == 1
    assert cdb['page_code'] == 0x88
    assert cdb['alloc_len'] == 300

    d = Inquiry.unmarshall_cdb(Inquiry.marshall_cdb(cdb))
    assert d == cdb
Beispiel #7
0
def main():
    dev = MockInquiry()
    dev.opcodes = spc
    s = SCSI(dev)

    # cdb for standard page request
    i = s.inquiry(alloclen=128)
    cdb = i.cdb
    assert cdb[0] == s.device.opcodes.INQUIRY.value
    assert cdb[1:3] == bytearray(2)
    assert scsi_ba_to_int(cdb[3:5]) == 128
    assert cdb[5] == 0
    cdb = i.unmarshall_cdb(cdb)
    assert cdb['opcode'] == s.device.opcodes.INQUIRY.value
    assert cdb['evpd'] == 0
    assert cdb['page_code'] == 0
    assert cdb['alloc_len'] == 128

    d = Inquiry.unmarshall_cdb(Inquiry.marshall_cdb(cdb))
    assert d == cdb

    # supported vpd pages
    i = s.inquiry(evpd=1, page_code=0x88, alloclen=300)
    cdb = i.cdb
    assert cdb[0] == s.device.opcodes.INQUIRY.value
    assert cdb[1] == 0x01
    assert cdb[2] == 0x88
    assert scsi_ba_to_int(cdb[3:5]) == 300
    assert cdb[5] == 0
    cdb = i.unmarshall_cdb(cdb)
    assert cdb['opcode'] == s.device.opcodes.INQUIRY.value
    assert cdb['evpd'] == 1
    assert cdb['page_code'] == 0x88
    assert cdb['alloc_len'] == 300

    d = Inquiry.unmarshall_cdb(Inquiry.marshall_cdb(cdb))
    assert d == cdb
Beispiel #8
0
def main():
    dev = MockInquiryStandard()
    dev.opcodes = sbc
    s = SCSI(dev)
    i = s.inquiry().result
    assert i['peripheral_qualifier'] == 1
    assert i['peripheral_device_type'] == 5
    assert i['rmb'] == 1
    assert i['version'] == 7
    assert i['normaca'] == 1
    assert i['hisup'] == 0
    assert i['response_data_format'] == 3
    assert i['additional_length'] == 64
    assert i['sccs'] == 1
    assert i['acc'] == 0
    assert i['tpgs'] == 3
    assert i['3pc'] == 1
    assert i['protect'] == 1
    assert i['encserv'] == 1
    assert i['vs'] == 1
    assert i['multip'] == 1
    assert i['addr16'] == 1
    assert i['wbus16'] == 1
    assert i['sync'] == 1
    assert i['cmdque'] == 1
    assert i['vs2'] == 1
    assert i['clocking'] == 2
    assert i['qas'] == 0
    assert i['ius'] == 1
    assert i['t10_vendor_identification'].decode("utf-8") == 'abcdefgh'
    assert i['product_identification'].decode("utf-8") == 'iiiiiiiijjjjjjjj'
    assert i['product_revision_level'].decode("utf-8") == 'revn'

    d = Inquiry.unmarshall_datain(Inquiry.marshall_datain(i))
    assert d == i

    dev = MockLBP()
    dev.opcodes = sbc
    s = SCSI(dev)
    i = s.inquiry(evpd=1, page_code=INQUIRY.VPD.LOGICAL_BLOCK_PROVISIONING).result
    assert i['peripheral_qualifier'] == 0
    assert i['peripheral_qualifier'] == 0
    assert i['threshold_exponent'] == 0x12
    assert i['lbpu'] == 1
    assert i['lpbws'] == 1
    assert i['lbpws10'] == 1
    assert i['lbprz'] == 1
    assert i['anc_sup'] == 1
    assert i['dp'] == 1
    assert i['provisioning_type'] == INQUIRY.PROVISIONING_TYPE.THIN_PROVISIONED

    d = Inquiry.unmarshall_datain(Inquiry.marshall_datain(i), evpd=1)
    assert d == i

    dev = MockUSN()
    dev.opcodes = sbc
    s = SCSI(dev)
    i = s.inquiry(evpd=1, page_code=INQUIRY.VPD.UNIT_SERIAL_NUMBER).result
    assert i['peripheral_qualifier'] == 0
    assert i['peripheral_qualifier'] == 0
    assert i['unit_serial_number'].decode("utf-8") == "ABCD"

    d = Inquiry.unmarshall_datain(Inquiry.marshall_datain(i), evpd=1)
    assert d == i

    dev = MockReferrals()
    dev.opcodes = sbc
    s = SCSI(dev)
    i = s.inquiry(evpd=1, page_code=INQUIRY.VPD.REFERRALS).result
    assert i['peripheral_qualifier'] == 0
    assert i['peripheral_qualifier'] == 0
    assert i['user_data_segment_size'] == 23
    assert i['user_data_segment_multiplier'] == 37

    d = Inquiry.unmarshall_datain(Inquiry.marshall_datain(i), evpd=1)
    assert d == i

    dev = MockExtendedInquiry()
    dev.opcodes = sbc
    s = SCSI(dev)
    i = s.inquiry(evpd=1, page_code=INQUIRY.VPD.EXTENDED_INQUIRY_DATA).result
    assert i['peripheral_qualifier'] == 0
    assert i['peripheral_qualifier'] == 0
    assert i['activate_microcode'] == 1
    assert i['spt'] == 2
    assert i['grd_chk'] == 1
    assert i['app_chk'] == 1
    assert i['ref_chk'] == 1
    assert i['uask_sup'] == 1
    assert i['group_sup'] == 1
    assert i['prior_sup'] == 0
    assert i['headsup'] == 0
    assert i['ordsup'] == 1
    assert i['simpsup'] == 1
    assert i['wu_sup'] == 0
    assert i['crd_sup'] == 1
    assert i['nv_sup'] == 0
    assert i['v_sup'] == 1
    assert i['p_i_i_sup'] == 1
    assert i['luiclr'] == 1
    assert i['r_sup'] == 1
    assert i['cbcs'] == 1
    assert i['multi_it_nexus_microcode_download'] == 3
    assert i['extended_self_test_completion_minutes'] == 15
    assert i['poa_sup'] == 1
    assert i['hra_sup'] == 1
    assert i['vsa_sup'] == 1
    assert i['maximum_supported_sense_data_length'] == 5

    d = Inquiry.unmarshall_datain(Inquiry.marshall_datain(i), evpd=1)
    assert d == i

    dev = MockDevId()
    dev.opcodes = sbc
    s = SCSI(dev)
    i = s.inquiry(evpd=1, page_code=INQUIRY.VPD.DEVICE_IDENTIFICATION).result
    assert i['peripheral_qualifier'] == 0
    assert i['peripheral_qualifier'] == 0
    dd = i['designator_descriptors']
    assert len(dd) == 2
    # T10 designation descriptor
    assert dd[0]['association'] == 2
    assert dd[0]['code_set'] == 2
    assert dd[0]['designator_length'] == 8
    assert dd[0]['designator_type'] == 1
    assert dd[0]['piv'] == 1
    assert dd[0]['protocol_identifier'] == 5
    assert dd[0]['designator']['t10_vendor_id'].decode("utf-8") == 'Test T10'
    assert dd[0]['designator']['vendor_specific_id'].decode("utf-8") == ''
    # EUI-64 designation descriptor
    assert dd[1]['association'] == 2
    assert dd[1]['code_set'] == 1
    assert dd[1]['designator_length'] == 8
    assert dd[1]['designator_type'] == 2
    assert dd[1]['piv'] == 0
    assert not hasattr(dd[1], 'protocol_identifier')
    assert dd[1]['designator']['ieee_company_id'] == 0x112233
    assert dd[1]['designator']['vendor_specific_extension_id'].decode("utf-8") == 'abcde'

    d = Inquiry.unmarshall_datain(Inquiry.marshall_datain(i), evpd=1)
    assert d == i
Beispiel #9
0
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)