Esempio n. 1
0
def on_write_request(self, controller_id, on_write_handler):
    write_request_uuid = PacketUuid(payload_type=Payload.WritePeripheral, controller_id=controller_id)
    write_without_response_request_uuid = PacketUuid(
        payload_type=Payload.WriteWithoutResponsePeripheral,
        controller_id=controller_id
    )

    if isinstance(on_write_handler, (tuple, list)):
        on_write_handler_fn = on_write_handler[0]
        on_write_handler_params = on_write_handler[1:]
    else:
        on_write_handler_fn = on_write_handler
        on_write_handler_params = []

    @asyncio.coroutine
    def on_request(packet):
        request = packet.get_dict([
            'controller_id',
            'connection_handle',
            'attribute_handle',
            ('value', bytes)
        ])

        handled = on_write_handler_fn(request, *on_write_handler_params)

        if handled and packet.payload_type != Payload.WriteWithoutResponsePeripheral:
            self.send_packet(packet)

    self.register_callback(write_request_uuid, callback=on_request)
    self.register_callback(write_without_response_request_uuid, callback=on_request)
    self.logger.debug("On write handler registered")
Esempio n. 2
0
def on_connected(self, controller_id, on_connected_handler):
    device_connected_uuid = PacketUuid(payload_type=Payload.DeviceConnected, controller_id=controller_id)

    if isinstance(on_connected_handler, (tuple, list)):
        on_connected_handler_fn = on_connected_handler[0]
        on_connected_handler_params = on_connected_handler[1:]
    else:
        on_connected_handler_fn = on_connected_handler
        on_connected_handler_params = []

    @asyncio.coroutine
    def on_device_connected(packet):
        if packet.status_code == StatusCode.Success:
            device = packet.get_dict([
                'controller_id',
                'connection_handle',
                'address',
                ('address_type', lambda value: 'public' if value == 0 else 'random')
            ])

            on_connected_handler_fn(device, *on_connected_handler_params)
        else:
            self.logger.debug("Failed connected event received: %s", packet)

    self.register_callback(device_connected_uuid, callback=on_device_connected)
    self.logger.debug("On connected handler registered")
Esempio n. 3
0
def on_read_request(self, controller_id, on_read_handler):
    read_request_uuid = PacketUuid(payload_type=Payload.ReadPeripheral, controller_id=controller_id)

    if isinstance(on_read_handler, (tuple, list)):
        on_read_handler_fn = on_read_handler[0]
        on_read_handler_params = on_read_handler[1:]
    else:
        on_read_handler_fn = on_read_handler
        on_read_handler_params = []

    @asyncio.coroutine
    def on_request(packet):
        request = packet.get_dict([
            'controller_id',
            'connection_handle',
            'attribute_handle'
        ])

        packet.value = on_read_handler_fn(request, *on_read_handler_params)

        if packet.value is not None:
            self.send_packet(packet)

    self.register_callback(read_request_uuid, callback=on_request)
    self.logger.debug("On read handler registered")
Esempio n. 4
0
    def on_connected(packet, future):
        self.logger.debug("Device connected event received with status={}".format(packet.status))
        self.remove_callback(packet.packet_uuid)

        if packet.status_code == StatusCode.Success:
            device = packet.get_dict([
                'controller_id',
                'connection_handle',
                'address',
                ('address_type', lambda value: 'public' if value == 0 else 'random')
            ])

            self.register_callback(
                PacketUuid(payload_type=Payload.DeviceDisconnected,
                           controller_id=controller_id,
                           connection_handle=device['connection_handle']),
                callback=on_unexpected_disconnection
            )

            on_connected_cb(True, device, None, *on_connected_params)
            future.set_result(device)
        else:
            error = BaBLEException(packet, "Failed to connect", address=address)
            on_connected_cb(False, None, error, *on_connected_params)
            future.set_exception(error)
            return
