#!/usr/bin/env python # ported from the xfree86-driver by Simon Budig (simon@gimp.org) import os, termios, select, time, re, string class Waconf: WC_RESET = "\r#" # reset to wacom IV command set or wacom V reset WC_RESET_BAUD = "\r$" # reset baud rate to default (wacom V) or # switch to wacom IIs (wacom IV) WC_CONFIG = "~R\r" # request a configuration string WC_COORD = "~C\r" # request max coordinates WC_MODEL = "~#\r" # request model and ROM version WC_MULTI = "MU1\r" # multi mode input WC_UPPER_ORIGIN = "OC1\r" # origin in upper left WC_SUPPRESS = "SU" # suppress mode WC_ALL_MACRO = "~M0\r" # enable all macro buttons WC_NO_MACRO1 = "~M1\r" # disable macro buttons of group 1 WC_RATE = "IT0\r" # max transmit rate (unit of 5 ms) WC_TILT_MODE = "FM1\r" # enable extra protocol for tilt management WC_NO_INCREMENT = "IN0\r" # do not enable increment mode WC_STREAM_MODE = "SR\r" # enable continuous mode WC_PRESSURE_MODE = "PH1\r" # enable pressure mode WC_ZFILTER = "ZF1\r" # stop sending coordinates WC_STOP = "\nSP\r" # stop sending coordinates WC_START = "ST\r" # start sending coordinates WC_NEW_RESOLUTION = "NR" # change the resolution # Intuos Sequences WC_V_SINGLE = "MT0\r" WC_V_MULTI = "MT1\r" WC_V_ID = "ID1\r" WC_V_19200 = "BA19\r" # WC_V_9600 = "BA96\r" WC_V_9600 = "$\r" WC_RESET_19200 = "\r$" # reset to 9600 baud WC_RESET_19200_IV = "\r#" artpad_setup_string = WC_MULTI + WC_UPPER_ORIGIN + WC_ALL_MACRO + \ WC_NO_MACRO1 + WC_RATE + WC_NO_INCREMENT + \ WC_STREAM_MODE + WC_ZFILTER; penpartner_setup_string = WC_PRESSURE_MODE + WC_START; intuos_setup_string = WC_V_MULTI + WC_V_ID + WC_RATE; COMMAND_SET_MASK = 0xc0 BAUD_RATE_MASK = 0x0a PARITY_MASK = 0x30 DATA_LENGTH_MASK = 0x40 STOP_BIT_MASK = 0x80 HEADER_BIT = 0x80 ZAXIS_SIGN_BIT = 0x40 ZAXIS_BIT = 0x04 ZAXIS_BITS = 0x3f POINTER_BIT = 0x20 PROXIMITY_BIT = 0x40 BUTTON_FLAG = 0x08 BUTTONS_BITS = 0x78 TILT_SIGN_BIT = 0x40 TILT_BITS = 0x3f # defines to discriminate second side button and the eraser ERASER_PROX = 4 OTHER_PROX = 1 def __init__ (self, device = "/dev/ttyUSB0"): self.fd = open (device, "w+", 0) self.setspeed(19200) self.write (Waconf.WC_RESET_BAUD) time.sleep (0.250) self.write (Waconf.WC_RESET) time.sleep (0.075) self.setspeed(9600) self.write (Waconf.WC_RESET_BAUD) time.sleep (0.250) self.write (Waconf.WC_STOP) time.sleep (0.030) while select.select ([self.fd], [], [], 0)[0]: a = self.fd.read(1) def setspeed (self, baud): # Setting serial parameters # tio = [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] tio = termios.tcgetattr (self.fd.fileno()) tio [0] = termios.IXOFF tio [1] = 0 if (baud == 9600): tio [2] = termios.B9600 | termios.CS8 | termios.CREAD | termios.CLOCAL elif (baud == 19200): tio [2] = termios.B19200 | termios.CS8 | termios.CREAD | termios.CLOCAL else: raise ValueError, "Only 19200 or 9600 baud allowes" tio [3] = 0 tio [6] [termios.VINTR] = 0 tio [6] [termios.VQUIT] = 0 tio [6] [termios.VERASE] = 0 tio [6] [termios.VEOF] = 0 if termios.__dict__.has_key("VWERASE"): tio [6] [termios.VWERASE] = 0 if termios.__dict__.has_key("VREPRINT"): tio [6] [termios.VREPRINT] = 0 tio [6] [termios.VKILL] = 0 tio [6] [termios.VEOF] = 0 tio [6] [termios.VEOL] = 0 if termios.__dict__.has_key("VEOL2"): tio [6] [termios.VEOL2] = 0 tio [6] [termios.VSUSP] = 0 if termios.__dict__.has_key("VDSUSP"): tio [6] [termios.VDSUSP] = 0 if termios.__dict__.has_key("VDISCARD"): tio [6] [termios.VDISCARD] = 0 if termios.__dict__.has_key("VLNEXT"): tio [6] [termios.VLNEXT] = 0 tio [6] [termios.VMIN] = 1 tio [6] [termios.VTIME] = 10 termios.tcsetattr (self.fd.fileno(), termios.TCSANOW, tio) def __del__ (self): self.fd.close() def write (self, request): self.fd.write (request) def send_request (self, request): answer = "" self.write (request) maxtry = 3 while maxtry >= 0 and answer [:2] != request [:2]: if select.select ([self.fd], [], [], 1)[0]: answer = answer + self.fd.read(1) else: raise IOError, "Wacom unable to read byte of request" answer = answer [-2:] maxtry = maxtry - 1 if answer [:2] != request [:2]: raise IOError, "Wacom does not respond to request" maxtry = 3 while answer[-1] != '\r' and maxtry > 0: if select.select ([self.fd], [], [], 1)[0]: answer = answer + self.fd.read(1) maxtry = 3 else: raise IOError, "Wacom unable to read byte of request" maxtry = maxtry - 1 if answer[-1] == '\r': answer = answer[:-1] return answer[2:] def identity (self): model = tab.send_request (Waconf.WC_MODEL) rom = model [string.rfind (model, 'V')+1:] self.model = "Art/UltraPad" self.rom = float (rom [:string.find (rom, '-')]) self.ProtocolLevel = 4 self.MaxX = 0 self.MaxY = 0 self.MaxZ = 240 self.ResolX = 0 self.ResolY = 0 self.ResolZ = 1270 self.StylusProximity = 0 self.PktLength = 7 if model[:2] == "GD": self.model = "Intuos" self.ProtocolLevel = 5 self.MaxZ = 1024 self.ResolX = 2540 self.ResolY = 2540 self.ResolZ = 2540 self.PktLength = 9 if model[:2] == "CT": self.model = "PenPartner" self.ResolX = 1000 self.ResolY = 1000 if model[:2] == "ET": self.model = "Graphire" self.MaxX = 5103; self.MaxY = 3711; self.MaxZ = 512; self.ResolX = 1000 self.ResolY = 1000 if self.ProtocolLevel == 4 and not (self.ResolX and self.ResolY): mconfig = self.send_request (Waconf.WC_CONFIG) resols = mconfig[string.rfind (mconfig[:string.rfind(mconfig, ',')], ',')+1:] self.ResolX, self.ResolY = tuple (map (int, string.split (resols, ','))) if self.model != "Graphire" and not (self.MaxX and self.MaxY): self.MaxX, self.MaxY = self.maxcoords() self.Threshold = - self.MaxZ / 3 self.SizeX = int (float(self.MaxX) / self.ResolX * 25.4 + 0.5) self.SizeY = int (float(self.MaxY) / self.ResolY * 25.4 + 0.5) return self.model, self.rom def maxcoords (self): mcoords = tab.send_request (Waconf.WC_COORD) return tuple (map (int, string.split(mcoords, ","))) def startcoords (self): if self.model != "PenPartner" and self.ProtocolLevel == 4 and self.rom >= 1.2: self.write (Waconf.WC_NEW_RESOLUTION + "2540\r" ) mconfig = self.send_request (Waconf.WC_CONFIG) resols = mconfig[string.rfind (mconfig[:string.rfind(mconfig, ',')], ',')+1:] self.ResolX, self.ResolY = tuple (map (int, string.split (resols, ','))) if self.model == "PenPartner": self.write (Waconf.penpartner_setup_string) elif self.ProtocolLevel == 4: self.write (Waconf.WC_RESET) time.sleep (0.075) self.write (Waconf.artpad_setup_string) else: self.write (Waconf.intuos_setup_string) if self.ProtocolLevel == 4 and self.PktLength == 9: self.write (Waconf.WC_TILT_MODE) if self.ProtocolLevel == 4: self.write (Waconf.WC_SUPPRESS + "5\r") self.write (Waconf.WC_START) def readevent (self): event = [1] # sync to the next packet while event[0] < 128: event[0] = ord(self.fd.read(1)) count = self.PktLength -1 while count > 0: event.append (ord(self.fd.read(1))) count = count - 1 return event def readevents (self): while 1: event = self.readevent() if self.parseevent(event): print self.parseevent(event) def parseevent (self, event): result = {} if self.ProtocolLevel == 4: result["event"] = "Protocol 4" result["x"] = (((event[0] & 0x3) << 14) + (event[1] << 7) + event[2]) result["y"] = (((event[3] & 0x3) << 14) + (event[4] << 7) + event[5]) if event[0] & Waconf.POINTER_BIT: result["type"] = "Stylus" else: result["type"] = "Cursor" result["z"] = ((event[6] & Waconf.ZAXIS_BITS) << 1) + ((event[3] & Waconf.ZAXIS_BIT) >> 2) if self.MaxZ == 512: result["z"] = result["z"]*2 + ((event[0] & Waconf.ZAXIS_BIT) >> 2) if event[6] & Waconf.ZAXIS_SIGN_BIT: result["z"] = result["z"] - (self.MaxZ / 2) if event[0] & Waconf.PROXIMITY_BIT: result["proximity"] = 1 else: result["proximity"] = 0 if self.model == "Graphire": if result["type"] == "Stylus": result["buttons"] = (((event[3] & 0x30) >> 3) | (result["z"] > self.Threshold)) else: result["buttons"] = (event[3] & 0x38) >> 3 result["wheel"] = (event[6] & 0x30) >> 4 if (event[6] & 0x40): result["wheel"] = - result["wheel"] result["is_button"] = (result["buttons"] != 0) else: result["buttons"] = (event[3] & Waconf.BUTTONS_BITS) >> 3 result["is_button"] = event[0] & Waconf.BUTTON_FLAG if result["type"] == "Stylus": if (not self.StylusProximity) and result["proximity"]: if self.model == "Graphire": if not (event[3] & 0x40): result["side"] = "stylus" self.StylusSide = "stylus" else: result["side"] = "eraser" self.StylusSide = "eraser" else: if result["buttons"] != 4: result["side"] = "stylus" self.StylusSide = "stylus" else: result["side"] = "eraser" self.StylusSide = "eraser" else: result["side"] = self.StylusSide self.StylusProximity = result["proximity"] if not result["proximity"]: result["side"] = "none " if self.PktLength == 9: result["tiltx"] = event[7] & Waconf.TILT_BITS result["tilty"] = event[8] & Waconf.TILT_BITS if event[7] & Waconf.TILT_SIGN_BIT: result["tiltx"] = result["tiltx"] - (TILT_BITS + 1) if event[8] & Waconf.TILT_SIGN_BIT: result["tilty"] = result["tilty"] - (TILT_BITS + 1) elif self.ProtocolLevel == 5: if event[0] & 0xfc == 0xc0: result["event"] = "Device ID" result["device_type_no"] = (((event[1] & 0x7f) << 5) | ((event[2] & 0x7c) >> 2)) & 0x07ff if result["device_type_no"] & 0x07ff == 0x0022: result["device_type"] = "Pen" elif result["device_type_no"] & 0x07ff == 0x002A: result["device_type"] = "Pen Eraser" elif result["device_type_no"] & 0x07ff == 0x0012: result["device_type"] = "Inking Pen" elif result["device_type_no"] & 0x07ff == 0x0032: result["device_type"] = "Stroking Pen" elif result["device_type_no"] & 0x07ff == 0x0112: result["device_type"] = "Airbrush" elif result["device_type_no"] & 0x07ff == 0x011A: result["device_type"] = "Airbrush Eraser" elif result["device_type_no"] & 0x07ff == 0x0094: result["device_type"] = "4D Mouse" elif result["device_type_no"] & 0x07ff == 0x0096: result["device_type"] = "Lens Cursor" else: result["device_type"] = "Unknown ???" self.DeviceType = result["device_type"] result["serial"] = ((long(event[2] & 0x03) << 30) | (long(event[3] & 0x7f) << 23) | \ (long(event[4] & 0x7f) << 16) | (long(event[5] & 0x7f) << 9) | \ (long(event[6] & 0x7f) << 23) | (long(event[7] & 0x60) >> 5)) self.SerialNum = result["serial"] elif event[0] & 0xfe == 0x80: result["event"] = "Leave" self.DeviceType = "None" self.SerialNum = 0 elif event[0] & 0xb8 == 0xa0: result["event"] = "Pen/Airbrush 1" result["x"] = (((event[1] & 0x7f) << 9) | ((event[2] & 0x7f) << 2) | ((event[3] & 0x60) >> 5)) result["y"] = (((event[3] & 0x1f) << 11) | ((event[4] & 0x7f) << 4) | ((event[5] & 0x78) >> 3)) result["pressure"] = (((event[5] & 0x07) << 7) | (event[6] & 0x7f)) - 512; result["buttons"] = (((event[0]) & 0x06) | (result["pressure"] >= self.Threshold)); result["tiltx"] = (event[7] & Waconf.TILT_BITS); result["tilty"] = (event[8] & Waconf.TILT_BITS); if (event[7] & Waconf.TILT_SIGN_BIT): result["tiltx"] = result["tiltx"] - (Waconf.TILT_BITS + 1); if (event[8] & Waconf.TILT_SIGN_BIT): result["tilty"] = result["tilty"] - (Waconf.TILT_BITS + 1); result["proximity"] = (event[0] & Waconf.PROXIMITY_BIT) != 0; elif event[0] & 0xbe == 0xb4: result["event"] = "Airbrush 2" result["x"] = (((event[1] & 0x7f) << 9) | ((event[2] & 0x7f) << 2) | ((event[3] & 0x60) >> 5)) result["y"] = (((event[3] & 0x1f) << 11) | ((event[4] & 0x7f) << 4) | ((event[5] & 0x78) >> 3)) result["wheel"] = (((event[5] & 0x07) << 7) | (event[6] & 0x7f)) - 512; result["buttons"] = (((event[0]) & 0x06) | (result["pressure"] >= self.Threshold)); result["tiltx"] = (event[7] & Waconf.TILT_BITS); result["tilty"] = (event[8] & Waconf.TILT_BITS); if (event[7] & Waconf.TILT_SIGN_BIT): result["tiltx"] = result["tiltx"] - (Waconf.TILT_BITS + 1); if (event[8] & Waconf.TILT_SIGN_BIT): result["tilty"] = result["tilty"] - (Waconf.TILT_BITS + 1); result["proximity"] = (event[0] & Waconf.PROXIMITY_BIT) != 0; elif event[0] & 0xbe == 0xa8 : result["event"] = "4D Mouse 1" result["x"] = (((event[1] & 0x7f) << 9) | ((event[2] & 0x7f) << 2) | ((event[3] & 0x60) >> 5)) result["y"] = (((event[3] & 0x1f) << 11) | ((event[4] & 0x7f) << 4) | ((event[5] & 0x78) >> 3)) result["wheel"] = (((event[5] & 0x07) << 7) | (event[6] & 0x7f)) if event[8] & 0x08: result["wheel"] = -result["wheel"] result["buttons"] = (((event[8] & 0x70) >> 1) | (event[8] & 0x07)) result["proximity"] = (event[0] & Waconf.PROXIMITY_BIT) != 0; elif event[0] & 0xbe == 0xaa: result["event"] = "4D Mouse 2" result["x"] = (((event[1] & 0x7f) << 9) | ((event[2] & 0x7f) << 2) | ((event[3] & 0x60) >> 5)) result["y"] = (((event[3] & 0x1f) << 11) | ((event[4] & 0x7f) << 4) | ((event[5] & 0x78) >> 3)) result["rotation"] = (((event[6] & 0x0f) << 7) | (event[7] & 0x7f)) result["rot2"] = float(900 - ((result["rotation"] + 900) % 1800)) / 900 * 180; result["proximity"] = (event[0] & Waconf.PROXIMITY_BIT) != 0; elif event[0] & 0xbe == 0xb0: result["event"] = "Lens Cursor" result["x"] = (((event[1] & 0x7f) << 9) | ((event[2] & 0x7f) << 2) | ((event[3] & 0x60) >> 5)) result["y"] = (((event[3] & 0x1f) << 11) | ((event[4] & 0x7f) << 4) | ((event[5] & 0x78) >> 3)) result["wheel"] = (((event[5] & 0x07) << 7) | (event[6] & 0x7f)) # Rotation?? if event[8] & 0x08: result["wheel"] = -result["wheel"] result["buttons"] = event[8] result["proximity"] = (event[0] & Waconf.PROXIMITY_BIT) != 0; else: result["event"] = "Unknown: 0x%x" % event[0] else: raise "Unknown ProtocolLevel" return result if __name__ == "__main__": tab = Waconf() print tab.identity() print tab.__dict__ tab.startcoords() tab.readevents()