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-
109from io import BytesIO
1110import typing
1211from typing import Dict , List , Set
1312
13+ import serial
14+
1415import braille
1516import brailleInput
1617import inputCore
17- import hwPortUtils
1818import bdDetect
1919import hwIo
2020from serial .win32 import INVALID_HANDLE_VALUE
5757SEIKA_KEYS = b"\xff \xff \xa6 "
5858SEIKA_KEYS_ROU = b"\xff \xff \xa8 "
5959
60- SEIKA_CONFIG = b"\x50 \x00 \x00 \x25 \x80 \x00 \x00 \x03 \x00 "
60+ BAUD = 9600
61+ SEIKA_HID_FEATURES = b"" .join ([
62+ b"\x50 \x00 \x00 " ,
63+ int .to_bytes (BAUD , length = 2 , byteorder = "big" , signed = False ), # b"\x25\x80"
64+ b"\x00 \x00 \x03 \x00 " ,
65+ ])
6166SEIKA_CMD_ON = b"\x41 \x01 "
6267
6368vidpid = "VID_10C4&PID_EA80"
6469hidvidpid = "HID\\ VID_10C4&PID_EA80"
6570SEIKA_NAME = "seikantk"
6671
67-
6872class BrailleDisplayDriver (braille .BrailleDisplayDriver ):
6973 _dev : hwIo .IoBase
7074 name = SEIKA_NAME
7175 # Translators: Name of a braille display.
7276 description = _ ("Seika Notetaker" )
73- path = ""
7477 isThreadSafe = True
75- for d in hwPortUtils .listHidDevices ():
76- if d ["hardwareID" ].startswith (hidvidpid ):
77- path = d ["devicePath" ]
7878
7979 @classmethod
8080 def getManualPorts (cls ) -> typing .Iterator [typing .Tuple [str , str ]]:
8181 """@return: An iterator containing the name and description for each port.
8282 """
8383 return braille .getSerialPorts ()
8484
85- def __init__ (self , port = "hid" ):
85+ def __init__ (self , port = bdDetect . KEY_HID ):
8686 super ().__init__ ()
8787 self .numCells = 0
8888 self .numBtns = 0
8989 self .numRoutingKeys = 0
9090 self .handle = None
91-
9291 self ._hidBuffer = b""
9392 self ._command : typing .Optional [bytes ] = None
9493 self ._argsLen : typing .Optional [int ] = None
95- log .info (f"Seika Notetaker braille driver path: { self .path } " )
96-
97- if self .path == "" :
98- raise RuntimeError ("No MINI-SEIKA display found, no path found" )
99- self ._dev = dev = hwIo .Hid (path = self .path , onReceive = self ._onReceive )
100- if dev ._file == INVALID_HANDLE_VALUE :
101- raise RuntimeError ("No MINI-SEIKA display found, open error" )
102- dev .setFeature (SEIKA_CONFIG ) # baud rate, stop bit usw
103- dev .setFeature (SEIKA_CMD_ON ) # device on
94+
95+ log .debug (f"Seika Notetaker braille driver: ({ port !r} )" )
96+ dev : typing .Optional [typing .Union [hwIo .Hid , hwIo .Serial ]] = None
97+ for match in self ._getTryPorts (port ):
98+ self .isHid = match .type == bdDetect .KEY_HID
99+ self .isSerial = match .type == bdDetect .KEY_SERIAL
100+ try :
101+ if self .isHid :
102+ log .info (f"Trying Seika notetaker on USB-HID" )
103+ self ._dev = dev = hwIo .Hid (
104+ path = match .port , # for a Hid match type 'port' is actually 'path'.
105+ onReceive = self ._onReceiveHID
106+ )
107+ dev .setFeature (SEIKA_HID_FEATURES ) # baud rate, stop bit usw
108+ dev .setFeature (SEIKA_CMD_ON ) # device on
109+ elif self .isSerial :
110+ log .info (f"Trying Seika notetaker on Bluetooth (serial) port:{ match .port } " )
111+ self ._dev = dev = hwIo .Serial (
112+ port = match .port ,
113+ onReceive = self ._onReceiveSerial ,
114+ baudrate = BAUD ,
115+ parity = serial .PARITY_NONE ,
116+ bytesize = serial .EIGHTBITS ,
117+ stopbits = serial .STOPBITS_ONE ,
118+ )
119+ # Does the device need to be sent SEIKA_CMD_ON as per USB-HID?
120+ else :
121+ log .debug (f"Port type not handled: { match .type } " )
122+ continue
123+ except EnvironmentError :
124+ log .debugWarning ("" , exc_info = True )
125+ continue
126+ if self ._getDeviceInfo (dev ):
127+ break
128+ elif dev :
129+ dev .close ()
130+ dev = None
131+
132+ if not dev :
133+ RuntimeError ("No MINI-SEIKA display found" )
134+ elif self .numCells == 0 :
135+ dev .close ()
136+ dev = None
137+ raise RuntimeError ("No MINI-SEIKA display found, no response" )
138+ else :
139+ log .info (
140+ f"Seika notetaker,"
141+ f" Cells { self .numCells } "
142+ f" Buttons { self .numBtns } "
143+ )
144+
145+ def _getDeviceInfo (self , dev : hwIo .IoBase ) -> bool :
146+ if not dev or dev ._file == INVALID_HANDLE_VALUE :
147+ log .debug ("No MINI-SEIKA display found, open error" )
148+ return False
149+
104150 dev .write (SEIKA_REQUEST_INFO ) # Request the Info from the device
105151
106152 # wait and try to get info from the Braille display
107153 for i in range (MAX_READ_ATTEMPTS ): # the info-block is about
108154 dev .waitForRead (READ_TIMEOUT_SECS )
109155 if self .numCells :
110- log .info (
111- f"Seika notetaker on USB-HID,"
112- f" Cells { self .numCells } "
113- f" Buttons { self .numBtns } "
114- )
115- break
116-
117- if self .numCells == 0 :
118- dev .close ()
119- raise RuntimeError ("No MINI-SEIKA display found, no response" )
156+ return True
157+ return False
120158
121159 def terminate (self ):
122160 try :
@@ -126,17 +164,29 @@ def terminate(self):
126164
127165 def display (self , cells : List [int ]):
128166 # cells will already be padded up to numCells.
129- cellBytes = SEIKA_SEND_TEXT + self .numCells . to_bytes ( 1 , 'little' ) + bytes (cells )
167+ cellBytes = SEIKA_SEND_TEXT + bytes ([ self .numCells ] ) + bytes (cells )
130168 self ._dev .write (cellBytes )
131169
132- def _onReceive (self , data : bytes ):
170+ def _onReceiveHID (self , data : bytes ):
171+ """Three bytes at a time expected, only the middle byte is used to construct the command, the first
172+ and third byte are discarded.
173+ """
174+ stream = BytesIO (data )
175+ cmd = stream .read (3 ) # Note, first and third bytes are discarded
176+ newByte : bytes = cmd [1 :2 ] # use range to return bytes type, containing only index 1
177+ self ._onReceive (newByte )
178+
179+ def _onReceiveSerial (self , data : bytes ):
180+ """One byte at a time is expected"""
181+ self ._onReceive (data )
182+
183+ def _onReceive (self , newByte : bytes ):
133184 """
134185 Note: Further insight into this function would be greatly appreciated.
135186 This function is a very simple state machine, each stage represents the collection of a field, when all
136187 fields are collected the command they represent can be processed.
137188
138- On each call to _onReceive three bytes are read from the device.
139- The first and third bytes are discarded, the second byte is appended to a buffer.
189+ On each call to _onReceive the new byte is appended to a buffer.
140190 The buffer is accumulated until the buffer has the required number of bytes for the field being collected.
141191 There are 3 fields to be collected before a command can be processed:
142192 1: first 3 bytes: command
@@ -146,9 +196,6 @@ def _onReceive(self, data: bytes):
146196 After accumulating enough bytes for each phase, the buffer is cleared and the next stage is entered.
147197 """
148198 COMMAND_LEN = 3
149- stream = BytesIO (data )
150- cmd = stream .read (3 ) # Note, first and third bytes are discarded
151- newByte : bytes = cmd [1 :2 ] # use range to return bytes
152199 self ._hidBuffer += newByte
153200 hasCommandBeenCollected = self ._command is not None
154201 hasArgLenBeenCollected = self ._argsLen is not None
0 commit comments