Esempio n. 5
0
def on_disconnected(self, controller_id, on_disconnected_handler):
    device_disconnected_uuid = PacketUuid(payload_type=Payload.DeviceDisconnected, controller_id=controller_id)

    if isinstance(on_disconnected_handler, (tuple, list)):
        on_disconnected_handler_fn = on_disconnected_handler[0]
        on_disconnected_handler_params = on_disconnected_handler[1:]
    else:
        on_disconnected_handler_fn = on_disconnected_handler
        on_disconnected_handler_params = []

    @asyncio.coroutine
    def on_device_disconnected(packet):
        if packet.status_code == StatusCode.Success:
            device = packet.get_dict([
                'controller_id',
                'connection_handle',
                'reason',
                'code'
            ])

            on_disconnected_handler_fn(device, *on_disconnected_handler_params)
        else:
            self.logger.debug("Failed disconnected event received: %s", packet)

    self.register_callback(device_disconnected_uuid, callback=on_device_disconnected)
    self.logger.debug("On disconnected handler registered")
Esempio n. 6
0
    def on_response_received(packet, future):
        self.logger.debug("Stop scan response received with status={}".format(packet.status))
        self.remove_callback(packet.packet_uuid)

        if packet.status_code == StatusCode.Success:
            self.remove_callback(PacketUuid(payload_type=Payload.DeviceFound, controller_id=controller_id))
            on_scan_stopped_cb(True, packet.get_dict(['controller_id']), None, *on_scan_stopped_params)
            future.set_result(True)
        else:
            error = BaBLEException(packet, "Failed to stop scan")
            on_scan_stopped_cb(False, None, error, *on_scan_stopped_params)
            future.set_exception(error)
Esempio n. 7
0
    def on_unexpected_disconnection(packet):
        self.logger.info(
            "Unexpected disconnection event received with status={}".format(
                packet.status))
        self.remove_callback(packet.packet_uuid)

        data = packet.get_dict(
            ['controller_id', 'connection_handle', 'reason', 'code'])
        self.remove_callback(PacketUuid(
            controller_id=controller_id,
            connection_handle=data['connection_handle']),
                             match_connection_only=True)
        on_disconnected_cb(True, data, None, *on_disconnected_params)
Esempio n. 8
0
def test_remove_callback():
    """ Test to remove a callback in the CommandsManager. """
    def callback():
        pass

    packet_uuid = PacketUuid()

    command_manager = CommandsManager(None)

    # Try to remove a callback that does not exist
    command_manager.remove_callback(packet_uuid)
    assert len(command_manager.callbacks) == 0

    command_manager.register_callback(packet_uuid=packet_uuid,
                                      callback=callback)
    assert len(command_manager.callbacks) == 1

    command_manager.remove_callback(packet_uuid)
    assert len(command_manager.callbacks) == 0
Esempio n. 9
0
    def on_device_disconnected(packet, future):
        self.logger.debug(
            "Device disconnected event received with status={}".format(
                packet.status))
        self.remove_callback(packet.packet_uuid)

        if packet.status_code == StatusCode.Success:
            data = packet.get_dict(
                ['controller_id', 'connection_handle', 'reason', 'code'])

            self.remove_callback(PacketUuid(
                controller_id=controller_id,
                connection_handle=connection_handle),
                                 match_connection_only=True)
            on_disconnected_cb(True, data, None, *on_disconnected_params)
            future.set_result(data)
        else:
            error = BaBLEException(packet, "Failed to disconnect")
            on_disconnected_cb(False, None, error, *on_disconnected_params)
            future.set_exception(error)
