Skip to content

Commit 74dd0ec

Browse files
authored
Merge af08d2f into 376edfc
2 parents 376edfc + af08d2f commit 74dd0ec

1 file changed

Lines changed: 276 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)