Skip to content

Commit 82cd5ea

Browse files
authored
Merge 344d3c2 into db664be
2 parents db664be + 344d3c2 commit 82cd5ea

1 file changed

Lines changed: 265 additions & 0 deletions

File tree

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
# A part of NonVisual Desktop Access (NVDA)
2+
# Copyright (C) 2012-2020 NV Access Limited, Ulf Beckmann <beckmann@flusoft.de>
3+
# This file may be used under the terms of the GNU General Public License, version 2 or later.
4+
# For more details see: https://www.gnu.org/licenses/gpl-2.0.html
5+
#
6+
# This file represents the braille display driver for
7+
# Seika Notetaker, a product from Nippon Telesoft
8+
# see www.seika-braille.com for more details
9+
# 29.06.2020 / 12:36
10+
11+
from io import BytesIO
12+
from typing import List
13+
import braille
14+
import brailleInput
15+
import inputCore
16+
import hwPortUtils
17+
import bdDetect
18+
import hwIo
19+
from serial.win32 import INVALID_HANDLE_VALUE
20+
from logHandler import log
21+
import config
22+
23+
TIMEOUT = 0.2
24+
25+
DOT_1 = 0x1
26+
DOT_2 = 0x2
27+
DOT_3 = 0x4
28+
DOT_4 = 0x8
29+
DOT_5 = 0x10
30+
DOT_6 = 0x20
31+
DOT_7 = 0x40
32+
DOT_8 = 0x80
33+
34+
35+
_keyNames = {
36+
0x000001: "BACKSPACE",
37+
0x000002: "SPACE",
38+
0x000004: "LB",
39+
0x000008: "RB",
40+
0x000010: "LJ_CENTER",
41+
0x000020: "LJ_LEFT",
42+
0x000040: "LJ_RIGHT",
43+
0x000080: "LJ_UP",
44+
0x000100: "LJ_DOWN",
45+
0x000200: "RJ_CENTER",
46+
0x000400: "RJ_LEFT",
47+
0x000800: "RJ_RIGHT",
48+
0x001000: "RJ_UP",
49+
0x002000: "RJ_DOWN"
50+
}
51+
52+
SEIKA_REQUEST_INFO = b"\x03\xff\xff\xa1"
53+
SEIKA_INFO = b"\xff\xff\xa2"
54+
SEIKA_SEND_TEXT = b"\x2c\xff\xff\xa3"
55+
SEIKA_ROUTING = b"\xff\xff\xa4"
56+
SEIKA_KEYS = b"\xff\xff\xa6"
57+
SEIKA_KEYS_ROU = b"\xff\xff\xa8"
58+
59+
SEIKA_CONFIG = b"\x50\x00\x00\x25\x80\x00\x00\x03\x00"
60+
SEIKA_CMD_ON = b"\x41\x01"
61+
62+
vidpid = "VID_10C4&PID_EA80"
63+
hidvidpid = "HID\\VID_10C4&PID_EA80"
64+
SEIKA_NAME = "seikantk"
65+
66+
_dotNames = {}
67+
for i in range(1, 9):
68+
key = globals()["DOT_%d" % i]
69+
_dotNames[key] = "d%d" % i
70+
bdDetect.addUsbDevices(SEIKA_NAME, bdDetect.KEY_HID, {vidpid, })
71+
72+
73+
class BrailleDisplayDriver(braille.BrailleDisplayDriver):
74+
_dev: hwIo.IoBase
75+
name = SEIKA_NAME
76+
# Translators: Name of a braille display.
77+
description = _("Seika Notetaker")
78+
path = ""
79+
isThreadSafe = True
80+
for d in hwPortUtils.listHidDevices():
81+
if d["hardwareID"].startswith(hidvidpid):
82+
path = d["devicePath"]
83+
84+
@classmethod
85+
def check(cls):
86+
return True
87+
88+
@classmethod
89+
def getManualPorts(cls):
90+
return cls.path
91+
92+
def __init__(self, port="hid"):
93+
super(BrailleDisplayDriver, self).__init__()
94+
self.numCells = 0
95+
self.numBtns = 0
96+
self.status = 0
97+
self.cmdlen = 0
98+
self.handle = None
99+
self._hidBuffer = b""
100+
log.info("path: " + self.path)
101+
102+
if self.path == "":
103+
raise RuntimeError("No MINI-SEIKA display found, no path found")
104+
self._dev = hwIo.Hid(path=self.path, onReceive=self._onReceive)
105+
if self._dev._file == INVALID_HANDLE_VALUE:
106+
raise RuntimeError("No MINI-SEIKA display found, open error")
107+
self._dev.setFeature(SEIKA_CONFIG) # baudrate, stopbit usw
108+
self._dev.setFeature(SEIKA_CMD_ON) # device on
109+
self._dev.write(SEIKA_REQUEST_INFO) # Request the Info from the device
110+
111+
# wait and try to get info from the Braille display
112+
for i in range(30): # the info-block is about
113+
self._dev.waitForRead(TIMEOUT)
114+
if self.numCells:
115+
log.info("Seikanotetaker an USB-HID, Cells {c} Buttons {b}".format(c=self.numCells, b=self.numBtns))
116+
break
117+
118+
if self.numCells == 0:
119+
self._dev.close()
120+
raise RuntimeError("No MINI-SEIKA display found, no response")
121+
122+
def terminate(self):
123+
try:
124+
super(BrailleDisplayDriver, self).terminate()
125+
finally:
126+
self._dev.close()
127+
128+
def display(self, cells: List[int]):
129+
# cells will already be padded up to numCells.
130+
cellBytes = SEIKA_SEND_TEXT + self.numCells.to_bytes(1, 'little') + bytes(cells)
131+
self._dev.write(cellBytes)
132+
133+
def _onReceive(self, data: bytes):
134+
stream = BytesIO(data)
135+
cmd = stream.read(3)
136+
self._hidBuffer += cmd[1:2]
137+
if len(self._hidBuffer) == 3:
138+
self.status = 1
139+
elif len(self._hidBuffer) == 4:
140+
self.cmdlen = cmd[1]
141+
self.status = 2
142+
elif self.status == 2 and len(self._hidBuffer) == self.cmdlen + 4:
143+
command = self._hidBuffer[0:3]
144+
arg = self._hidBuffer[3:self.cmdlen + 4]
145+
self.status = 0
146+
self._hidBuffer = b""
147+
148+
if command == SEIKA_INFO:
149+
self._handInfo(arg)
150+
elif command == SEIKA_ROUTING:
151+
self._handRouting(arg)
152+
elif command == SEIKA_KEYS:
153+
self._handKeys(arg)
154+
elif command == SEIKA_KEYS_ROU:
155+
self._handKeysRouting(arg)
156+
else:
157+
log.debug("other data.")
158+
159+
def _handInfo(self, arg: bytes):
160+
self.numCells = arg[2]
161+
self.numBtns = arg[1]
162+
163+
def _handRouting(self, arg: bytes):
164+
Rou = 0
165+
for i in range(arg[0]):
166+
for j in range(8):
167+
if arg[i + 1] & (1 << j):
168+
Rou = i * 8 + j
169+
170+
gesture = InputGestureRouting(Rou)
171+
try:
172+
inputCore.manager.executeGesture(gesture)
173+
except inputCore.NoInputGestureAction:
174+
log.debug("No Action for routing command")
175+
pass
176+
177+
def _handKeys(self, arg: bytes):
178+
Brl = arg[1]
179+
Key = arg[2] | (arg[3] << 8)
180+
Btn = 0 # Seika has no button
181+
if not (Key or Btn or Brl):
182+
pass
183+
if Key: # Mini Seika has 2 Top and 4 Front ....
184+
gesture = InputGesture(keys=Key)
185+
if Btn: # Mini Seika has no Btn ....
186+
gesture = InputGesture(keys=Btn)
187+
if Brl: # or how to handle Brailleinput?
188+
gesture = InputGesture(dots=Brl)
189+
if Key or Btn or Brl:
190+
try:
191+
inputCore.manager.executeGesture(gesture)
192+
except inputCore.NoInputGestureAction:
193+
log.debug("No Action for keys ")
194+
pass
195+
196+
def _handKeysRouting(self, arg: bytes):
197+
argk = b"\x03" + arg[1:]
198+
argr = (arg[0] - 3).to_bytes(1, 'little') + arg[4:]
199+
self._handRouting(argr)
200+
self._handKeys(argk)
201+
202+
gestureMap = inputCore.GlobalGestureMap({
203+
"globalCommands.GlobalCommands": {
204+
"braille_routeTo": ("br(seikantk):routing",),
205+
"braille_scrollBack": ("br(seikantk):LB",),
206+
"braille_scrollForward": ("br(seikantk):RB",),
207+
"braille_previousLine": ("br(seikantk):LJ_UP",),
208+
"braille_nextLine": ("br(seikantk):LJ_DOWN",),
209+
"braille_toggleTether": ("br(seikantk):LJ_CENTER",),
210+
"sayAll": ("br(seikantk):SPACE+BACKSPACE",),
211+
"showGui": ("br(seikantk):RB+LB",),
212+
"kb:tab": ("br(seikantk):LJ_RIGHT",),
213+
"kb:shift+tab": ("br(seikantk):LJ_LEFT",),
214+
"kb:upArrow": ("br(seikantk):RJ_UP",),
215+
"kb:downArrow": ("br(seikantk):RJ_DOWN",),
216+
"kb:leftArrow": ("br(seikantk):RJ_LEFT",),
217+
"kb:rightArrow": ("br(seikantk):RJ_RIGHT",),
218+
"kb:shift+upArrow": ("br(seikantk):SPACE+RJ_UP", "br(seikantk):BACKSPACE+RJ_UP"),
219+
"kb:shift+downArrow": ("br(seikantk):SPACE+RJ_DOWN", "br(seikantk):BACKSPACE+RJ_DOWN"),
220+
"kb:shift+leftArrow": ("br(seikantk):SPACE+RJ_LEFT", "br(seikantk):BACKSPACE+RJ_LEFT"),
221+
"kb:shift+rightArrow": ("br(seikantk):SPACE+RJ_RIGHT", "br(seikantk):BACKSPACE+RJ_RIGHT"),
222+
"kb:escape": ("br(seikantk):SPACE+RJ_CENTER",),
223+
"kb:windows": ("br(seikantk):BACKSPACE+RJ_CENTER",),
224+
"kb:space": ("br(seikantk):BACKSPACE", "br(seikantk):SPACE",),
225+
"kb:backspace": ("br(seikantk):d7",),
226+
"kb:pageup": ("br(seikantk):SPACE+LJ_RIGHT",),
227+
"kb:pagedown": ("br(seikantk):SPACE+LJ_LEFT",),
228+
"kb:home": ("br(seikantk):SPACE+LJ_UP",),
229+
"kb:end": ("br(seikantk):SPACE+LJ_DOWN",),
230+
"kb:control+home": ("br(seikantk):BACKSPACE+LJ_UP",),
231+
"kb:control+end": ("br(seikantk):BACKSPACE+LJ_DOWN",),
232+
"kb:enter": ("br(seikantk):RJ_CENTER", "br(seikantk):d8"),
233+
},
234+
})
235+
236+
237+
class InputGestureRouting(braille.BrailleDisplayGesture):
238+
source = BrailleDisplayDriver.name
239+
240+
def __init__(self, index):
241+
super(InputGestureRouting, self).__init__()
242+
self.id = "routing"
243+
self.routingIndex = index
244+
245+
246+
class InputGesture(braille.BrailleDisplayGesture, brailleInput.BrailleInputGesture):
247+
source = BrailleDisplayDriver.name
248+
249+
def __init__(self, keys=None, dots=None, space=False, routing=None):
250+
super(braille.BrailleDisplayGesture, self).__init__()
251+
# see what thumb keys are pressed:
252+
names = set()
253+
if keys is not None:
254+
names.update(_keyNames[1 << i] for i in range(22) if (1 << i) & keys)
255+
elif dots is not None:
256+
# now the dots
257+
self.dots = dots
258+
if space:
259+
self.space = space
260+
names.add(_keyNames[1])
261+
names.update(_dotNames[1 << i] for i in range(8) if (1 << i) & dots)
262+
elif routing is not None:
263+
self.routingIndex = routing
264+
names.add('routing')
265+
self.id = "+".join(names)

0 commit comments

Comments
 (0)