Esempio n. 10
0
def test_register_callback():
    """ Test to register a callback in the CommandsManager. """
    def callback():
        pass

    packet_uuid = PacketUuid()

    command_manager = CommandsManager(None)
    assert len(command_manager.callbacks) == 0

    command_manager.register_callback(packet_uuid=packet_uuid,
                                      callback=callback)
    assert len(command_manager.callbacks) == 1

    # Multiple callbacks with the same packet_uuid can be added
    command_manager.register_callback(packet_uuid=packet_uuid,
                                      callback=callback)
    assert len(command_manager.callbacks) == 2

    # If replace=True, all the callbacks with the same packet_uuid are removed before adding this one
    command_manager.register_callback(packet_uuid=packet_uuid,
                                      callback=callback,
                                      replace=True)
    assert len(command_manager.callbacks) == 1
Esempio n. 11
0
def set_notification(self, controller_id, enabled, connection_handle, characteristic, on_notification_set,
                     on_notification_received, timeout):

    if not isinstance(characteristic, Characteristic):
        raise ValueError("Characteristic parameter must be a 'bable_interface.models.Characteristic' object")

    notification_event_uuid = PacketUuid(payload_type=Payload.NotificationReceived,
                                         controller_id=controller_id,
                                         connection_handle=connection_handle,
                                         attribute_handle=characteristic.value_handle)

    if isinstance(on_notification_set, (tuple, list)):
        on_notification_set_cb = on_notification_set[0]
        on_notification_set_params = on_notification_set[1:]
    else:
        on_notification_set_cb = on_notification_set
        on_notification_set_params = []

    if isinstance(on_notification_received, (tuple, list)):
        on_notification_received_cb = on_notification_received[0]
        on_notification_received_params = on_notification_received[1:]
    else:
        on_notification_received_cb = on_notification_received
        on_notification_received_params = []

    @asyncio.coroutine
    def on_notification_event(packet):
        result = packet.get_dict([
            'controller_id',
            'connection_handle',
            'attribute_handle',
            ('value', bytes)
        ])

        on_notification_received_cb(True, result, None, *on_notification_received_params)

    try:
        read_result = yield from self.read(controller_id, connection_handle, characteristic.config_handle, none_cb,
                                           timeout)
    except (RuntimeError, BaBLEException) as err:
        on_notification_set_cb(
            False,
            None,
            "Error while reading notification configuration (exception={})".format(err),
            *on_notification_set_params
        )
        raise RuntimeError("Error while reading notification config (exception={})".format(err))

    current_state = struct.unpack('H', read_result['value'])[0]

    if enabled:
        self.register_callback(notification_event_uuid, callback=on_notification_event)
        new_state = current_state | 1
    else:
        self.remove_callback(notification_event_uuid)
        new_state = current_state & 0xFFFE

    if new_state == current_state:
        on_notification_set_cb(True, read_result, None, *on_notification_set_params)
        return read_result

    value = to_bytes(new_state, 2, byteorder='little')

    try:
        result = yield from self.write(controller_id, connection_handle, characteristic.config_handle, value, none_cb,
                                       timeout)
        on_notification_set_cb(True, result, None, *on_notification_set_params)
        return result
    except (RuntimeError, BaBLEException) as err:
        if enabled:
            self.remove_callback(notification_event_uuid)
        on_notification_set_cb(
            False,
            None,
            "Error while writing notification configuration (exception={})".format(err),
            *on_notification_set_params
        )
        raise RuntimeError("Error while writing notification config (exception={})".format(err))
