~stomato463/+junk/nvdajp

« back to all changes in this revision

Viewing changes to source/brailleDisplayDrivers/baum.py

  • Committer: Masataka Shinke
  • Date: 2011-10-25 12:35:26 UTC
  • mfrom: (4185 jpmain)
  • mto: This revision was merged to the branch mainline in revision 4211.
  • Revision ID: mshinke@users.sourceforge.jp-20111025123526-ze527a2rl3z0g2ky
lp:~nishimotz/nvdajp/main : 4185 をマージ

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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)