Skip to content

Commit 2ff7f42

Browse files
authored
Merge ff7f90c into c438163
2 parents c438163 + ff7f90c commit 2ff7f42

4 files changed

Lines changed: 272 additions & 66 deletions

File tree

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
Note: This is a transcribed and reformatted copy of SeikaNotetaker.pdf. This PDF was taken from [this comment on GitHub](https://github.com/nvaccess/nvda/pull/12581#issuecomment-867517014) ([archive](https://archive.is/NBKRC)).
2+
3+
4+
# Seika Notetaker Protocol V6.2.0
5+
Acronyms:
6+
7+
- (SR) Screen Reader, eg NVDA
8+
- (SBD) Seika Notetaker Braille Display
9+
10+
NOTE: When the routing key or/and button is pressed, there is no information send
11+
from SBD to SR. When all routing key or/and button is released, then the information
12+
will be send from SBD to SR
13+
14+
## 1. Handshake of SR and SBD
15+
16+
SR->SBD: `0xff 0xff 0xa1`
17+
18+
If SBD is prepare OK, then SBD will answer the SR handshake request:
19+
20+
SBD->SR: `0xff 0xff 0xa2 N B E R S1 S2…S(N-3)`
21+
22+
N means the number of the following bytes
23+
24+
B means the number of the buttons in the SBD
25+
26+
E means the number of cells in the SBD
27+
28+
R means the number of the routing switches in the SBD
29+
30+
S1…S(N-3) means the SBD description,each byte in the S1…S(N-3) is corresponding to
31+
the ASCII character.
32+
33+
For example:
34+
35+
1. Seika Notetaker 16cell: `0xff 0xff 0xa2 0x11 0x16 0x10 0x10 S1 S2 … S14`
36+
2. Seika Notetaker 40cell: `0xff 0xff 0xa2 0x11 0x16 0x28 0x28 S’1 S’2 … S’14`
37+
38+
## 2. SR will send SSD the braille message:
39+
SR->SBD: `0xff 0xff 0xa3 E C1 C2…CE`
40+
41+
E means the number of the following bytes,
42+
43+
C1… CE means the braille message which will be displayed on the Seika.
44+
45+
The byte C1 will be display in the leftmost of SBD, and the byte CE will be display in
46+
the rightmost of BD.
47+
48+
For example::
49+
1. Seika Notetaker 16cell: `0xff 0xff 0xa3 0x10 C1……C16`
50+
2. Seika Notetaker 40cell: `0xff 0xff 0xa3 0x28 C1……C40`
51+
52+
The bits in the symbol byte correspond to the dot values of the Braille character to
53+
be displayed.
54+
55+
The braille dots in a cell are numbered as follows:
56+
57+
58+
| | |
59+
--|--
60+
1 | 4
61+
2 | 5
62+
3 | 6
63+
7 | 8
64+
65+
Braille dot 1 corresponds to bit 0, braille dot 2 corresponds to bit 1, and so on.…
66+
67+
For
68+
example, the character "a" is represented by braille dot 1.
69+
Therefore, bit 0 in the symbol byte must be set on and all other bits set off.…
70+
71+
The
72+
binary value for "a" is 00000001, or hexadecimal 01.…
73+
74+
For the character "d",
75+
represented by braille dots 1,4,5, the binary value is 00011001, or hexadecimal 0x19.
76+
77+
It is recommended that a table look-up system be used to perform this translation.
78+
79+
## 3. Routing button
80+
SBD->SR `0xff 0xff 0xa4 G HZ1 HZ2 … HZG` ,
81+
82+
G means the smallest integral value that is not less than R/8. R means the number of
83+
the routing switches in the SBD.
84+
85+
If a button of cursor routing keys board is released, the below packet of data
86+
will be send from SBD to SR, which bit =1 means the key is pressed and
87+
released, bit=0 means the key is not pressed:
88+
89+
HZ1-HZG hold data for the horizontal cursor routing keys on a SBD
90+
91+
Ex.:
92+
1. routing key 1 is in bit 0 of HZ1.
93+
2. routing key 8 is in bit 7 of HZ1.
94+
3. routing key 9 is in bit 0 of HZ2.
95+
4. routing key 40 is in bit 7 of HZ5.
96+
97+
If the cell number is not the 8 times G, then the high bit will be Zero. For
98+
example, the 20 routing key, then 0xff 0xff 0xa4 0x03 HZ1 HZ2 HZ3, and the HZ3=
99+
0000 xxxx, the 17th routing key is HZ3 bit0, the 20th routing key is HZ3 bit3.
100+
101+
For example:
102+
1. Seika Notetaker 16cell: 0xff 0xff 0xa4 0x02 HZ1 HZ2
103+
2. Seika Notetaker 40cell: 0xff 0xff 0xa4 0x05 HZ1 HZ2 HZ3 HZ4 HZ5
104+
105+
## 4. Button
106+
SBD->SR, `0xff 0xff 0xa6 M P1 P2 ... PM`,
107+
108+
M means the smallest integral value that is not less than B/8. B means the
109+
number of the buttons in the SBD.
110+
111+
If a button is released, the below packet of data will be send from SBD to SR,
112+
which bit =1 means the key is pressed and released, bit=0 means the key is not
113+
pressed, if there are more than one key is pressed, then there will be more than
114+
one bit =1:
115+
116+
P1 P2 P3 hold data for the buttons on a SBD
117+
3
118+
119+
Ex.:
120+
1. k1 is in bit 0 of P1.
121+
2. K8 is in bit 7 of P1.
122+
3. K22 is in bit 5 of P3.
123+
124+
### Seika Notetaker key map (transcribed diagram):
125+
126+
At the top of the device, a row of 8 buttons (braille keyboard): K7 K3 K2 K1 K4 K5 K6 K8
127+
128+
In the middle of the device, a panel with:
129+
* Routing keys 1-16 or 1-40, across the top of the panel
130+
* Cells 1-16 or 1-40, below the routing keys
131+
* K11 on the left of the panel (Left Button)
132+
* K12 on the right of the panel (Right Button)
133+
134+
Below the panel, at the bottom of the device, from left to right:
135+
* Left Joystick (LJ)
136+
- K13 (Center)
137+
- K14 (Left)
138+
- K15 (Right)
139+
- K16 (Up)
140+
- K17 (Down)
141+
* K9 (Backspace)
142+
* K10 (Space)
143+
* Right Joystick (RJ)
144+
- K18 (Center)
145+
- K19 (Left)
146+
- K20 (Right)
147+
- K21 (Up)
148+
- K22 (Down)
149+
150+
# 5. Button and routing key combined
151+
SSD->SR `0xff 0xff 0xa8 (M+G) P1 P2 … PM HZ1 HZ2 … HZG`
152+
153+
P1 P2 P3 and HZ1…HZG are defined as the above section3 and section4
154+
155+
ex:
156+
1. Seika Notetaker 16cell SSD->SR `0xff 0xff 0xa8 0x05 0x00 0x90 0x00 0x00 0x40` means that: 13th button, 16th button, and 15th routing key are pressed and
157+
released.
158+
2. Seika Notetaker 40cell SSD->SR `0xff 0xff 0xa8 0x08 0x00 0x20 0x01 0x00 0x00 0x02 0x00 0x00` means that: 14th button, 17th button and 18
159+
th routing key
160+
are pressed and released.
161+
(Note: The example bytestring from the PDF has been changed to correctly reflect the expected results of the driver.)
652 KB
Binary file not shown.

source/brailleDisplayDrivers/seikantk.py

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
# This file represents the braille display driver for
77
# Seika Notetaker, a product from Nippon Telesoft
88
# see www.seika-braille.com for more details
9+
# Driver information can be found in .\devDocs\brailleDrivers\SeikaNotetaker.md
910

1011
from io import BytesIO
1112
import typing
12-
from typing import List
13+
from typing import List, Set
1314

1415
import braille
1516
import brailleInput
@@ -47,7 +48,7 @@
4748
0x000400: "RJ_LEFT",
4849
0x000800: "RJ_RIGHT",
4950
0x001000: "RJ_UP",
50-
0x002000: "RJ_DOWN"
51+
0x002000: "RJ_DOWN",
5152
}
5253

