def write_without_response(self, controller_id, connection_handle, attribute_handle, value, timeout): @asyncio.coroutine def on_ack_received(packet, future): self.logger.debug("WriteWithoutResponse ack received with status={}".format(packet.status)) self.remove_callback(packet.packet_uuid) if packet.status_code == StatusCode.Success: future.set_result(True) else: error = BaBLEException(packet, "Failed to send WriteWithoutResponse packet", connection_handle=connection_handle, attribute_handle=attribute_handle) future.set_exception(error) future = asyncio.Future() request_packet = Packet.build( WriteWithoutResponseCentral, controller_id=controller_id, connection_handle=connection_handle, attribute_handle=attribute_handle, value=bytes(value) ) self.register_callback(request_packet.packet_uuid, callback=on_ack_received, params={'future': future}) self.send_packet(request_packet) try: result = yield from asyncio.wait_for(future, timeout=timeout) self.logger.debug("Write without response command sent") return result except asyncio.TimeoutError: self.remove_callback(request_packet.packet_uuid) raise RuntimeError("WriteWithoutResponse timed out")
def test_build(): """ Test building a packet. """ packet = Packet.build(payload_module=ReadCentral, controller_id=0, connection_handle=0x0040, attribute_handle=0x0003) assert isinstance(packet, Packet) assert packet.controller_id == 0 assert packet.payload_type == Payload.Payload.ReadCentral assert packet.get('connection_handle') == 0x0040 assert packet.get('attribute_handle') == 0x0003 # Try to get a parameter that does not exist in the protocol with pytest.raises(KeyError): Packet.build(payload_module=ReadCentral, does_not_exist="test")
def list_controllers(self, timeout): @asyncio.coroutine def on_response_received(packet, future): self.logger.debug("List of controllers received with status={}".format(packet.status)) self.remove_callback(packet.packet_uuid) if packet.status_code == StatusCode.Success: controllers = packet.get( name='controllers', format_function=lambda raw_ctrls: [Controller.from_flatbuffers(raw_ctrl) for raw_ctrl in raw_ctrls] ) future.set_result(controllers) else: future.set_exception(BaBLEException(packet, "Failed to list controllers")) future = asyncio.Future() request_packet = Packet.build(GetControllersList) self.register_callback( request_packet.packet_uuid, callback=on_response_received, params={'future': future} ) self.send_packet(request_packet) self.logger.debug("Waiting for list of controllers...") try: controllers = yield from asyncio.wait_for(future, timeout=timeout) return controllers except asyncio.TimeoutError: self.remove_callback(request_packet.packet_uuid) raise RuntimeError("List controllers timed out")
def read(self, controller_id, connection_handle, attribute_handle, on_read, timeout): if isinstance(on_read, (tuple, list)): on_read_cb = on_read[0] on_read_params = on_read[1:] else: on_read_cb = on_read on_read_params = [] @asyncio.coroutine def on_response_received(packet, future): self.logger.debug("Read response 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', 'attribute_handle', ('value', bytes) ]) on_read_cb(True, data, None, *on_read_params) future.set_result(data) else: error = BaBLEException(packet, "Failed to read value", connection_handle=connection_handle, attribute_handle=attribute_handle) on_read_cb(False, None, error, *on_read_params) future.set_exception(error) future = asyncio.Future() request_packet = Packet.build(ReadCentral, controller_id=controller_id, connection_handle=connection_handle, attribute_handle=attribute_handle) self.register_callback(request_packet.packet_uuid, callback=on_response_received, params={'future': future}) self.send_packet(request_packet) self.logger.debug("Reading...") try: result = yield From(asyncio.wait_for(future, timeout=timeout)) raise asyncio.Return(result) except asyncio.TimeoutError: self.remove_callback(request_packet.packet_uuid) on_read_cb(False, None, "Read timed out", *on_read_params) raise RuntimeError("Read timed out")
def probe_characteristics(self, controller_id, connection_handle, start_handle, end_handle, on_characteristics_probed, timeout): if isinstance(on_characteristics_probed, (tuple, list)): on_characteristics_probed_cb = on_characteristics_probed[0] on_characteristics_probed_params = on_characteristics_probed[1:] else: on_characteristics_probed_cb = on_characteristics_probed on_characteristics_probed_params = [] @asyncio.coroutine def on_response_received(packet, future): self.logger.debug( "Probe characteristics response received with status={}".format( packet.status)) self.remove_callback(packet.packet_uuid) if packet.status_code == StatusCode.Success: characteristics = packet.get_dict([ 'controller_id', 'connection_handle', ('characteristics', lambda chars: [Characteristic.from_flatbuffers(char) for char in chars]) ]) on_characteristics_probed_cb(True, characteristics, None, *on_characteristics_probed_params) future.set_result(characteristics) else: error = BaBLEException(packet, "Failed to probe characteristics", connection_handle=connection_handle) on_characteristics_probed_cb(False, None, error, *on_characteristics_probed_params) future.set_exception(error) future = asyncio.Future() request_packet = Packet.build(ProbeCharacteristics, controller_id=controller_id, connection_handle=connection_handle, start_handle=start_handle, end_handle=end_handle) self.register_callback(request_packet.packet_uuid, callback=on_response_received, params={'future': future}) self.send_packet(request_packet) self.logger.debug("Waiting for characteristics...") try: characteristics = yield From(asyncio.wait_for(future, timeout=timeout)) raise asyncio.Return(characteristics) except asyncio.TimeoutError: self.remove_callback(request_packet.packet_uuid) raise RuntimeError("Probe characteristics timed out")
def test_send_packet(bridge_subprocess): """ Test sending a packet from CommandsManager. """ command_manager = CommandsManager(bridge_subprocess) packet = Packet.build(StartScan, controller_id=0) command_manager.send_packet(packet) assert len(bridge_subprocess.stdin.input_buffer) == 1 sent_packet = bridge_subprocess.stdin.input_buffer[0] assert sent_packet.payload_type == Payload.Payload.StartScan assert sent_packet.controller_id == 0
def test_handle(bridge_subprocess): """ Test if a packet is correctly handled if it is expected in CommandsManager. """ def on_response_received(packet, test): assert packet.payload_type == Payload.Payload.StartScan assert test is True command_manager = CommandsManager(bridge_subprocess) # Create a request packet and register the response callback request_packet = Packet.build(StartScan, controller_id=0) command_manager.register_callback(request_packet.packet_uuid, callback=on_response_received, params={'test': True}) # If wrong response packet is received, should not handle it wrong_response_packet = Packet.build(StartScan, controller_id=0) assert command_manager.handle(wrong_response_packet, lambda cb: None) is False # If expected response packet received, should be handled good_response_packet = Packet.build(StartScan, controller_id=0) good_response_packet.packet_uuid.set(uuid=request_packet.packet_uuid.uuid) assert command_manager.handle(good_response_packet, lambda cb: None) is True
def set_gatt_table(self, controller_id, services, characteristics, on_set, timeout): if isinstance(on_set, (tuple, list)): on_set_cb = on_set[0] on_set_params = on_set[1:] else: on_set_cb = on_set on_set_params = [] @asyncio.coroutine def on_response_received(packet, future): self.logger.debug("GATT table set with status={}".format(packet.status)) self.remove_callback(packet.packet_uuid) if packet.status_code == StatusCode.Success: data = packet.get_dict(['controller_id']) on_set_cb(True, data, None, *on_set_params) future.set_result(data) else: error = BaBLEException(packet, "Failed to set GATT table") on_set_cb(False, None, error, *on_set_params) future.set_exception(error) future = asyncio.Future() request_packet = Packet.build( SetGATTTable, controller_id=controller_id, services=services, characteristics=characteristics ) self.register_callback( request_packet.packet_uuid, callback=on_response_received, params={'future': future} ) self.send_packet(request_packet) self.logger.debug("Waiting for setting GATT table response...") try: result = yield from asyncio.wait_for(future, timeout=timeout) return result except asyncio.TimeoutError: self.remove_callback(request_packet.packet_uuid) raise RuntimeError("Set GATT table timed out")
def stop_scan(self, controller_id, on_scan_stopped, timeout): if isinstance(on_scan_stopped, (tuple, list)): on_scan_stopped_cb = on_scan_stopped[0] on_scan_stopped_params = on_scan_stopped[1:] else: on_scan_stopped_cb = on_scan_stopped on_scan_stopped_params = [] @asyncio.coroutine 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) future = asyncio.Future() request_packet = Packet.build(StopScan, controller_id=controller_id) 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 stop...") try: result = yield From(asyncio.wait_for(future, timeout=timeout)) raise asyncio.Return(result) except asyncio.TimeoutError: self.remove_callback(request_packet.packet_uuid) on_scan_stopped_cb(False, None, "Stop scan timed out", *on_scan_stopped_params) raise RuntimeError("Stop scan timed out")
def cancel_connection(self, controller_id, on_connection_cancelled, timeout): if isinstance(on_connection_cancelled, (tuple, list)): on_connection_cancelled_cb = on_connection_cancelled[0] on_connection_cancelled_params = on_connection_cancelled[1:] else: on_connection_cancelled_cb = on_connection_cancelled on_connection_cancelled_params = [] @asyncio.coroutine def on_response_received(packet, future): self.logger.debug( "Cancel connection response received with status={}".format( packet.status)) self.remove_callback(packet.packet_uuid) if packet.status_code == StatusCode.Success: on_connection_cancelled_cb(True, None, None, *on_connection_cancelled_params) future.set_result(True) else: error = BaBLEException(packet, "Failed to cancel connection") on_connection_cancelled_cb(False, None, error, *on_connection_cancelled_params) future.set_exception(error) future = asyncio.Future() request_packet = Packet.build(CancelConnection, controller_id=controller_id) self.register_callback(request_packet.packet_uuid, callback=on_response_received, params={'future': future}) self.send_packet(request_packet) self.logger.debug("Waiting for connection to cancel...") try: result = yield From(asyncio.wait_for(future, timeout=timeout)) raise asyncio.Return(result) except asyncio.TimeoutError: self.remove_callback(request_packet.packet_uuid) on_connection_cancelled_cb(False, None, "Cancel connection timed out", *on_connection_cancelled_params) raise RuntimeError("Cancel connection timed out")
def notify(self, controller_id, connection_handle, attribute_handle, value, timeout): # TODO: use characteristic instead of attribute_handle @asyncio.coroutine def on_ack_received(packet, future): self.logger.debug( "EmitNotification ack received with status={}".format( packet.status)) self.remove_callback(packet.packet_uuid) if packet.status_code == StatusCode.Success: future.set_result(True) else: error = BaBLEException(packet, "Failed to send EmitNotification packet", connection_handle=connection_handle, attribute_handle=attribute_handle) future.set_exception(error) future = asyncio.Future() request_packet = Packet.build(EmitNotification, controller_id=controller_id, connection_handle=connection_handle, attribute_handle=attribute_handle, value=bytes(value)) self.register_callback(request_packet.packet_uuid, callback=on_ack_received, params={'future': future}) self.send_packet(request_packet) try: result = yield From(asyncio.wait_for(future, timeout=timeout)) self.logger.debug("Notification sent") raise asyncio.Return(result) except asyncio.TimeoutError: self.remove_callback(request_packet.packet_uuid) raise RuntimeError("Notification timed out")
def set_advertising(self, controller_id, enabled, uuids, name, company_id, advertising_data, scan_response, on_set, timeout): if isinstance(on_set, (tuple, list)): on_set_cb = on_set[0] on_set_params = on_set[1:] else: on_set_cb = on_set on_set_params = [] if uuids is not None: if not isinstance(uuids, (tuple, list)): uuids = [uuids] for i, uuid in enumerate(uuids): if isinstance(uuid, UUID): uuids[i] = uuid_to_string(uuid) elif isinstance(uuid, string_types): uuids[i] = switch_endianness_string(uuid) else: raise ValueError("UUID must be either a uuid.UUID object or a string") @asyncio.coroutine def on_response_received(packet, future): self.logger.debug("Set advertising response received with status={}".format(packet.status)) self.remove_callback(packet.packet_uuid) if packet.status_code == StatusCode.Success: data = packet.get_dict([ 'controller_id', 'state' ]) on_set_cb(True, data, None, *on_set_params) future.set_result(data) else: error = BaBLEException(packet, "Failed to set advertising") on_set_cb(False, None, error, *on_set_params) future.set_exception(error) future = asyncio.Future() request_packet = Packet.build( SetAdvertising, controller_id=controller_id, state=enabled, uuids=uuids, name=name, company_id=company_id, adv_manufacturer_data=advertising_data, scan_manufacturer_data=scan_response ) self.register_callback( request_packet.packet_uuid, callback=on_response_received, params={'future': future} ) self.send_packet(request_packet) self.logger.debug("Waiting for setting advertising response...") try: result = yield from asyncio.wait_for(future, timeout=timeout) return result except asyncio.TimeoutError: self.remove_callback(request_packet.packet_uuid) raise RuntimeError("Set advertising timed out")
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")