mirror of https://github.com/tuhiproject/tuhi.git
Merge branch 'master' into wip/tuhigui
commit
ee5640b783
|
@ -292,7 +292,6 @@ class TuhiKeteDevice(_DBusObject):
|
|||
else:
|
||||
logger.debug(f'{self}: Download done')
|
||||
|
||||
|
||||
def _on_properties_changed(self, proxy, changed_props, invalidated_props):
|
||||
if changed_props is None:
|
||||
return
|
||||
|
|
59
tuhi/base.py
59
tuhi/base.py
|
@ -241,6 +241,7 @@ class TuhiDevice(GObject.Object):
|
|||
self.mode = DeviceMode.LISTEN
|
||||
|
||||
def _on_listening_updated(self, dbus_device, pspec):
|
||||
# Callback when a DBus client calls Start/Stop listening
|
||||
self.notify('listening')
|
||||
|
||||
def _on_live_updated(self, dbus_device, pspec):
|
||||
|
@ -274,6 +275,10 @@ class TuhiDevice(GObject.Object):
|
|||
|
||||
|
||||
class Tuhi(GObject.Object):
|
||||
'''
|
||||
The Tuhi object is the main entry point and glue object between the
|
||||
backend and the DBus server.
|
||||
'''
|
||||
__gsignals__ = {
|
||||
'device-added':
|
||||
(GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
|
||||
|
@ -305,8 +310,10 @@ class Tuhi(GObject.Object):
|
|||
for dev in self.bluez.devices:
|
||||
self._add_device(self.bluez, dev)
|
||||
|
||||
self.bluez.connect('device-added', self._on_bluez_device_updated)
|
||||
self.bluez.connect('device-updated', self._on_bluez_device_updated)
|
||||
self.bluez.connect('device-added',
|
||||
lambda mgr, dev: self._add_device(mgr, dev, True))
|
||||
self.bluez.connect('device-updated',
|
||||
lambda mgr, dev: self._add_device(mgr, dev, True))
|
||||
|
||||
def _on_tuhi_bus_name_lost(self, dbus_server):
|
||||
self.emit('terminate')
|
||||
|
@ -327,14 +334,6 @@ class Tuhi(GObject.Object):
|
|||
for addr in unregistered:
|
||||
del self.devices[addr]
|
||||
|
||||
@classmethod
|
||||
def _device_in_register_mode(cls, bluez_device):
|
||||
if bluez_device.vendor_id not in WACOM_COMPANY_IDS:
|
||||
return False
|
||||
|
||||
manufacturer_data = bluez_device.manufacturer_data
|
||||
return manufacturer_data is not None and len(manufacturer_data) == 4
|
||||
|
||||
def _on_bluez_discovery_started(self, manager):
|
||||
# Something else may turn discovery mode on, we don't care about
|
||||
# it then
|
||||
|
@ -348,27 +347,40 @@ class Tuhi(GObject.Object):
|
|||
# restart discovery if some users are already in the listening mode
|
||||
self._on_listening_updated(None, None)
|
||||
|
||||
def _add_device(self, manager, bluez_device, hotplugged=False):
|
||||
# Note: this function gets called every time the bluez device
|
||||
# changes a property too (like signal strength). IOW, it gets called
|
||||
# every second or so.
|
||||
def _add_device(self, manager, bluez_device, from_live_update=False):
|
||||
'''
|
||||
Process a new BlueZ device that may be one of our devices.
|
||||
|
||||
uuid = None
|
||||
This function is called once during intial setup to enumerate the
|
||||
BlueZ devices and for every BlueZ device property change. Including
|
||||
RSSI which will give you a value every second or so.
|
||||
|
||||
# check if the device is already known by us
|
||||
.. :param from_live_update: True if this function was called from a BlueZ
|
||||
device property update. False when called during the initial setup
|
||||
stage.
|
||||
'''
|
||||
|
||||
# We have a reverse-engineered protocol. Let's not talk to anyone
|
||||
# who doesn't look like we know them to avoid potentially bricking a
|
||||
# device.
|
||||
if bluez_device.vendor_id not in WACOM_COMPANY_IDS:
|
||||
return
|
||||
|
||||
# check if the device is already known to us
|
||||
try:
|
||||
config = self.config.devices[bluez_device.address]
|
||||
uuid = config['uuid']
|
||||
except KeyError:
|
||||
pass
|
||||
uuid = None
|
||||
|
||||
if uuid is None and bluez_device.vendor_id not in WACOM_COMPANY_IDS:
|
||||
return
|
||||
|
||||
# if the device has been 'hotplugged' in the bluez stack,
|
||||
# if we got here from a currently live BlueZ device,
|
||||
# ManufacturerData is reliable. Else, consider the device not in
|
||||
# register mode
|
||||
if hotplugged and Tuhi._device_in_register_mode(bluez_device):
|
||||
#
|
||||
# When the device is in register mode (blue light blinking), the
|
||||
# manufacturer is merely 4 bytes. This will reset to 7 bytes even
|
||||
# when the device simply times out and does not register fully.
|
||||
if from_live_update and len(bluez_device.manufacturer_data or []) == 4:
|
||||
mode = DeviceMode.REGISTER
|
||||
else:
|
||||
mode = DeviceMode.LISTEN
|
||||
|
@ -392,9 +404,6 @@ class Tuhi(GObject.Object):
|
|||
elif d.listening:
|
||||
d.listen()
|
||||
|
||||
def _on_bluez_device_updated(self, manager, bluez_device):
|
||||
self._add_device(manager, bluez_device, True)
|
||||
|
||||
def _on_listening_updated(self, tuhi_dbus_device, pspec):
|
||||
listen = self._search_stop_handler is not None
|
||||
for dev in self.devices.values():
|
||||
|
|
|
@ -28,18 +28,15 @@ from .uhid import UHIDDevice
|
|||
|
||||
logger = logging.getLogger('tuhi.wacom')
|
||||
|
||||
NORDIC_UART_SERVICE_UUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e'
|
||||
NORDIC_UART_CHRC_TX_UUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e'
|
||||
NORDIC_UART_CHRC_RX_UUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e'
|
||||
|
||||
WACOM_LIVE_SERVICE_UUID = '00001523-1212-efde-1523-785feabcd123'
|
||||
WACOM_CHRC_LIVE_PEN_DATA_UUID = '00001524-1212-efde-1523-785feabcd123'
|
||||
|
||||
WACOM_OFFLINE_SERVICE_UUID = 'ffee0001-bbaa-9988-7766-554433221100'
|
||||
WACOM_OFFLINE_CHRC_PEN_DATA_UUID = 'ffee0003-bbaa-9988-7766-554433221100'
|
||||
|
||||
MYSTERIOUS_NOTIFICATION_SERVICE_UUID = '3a340720-c572-11e5-86c5-0002a5d5c51b'
|
||||
MYSTERIOUS_NOTIFICATION_CHRC_UUID = '3a340721-c572-11e5-86c5-0002a5d5c51b'
|
||||
NORDIC_UART_SERVICE_UUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e' # NOQA
|
||||
NORDIC_UART_CHRC_TX_UUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e' # NOQA
|
||||
NORDIC_UART_CHRC_RX_UUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e' # NOQA
|
||||
WACOM_LIVE_SERVICE_UUID = '00001523-1212-efde-1523-785feabcd123' # NOQA
|
||||
WACOM_CHRC_LIVE_PEN_DATA_UUID = '00001524-1212-efde-1523-785feabcd123' # NOQA
|
||||
WACOM_OFFLINE_SERVICE_UUID = 'ffee0001-bbaa-9988-7766-554433221100' # NOQA
|
||||
WACOM_OFFLINE_CHRC_PEN_DATA_UUID = 'ffee0003-bbaa-9988-7766-554433221100' # NOQA
|
||||
SYSEVENT_NOTIFICATION_SERVICE_UUID = '3a340720-c572-11e5-86c5-0002a5d5c51b' # NOQA
|
||||
SYSEVENT_NOTIFICATION_CHRC_UUID = '3a340721-c572-11e5-86c5-0002a5d5c51b' # NOQA
|
||||
|
||||
|
||||
@enum.unique
|
||||
|
@ -141,6 +138,8 @@ class DataLogger(object):
|
|||
This uses a logger for stdout, but it also writes the log files to disk
|
||||
for future re-use.
|
||||
|
||||
Targets for log are $HOME/.share/tuhi/12:AB:23:CD:.../<timestamp>.yml
|
||||
|
||||
'''
|
||||
class _Nordic(object):
|
||||
source = 'NORDIC'
|
||||
|
@ -163,8 +162,8 @@ class DataLogger(object):
|
|||
def recv(self, data):
|
||||
return self.parent._recv(self.source, data)
|
||||
|
||||
class _Mysterious(object):
|
||||
source = 'MYSTERIOUS'
|
||||
class _SysEvent(object):
|
||||
source = 'SYSEVENT'
|
||||
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
|
@ -204,7 +203,7 @@ class DataLogger(object):
|
|||
|
||||
self.nordic = DataLogger._Nordic(self)
|
||||
self.pen = DataLogger._Pen(self)
|
||||
self.mysterious = DataLogger._Mysterious(self)
|
||||
self.sysevent = DataLogger._SysEvent(self)
|
||||
self.logfile = None
|
||||
|
||||
def _on_bluez_connected(self, bluez_device):
|
||||
|
@ -237,10 +236,12 @@ class DataLogger(object):
|
|||
|
||||
def _recv(self, source, data):
|
||||
if source in ['NORDIC', 'PEN']:
|
||||
def _convert(values): return list2hex(values)
|
||||
def _convert(values):
|
||||
return list2hex(values)
|
||||
convert = _convert
|
||||
else:
|
||||
def _convert(values): return binascii.hexlify(bytes(values))
|
||||
def _convert(values):
|
||||
return binascii.hexlify(bytes(values))
|
||||
convert = _convert
|
||||
|
||||
self.logger.debug(f'{self.btaddr}: RX {source} <-- {convert(data)}')
|
||||
|
@ -253,7 +254,7 @@ class DataLogger(object):
|
|||
|
||||
def _send(self, source, data):
|
||||
command = data[0]
|
||||
arguments = data[1:]
|
||||
arguments = data[2:]
|
||||
|
||||
if data[0] in self.commands:
|
||||
self.logger.debug(f'command: {self.commands[data[0]]}')
|
||||
|
@ -288,6 +289,10 @@ class WacomEEAGAINException(WacomException):
|
|||
errno = errno.EAGAIN
|
||||
|
||||
|
||||
class WacomUnsupportedCommandException(WacomException):
|
||||
errno = errno.ENOMSG
|
||||
|
||||
|
||||
class WacomWrongModeException(WacomException):
|
||||
errno = errno.EBADE
|
||||
|
||||
|
@ -453,8 +458,8 @@ class WacomPacketHandlerUnknownFixedStrokeDataIntuosPro(WacomPacketHandler):
|
|||
class WacomProtocolLowLevelComm(GObject.Object):
|
||||
'''
|
||||
Internal class to handle the communication with the Wacom device.
|
||||
No-one should directly instanciate this.
|
||||
|
||||
No-one should directly instanciate this, use the device-specific
|
||||
subclass instead (e.g. WacomProtocolIntuosPro).
|
||||
|
||||
:param device: the BlueZDevice object that is this wacom device
|
||||
'''
|
||||
|
@ -480,30 +485,29 @@ class WacomProtocolLowLevelComm(GObject.Object):
|
|||
self.fw_logger.nordic.send(data)
|
||||
chrc.write_value(data)
|
||||
|
||||
def check_nordic_incoming(self):
|
||||
def pop_next_message(self):
|
||||
answer = self.nordic_answer
|
||||
length = answer[1]
|
||||
args = answer[2:]
|
||||
if length > len(args):
|
||||
raise WacomException(f'error while processing answer, should get an answer of size {length} instead of {len(args)}')
|
||||
raise WacomException(f'Invalid answer message length: expected {length}, got {len(args)}')
|
||||
self.nordic_answer = self.nordic_answer[length + 2:] # opcode + len
|
||||
return NordicData(answer)
|
||||
return NordicData(answer[:length + 2])
|
||||
|
||||
def wait_nordic_data(self, expected_opcode, timeout=None):
|
||||
if not self.nordic_event.acquire(timeout=timeout):
|
||||
# timeout
|
||||
raise WacomTimeoutException(f'{self.device.name}: Timeout while reading data')
|
||||
|
||||
data = self.check_nordic_incoming()
|
||||
data = self.pop_next_message()
|
||||
|
||||
# logger.debug(f'received {data.opcode:02x} / {data.length:02x} / {b2hex(bytes(data))}')
|
||||
|
||||
if isinstance(expected_opcode, list):
|
||||
if not isinstance(expected_opcode, list):
|
||||
expected_opcode = [expected_opcode]
|
||||
|
||||
if data.opcode not in expected_opcode:
|
||||
raise WacomException(f'unexpected opcode: {data.opcode:02x}')
|
||||
else:
|
||||
if data.opcode != expected_opcode:
|
||||
raise WacomException(f'unexpected opcode: {data.opcode:02x}')
|
||||
|
||||
return data
|
||||
|
||||
|
@ -516,7 +520,7 @@ class WacomProtocolLowLevelComm(GObject.Object):
|
|||
elif data[0] == 0x02:
|
||||
raise WacomEEAGAINException(f'unexpected answer: {data[0]:02x}')
|
||||
elif data[0] == 0x05:
|
||||
raise WacomCorruptDataException(f'invalid opcode')
|
||||
raise WacomUnsupportedCommandException(f'invalid opcode')
|
||||
elif data[0] == 0x07:
|
||||
raise WacomNotRegisteredException(f'wrong device, please re-register')
|
||||
elif data[0] != 0x00:
|
||||
|
@ -560,7 +564,7 @@ class WacomRegisterHelper(WacomProtocolLowLevelComm):
|
|||
|
||||
@classmethod
|
||||
def is_spark(cls, device):
|
||||
return MYSTERIOUS_NOTIFICATION_CHRC_UUID not in device.characteristics
|
||||
return SYSEVENT_NOTIFICATION_CHRC_UUID not in device.characteristics
|
||||
|
||||
def register_device(self, uuid):
|
||||
protocol = Protocol.UNKNOWN
|
||||
|
@ -1002,7 +1006,7 @@ class WacomProtocolBase(WacomProtocolLowLevelComm):
|
|||
arguments=None)
|
||||
self.set_time()
|
||||
self.read_time()
|
||||
name = self.get_name()
|
||||
self.get_name()
|
||||
self.get_firmware_version()
|
||||
|
||||
def live_mode(self, mode, uhid):
|
||||
|
@ -1061,8 +1065,8 @@ class WacomProtocolSlate(WacomProtocolSpark):
|
|||
def __init__(self, device, uuid):
|
||||
super().__init__(device, uuid)
|
||||
|
||||
device.connect_gatt_value(MYSTERIOUS_NOTIFICATION_CHRC_UUID,
|
||||
self._on_mysterious_data_received)
|
||||
device.connect_gatt_value(SYSEVENT_NOTIFICATION_CHRC_UUID,
|
||||
self._on_sysevent_data_received)
|
||||
|
||||
def live_mode(self, mode, uhid):
|
||||
# Slate tablet has two models A5 and A4
|
||||
|
@ -1075,8 +1079,8 @@ class WacomProtocolSlate(WacomProtocolSpark):
|
|||
|
||||
return super().live_mode(mode, uhid)
|
||||
|
||||
def _on_mysterious_data_received(self, name, value):
|
||||
self.fw_logger.mysterious.recv(value)
|
||||
def _on_sysevent_data_received(self, name, value):
|
||||
self.fw_logger.sysevent.recv(value)
|
||||
|
||||
def ack_transaction(self):
|
||||
self.send_nordic_command_sync(command=0xca)
|
||||
|
@ -1100,7 +1104,7 @@ class WacomProtocolSlate(WacomProtocolSpark):
|
|||
self.set_time()
|
||||
self.read_time()
|
||||
self.ec_command()
|
||||
name = self.get_name()
|
||||
self.get_name()
|
||||
|
||||
w, h = self.get_dimensions()
|
||||
if self.width != w or self.height != h:
|
||||
|
@ -1122,7 +1126,7 @@ class WacomProtocolSlate(WacomProtocolSpark):
|
|||
self.height = h
|
||||
self.notify('dimensions')
|
||||
|
||||
fw = self.get_firmware_version()
|
||||
self.get_firmware_version()
|
||||
self.ec_command()
|
||||
if self.read_offline_data() == 0:
|
||||
logger.info('no data to retrieve')
|
||||
|
|
Loading…
Reference in New Issue