Esempio n. 12
0
def disconnect(self, controller_id, connection_handle, on_disconnected, timeout):

    disconnected_event_uuid = PacketUuid(
        payload_type=Payload.DeviceDisconnected,
        controller_id=controller_id,
        connection_handle=connection_handle
    )

    if isinstance(on_disconnected, (tuple, list)):
        on_disconnected_cb = on_disconnected[0]
        on_disconnected_params = on_disconnected[1:]
    else:
        on_disconnected_cb = on_disconnected
        on_disconnected_params = []

    @asyncio.coroutine
    def on_device_disconnected(packet, future):
        self.logger.debug("Device disconnected event received with status={}".format(packet.status))
        self.remove_callback(packet.packet_uuid)

        if packet.status_code == StatusCode.Success:
            data = packet.get_dict([
                'controller_id',
                'connection_handle',
                'reason',
                'code'
            ])

            self.remove_callback(
                PacketUuid(controller_id=controller_id, connection_handle=connection_handle),
                match_connection_only=True
            )
            on_disconnected_cb(True, data, None, *on_disconnected_params)
            future.set_result(data)
        else:
            error = BaBLEException(packet, "Failed to disconnect")
            on_disconnected_cb(False, None, error, *on_disconnected_params)
            future.set_exception(error)

    @asyncio.coroutine
    def on_response_received(packet, future):
        self.logger.debug("Disconnect response received with status={}".format(packet.status))
        self.remove_callback(packet.packet_uuid)

        if packet.status_code != StatusCode.Success:
            self.remove_callback(disconnected_event_uuid)

            error = BaBLEException(packet, "Failed to disconnect")
            on_disconnected_cb(False, None, error, *on_disconnected_params)
            future.set_exception(error)

    future = asyncio.Future()
    request_packet = Packet.build(Disconnect, controller_id=controller_id, connection_handle=connection_handle)

    self.register_callback(request_packet.packet_uuid, callback=on_response_received, params={'future': future})
    self.register_callback(
        disconnected_event_uuid,
        callback=on_device_disconnected,
        params={'future': future},
        replace=True
    )

    self.send_packet(request_packet)

    self.logger.debug("Disconnecting...")
    try:
        result = yield from asyncio.wait_for(future, timeout=timeout)
        return result
    except asyncio.TimeoutError:
        self.remove_callback(disconnected_event_uuid)
        on_disconnected_cb(False, None, "Disconnection timed out", *on_disconnected_params)
        raise RuntimeError("Disconnection timed out")
Esempio n. 13
0
def connect(self, controller_id, address, address_type, connection_interval, on_connected_with_info, on_disconnected,
            timeout):

    if not isinstance(connection_interval, (tuple, list)) \
       or len(connection_interval) != 2 \
       or not all(isinstance(v, (int, float)) for v in connection_interval):
        raise ValueError("connection_interval must be a 2-number tuple or list ([min, max])")

    connected_event_uuid = PacketUuid(
        payload_type=Payload.DeviceConnected,
        controller_id=controller_id,
        address=address
    )

    if isinstance(on_connected_with_info, (tuple, list)):
        on_connected_cb = on_connected_with_info[0]
        on_connected_params = on_connected_with_info[1:]
    else:
        on_connected_cb = on_connected_with_info
        on_connected_params = []

    if isinstance(on_disconnected, (tuple, list)):
        on_disconnected_cb = on_disconnected[0]
        on_disconnected_params = on_disconnected[1:]
    else:
        on_disconnected_cb = on_disconnected
        on_disconnected_params = []

    @asyncio.coroutine
    def on_unexpected_disconnection(packet):
        self.logger.info("Unexpected disconnection event received with status={}".format(packet.status))
        self.remove_callback(packet.packet_uuid)

        data = packet.get_dict([
            'controller_id',
            'connection_handle',
            'reason',
            'code'
        ])
        self.remove_callback(
            PacketUuid(controller_id=controller_id, connection_handle=data['connection_handle']),
            match_connection_only=True
        )
        on_disconnected_cb(True, data, None, *on_disconnected_params)

    @asyncio.coroutine
    def on_connected(packet, future):
        self.logger.debug("Device connected event received with status={}".format(packet.status))
        self.remove_callback(packet.packet_uuid)

        if packet.status_code == StatusCode.Success:
            device = packet.get_dict([
                'controller_id',
                'connection_handle',
                'address',
                ('address_type', lambda value: 'public' if value == 0 else 'random')
            ])

            self.register_callback(
                PacketUuid(payload_type=Payload.DeviceDisconnected,
                           controller_id=controller_id,
                           connection_handle=device['connection_handle']),
                callback=on_unexpected_disconnection
            )

            on_connected_cb(True, device, None, *on_connected_params)
            future.set_result(device)
        else:
            error = BaBLEException(packet, "Failed to connect", address=address)
            on_connected_cb(False, None, error, *on_connected_params)
            future.set_exception(error)
            return

    @asyncio.coroutine
    def on_response_received(packet, future):
        self.logger.debug("Connect response received with status={}".format(packet.status))
        self.remove_callback(packet.packet_uuid)

        if packet.status_code != StatusCode.Success:
            self.remove_callback(connected_event_uuid)
            error = BaBLEException(packet, "Failed to connect", address=address)
            on_connected_cb(False, None, error, *on_connected_params)
            future.set_exception(error)

    future = asyncio.Future()
    request_packet = Packet.build(
        Connect,
        controller_id=controller_id,
        address=address,
        address_type=0 if address_type == 'public' else 1,
        connection_interval_min=connection_interval[0],
        connection_interval_max=connection_interval[1]
    )

    self.register_callback(request_packet.packet_uuid, callback=on_response_received, params={'future': future})
    self.register_callback(connected_event_uuid, callback=on_connected, params={'future': future})

    self.send_packet(request_packet)

    self.logger.debug("Connecting...")
    try:
        result = yield from asyncio.wait_for(future, timeout=timeout)
        return result
    except asyncio.TimeoutError:
        self.remove_callback(connected_event_uuid)
        on_connected_cb(False, None, "Connection timed out", *on_connected_params)
        raise RuntimeError("Connection timed out")
