99import serial
1010from io import BytesIO
1111import hwIo
12+ from hwIo import intToByte
1213import braille
1314from logHandler import log
1415from collections import OrderedDict
1718from baseObject import AutoPropertyObject
1819import time
1920import bdDetect
21+ import math
2022
2123BAUD_RATE = 115200
2224PARITY = serial .PARITY_NONE
2325
26+ # HID
27+ HR_CAPS = b"\x01 "
28+
2429class Model (AutoPropertyObject ):
2530 """Extend from this base class to define model specific behavior."""
2631 #: Two bytes device identifier, used in the protocol to identify the device
@@ -135,6 +140,40 @@ def _get_keys(self) -> dict[str, str]:
135140 return keys
136141
137142
143+ class BrailleEdge3S (BrailleEdge ):
144+ """This device is the BrailleEdge which doesn't use the hims driver.
145+ It only uses a Braille HID connection.
146+ """
147+ usbId = "VID_045E&PID_940A"
148+
149+ def _get_keys (self ) -> dict [str , str ]:
150+ return OrderedDict ({
151+ # Braille keyboard, not used for SyncBraille
152+ 0x01 << 8 : "dot1" ,
153+ 0x02 << 8 : "dot2" ,
154+ 0x04 << 8 : "dot3" ,
155+ 0x08 << 8 : "dot4" ,
156+ 0x10 << 8 : "dot5" ,
157+ 0x20 << 8 : "dot6" ,
158+ 0x40 << 8 : "dot7" ,
159+ 0x80 << 8 : "dot8" ,
160+ 0x01 << 88 : "space" ,
161+ 0x01 << 64 : "f1" ,
162+ 0x04 << 64 : "f2" ,
163+ 0x10 << 64 : "f3" ,
164+ 0x40 << 64 : "f4" ,
165+ 0x08 << 64 : "leftSideScrollUp" ,
166+ 0x08 << 64 : "rightSideScrollUp" ,
167+ 0x80 << 64 : "leftSideScrollDown" ,
168+ 0x80 << 64 : "rightSideScrollDown" ,
169+ 0x02 << 64 : "leftCursor" ,
170+ 0x20 << 64 : "rightCursor" ,
171+ 0x01 << 72 : "control" ,
172+ 0x02 << 72 : "alt" ,
173+ 0x04 << 72 : "home" ,
174+ })
175+
176+
138177class BrailleSense2S (BrailleSense ):
139178 """Braille Sense with one scroll key on both sides.
140179 Also referred to as Braille Sense Classic."""
@@ -191,6 +230,7 @@ def _get_keys(self):
191230 BrailleSenseQ ,
192231 BrailleEdge ,
193232 BrailleEdge2S ,
233+ BrailleEdge3S ,
194234 SmartBeetle ,
195235 BrailleSense4S ,
196236 BrailleSense2S ,
@@ -207,6 +247,11 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver):
207247
208248 @classmethod
209249 def registerAutomaticDetection (cls , driverRegistrar : bdDetect .DriverRegistrar ):
250+ # Hid device
251+ driverRegistrar .addUsbDevices (bdDetect .DeviceType .HID , {
252+ "VID_045E&PID_940A" , # Braille Edge3S 40
253+ })
254+
210255 # Bulk devices
211256 driverRegistrar .addUsbDevices (bdDetect .DeviceType .CUSTOM , {
212257 "VID_045E&PID_930A" , # Braille Sense & Smart Beetle
@@ -237,9 +282,12 @@ def __init__(self, port="auto"):
237282 for match in self ._getTryPorts (port ):
238283 portType , portId , port , portInfo = match
239284 self .isBulk = portType == bdDetect .DeviceType .CUSTOM
285+ self .isHID = portType == bdDetect .DeviceType .HID
240286 # Try talking to the display.
241287 try :
242- if self .isBulk :
288+ if self .isHID :
289+ self ._dev = hwIo .Hid (port , onReceive = self ._hidOnReceive )
290+ elif self .isBulk :
243291 # onReceiveSize based on max packet size according to USB endpoint information.
244292 self ._dev = hwIo .Bulk (port , 0 , 1 , self ._onReceive , onReceiveSize = 64 )
245293 else :
@@ -278,11 +326,26 @@ def __init__(self, port="auto"):
278326 def display (self , cells : List [int ]):
279327 # cells will already be padded up to numCells.
280328 cellBytes = bytes (cells )
281- self ._sendPacket (b"\xfc " , b"\x01 " , cellBytes )
329+ if self .isHID :
330+ outputReport : bytes = b"" .join ([
331+ intToByte (self .numCells ), # length
332+ cellBytes
333+ ])
334+
335+ self ._dev .setOutputReport (outputReport )
336+ else :
337+ self ._sendPacket (b"\xfc " , b"\x01 " , cellBytes )
282338
283339 def _sendCellCountRequest (self ):
284- log .debug ("Sending cell count request..." )
285- self ._sendPacket (b"\xfb " , b"\x01 " , bytes (32 )) # send 32 null bytes
340+ if self .isHID :
341+ try :
342+ data : bytes = self ._dev .getFeature (HR_CAPS )
343+ self .numCells = data [9 ]
344+ except WindowsError :
345+ return # Fail!
346+ else :
347+ log .debug ("Sending cell count request..." )
348+ self ._sendPacket (b"\xfb " , b"\x01 " , bytes (32 )) # send 32 null bytes
286349
287350 def _sendIdentificationRequests (self , match : bdDetect .DeviceMatch ):
288351 log .debug ("Considering sending identification requests for device %s" % str (match ))
@@ -293,7 +356,7 @@ def _sendIdentificationRequests(self, match: bdDetect.DeviceMatch):
293356 and match .id .startswith (modelTuple [1 ].bluetoothPrefix )
294357 )
295358 ]
296- else : # USB Bulk, Serial
359+ else : # USB Bulk, Serial, HID
297360 matchedModelsMap = [
298361 modelTuple for modelTuple in modelMap if (
299362 modelTuple [1 ].usbId == match .id
@@ -385,6 +448,36 @@ def _handlePacket(self, packet: bytes):
385448 elif mode == 0x02 : # Cell count
386449 self .numCells = packet [3 ]
387450
451+ def _hidOnReceive (self , data : bytes ):
452+ if data == bytes ([0x00 ] * 12 ): # All key lifted
453+ return
454+
455+ routingKey = int .from_bytes (data [2 :7 ], "little" , signed = False )
456+ if routingKey != 0 : # Routing key
457+ try :
458+ inputCore .manager .executeGesture (RoutingInputGesture (int (math .log (routingKey , 2 ))))
459+ except inputCore .NoInputGestureAction :
460+ pass
461+ else : # Other key
462+ if not self ._model :
463+ return
464+ _keys = int .from_bytes (data , "little" , signed = False )
465+ keys = set ()
466+ for keyHex in self ._model .keys :
467+ if _keys & keyHex :
468+ # This key is pressed
469+ _keys -= keyHex
470+ keys .add (keyHex )
471+ if _keys == 0 :
472+ break
473+ if _keys :
474+ log .error ("Unknown key(s) 0x%x received from Hims display" % _keys )
475+ return
476+ try :
477+ inputCore .manager .executeGesture (KeyInputGesture (self ._model , keys , True ))
478+ except inputCore .NoInputGestureAction :
479+ pass
480+
388481 def _onReceive (self , data : bytes ):
389482 if self .isBulk :
390483 # data contains the entire packet.
@@ -701,7 +794,7 @@ class KeyInputGesture(braille.BrailleDisplayGesture, brailleInput.BrailleInputGe
701794
702795 source = BrailleDisplayDriver .name
703796
704- def __init__ (self , model , keys ):
797+ def __init__ (self , model , keys , isHid = False ):
705798 super (KeyInputGesture , self ).__init__ ()
706799 # Model identifiers should not contain spaces.
707800 self .model = model .name .replace (" " , "" )
@@ -710,15 +803,26 @@ def __init__(self, model, keys):
710803 isBrailleInput = True
711804 for key in keys :
712805 if isBrailleInput :
713- if 0xff & key :
714- self .dots |= key
715- elif model .keys .get (key )== "space" :
716- self .space = True
806+ if isHid :
807+ if 8 <= int (math .log (key , 2 )) <= 15 :
808+ self .dots |= key >> 8
809+ elif model .keys .get (key ) == "space" :
810+ self .space = True
811+ else :
812+ # This is not braille input.
813+ isBrailleInput = False
814+ self .dots = 0
815+ self .space = False
717816 else :
718- # This is not braille input.
719- isBrailleInput = False
720- self .dots = 0
721- self .space = False
817+ if 0xff & key :
818+ self .dots |= key
819+ elif model .keys .get (key ) == "space" :
820+ self .space = True
821+ else :
822+ # This is not braille input.
823+ isBrailleInput = False
824+ self .dots = 0
825+ self .space = False
722826 names .add (model .keys [key ])
723827
724828 self .id = "+" .join (names )
0 commit comments