diff --git a/examples/demo.py b/examples/demo.py index 425b711..9cfe2bf 100644 --- a/examples/demo.py +++ b/examples/demo.py @@ -207,7 +207,7 @@ def get_options(): '-c', '--connection', default='auto://', help='''Specify connection URL to use, `protocol://mac?param=X` with protocol in: - "gatt","pygatt","gattlib","gattool", "bluepy","bluegiga"''' + "gatt", "pygatt", "gattlib", "gattool", "bluepy", "bluegiga", "bleak"''' ) arg_parser.add_argument( '-d', '--demo', @@ -246,7 +246,7 @@ def connection_from_url(url): if __name__ == '__main__': - logging.basicConfig(level=logging.INFO, format='%(relativeCreated)d\t%(levelname)s\t%(name)s\t%(message)s') + logging.basicConfig(level=logging.DEBUG, format='%(relativeCreated)d\t%(levelname)s\t%(name)s\t%(message)s') parser = get_options() options = parser.parse_args() parameters = {} @@ -256,7 +256,7 @@ def connection_from_url(url): except ValueError as err: parser.error(err.args[0]) - hub = MoveHub(**parameters) + hub = MoveHub(get_connection_bleak(hub_name='LEGO Move Hub')) try: demo = DEMO_CHOICES[options.demo] demo(hub) diff --git a/pylgbst/__init__.py b/pylgbst/__init__.py index 0958a69..616a74c 100644 --- a/pylgbst/__init__.py +++ b/pylgbst/__init__.py @@ -2,6 +2,7 @@ import traceback from pylgbst.comms import DebugServer +from pylgbst.comms.cbleak import BleakConnection, BleakConnection2 log = logging.getLogger('pylgbst') @@ -48,7 +49,7 @@ def get_connection_bleak(controller='hci0', hub_mac=None, hub_name=None): del controller # to prevent code analysis warning from pylgbst.comms.cbleak import BleakDriver - return BleakDriver(hub_mac, hub_name) + return BleakConnection2().connect(hub_mac, hub_name) def get_connection_auto(controller='hci0', hub_mac=None, hub_name=None): diff --git a/pylgbst/comms/__init__.py b/pylgbst/comms/__init__.py index 0bb285e..c0bfe68 100644 --- a/pylgbst/comms/__init__.py +++ b/pylgbst/comms/__init__.py @@ -24,7 +24,7 @@ class Connection(object): - def connect(self, hub_mac=None): + def connect(self, hub_mac=None, hub_name=None): pass @abstractmethod diff --git a/pylgbst/comms/cbleak.py b/pylgbst/comms/cbleak.py index cb97c46..ecc60e7 100644 --- a/pylgbst/comms/cbleak.py +++ b/pylgbst/comms/cbleak.py @@ -15,6 +15,91 @@ req_queue = queue.Queue() + + +class BleakConnection2(Connection): + """ + :type _client: BleakClient + """ + + def __init__(self) -> None: + super().__init__() + self._abort = False + self._loop = asyncio.new_event_loop() + asyncio.set_event_loop(self._loop) + self._client = None # noqa + self._notify_queue = queue.Queue() + + def connect(self, hub_mac=None, hub_name=None): + #logging.getLogger('bleak.backends.dotnet.client').setLevel(logging.WARNING) + #logging.getLogger('bleak.backends.bluezdbus.client').setLevel(logging.WARNING) + #logging.getLogger('bleak.backends.dotnet.discovery').setLevel(logging.WARNING) + #logging.getLogger('bleak.backends.bluezdbus.discovery').setLevel(logging.WARNING) + + log.info("Discovering devices... Press green button on Hub") + for i in range(0, 30): + devices = self._loop.run_until_complete(discover(timeout=1)) + log.debug("Devices: %s", devices) + for dev in devices: + log.debug(dev) + address = dev.address + name = dev.name + if self._is_device_matched(address, name, hub_mac, hub_name): + log.info('Device matched: %r', dev) + device = dev + break + else: + continue + + break + else: + raise ConnectionError('Device not found.') + + self._client = BleakClient(device.address, self._loop) + status = self._loop.run_until_complete(self._client.connect()) + log.debug('Connection status: %s', status) + + def enqueue(handle, data): + log.debug("Put into queue: %s", data) + self._notify_queue.put((handle, data)) + + self._loop.run_until_complete(self._client.start_notify(MOVE_HUB_HW_UUID_CHAR, enqueue)) + + return self + + def disconnect(self): + self._abort = True + if self.is_alive(): + log.debug("Disconnecting bleak connection") + self._loop.run_until_complete(self._client.disconnect()) + + def is_alive(self): + log.debug("Checking if bleak conn is alive") + return self._loop.run_until_complete(self._client.is_connected()) + + def write(self, handle, data): + desc = self._client.services.get_descriptor(handle) + + if not isinstance(data, bytearray): + data = bytearray(data) + + if desc is None: + # dedicated handle not found, try to send by using LEGO Move Hub default characteristic + self._loop.run_until_complete(self._client.write_gatt_char(MOVE_HUB_HW_UUID_CHAR, data)) + else: + self._loop.run_until_complete(self._client.write_gatt_char(desc.characteristic_uuid, data)) + + def set_notify_handler(self, handler): + def _processing(): + while not self._abort: + handle, data = self._notify_queue.get(block=True) + handler(handle, data) + + log.info("Processing thread has exited") + + threading.Thread(target=_processing, daemon=True).start() + + class BleakDriver(object): """Driver that provides interface between API and Bleak.""" @@ -68,7 +153,7 @@ async def _bleak_thread(self): data = req_queue.get() await bleak.write(data[0], data[1]) - logging.info("Communications thread has exited") + log.info("Communications thread has exited") @staticmethod def _safe_handler(handler, data): @@ -81,7 +166,7 @@ def _processing(self): self._handler(msg[0], msg[1]) time.sleep(0.01) - logging.info("Processing thread has exited") + log.info("Processing thread has exited") def write(self, handle, data): """ @@ -122,7 +207,7 @@ class BleakConnection(Connection): def __init__(self): """Initialize new instance of BleakConnection class.""" - Connection.__init__(self) + super().__init__(self) self.loop = asyncio.get_event_loop() self._device = None @@ -213,4 +298,4 @@ def is_alive(self): This method does nothing. :return: None. """ - pass + return self._client.is_connected() diff --git a/pylgbst/hub.py b/pylgbst/hub.py index d1d9041..6fb0213 100644 --- a/pylgbst/hub.py +++ b/pylgbst/hub.py @@ -143,10 +143,8 @@ def _handle_device_change(self, msg): log.info("Attached peripheral: %s", self.peripherals[msg.port]) if msg.event == msg.EVENT_ATTACHED: - hw_revision = reversed([usbyte(msg.payload, x) for x in range(2, 6)]) - sw_revision = reversed([usbyte(msg.payload, x) for x in range(6, 10)]) - # what to do with this info? it's useless, I guess - del hw_revision, sw_revision + self.peripherals[port].hw_revision = reversed([usbyte(msg.payload, x) for x in range(2, 6)]) + self.peripherals[port].sw_revision = reversed([usbyte(msg.payload, x) for x in range(6, 10)]) elif msg.event == msg.EVENT_ATTACHED_VIRTUAL: self.peripherals[port].virtual_ports = (usbyte(msg.payload, 2), usbyte(msg.payload, 3)) @@ -206,7 +204,6 @@ def __init__(self, connection=None): self.info = {} # shorthand fields - self.button = Button(self) self.led = None self.current = None self.voltage = None @@ -219,6 +216,14 @@ def __init__(self, connection=None): self.port_C = None self.port_D = None + self.info = {} + + if connection is None: + connection = get_connection_auto(hub_name="LEGO Move Hub") + + super(MoveHub, self).__init__(connection) + self.button = Button(self) + self._wait_for_devices() self._report_status() @@ -232,7 +237,7 @@ def _wait_for_devices(self, get_dev_set=None): log.debug("All devices are present: %s", devices) return log.debug("Waiting for builtin devices to appear: %s", devices) - time.sleep(0.1) + time.sleep(0.2) log.warning("Got only these devices: %s", get_dev_set()) def _report_status(self): diff --git a/pylgbst/peripherals.py b/pylgbst/peripherals.py index 2f352ec..5f1854e 100644 --- a/pylgbst/peripherals.py +++ b/pylgbst/peripherals.py @@ -55,6 +55,8 @@ def __init__(self, parent, port): :type port: int """ super(Peripheral, self).__init__() + self.sw_revision = None + self.hw_revision = None self.virtual_ports = () self.hub = parent self.port = port