Esempio n. 1
0
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
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
Esempio n. 3
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)
Esempio n. 5
0
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)
Esempio n. 6
0
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)