Package brailleDisplayDrivers :: Module baum
[hide private]
[frames] | no frames]

Source Code for Module brailleDisplayDrivers.baum

  1  #brailleDisplayDrivers/baum.py 
  2  #A part of NonVisual Desktop Access (NVDA) 
  3  #This file is covered by the GNU General Public License. 
  4  #See the file COPYING for more details. 
  5  #Copyright (C) 2010-2011 James Teh <jamie@jantrid.net> 
  6   
  7  import time 
  8  import wx 
  9  import serial 
 10  import hwPortUtils 
 11  import braille 
 12  import inputCore 
 13  from logHandler import log 
 14   
 15  TIMEOUT = 0.2 
 16  BAUD_RATE = 19200 
 17  READ_INTERVAL = 50 
 18   
 19  ESCAPE = "\x1b" 
 20   
 21  BAUM_DISPLAY_DATA = "\x01" 
 22  BAUM_CELL_COUNT = "\x01" 
 23  BAUM_PROTOCOL_ONOFF = "\x15" 
 24  BAUM_COMMUNICATION_CHANNEL = "\x16" 
 25  BAUM_POWERDOWN = "\x17" 
 26  BAUM_ROUTING_KEYS = "\x22" 
 27  BAUM_DISPLAY_KEYS = "\x24" 
 28  BAUM_BRAILLE_KEYS = "\x33" 
 29  BAUM_JOYSTICK_KEYS = "\x34" 
 30  BAUM_DEVICE_ID = "\x84" 
 31  BAUM_SERIAL_NUMBER = "\x8A" 
 32   
 33  BAUM_RSP_LENGTHS = { 
 34          BAUM_CELL_COUNT: 1, 
 35          BAUM_POWERDOWN: 1, 
 36          BAUM_COMMUNICATION_CHANNEL: 1, 
 37          BAUM_DISPLAY_KEYS: 1, 
 38          BAUM_BRAILLE_KEYS: 2, 
 39          BAUM_JOYSTICK_KEYS: 1, 
 40          BAUM_DEVICE_ID: 16, 
 41          BAUM_SERIAL_NUMBER: 8, 
 42  } 
 43   
 44  KEY_NAMES = { 
 45          BAUM_ROUTING_KEYS: None, 
 46          BAUM_DISPLAY_KEYS: ("d1", "d2", "d3", "d4", "d5", "d6"), 
 47          BAUM_BRAILLE_KEYS: ("b9", "b10", "b11", None, "c1", "c2", "c3", "c4", # byte 1 
 48                  "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8"), # byte 2 
 49          BAUM_JOYSTICK_KEYS: ("up", "left", "down", "right", "select"), 
 50  } 
 51   
 52  USB_IDS = frozenset(( 
 53          "VID_0403&PID_FE70", # Vario 40 
 54          "VID_0403&PID_FE71", # PocketVario 
 55          "VID_0403&PID_FE72", # SuperVario/Brailliant 40 
 56          "VID_0403&PID_FE73", # SuperVario/Brailliant 32 
 57          "VID_0403&PID_FE74", # SuperVario/Brailliant 64 
 58          "VID_0403&PID_FE75", # SuperVario/Brailliant 80 
 59          "VID_0403&PID_FE76", # VarioPro 80 
 60          "VID_0403&PID_FE77", # VarioPro 64 
 61          "VID_0904&PID_2000", # VarioPro 40 
 62          "VID_0904&PID_2001", # EcoVario 24 
 63          "VID_0904&PID_2002", # EcoVario 40 
 64          "VID_0904&PID_2007", # VarioConnect/BrailleConnect 40 
 65          "VID_0904&PID_2008", # VarioConnect/BrailleConnect 32 
 66          "VID_0904&PID_2009", # VarioConnect/BrailleConnect 24 
 67          "VID_0904&PID_2010", # VarioConnect/BrailleConnect 64 
 68          "VID_0904&PID_2011", # VarioConnect/BrailleConnect 80 
 69          "VID_0904&PID_2014", # EcoVario 32 
 70          "VID_0904&PID_2015", # EcoVario 64 
 71          "VID_0904&PID_2016", # EcoVario 80 
 72          "VID_0904&PID_3000", # RefreshaBraille 18 
 73  )) 
 74   
 75  BLUETOOTH_NAMES = ( 
 76          "Baum SuperVario", 
 77          "Baum PocketVario", 
 78          "Baum SVario", 
 79          "HWG Brailliant", 
 80          "Refreshabraille", 
 81          "VarioConnect", 
 82          "BrailleConnect", 
 83  ) 
