PageRenderTime 27ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/source/brailleDisplayDrivers/brailliantB.py

https://bitbucket.org/mr_indoj/nvda_gwp
Python | 262 lines | 225 code | 14 blank | 23 comment | 26 complexity | f758bf4f591a924cdb6527065bf67b10 MD5 | raw file
  1. #brailleDisplayDrivers/brailliantB.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) 2012-2015 NV Access Limited
  6. import os
  7. import time
  8. import _winreg
  9. import itertools
  10. import wx
  11. import serial
  12. import hwPortUtils
  13. import braille
  14. import inputCore
  15. from logHandler import log
  16. import brailleInput
  17. TIMEOUT = 0.2
  18. BAUD_RATE = 115200
  19. PARITY = serial.PARITY_EVEN
  20. READ_INTERVAL = 50
  21. HEADER = "\x1b"
  22. MSG_INIT = "\x00"
  23. MSG_INIT_RESP = "\x01"
  24. MSG_DISPLAY = "\x02"
  25. MSG_KEY_DOWN = "\x05"
  26. MSG_KEY_UP = "\x06"
  27. KEY_NAMES = {
  28. # Braille keyboard.
  29. 2: "dot1",
  30. 3: "dot2",
  31. 4: "dot3",
  32. 5: "dot4",
  33. 6: "dot5",
  34. 7: "dot6",
  35. 8: "dot7",
  36. 9: "dot8",
  37. 10: "space",
  38. # Command keys.
  39. 11: "c1",
  40. 12: "c2",
  41. 13: "c3",
  42. 14: "c4",
  43. 15: "c5",
  44. 16: "c6",
  45. # Thumb keys.
  46. 17: "up",
  47. 18: "left",
  48. 19: "right",
  49. 20: "down",
  50. }
  51. FIRST_ROUTING_KEY = 80
  52. DOT1_KEY = 2
  53. DOT8_KEY = 9
  54. SPACE_KEY = 10
  55. def _getPorts():
  56. # USB.
  57. try:
  58. rootKey = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Enum\USB\Vid_1c71&Pid_c005")
  59. except WindowsError:
  60. # A display has never been connected via USB.
  61. pass
  62. else:
  63. with rootKey:
  64. for index in itertools.count():
  65. try:
  66. keyName = _winreg.EnumKey(rootKey, index)
  67. except WindowsError:
  68. break
  69. try:
  70. with _winreg.OpenKey(rootKey, os.path.join(keyName, "Device Parameters")) as paramsKey:
  71. yield "USB", _winreg.QueryValueEx(paramsKey, "PortName")[0]
  72. except WindowsError:
  73. continue
  74. # Bluetooth.
  75. for portInfo in hwPortUtils.listComPorts(onlyAvailable=True):
  76. try:
  77. btName = portInfo["bluetoothName"]
  78. except KeyError:
  79. continue
  80. if btName.startswith("Brailliant B") or btName == "Brailliant 80":
  81. yield "bluetooth", portInfo["port"]
  82. class BrailleDisplayDriver(braille.BrailleDisplayDriver):
  83. name = "brailliantB"
  84. # Translators: The name of a series of braille displays.
  85. description = _("HumanWare Brailliant BI/B series")
  86. @classmethod
  87. def check(cls):
  88. try:
  89. next(_getPorts())
  90. except StopIteration:
  91. # No possible ports found.
  92. return False
  93. return True
  94. def __init__(self):
  95. super(BrailleDisplayDriver, self).__init__()
  96. self.numCells = 0
  97. for portType, port in _getPorts():
  98. # Try talking to the display.
  99. try:
  100. self._ser = serial.Serial(port, baudrate=BAUD_RATE, parity=PARITY, timeout=TIMEOUT, writeTimeout=TIMEOUT)
  101. except serial.SerialException:
  102. continue
  103. # This will cause the number of cells to be returned.
  104. self._sendMessage(MSG_INIT)
  105. # #5406: With the new USB driver, the first command is ignored after a reconnection.
  106. # Worse, if we don't receive a reply,
  107. # _handleResponses freezes for some reason despite the timeout.
  108. # Send the init message again just in case.
  109. self._sendMessage(MSG_INIT)
  110. self._handleResponses(wait=True)
  111. if not self.numCells:
  112. # HACK: When connected via bluetooth, the display sometimes reports communication not allowed on the first attempt.
  113. self._sendMessage(MSG_INIT)
  114. self._handleResponses(wait=True)
  115. if self.numCells:
  116. # A display responded.
  117. log.info("Found display with {cells} cells connected via {type} ({port})".format(
  118. cells=self.numCells, type=portType, port=port))
  119. break
  120. else:
  121. raise RuntimeError("No display found")
  122. self._readTimer = wx.PyTimer(self._handleResponses)
  123. self._readTimer.Start(READ_INTERVAL)
  124. self._keysDown = set()
  125. self._ignoreKeyReleases = False
  126. def terminate(self):
  127. try:
  128. super(BrailleDisplayDriver, self).terminate()
  129. self._readTimer.Stop()
  130. self._readTimer = None
  131. finally:
  132. # We absolutely must close the Serial object, as it does not have a destructor.
  133. # If we don't, we won't be able to re-open it later.
  134. self._ser.close()
  135. def _sendMessage(self, msgId, payload=""):
  136. if isinstance(payload, (int, bool)):
  137. payload = chr(payload)
  138. self._ser.write("{header}{id}{length}{payload}".format(
  139. header=HEADER, id=msgId,
  140. length=chr(len(payload)), payload=payload))
  141. def _handleResponses(self, wait=False):
  142. while wait or self._ser.inWaiting():
  143. msgId, payload = self._readPacket()
  144. if msgId:
  145. self._handleResponse(msgId, payload)
  146. wait = False
  147. def _readPacket(self):
  148. # Wait for the header.
  149. while True:
  150. char = self._ser.read(1)
  151. if char == HEADER:
  152. break
  153. msgId = self._ser.read(1)
  154. length = ord(self._ser.read(1))
  155. payload = self._ser.read(length)
  156. return msgId, payload
  157. def _handleResponse(self, msgId, payload):
  158. if msgId == MSG_INIT_RESP:
  159. if ord(payload[0]) != 0:
  160. # Communication not allowed.
  161. log.debugWarning("Display at %r reports communication not allowed" % self._ser.port)
  162. return
  163. self.numCells = ord(payload[2])
  164. elif msgId == MSG_KEY_DOWN:
  165. payload = ord(payload)
  166. self._keysDown.add(payload)
  167. # This begins a new key combination.
  168. self._ignoreKeyReleases = False
  169. elif msgId == MSG_KEY_UP:
  170. payload = ord(payload)
  171. if not self._ignoreKeyReleases and self._keysDown:
  172. try:
  173. inputCore.manager.executeGesture(InputGesture(self._keysDown))
  174. except inputCore.NoInputGestureAction:
  175. pass
  176. # Any further releases are just the rest of the keys in the combination being released,
  177. # so they should be ignored.
  178. self._ignoreKeyReleases = True
  179. self._keysDown.discard(payload)
  180. else:
  181. log.debugWarning("Unknown message: id {id!r}, payload {payload!r}".format(id=msgId, payload=payload))
  182. def display(self, cells):
  183. # cells will already be padded up to numCells.
  184. self._sendMessage(MSG_DISPLAY, "".join(chr(cell) for cell in cells))
  185. gestureMap = inputCore.GlobalGestureMap({
  186. "globalCommands.GlobalCommands": {
  187. "braille_scrollBack": ("br(brailliantB):left",),
  188. "braille_scrollForward": ("br(brailliantB):right",),
  189. "braille_previousLine": ("br(brailliantB):up",),
  190. "braille_nextLine": ("br(brailliantB):down",),
  191. "braille_routeTo": ("br(brailliantB):routing",),
  192. "braille_toggleTether": ("br(brailliantB):up+down",),
  193. "kb:upArrow": ("br(brailliantB):space+dot1",),
  194. "kb:downArrow": ("br(brailliantB):space+dot4",),
  195. "kb:leftArrow": ("br(brailliantB):space+dot3",),
  196. "kb:rightArrow": ("br(brailliantB):space+dot6",),
  197. "showGui": ("br(brailliantB):c1+c3+c4+c5",),
  198. "kb:shift+tab": ("br(brailliantB):space+dot1+dot3",),
  199. "kb:tab": ("br(brailliantB):space+dot4+dot6",),
  200. "kb:alt": ("br(brailliantB):space+dot1+dot3+dot4",),
  201. "kb:escape": ("br(brailliantB):space+dot1+dot5",),
  202. "kb:enter": ("br(brailliantB):dot8",),
  203. "kb:windows+d": ("br(brailliantB):c1+c4+c5",),
  204. "kb:windows": ("br(brailliantB):space+dot3+dot4",),
  205. "kb:alt+tab": ("br(brailliantB):space+dot2+dot3+dot4+dot5",),
  206. "sayAll": ("br(brailliantB):c1+c2+c3+c4+c5+c6",),
  207. },
  208. })
  209. class InputGesture(braille.BrailleDisplayGesture, brailleInput.BrailleInputGesture):
  210. source = BrailleDisplayDriver.name
  211. def __init__(self, keys):
  212. super(InputGesture, self).__init__()
  213. self.keyCodes = set(keys)
  214. self.keyNames = names = set()
  215. isBrailleInput = True
  216. for key in self.keyCodes:
  217. if isBrailleInput:
  218. if DOT1_KEY <= key <= DOT8_KEY:
  219. self.dots |= 1 << (key - DOT1_KEY)
  220. elif key == SPACE_KEY:
  221. self.space = True
  222. else:
  223. # This is not braille input.
  224. isBrailleInput = False
  225. self.dots = 0
  226. self.space = False
  227. if key >= FIRST_ROUTING_KEY:
  228. names.add("routing")
  229. self.routingIndex = key - FIRST_ROUTING_KEY
  230. else:
  231. try:
  232. names.add(KEY_NAMES[key])
  233. except KeyError:
  234. log.debugWarning("Unknown key with id %d" % key)
  235. self.id = "+".join(names)