diff --git a/tuhi.py b/tuhi.py index 52bb9a3..fd7afaa 100755 --- a/tuhi.py +++ b/tuhi.py @@ -82,13 +82,19 @@ class TuhiDevice(GObject.Object): self._wacom_device.connect('drawing', self._on_drawing_received) self._wacom_device.connect('done', self._on_fetching_finished, bluez_device) self.drawings = [] + self.pairing_mode = False bluez_device.connect('connected', self._on_bluez_device_connected) bluez_device.connect('disconnected', self._on_bluez_device_disconnected) + self._bluez_device = bluez_device + + def connect_device(self): + self._bluez_device.connect_device() def _on_bluez_device_connected(self, bluez_device): logger.debug('{}: connected'.format(bluez_device.address)) - self._wacom_device.start() + self._wacom_device.start(self.pairing_mode) + self.pairing_mode = False def _on_bluez_device_disconnected(self, bluez_device): logger.debug('{}: disconnected'.format(bluez_device.address)) @@ -140,6 +146,7 @@ class Tuhi(GObject.Object): self.server.connect('bus-name-acquired', self._on_tuhi_bus_name_acquired) self.server.connect('pairing-start-requested', self._on_start_pairing_requested) self.server.connect('pairing-stop-requested', self._on_stop_pairing_requested) + self.server.connect('pair-device-requested', self._on_pair_device_requested) self.bluez = BlueZDeviceManager() self.bluez.connect('device-added', self._on_bluez_device_added) self.bluez.connect('device-updated', self._on_bluez_device_updated) @@ -165,6 +172,13 @@ class Tuhi(GObject.Object): self.bluez.stop_discovery() self._pairable_device_handler = None + def _on_pair_device_requested(self, dbusserver, bluez_device): + tuhi_dbus_device = self.server.create_device(bluez_device) + d = TuhiDevice(bluez_device, tuhi_dbus_device) + d.pairing_mode = True + self.devices[bluez_device.address] = d + d.connect_device() + @classmethod def _is_pairing_device(cls, bluez_device): if bluez_device.vendor_id != WACOM_COMPANY_ID: diff --git a/tuhi/dbusserver.py b/tuhi/dbusserver.py index 37cb4de..f1f9b1a 100755 --- a/tuhi/dbusserver.py +++ b/tuhi/dbusserver.py @@ -11,6 +11,7 @@ # GNU General Public License for more details. # +import os import logging from gi.repository import GObject, Gio, GLib @@ -32,6 +33,11 @@ INTROSPECTION_XML = """ + + + + + @@ -39,7 +45,6 @@ INTROSPECTION_XML = """ - @@ -157,6 +162,11 @@ class TuhiDBusServer(GObject.Object): (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), "pairing-stop-requested": (GObject.SIGNAL_RUN_FIRST, None, ()), + # Signal arguments: + # address + # string of the Bluetooth device address + "pair-device-requested": + (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), } def __init__(self): @@ -199,6 +209,10 @@ class TuhiDBusServer(GObject.Object): elif methodname == 'StopPairing': self._stop_pairing() invocation.return_value() + elif methodname == 'Pair': + result = self._pair(args[0]) + result = GLib.Variant.new_int32(result) + invocation.return_value(GLib.Variant.new_tuple(result)) def _property_read_cb(self, connection, sender, objpath, interface, propname): if interface != INTF_MANAGER: @@ -226,6 +240,19 @@ class TuhiDBusServer(GObject.Object): self._is_pairing = False self.emit("pairing-stop-requested") + def _pair(self, address): + if not self._is_pairing: + return os.errno.ECONNREFUSED + + if address not in self._pairable_devices: + return os.errno.ENODEV + + self.emit('pair-device-requested', self._pairable_devices[address]) + + # FIXME: we should cache the method invocation here, wait for a + # successful result from Tuhi and then return the value + return 0 + def _on_pairing_stop(self, status): """ Called by whoever handles the pairing-start-requested signal diff --git a/tuhi/wacom.py b/tuhi/wacom.py index 9987836..c6b69ca 100644 --- a/tuhi/wacom.py +++ b/tuhi/wacom.py @@ -97,6 +97,10 @@ class WacomEEAGAINException(WacomException): pass +class WacomWrongModeException(WacomException): + pass + + class WacomNotPairedException(WacomException): pass @@ -241,6 +245,8 @@ class WacomDevice(GObject.Object): raise WacomNotPairedException(f"wrong device, please redo pairing") if data[0] == 0x02: raise WacomEEAGAINException(f"unexpected answer: {data[0]:02x}") + if data[0] == 0x01: + raise WacomWrongModeException(f"wrong device mode") def send_nordic_command_sync(self, command, @@ -571,6 +577,57 @@ class WacomDevice(GObject.Object): except WacomEEAGAINException: logger.warning("no data, please make sure the LED is blue and the button is pressed to switch it back to green") + def register_device_slate(self): + self.register_connection() + logger.info("Press the button now to confirm") + data = self.wait_nordic_data([0xe4, 0xb3], 10) + if data.opcode == 0xb3: + # generic ACK + self.check_ack(data) + self.set_time() + self.read_time() + self.ec_command() + self.bb_command() + w = self.get_dimensions('width') + h = self.get_dimensions('height') + if self.width != w or self.height != h: + logger.error(f'Uncompatible dimensions: {w}x{h}') + fw_high = self.get_firmware_version(0) + fw_low = self.get_firmware_version(1) + logger.info(f'firmware is {fw_high}-{fw_low}') + logger.info("pairing completed") + + def register_device_spark(self): + try: + self.check_connection() + except WacomWrongModeException: + # this is expected + pass + self.send_nordic_command(command=0xe3, + arguments=[0x01]) + logger.info("Press the button now to confirm") + # Wait for the button confirmation event, or any error + data = self.wait_nordic_data([0xe4, 0xb3], 10) + if data.opcode == 0xb3: + # generic ACK + self.check_ack(data) + self.send_nordic_command_sync(command=0xe5, + arguments=None, + expected_opcode=0xb3) + self.set_time() + self.read_time() + self.bb_command() + fw_high = self.get_firmware_version(0) + fw_low = self.get_firmware_version(1) + logger.info(f'firmware is {fw_high}-{fw_low}') + logger.info("pairing completed") + + def register_device(self): + if self.is_slate(): + self.register_device_slate() + else: + self.register_device_spark() + def run(self): if self._is_running: logger.error('{}: already synching, ignoring this request'.format(self.device.address)) @@ -579,11 +636,16 @@ class WacomDevice(GObject.Object): logger.debug('{}: starting'.format(self.device.address)) self._is_running = True try: - self.retrieve_data() + if self._pairing_mode: + self.register_device() + else: + self.retrieve_data() finally: + self._pairing_mode = False self._is_running = False self.emit("done") - def start(self): + def start(self, pairing_mode): + self._pairing_mode = pairing_mode self.thread = threading.Thread(target=self.run) self.thread.start()