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 , {
73+ vidpid , # USB-HID adapter
74+ })
75+
76+
77+ class BrailleDisplayDriver (braille .BrailleDisplayDriver ):
78+ _dev : hwIo .IoBase
79+ name = SEIKA_NAME
80+ description = "Seika Notetaker"
81+ path = ""
82+ isThreadSafe = True
83+ for d in hwPortUtils .listHidDevices ():
84+ if d ["hardwareID" ].startswith (hidvidpid ):
85+ path = d ["devicePath" ]
86+ # log.info("Devicepath: "+path)
87+
88+ @classmethod
89+ def check (cls ):
90+ return True
91+
92+ @classmethod
93+ def getManualPorts (cls ):
94+ return self .path
95+
96+ def __init__ (self , port = "hid" ):
97+ super (BrailleDisplayDriver , self ).__init__ ()
98+ self .numCells = 0
99+ self .numBtns = 0
100+ self .status = 0
101+ self .cmdlen = 0
102+ self .handle = None
103+ self ._hidBuffer = b""
104+ log .info ("Versuch: " + self .path )
105+ # for portType, portId, port, portInfo in self._getTryPorts(port):
106+ # log.info("port:"+port)
107+ if self .path == "" :
108+ raise RuntimeError ("No MINI-SEIKA display found, no path found" )
109+ self ._dev = hwIo .Hid (path = self .path , onReceive = self ._onReceive )
110+ if self ._dev ._file == INVALID_HANDLE_VALUE :
111+ raise RuntimeError ("No MINI-SEIKA display found, open error" )
112+ self ._dev .setFeature (SEIKA_CONFIG ) # baudrate, stopbit usw
113+ self ._dev .setFeature (SEIKA_CMD_ON ) # device on
114+ self ._dev .write (SEIKA_REQUEST_INFO ) # Request the Info from the device
115+ for i in range (30 ): # the info-block is about
116+ self ._dev .waitForRead (TIMEOUT )
117+ if self .numCells :
118+ log .info ("Seikanotetaker an USB-HID, Cells {c} Buttons {b}" .format (c = self .numCells , b = self .numBtns ))
119+ config .conf ["braille" ]["display" ] = SEIKA_NAME
120+ break
121+
122+ if self .numCells == 0 :
123+ self ._dev .close ()
124+ raise RuntimeError ("No MINI-SEIKA display found, no response" )
125+
126+ def terminate (self ):
127+ try :
128+ super (BrailleDisplayDriver , self ).terminate ()
129+ finally :
130+ self ._dev .close ()
131+
132+ def display (self , cells : List [int ]):
133+ # cells will already be padded up to numCells.
134+ cellBytes = SEIKA_SEND_TEXT + self .numCells .to_bytes (1 , 'little' ) + bytes (cells )
135+ self ._dev .write (cellBytes )
136+
137+ def _onReceive (self , data : bytes ):
138+ stream = BytesIO (data )
139+ cmd = stream .read (3 )
140+ self ._hidBuffer += cmd [1 :2 ]
141+ if len (self ._hidBuffer ) == 3 :
142+ self .status = 1
143+ elif len (self ._hidBuffer ) == 4 :
144+ self .cmdlen = cmd [1 ]
145+ self .status = 2
146+ elif self .status == 2 and len (self ._hidBuffer ) == self .cmdlen + 4 :
147+ command = self ._hidBuffer [0 :3 ]
148+ arg = self ._hidBuffer [3 :self .cmdlen + 4 ]
149+ self .status = 0
150+ self ._hidBuffer = b""
151+ # log.info("auswerten nach cmd"+
152+ # "".join(format(x, ' 02X') for x in command)+" ... "+"".join(format(x, ' 02X') for x in arg));
153+ if command == SEIKA_INFO :
154+ self ._handInfo (arg )
155+ elif command == SEIKA_ROUTING :
156+ self ._handRouting (arg )
157+ elif command == SEIKA_KEYS :
158+ self ._handKeys (arg )
159+ elif command == SEIKA_KEYS_ROU :
160+ self ._handKeysRouting (arg )
161+ else :
162+ log .debug ("hier haette ich nicht ankommen sollen." )
163+
164+ def _handInfo (self , arg : bytes ):
165+ self .numCells = arg [2 ]
166+ self .numBtns = arg [1 ]
167+
168+ def _handRouting (self , arg : bytes ):
169+ Rou = 0
170+ # log.info("Rou: "+""+"".join(format(x, ' 02X') for x in arg));
171+ for i in range (arg [0 ]):
172+ for j in range (8 ):
173+ if arg [i + 1 ] & (1 << j ):
174+ Rou = i * 8 + j
175+ # log.info("Routing: {c}".format(c=Rou))
176+ gesture = InputGestureRouting (Rou )
177+ try :
178+ inputCore .manager .executeGesture (gesture )
179+ except inputCore .NoInputGestureAction :
180+ log .debug ("No Action for routing command" )
181+ pass
182+
183+ def _handKeys (self , arg : bytes ):
184+ # log.info("Keys: "+""+"".join(format(x, ' 02X') for x in arg));
185+ Brl = arg [1 ]
186+ Key = arg [2 ] | (arg [3 ] << 8 )
187+ # space = arg[2] & 2
188+ Btn = 0 # Seika has no button
189+ if not (Key or Btn or Brl ):
190+ pass
191+ if Key : # Mini Seika has 2 Top and 4 Front ....
192+ gesture = InputGesture (keys = Key )
193+ if Btn : # Mini Seika has no Btn ....
194+ gesture = InputGesture (keys = Btn )
195+ if Brl : # or how to handle Brailleinput?
196+ gesture = InputGesture (dots = Brl )
197+ if Key or Btn or Brl :
198+ try :
199+ inputCore .manager .executeGesture (gesture )
200+ except inputCore .NoInputGestureAction :
201+ log .debug ("No Action for keys " )
202+ pass
203+
204+ def _handKeysRouting (self , arg : bytes ):
205+ argk = b"\x03 " + arg [1 :]
206+ argr = (arg [0 ] - 3 ).to_bytes (1 , 'little' ) + arg [4 :]
207+ self ._handRouting (argr )
208+ self ._handKeys (argk )
209+
210+ gestureMap = inputCore .GlobalGestureMap ({
211+ "globalCommands.GlobalCommands" : {
212+ "braille_routeTo" : ("br(seikantk):routing" ,),
213+ "braille_scrollBack" : ("br(seikantk):LB" ,),
214+ "braille_scrollForward" : ("br(seikantk):RB" ,),
215+ "braille_previousLine" : ("br(seikantk):LJ_UP" ,),
216+ "braille_nextLine" : ("br(seikantk):LJ_DOWN" ,),
217+ "braille_toggleTether" : ("br(seikantk):LJ_CENTER" ,),
218+ "sayAll" : ("br(seikantk):SPACE+BACKSPACE" ,),
219+ "showGui" : ("br(seikantk):RB+LB" ,),
220+ "kb:tab" : ("br(seikantk):LJ_RIGHT" ,),
221+ "kb:shift+tab" : ("br(seikantk):LJ_LEFT" ,),
222+ "kb:upArrow" : ("br(seikantk):RJ_UP" ,),
223+ "kb:downArrow" : ("br(seikantk):RJ_DOWN" ,),
224+ "kb:leftArrow" : ("br(seikantk):RJ_LEFT" ,),
225+ "kb:rightArrow" : ("br(seikantk):RJ_RIGHT" ,),
226+ "kb:shift+upArrow" : ("br(seikantk):SPACE+RJ_UP" ,),
227+ "kb:shift+downArrow" : ("br(seikantk):SPACE+RJ_DOWN" ,),
228+ "kb:shift+leftArrow" : ("br(seikantk):SPACE+RJ_LEFT" ,),
229+ "kb:shift+rightArrow" : ("br(seikantk):SPACE+RJ_RIGHT" ,),
230+ "kb:escape" : ("br(seikantk):SPACE+RJ_CENTER" ,),
231+ "kb:shift+upArrow" : ("br(seikantk):BACKSPACE+RJ_UP" ,),
232+ "kb:shift+downArrow" : ("br(seikantk):BACKSPACE+RJ_DOWN" ,),
233+ "kb:shift+leftArrow" : ("br(seikantk):BACKSPACE+RJ_LEFT" ,),
234+ "kb:shift+rightArrow" : ("br(seikantk):BACKSPACE+RJ_RIGHT" ,),
235+ "kb:windows" : ("br(seikantk):BACKSPACE+RJ_CENTER" ,),
236+ "kb:space" : ("br(seikantk):BACKSPACE" , "br(seikantk):SPACE" ,),
237+ "kb:backspace" : ("br(seikantk):d7" ,),
238+ "kb:pageup" : ("br(seikantk):SPACE+LJ_RIGHT" ,),
239+ "kb:pagedown" : ("br(seikantk):SPACE+LJ_LEFT" ,),
240+ "kb:home" : ("br(seikantk):SPACE+LJ_UP" ,),
241+ "kb:end" : ("br(seikantk):SPACE+LJ_DOWN" ,),
242+ "kb:control+home" : ("br(seikantk):BACKSPACE+LJ_UP" ,),
243+ "kb:control+end" : ("br(seikantk):BACKSPACE+LJ_DOWN" ,),
244+ "kb:enter" : ("br(seikantk):RJ_CENTER" , "br(seikantk):d8" ),
245+ },
246+ })
247+
248+
249+ class InputGestureRouting (braille .BrailleDisplayGesture ):
250+ source = BrailleDisplayDriver .name
251+
252+ def __init__ (self , index ):
253+ super (InputGestureRouting , self ).__init__ ()
254+ self .id = "routing"
255+ self .routingIndex = index
256+
257+
258+ class InputGesture (braille .BrailleDisplayGesture , brailleInput .BrailleInputGesture ):
259+ source = BrailleDisplayDriver .name
260+
261+ def __init__ (self , keys = None , dots = None , space = False , routing = None ):
262+ super (braille .BrailleDisplayGesture , self ).__init__ ()
263+ # see what thumb keys are pressed:
264+ names = set ()
265+ if keys is not None :
266+ names .update (_keyNames [1 << i ] for i in range (22 ) if (1 << i ) & keys )
267+ elif dots is not None :
268+ # now the dots
269+ self .dots = dots
270+ if space :
271+ self .space = space
272+ names .add (_keyNames [1 ])
273+ names .update (_dotNames [1 << i ] for i in range (8 ) if (1 << i ) & dots )
274+ elif routing is not None :
275+ self .routingIndex = routing
276+ names .add ('routing' )
277+ self .id = "+" .join (names )
278+ # log.info("SNT: keys {keys}".format(keys=names))
0 commit comments