5354
SEIKA_REQUEST_INFO = b"\x03\xff\xff\xa1"
@@ -100,6 +101,7 @@ def __init__(self, port="hid"):
100101
super().__init__()
101102
self.numCells = 0
102103
self.numBtns = 0
104+
self.numRoutingKeys = 0
103105
self.handle = None
104106

105107
self._hidBuffer = b""
@@ -153,7 +155,7 @@ def _onReceive(self, data: bytes):
153155
The buffer is accumulated until the buffer has the required number of bytes for the field being collected.
154156
There are 3 fields to be collected before a command can be processed:
155157
1: first 3 bytes: command
156-
2: 1 byte: specify total length in bytes?
158+
2: 1 byte: specify length of subsequent arguments in bytes
157159
3: variable length: arguments for command type
158160
159161
After accumulating enough bytes for each phase, the buffer is cleared and the next stage is entered.
@@ -175,16 +177,12 @@ def _onReceive(self, data: bytes):
175177
hasCommandBeenCollected
176178
and not hasArgLenBeenCollected # argsLen has not
177179
):
178-
# Unknown why we must wait for 4 extra bytes. Without a device to inspect actual data
179-
# it has to be assumed that the prior approach is correct, and infer what we can from
180-
# it.
181-
# Best guess: the data is sent with the following structure
180+
# the data is sent with the following structure
182181
# - command name (3 bytes)
183-
# - total bytes Command + Args size (1 byte)
182+
# - number of subsequent bytes to read (1 byte)
184183
# - Args (variable bytes)
185-
# - Constant 4 bytes containing unknown
186-
self._argsLen = ord(newByte) - COMMAND_LEN + 4
187-
# don't reset _hidBuffer the value for total length
184+
self._argsLen = ord(newByte)
185+
self._hidBuffer = b""
188186
elif ( # now collect the args,
189187
hasCommandBeenCollected
190188
and hasArgLenBeenCollected
@@ -212,39 +210,37 @@ def _processCommand(self, command: bytes, arg: bytes) -> None:
212210
log.warning(f"Seika device has received an unknown command {command}")
213211

214212
def _handInfo(self, arg: bytes):
215-
self.numCells = arg[2]
216-
self.numBtns = arg[1]
213+
self.numBtns = arg[0]
214+
self.numCells = arg[1]
215+
self.numRoutingKeys = arg[2]
216+
self._description = arg[3:].decode("ascii")
217217

218218
def _handRouting(self, arg: bytes):
219-
for i in range(arg[0]):
220-
for j in range(8):
221-
if arg[i + 1] & (1 << j):
222-
routingIndex = i * 8 + j
223-
gesture = InputGestureRouting(routingIndex)
224-
try:
225-
inputCore.manager.executeGesture(gesture)
226-
except inputCore.NoInputGestureAction:
227-
log.debug("No action for Seika Notetaker routing command")
219+
routingIndexes = _getRoutingIndexes(arg)
220+
for routingIndex in routingIndexes:
221+
gesture = InputGestureRouting(routingIndex)
222+
try:
223+
inputCore.manager.executeGesture(gesture)
224+
except inputCore.NoInputGestureAction:
225+
log.debug("No action for Seika Notetaker routing command")
228226

229227
def _handKeys(self, arg: bytes):
230-
brailleDots = arg[1]
231-
key = arg[2] | (arg[3] << 8)
232-
gesture = None
233-
if key: # Mini Seika has 2 Top and 4 Front
234-
gesture = InputGesture(keys=key)
228+
brailleDots = arg[0]
229+
key = arg[1] | (arg[2] << 8)
230+
gestures = []
231+
if key:
232+
gestures.append(InputGesture(keys=key))
235233
if brailleDots:
236-
gesture = InputGesture(dots=brailleDots)
237-
if gesture is not None:
234+
gestures.append(InputGesture(dots=brailleDots))
235+
for gesture in gestures:
238236
try:
239237
inputCore.manager.executeGesture(gesture)
240238
except inputCore.NoInputGestureAction:
241-
log.debug("No action for Seika Notetaker keys.")
239+
log.debug("No action for Seika Notetaker keys.")
242240

243241
def _handKeysRouting(self, arg: bytes):
244-
argk = b"\x03" + arg[1:]
245-
argr = (arg[0] - 3).to_bytes(1, 'little') + arg[4:]
246-
self._handRouting(argr)
247-
self._handKeys(argk)
242+
self._handRouting(arg[3:])
243+
self._handKeys(arg[:3])
248244

249245
gestureMap = inputCore.GlobalGestureMap({
250246
"globalCommands.GlobalCommands": {
@@ -290,6 +286,18 @@ def __init__(self, index):
290286
self.routingIndex = index
291287

292288

289+
def _getKeyNames(keys: int) -> Set[int]:
290+
return {_keyNames[1 << i] for i in range(16) if (1 << i) & keys}
291+
292+
293+
def _getDotNames(dots: int) -> Set[int]:
294+
return {_dotNames[1 << i] for i in range(8) if (1 << i) & dots}
295+
296+
297+
def _getRoutingIndexes(routingKeys: bytes) -> Set[int]:
298+
return {i * 8 + j for i in range(len(routingKeys)) for j in range(8) if routingKeys[i] & (1 << j)}
299+
300+
293301
class InputGesture(braille.BrailleDisplayGesture, brailleInput.BrailleInputGesture):
294302
source = BrailleDisplayDriver.name
295303

@@ -298,13 +306,13 @@ def __init__(self, keys=None, dots=None, space=False, routing=None):
298306
# see what thumb keys are pressed:
299307
names = set()
300308
if keys is not None:
301-
names.update(_keyNames[1 << i] for i in range(22) if (1 << i) & keys)
309+
names.update(_getKeyNames(keys))
302310
elif dots is not None:
303311
self.dots = dots
304312
if space:
305313
self.space = space
306314
names.add(_keyNames[1])
307-
names.update(_dotNames[1 << i] for i in range(8) if (1 << i) & dots)
315+
names.update(_getDotNames(dots))
308316
elif routing is not None:
309317
self.routingIndex = routing
310318
names.add('routing')

0 commit comments

Comments
 (0)