Implement pairing of a new device
This includes the new pairing code for the Spark which is slightly different to the one from the Slatepull/8/head
parent
aa1820e21c
commit
06e8d69e9d
16
tuhi.py
16
tuhi.py
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
if self._pairing_mode:
|
||||||
|
self.register_device()
|
||||||
|
else:
|
||||||
self.retrieve_data()
|
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()
|
||||||
|
|
Loading…
Reference in New Issue