Implement pairing of a new device

This includes the new pairing code for the Spark which is slightly different
to the one from the Slate
pull/8/head
Benjamin Tissoires 2018-01-17 17:01:12 +01:00 committed by Peter Hutterer
parent aa1820e21c
commit 06e8d69e9d
3 changed files with 107 additions and 4 deletions

16
tuhi.py
View File

@ -82,13 +82,19 @@ class TuhiDevice(GObject.Object):
self._wacom_device.connect('drawing', self._on_drawing_received) self._wacom_device.connect('drawing', self._on_drawing_received)
self._wacom_device.connect('done', self._on_fetching_finished, bluez_device) self._wacom_device.connect('done', self._on_fetching_finished, bluez_device)
self.drawings = [] self.drawings = []
self.pairing_mode = False
bluez_device.connect('connected', self._on_bluez_device_connected) bluez_device.connect('connected', self._on_bluez_device_connected)
bluez_device.connect('disconnected', self._on_bluez_device_disconnected) 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): def _on_bluez_device_connected(self, bluez_device):
logger.debug('{}: connected'.format(bluez_device.address)) 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): def _on_bluez_device_disconnected(self, bluez_device):
logger.debug('{}: disconnected'.format(bluez_device.address)) 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('bus-name-acquired', self._on_tuhi_bus_name_acquired)
self.server.connect('pairing-start-requested', self._on_start_pairing_requested) 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('pairing-stop-requested', self._on_stop_pairing_requested)
self.server.connect('pair-device-requested', self._on_pair_device_requested)
self.bluez = BlueZDeviceManager() self.bluez = BlueZDeviceManager()
self.bluez.connect('device-added', self._on_bluez_device_added) self.bluez.connect('device-added', self._on_bluez_device_added)
self.bluez.connect('device-updated', self._on_bluez_device_updated) self.bluez.connect('device-updated', self._on_bluez_device_updated)
@ -165,6 +172,13 @@ class Tuhi(GObject.Object):
self.bluez.stop_discovery() self.bluez.stop_discovery()
self._pairable_device_handler = None 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 @classmethod
def _is_pairing_device(cls, bluez_device): def _is_pairing_device(cls, bluez_device):
if bluez_device.vendor_id != WACOM_COMPANY_ID: if bluez_device.vendor_id != WACOM_COMPANY_ID:

View File

@ -11,6 +11,7 @@
# GNU General Public License for more details. # GNU General Public License for more details.
# #
import os
import logging import logging
from gi.repository import GObject, Gio, GLib from gi.repository import GObject, Gio, GLib
@ -32,6 +33,11 @@ INTROSPECTION_XML = """
<annotation name='org.freedesktop.DBus.Method.NoReply' value='true'/> <annotation name='org.freedesktop.DBus.Method.NoReply' value='true'/>
</method> </method>
<method name='Pair'>
<arg name='address' type='s' direction='in'/>
<arg name='result' type='i' direction='out'/>
</method>
<signal name='PairingStopped'> <signal name='PairingStopped'>
<arg name='status' type='i' /> <arg name='status' type='i' />
</signal> </signal>
@ -39,7 +45,6 @@ INTROSPECTION_XML = """
<signal name='PairableDevice'> <signal name='PairableDevice'>
<arg name='info' type='a{sv}' /> <arg name='info' type='a{sv}' />
</signal> </signal>
</interface> </interface>
<interface name='org.freedesktop.tuhi1.Device'> <interface name='org.freedesktop.tuhi1.Device'>
@ -157,6 +162,11 @@ class TuhiDBusServer(GObject.Object):
(GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
"pairing-stop-requested": "pairing-stop-requested":
(GObject.SIGNAL_RUN_FIRST, None, ()), (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): def __init__(self):
@ -199,6 +209,10 @@ class TuhiDBusServer(GObject.Object):
elif methodname == 'StopPairing': elif methodname == 'StopPairing':
self._stop_pairing() self._stop_pairing()
invocation.return_value() 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): def _property_read_cb(self, connection, sender, objpath, interface, propname):
if interface != INTF_MANAGER: if interface != INTF_MANAGER:
@ -226,6 +240,19 @@ class TuhiDBusServer(GObject.Object):
self._is_pairing = False self._is_pairing = False
self.emit("pairing-stop-requested") 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): def _on_pairing_stop(self, status):
""" """
Called by whoever handles the pairing-start-requested signal Called by whoever handles the pairing-start-requested signal

View File

@ -97,6 +97,10 @@ class WacomEEAGAINException(WacomException):
pass pass
class WacomWrongModeException(WacomException):
pass
class WacomNotPairedException(WacomException): class WacomNotPairedException(WacomException):
pass pass
@ -241,6 +245,8 @@ class WacomDevice(GObject.Object):
raise WacomNotPairedException(f"wrong device, please redo pairing") raise WacomNotPairedException(f"wrong device, please redo pairing")
if data[0] == 0x02: if data[0] == 0x02:
raise WacomEEAGAINException(f"unexpected answer: {data[0]:02x}") raise WacomEEAGAINException(f"unexpected answer: {data[0]:02x}")
if data[0] == 0x01:
raise WacomWrongModeException(f"wrong device mode")
def send_nordic_command_sync(self, def send_nordic_command_sync(self,
command, command,
@ -571,6 +577,57 @@ class WacomDevice(GObject.Object):
except WacomEEAGAINException: except WacomEEAGAINException:
logger.warning("no data, please make sure the LED is blue and the button is pressed to switch it back to green") 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): def run(self):
if self._is_running: if self._is_running:
logger.error('{}: already synching, ignoring this request'.format(self.device.address)) 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)) logger.debug('{}: starting'.format(self.device.address))
self._is_running = True self._is_running = True
try: try:
self.retrieve_data() if self._pairing_mode:
self.register_device()
else:
self.retrieve_data()
finally: finally:
self._pairing_mode = False
self._is_running = False self._is_running = False
self.emit("done") 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 = threading.Thread(target=self.run)
self.thread.start() self.thread.start()