Esempio n. 14
0
def start_scan(self, controller_id, active_scan, on_device_found, on_scan_started, timeout):

    if isinstance(on_device_found, (tuple, list)):
        on_device_found_cb = on_device_found[0]
        on_device_found_params = on_device_found[1:]
    else:
        on_device_found_cb = on_device_found
        on_device_found_params = []

    if isinstance(on_scan_started, (tuple, list)):
        on_scan_started_cb = on_scan_started[0]
        on_scan_started_params = on_scan_started[1:]
    else:
        on_scan_started_cb = on_scan_started
        on_scan_started_params = []

    @asyncio.coroutine
    def on_device_found_event(packet):
        result = packet.get_dict([
            'controller_id',
            'type',
            ('address', lambda value: value.decode()),
            ('address_type', lambda value: 'public' if value == 0 else 'random'),
            'rssi',
            ('uuid', lambda value: string_to_uuid(value, input_byteorder='little')),
            'company_id',
            ('device_name', lambda value: value.decode()),
            ('manufacturer_data', bytes)
        ])

        on_device_found_cb(True, result, None, *on_device_found_params)

    @asyncio.coroutine
    def on_response_received(packet, future):
        self.logger.debug("Start scan response received with status={}".format(packet.status))
        self.remove_callback(packet.packet_uuid)

        if packet.status_code == StatusCode.Success:
            on_scan_started_cb(True, packet.get_dict(['controller_id']), None, *on_scan_started_params)
            future.set_result(True)
        else:
            self.remove_callback(PacketUuid(payload_type=Payload.DeviceFound, controller_id=controller_id))
            error = BaBLEException(packet, "Failed to start scan")
            on_scan_started_cb(False, None, error, *on_scan_started_params)
            future.set_exception(error)

    future = asyncio.Future()
    request_packet = Packet.build(StartScan, controller_id=controller_id, active_scan=active_scan)

    self.register_callback(
        PacketUuid(payload_type=Payload.DeviceFound, controller_id=controller_id),
        callback=on_device_found_event
    )
    self.register_callback(
        request_packet.packet_uuid,
        callback=on_response_received,
        params={'future': future}
    )

    self.send_packet(request_packet)

    self.logger.debug("Waiting for scan to start...")
    try:
        result = yield from asyncio.wait_for(future, timeout=timeout)
        return result
    except asyncio.TimeoutError:
        self.remove_callback([
            request_packet.packet_uuid,
            PacketUuid(payload_type=Payload.DeviceFound, controller_id=controller_id)
        ])
        on_scan_started_cb(False, None, "Start scan timed out", *on_scan_started_params)
        raise RuntimeError("Start scan timed out")
