1+ #A part of NonVisual Desktop Access (NVDA)
2+ #This file is covered by the GNU General Public License.
3+ #See the file COPYING for more details.
4+ #Copyright (C) 2012/20 Ulf Beckmann <beckmann@flusoft.de>
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 Union , List , Optional
13+ from ctypes import *
14+ import time
15+ import wx
16+ import braille
17+ import brailleInput
18+ import inputCore
19+ import hwPortUtils
20+ import bdDetect
21+ import hwIo
22+ from serial .win32 import INVALID_HANDLE_VALUE
23+ from logHandler import log
24+ import config
25+
26+ TIMEOUT = 0.2
27+
28+ DOT_1 = 0x1
29+ DOT_2 = 0x2
30+ DOT_3 = 0x4
31+ DOT_4 = 0x8
32+ DOT_5 = 0x10
33+ DOT_6 = 0x20
34+ DOT_7 = 0x40
35+ DOT_8 = 0x80
36+
37+
38+ _keyNames = {
39+ 0x000001 : "BACKSPACE" ,
40+ 0x000002 : "SPACE" ,
41+ 0x000004 : "LB" ,
42+ 0x000008 : "RB" ,
43+ 0x000010 : "LJ_CENTER" ,
44+ 0x000020 : "LJ_LEFT" ,
45+ 0x000040 : "LJ_RIGHT" ,
46+ 0x000080 : "LJ_UP" ,
47+ 0x000100 : "LJ_DOWN" ,
48+ 0x000200 : "RJ_CENTER" ,
49+ 0x000400 : "RJ_LEFT" ,
50+ 0x000800 : "RJ_RIGHT" ,
51+ 0x001000 : "RJ_UP" ,
52+ 0x002000 : "RJ_DOWN"
53+ }
54+
55+ SEIKA_REQUEST_INFO = b"\x03 \xff \xff \xa1 "
56+ SEIKA_INFO = b"\xff \xff \xa2 "
57+ SEIKA_SEND_TEXT = b"\x2c \xff \xff \xa3 "
58+ SEIKA_ROUTING = b"\xff \xff \xa4 "
59+ SEIKA_KEYS = b"\xff \xff \xa6 "
60+ SEIKA_KEYS_ROU = b"\xff \xff \xa8 "
61+
62+ SEIKA_CONFIG = b"\x50 \x00 \x00 \x25 \x80 \x00 \x00 \x03 \x00 "
63+ SEIKA_CMD_ON = b"\x41 \x01 "
64+
65+ vidpid = "VID_10C4&PID_EA80"
66+ hidvidpid = "HID\\ VID_10C4&PID_EA80"
67+ SEIKA_NAME = "seikantk"
68+
69+ _dotNames = {}
70+ for i in range (1 ,9 ):
71+ key = globals ()["DOT_%d" % i ]
72+ _dotNames [key ] = "d%d" % i
73+ bdDetect .addUsbDevices (SEIKA_NAME , bdDetect .KEY_HID , {
74+ vidpid , # USB-HID adapter
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 path
95+
96+
97+ def __init__ (self , port = "hid" ):
98+ super (BrailleDisplayDriver , self ).__init__ ()
99+ self .numCells = 0
100+ self .numBtns = 0
101+ self .status = 0
102+ self .cmdlen = 0
103+ self .handle = None
104+ self ._hidBuffer = b""
105+ log .info ("Versuch: " + self .path )
106+ # for portType, portId, port, portInfo in self._getTryPorts(port):
107+ # log.info("port:"+port)
108+ if self .path == "" :
109+ raise RuntimeError ("No MINI-SEIKA display found, no path found" )
110+ self ._dev = hwIo .Hid (path = self .path , onReceive = self ._onReceive )
111+ if self ._dev ._file == INVALID_HANDLE_VALUE :
112+ raise RuntimeError ("No MINI-SEIKA display found, open error" )
113+ self ._dev .setFeature (SEIKA_CONFIG ) # baudrate, stopbit usw
114+ self ._dev .setFeature (SEIKA_CMD_ON ) # device on
115+ self ._dev .write (SEIKA_REQUEST_INFO ) # Request the Info from the device
116+ for i in range (30 ): # the info-block is about
117+ self ._dev .waitForRead (TIMEOUT )
118+ if self .numCells :
119+ log .info ("Seikanotetaker an USB-HID, Cells {c} Buttons {b}" .format (c = self .numCells ,b = self .numBtns ))
120+ config .conf ["braille" ]["display" ] = SEIKA_NAME
121+ break
122+
123+ if self .numCells == 0 :
124+ self ._dev .close ()
125+ raise RuntimeError ("No MINI-SEIKA display found, no response" )
126+
127+ def terminate (self ):
128+ try :
129+ super (BrailleDisplayDriver , self ).terminate ()
130+ finally :
131+ self ._dev .close ()
132+
133+ def display (self , cells : List [int ]):
134+ # cells will already be padded up to numCells.
135+ cellBytes = SEIKA_SEND_TEXT + self .numCells .to_bytes (1 ,'little' ) + bytes (cells )
136+ self ._dev .write (cellBytes )
137+
138+ def _onReceive (self , data : bytes ):
139+ stream = BytesIO (data )
140+ cmd = stream .read (3 )
141+ self ._hidBuffer += cmd [1 :2 ]
142+ if len (self ._hidBuffer ) == 3 :
143+ self .status = 1
144+ elif len (self ._hidBuffer ) == 4 :
145+ self .cmdlen = cmd [1 ]
146+ self .status = 2
147+ elif self .status == 2 and len (self ._hidBuffer ) == self .cmdlen + 4 :
148+ command = self ._hidBuffer [0 :3 ]
149+ arg = self ._hidBuffer [3 :self .cmdlen + 4 ]
150+ self .status = 0
151+ self ._hidBuffer = b""
152+ # log.info("auswerten nach cmd"+"".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+ class InputGestureRouting (braille .BrailleDisplayGesture ):
249+ source = BrailleDisplayDriver .name
250+
251+ def __init__ (self , index ):
252+ super (InputGestureRouting , self ).__init__ ()
253+ self .id = "routing"
254+ self .routingIndex = index
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 )
265+ if (1 << i ) & keys )
266+ elif dots is not None :
267+ # now the dots
268+ self .dots = dots
269+ if space :
270+ self .space = space
271+ names .add (_keyNames [1 ])
272+ names .update (_dotNames [1 << i ] for i in range (8 )
273+ 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))
279+
0 commit comments