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")
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")
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")
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
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")
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)
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)
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
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)
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
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))
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")
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")
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")
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)
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)