84 85 -class BrailleDisplayDriver(braille.BrailleDisplayDriver):
86 name = "baum" 87 description = _("Baum/HumanWare/APH braille displays") 88 89 @classmethod
90 - def check(cls):
91 return True
92
93 - def __init__(self):
94 super(BrailleDisplayDriver, self).__init__() 95 self.numCells = 0 96 self._deviceID = None 97 98 # Scan all available com ports. 99 # Try bluetooth ports last. 100 for portInfo in sorted(hwPortUtils.listComPorts(onlyAvailable=True), key=lambda item: "bluetoothName" in item): 101 port = portInfo["port"] 102 hwID = portInfo["hardwareID"] 103 if hwID.startswith(r"FTDIBUS\COMPORT"): 104 # USB. 105 portType = "USB" 106 try: 107 usbID = hwID.split("&", 1)[1] 108 except IndexError: 109 continue 110 if usbID not in USB_IDS: 111 continue 112 elif "bluetoothName" in portInfo: 113 # Bluetooth. 114 portType = "bluetooth" 115 btName = portInfo["bluetoothName"] 116 if not any(btName.startswith(prefix) for prefix in BLUETOOTH_NAMES): 117 continue 118 else: 119 continue 120 121 # At this point, a port bound to this display has been found. 122 # Try talking to the display. 123 try: 124 self._ser = serial.Serial(port, baudrate=BAUD_RATE, timeout=TIMEOUT, writeTimeout=TIMEOUT) 125 except serial.SerialException: 126 continue 127 # This will cause the number of cells to be returned. 128 self._sendRequest(BAUM_DISPLAY_DATA) 129 # Send again in case the display misses the first one. 130 self._sendRequest(BAUM_DISPLAY_DATA) 131 # We just sent less bytes than we should, 132 # so we need to send another request in order for the display to know the previous request is finished. 133 self._sendRequest(BAUM_DEVICE_ID) 134 self._handleResponses(wait=True) 135 if self.numCells: 136 # A display responded. 137 if not self._deviceID: 138 # Bah. The response to our device ID query hasn't arrived yet, so wait for it. 139 self._handleResponses(wait=True) 140 log.info("Found {device} connected via {type} ({port})".format( 141 device=self._deviceID, type=portType, port=port)) 142 break 143 144 else: 145 raise RuntimeError("No Baum display found") 146 147 self._readTimer = wx.PyTimer(self._handleResponses) 148 self._readTimer.Start(READ_INTERVAL) 149 self._keysDown = {} 150 self._ignoreKeyReleases = False
151
152 - def terminate(self):
153 try: 154 super(BrailleDisplayDriver, self).terminate() 155 self._readTimer.Stop() 156 self._readTimer = None 157 self._sendRequest(BAUM_PROTOCOL_ONOFF, False) 158 finally: 159 # We absolutely must close the Serial object, as it does not have a destructor. 160 # If we don't, we won't be able to re-open it later. 161 self._ser.close()
162
163 - def _sendRequest(self, command, arg=""):
164 if isinstance(arg, (int, bool)): 165 arg = chr(arg) 166 self._ser.write("\x1b{command}{arg}".format(command=command, 167 arg=arg.replace(ESCAPE, ESCAPE * 2)))
168
169 - def _handleResponses(self, wait=False):
170 while wait or self._ser.inWaiting(): 171 command, arg = self._readPacket() 172 if command: 173 self._handleResponse(command, arg) 174 wait = False
175
176 - def _readPacket(self):
177 # Find the escape. 178 chars = [] 179 escapeFound = False 180 while True: 181 char = self._ser.read(1) 182 if char == ESCAPE: 183 escapeFound = True 184 break 185 else: 186 chars.append(char) 187 if not self._ser.inWaiting(): 188 break 189 if chars: 190 log.debugWarning("Ignoring data before escape: %r" % "".join(chars)) 191 if not escapeFound: 192 return None, None 193 194 command = self._ser.read(1) 195 length = BAUM_RSP_LENGTHS.get(command, 0) 196 if command == BAUM_ROUTING_KEYS: 197 length = self.numCells / 8 198 arg = self._ser.read(length) 199 return command, arg
200
201 - def _handleResponse(self, command, arg):
202 if command == BAUM_CELL_COUNT: 203 self.numCells = ord(arg) 204 elif command == BAUM_DEVICE_ID: 205 self._deviceID = arg 206 207 elif command in KEY_NAMES: 208 arg = sum(ord(byte) << offset * 8 for offset, byte in enumerate(arg)) 209 if arg < self._keysDown.get(command, 0): 210 # Release. 211 if not self._ignoreKeyReleases: 212 # The first key released executes the key combination. 213 try: 214 inputCore.manager.executeGesture(InputGesture(self._keysDown)) 215 except inputCore.NoInputGestureAction: 216 pass 217 # Any further releases are just the rest of the keys in the combination being released, 218 # so they should be ignored. 219 self._ignoreKeyReleases = True 220 else: 221 # Press. 222 # This begins a new key combination. 223 self._ignoreKeyReleases = False 224 self._keysDown[command] = arg 225 226 elif command == BAUM_POWERDOWN: 227 log.debug("Power down") 228 elif command in (BAUM_COMMUNICATION_CHANNEL, BAUM_SERIAL_NUMBER): 229 pass 230 231 else: 232 log.debugWarning("Unknown command {command!r}, arg {arg!r}".format(command=command, arg=arg))
233
234 - def display(self, cells):
235 # cells will already be padded up to numCells. 236 self._sendRequest(BAUM_DISPLAY_DATA, "".join(chr(cell) for cell in cells))
237 238 gestureMap = inputCore.GlobalGestureMap({ 239 "globalCommands.GlobalCommands": { 240 "braille_scrollBack": ("br(baum):d2",), 241 "braille_scrollForward": ("br(baum):d5",), 242 "braille_previousLine": ("br(baum):d1",), 243 "braille_nextLine": ("br(baum):d3",), 244 "braille_routeTo": ("br(baum):routing",), 245 "kb:upArrow": ("br(baum):up",), 246 "kb:downArrow": ("br(baum):down",), 247 "kb:leftArrow": ("br(baum):left",), 248 "kb:rightArrow": ("br(baum):right",), 249 "kb:enter": ("br(baum):select",), 250 }, 251 })
252
253 -class InputGesture(braille.BrailleDisplayGesture):
254 255 source = BrailleDisplayDriver.name 256
257 - def __init__(self, keysDown):
258 super(InputGesture, self).__init__() 259 self.keysDown = dict(keysDown) 260 261 self.keyNames = names = set() 262 for group, groupKeysDown in keysDown.iteritems(): 263 if group == BAUM_ROUTING_KEYS: 264 for index in xrange(braille.handler.display.numCells): 265 if groupKeysDown & (1 << index): 266 self.routingIndex = index 267 names.add("routing") 268 break 269 else: 270 for index, name in enumerate(KEY_NAMES[group]): 271 if groupKeysDown & (1 << index): 272 names.add(name) 273 274 self.id = "+".join(names)
275