Esempio n. 15
0
def test_not_match():
    """ Test cases where packet uuids should not match. """
    packet_uuid1 = PacketUuid(payload_type=Payload.ReadCentral,
                              controller_id=0,
                              connection_handle=0x0040,
                              attribute_handle=0x0003)

    # Payload types are different
    packet_uuid2 = PacketUuid(payload_type=Payload.WriteCentral,
                              controller_id=0,
                              connection_handle=0x0040,
                              attribute_handle=0x0003)
    assert not packet_uuid1.match(packet_uuid2)

    # Controller ids are different
    packet_uuid3 = PacketUuid(payload_type=Payload.ReadCentral,
                              controller_id=1,
                              connection_handle=0x0040,
                              attribute_handle=0x0003)
    assert not packet_uuid1.match(packet_uuid3)

    # Connection handles are different
    packet_uuid4 = PacketUuid(payload_type=Payload.ReadCentral,
                              controller_id=0,
                              connection_handle=0x0041,
                              attribute_handle=0x0003)
    assert not packet_uuid1.match(packet_uuid4)

    # Attribute handles are different
    packet_uuid5 = PacketUuid(payload_type=Payload.ReadCentral,
                              controller_id=0,
                              connection_handle=0x0040,
                              attribute_handle=0x0001)
    assert not packet_uuid1.match(packet_uuid5)

    # Uuids are different
    packet_uuid6 = PacketUuid(payload_type=Payload.ReadCentral,
                              controller_id=0,
                              connection_handle=0x0040,
                              attribute_handle=0x0003,
                              uuid="test")
    assert not packet_uuid6.match(packet_uuid1)
Esempio n. 16
0
def test_match():
    """ Test cases where packet uuids should match. """
    packet_uuid1 = PacketUuid(payload_type=Payload.ReadCentral,
                              controller_id=0,
                              connection_handle=0x0040,
                              attribute_handle=0x0003)

    # Packet uuids are equal
    assert packet_uuid1.match(packet_uuid1)

    # Test with different packet that should still match (same required information)
    packet_uuid2 = PacketUuid(payload_type=Payload.ReadCentral,
                              controller_id=0,
                              connection_handle=0x0040,
                              attribute_handle=0x0003,
                              address="11:22:33:44:55:66")
    assert packet_uuid1.match(packet_uuid2)

    # Test with uuids
    packet_uuid1.set(uuid="test")
    packet_uuid3 = PacketUuid(uuid="test")
    assert packet_uuid1.match(packet_uuid3)

    # Test with a generic packet uuid (only payload_type)
    packet_uuid4 = PacketUuid(payload_type=Payload.ReadCentral)

    packet_uuid5 = PacketUuid(payload_type=Payload.WriteCentral)

    assert packet_uuid4.match(packet_uuid1)
    assert packet_uuid4.match(packet_uuid2)
    assert not packet_uuid4.match(packet_uuid5)

    # Test with match_connection_only flag
    packet_uuid6 = PacketUuid(controller_id=0,
                              connection_handle=0x0040,
                              attribute_handle=0x0000)

    packet_uuid7 = PacketUuid(payload_type=Payload.ReadCentral,
                              controller_id=0,
                              connection_handle=0x0041,
                              attribute_handle=0x0000)

    assert not packet_uuid6.match(packet_uuid1)
    assert packet_uuid6.match(packet_uuid1, match_connection_only=True)
    assert packet_uuid6.match(packet_uuid2, match_connection_only=True)
    assert not packet_uuid6.match(packet_uuid7, match_connection_only=True)