From 138497a73b16aff37c7a900fd3518a9d92eec247 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 7 Feb 2018 15:31:01 +1000 Subject: [PATCH] wacom: add support for the Intuos Pro 2 paper --- tuhi/base.py | 2 +- tuhi/wacom.py | 128 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 125 insertions(+), 5 deletions(-) diff --git a/tuhi/base.py b/tuhi/base.py index 86d167d..acb7054 100644 --- a/tuhi/base.py +++ b/tuhi/base.py @@ -27,7 +27,7 @@ logging.basicConfig(format='%(levelname)s: %(name)s: %(message)s', level=logging.INFO) logger = logging.getLogger('tuhi') -WACOM_COMPANY_IDS = [0x4755] +WACOM_COMPANY_IDS = [0x4755, 0x4157] class TuhiDevice(GObject.Object): diff --git a/tuhi/wacom.py b/tuhi/wacom.py index 9dd24d8..086348b 100644 --- a/tuhi/wacom.py +++ b/tuhi/wacom.py @@ -287,11 +287,13 @@ class WacomRegisterHelper(WacomProtocolLowLevelComm): self.emit('button-press-required') # Wait for the button confirmation event, or any error - data = self.wait_nordic_data([0xe4, 0xb3], 10) + data = self.wait_nordic_data([0xe4, 0xb3, 0x53], 10) if protocol == Protocol.UNKNOWN: if data.opcode == 0xe4: protocol = Protocol.SLATE + elif data.opcode == 0x53: + protocol = Protocol.INTUOS_PRO else: raise WacomException(f'unexpected opcode to register reply: {data.opcode:02x}') @@ -836,6 +838,125 @@ class WacomProtocolSlate(WacomProtocolSpark): return True +class WacomProtocolIntuosPro(WacomProtocolSlate): + ''' + Subclass to handle the communication oddities with the Wacom + IntuosPro-like devices. + + :param device: the BlueZDevice object that is this wacom device + :param uuid: the UUID {to be} assigned to the device + ''' + width = 44800 + height = 29600 + protocol = Protocol.INTUOS_PRO + + def __init__(self, device, uuid): + super().__init__(device, uuid) + + def time_to_bytes(self): + t = int(time.time()) + return list(t.to_bytes(length=4, byteorder='little')) + [0x00, 0x00] + + def time_from_bytes(self, data): + seconds = int.from_bytes(data[0:4], byteorder='little') + return time.gmtime(seconds) + + # set_time is identical to spark/slate except the timestamp format + + def read_time(self): + data = self.send_nordic_command_sync(command=0xd6, + expected_opcode=0xbd) + + # Last two bytes are unknown + t = self.time_from_bytes(data) + ts = time.strftime('%y-%m-%d %H:%M:%S', time.localtime(t)) + logger.debug(f'b6 returned: {ts}') + + def get_firmware_version(self, arg): + data = self.send_nordic_command_sync(command=0xb7, + expected_opcode=0xb8, + arguments=(arg,)) + fw = ''.join([chr(d) for d in data[1:]]) + return fw + + def get_name(self): + data = self.send_nordic_command_sync(command=0xdb, + expected_opcode=0xbc) + return bytes(data) + + def set_name(self, name): + args = [ord(c) for c in name] + data = self.send_nordic_command_sync(command=0xbb, + arguments=args, + expected_opcode=0xb3) + return bytes(data) + + def check_connection(self): + args = [int(i) for i in binascii.unhexlify(self._uuid)] + self.send_nordic_command_sync(command=0xe6, + expected_opcode=[0x50, 0x51], + arguments=args) + + def parse_pen_data_prefix(self, data): + expected_prefix = b'\x67\x82\x69\x65' + prefix = data[:4] + offset = len(prefix) + # not sure if we really need this check + if bytes(prefix) != expected_prefix: + logger.debug(f'Expected pen data prefix {expected_prefix} but got {prefix}') + return False, 0 + + # This is the time the button was pressed after drawing, i.e. the + # end of the drawing + t = self.time_from_bytes(data[offset:]) + offset += 6 + + # Confirmed it's LE for at least 2 bytes (that was fun...), but + # could be 4 or more. Are 0xffff strokes enough for everybody? + nstrokes = int.from_bytes(data[offset:offset + 2], byteorder='little') + offset += 2 + + # Can't have enough zeroes. They'll come in handy one day + expected_header = b'\x00\x00\x00\x00' + data_header = data[offset:offset + len(expected_header)] + if bytes(data_header) != expected_header: + logger.debug(f'Missing zeroes, got {data_header}') + offset += 4 + + # First stroke timestamp, note this is less than the above timestamp + # ff fa c3 <6-byte-timestamp> + expected_header = b'\xff\xfa\xc3' + data_header = data[offset:offset + len(expected_header)] + if bytes(data_header) != expected_header: + logger.debug(f'Missing first stroke timestamp, got {data_header}') + offset += len(expected_header) + + ot = self.time_from_bytes(data[offset:]) + offset += 6 + + # Unclear what this is + expected_header = b'\xff\x0a\x87\x75\x80\x28\x42\x00\x10' + data_header = data[offset:offset + len(expected_header)] + if bytes(data_header) != expected_header: + logger.debug(f'Missing header 2, got {data_header}') + offset += len(expected_header) + + t = time.strftime("%y%m%d%H%M%S", t) + ot = time.strftime("%y%m%d%H%M%S", ot) + logger.debug(f'Drawing timestamp: {t}, {nstrokes} strokes, other timestamp {ot}') + + return True, offset + + def parse_next_stroke_prefix(self, opcode, raw_args): + if opcode != 0x03fa: + return False + + t = self.time_from_bytes(raw_args[2:]) + t = time.strftime("%y%m%d%H%M%S", t) + logger.info(f'stroke time: {t}') + return True + + class WacomDevice(GObject.Object): ''' Class to communicate with the Wacom device. Communication is handled in @@ -896,10 +1017,9 @@ class WacomDevice(GObject.Object): self._wacom_protocol = WacomProtocolSpark(self._device, self._uuid) elif protocol == Protocol.SLATE: self._wacom_protocol = WacomProtocolSlate(self._device, self._uuid) + elif protocol == Protocol.INTUOS_PRO: + self._wacom_protocol = WacomProtocolIntuosPro(self._device, self._uuid) else: - # FIXME: change to an assert once intuos-pro is implemented, we - # never get here - logger.error(f'Unknown Protocol {protocol}') raise WacomCorruptDataException(f'Protocol "{protocol}" not implemented') logger.debug(f'{self._device.name} is using protocol {protocol}')