def _getAutoPorts(cls, comPorts): for portInfo in hwPortUtils.listHidDevices(): if portInfo.get("usbID") in USB_IDS_HID: yield portInfo["devicePath"], "USB HID" # Try bluetooth ports last. for portInfo in sorted(comPorts, key=lambda item: "bluetoothName" in item): port = portInfo["port"] hwID = portInfo["hardwareID"] if hwID.startswith(r"FTDIBUS\COMPORT"): # USB. portType = "USB serial" try: usbID = hwID.split("&", 1)[1] except IndexError: continue if usbID not in USB_IDS_SER: continue elif hwID == r"USB\VID_0483&PID_5740&REV_0200": # Generic STMicroelectronics Virtual COM Port used by Orbit Reader 20. portType = "USB serial" elif "bluetoothName" in portInfo: # Bluetooth. portType = "bluetooth" btName = portInfo["bluetoothName"] if not any(btName.startswith(prefix) for prefix in BLUETOOTH_NAMES): continue else: continue yield port, portType
def _getAutoPorts(cls, comPorts): for portInfo in hwPortUtils.listHidDevices(): if portInfo.get("usbID") in USB_IDS_HID_CONVERTER: yield portInfo["devicePath"], "USB HID serial converter" if portInfo.get("usbID") in USB_IDS_HID_NATIVE: yield portInfo["devicePath"], "USB HID" # Try bluetooth ports last. for portInfo in sorted(comPorts, key=lambda item: "bluetoothName" in item): port = portInfo["port"] hwId = portInfo["hardwareID"] if hwId.startswith(r"FTDIBUS\COMPORT"): # USB. # TODO: It seems there is also another chip (Gohubs) used in some models. See if we can autodetect that as well. portType = "USB serial" try: usbId = hwId.split("&", 1)[1] except IndexError: continue if usbId not in USB_IDS_SER: continue elif "bluetoothName" in portInfo: # Bluetooth. portType = "bluetooth" btName = portInfo["bluetoothName"] if not any( btName.startswith(prefix) for prefix in BLUETOOTH_NAMES): continue else: continue yield port, portType
def _getAutoPorts(cls, comPorts): for portInfo in hwPortUtils.listHidDevices(): if portInfo.get("usbID") in USB_IDS_HID: yield portInfo["devicePath"], "USB HID" # Try bluetooth ports last. for portInfo in sorted(comPorts, key=lambda item: "bluetoothName" in item): port = portInfo["port"] hwID = portInfo["hardwareID"] if hwID.startswith(r"FTDIBUS\COMPORT"): # USB. portType = "USB serial" try: usbID = hwID.split("&", 1)[1] except IndexError: continue if usbID not in USB_IDS_SER: continue elif hwID == r"USB\VID_0483&PID_5740&REV_0200": # Generic STMicroelectronics Virtual COM Port used by Orbit Reader 20. portType = "USB serial" elif "bluetoothName" in portInfo: # Bluetooth. portType = "bluetooth" btName = portInfo["bluetoothName"] if not any( btName.startswith(prefix) for prefix in BLUETOOTH_NAMES): continue else: continue yield port, portType
def _getPorts(): # USB HID. for portInfo in hwPortUtils.listHidDevices(): if portInfo.get("usbID") == "VID_1C71&PID_C006": yield "USB HID", portInfo["devicePath"] # USB serial. try: rootKey = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Enum\USB\Vid_1c71&Pid_c005") except WindowsError: # A display has never been connected via USB. pass else: with rootKey: for index in itertools.count(): try: keyName = _winreg.EnumKey(rootKey, index) except WindowsError: break try: with _winreg.OpenKey(rootKey, os.path.join(keyName, "Device Parameters")) as paramsKey: yield "USB serial", _winreg.QueryValueEx(paramsKey, "PortName")[0] except WindowsError: continue # Bluetooth. for portInfo in hwPortUtils.listComPorts(onlyAvailable=True): try: btName = portInfo["bluetoothName"] except KeyError: continue if btName.startswith("Brailliant B") or btName == "Brailliant 80": yield "bluetooth", portInfo["port"]
def _getAutoPorts(cls, comPorts): for portInfo in hwPortUtils.listHidDevices(): if portInfo.get("usbID") in USB_IDS_HID_CONVERTER: yield portInfo["devicePath"], "USB HID serial converter" if portInfo.get("usbID") in USB_IDS_HID_NATIVE: yield portInfo["devicePath"], "USB HID" # Try bluetooth ports last. for portInfo in sorted(comPorts, key=lambda item: "bluetoothName" in item): port = portInfo["port"] hwId = portInfo["hardwareID"] if hwId.startswith(r"FTDIBUS\COMPORT"): # USB. # TODO: It seems there is also another chip (Gohubs) used in some models. See if we can autodetect that as well. portType = "USB serial" try: usbId = hwId.split("&", 1)[1] except IndexError: continue if usbId not in USB_IDS_SER: continue elif "bluetoothName" in portInfo: # Bluetooth. portType = "bluetooth" btName = portInfo["bluetoothName"] if not any(btName.startswith(prefix) for prefix in BLUETOOTH_NAMES): continue else: continue yield port, portType
def _getAutoPorts(cls, comPorts): for portInfo in hwPortUtils.listHidDevices(): if portInfo.get("usbID","") in USB_IDS: yield portInfo["devicePath"], "USB HID", portInfo["usbID"] for portInfo in comPorts: if not portInfo.get("bluetoothName","").startswith("ALVA "): continue yield portInfo["port"], "bluetooth", portInfo["bluetoothName"]
def _getAutoPorts(cls, comPorts): for portInfo in hwPortUtils.listHidDevices(): if portInfo.get("usbID", "") in USB_IDS: yield portInfo["devicePath"], "USB HID", portInfo["usbID"] for portInfo in comPorts: if not portInfo.get("bluetoothName", "").startswith("ALVA "): continue yield portInfo["port"], "bluetooth", portInfo["bluetoothName"]
def _getAutoPorts(cls, comPorts): for portInfo in hwPortUtils.listHidDevices(): if portInfo.get("usbID") in USB_IDS_HID: yield portInfo["devicePath"], "USB HID" # Try bluetooth ports last. for portInfo in sorted(comPorts, key=lambda item: "bluetoothName" in item): port = portInfo["port"] if "bluetoothName" in portInfo: # Bluetooth. portType = "bluetooth" btName = portInfo["bluetoothName"] if not any(btName.startswith(prefix) for prefix in BLUETOOTH_NAMES): continue else: continue yield port, portType
def _getPorts(): # HID. for portInfo in hwPortUtils.listHidDevices(): if portInfo.get("usbID") in USB_IDS_HID: yield "USB HID", portInfo["devicePath"] # In Windows 10, the Bluetooth vendor and product ids don't get recognised. # Use strings instead. elif portInfo.get("manufacturer") == "Humanware" and portInfo.get( "product") == "Brailliant HID": yield "Bluetooth HID", portInfo["devicePath"] # USB serial. for usbId in USB_IDS_SER: try: rootKey = _winreg.OpenKey( _winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Enum\USB\%s" % usbId) except WindowsError: # A display with this id has never been connected via USB. continue with rootKey: for index in itertools.count(): try: keyName = _winreg.EnumKey(rootKey, index) except WindowsError: break # No more sub-keys. try: with _winreg.OpenKey( rootKey, os.path.join(keyName, "Device Parameters")) as paramsKey: yield "USB serial", _winreg.QueryValueEx( paramsKey, "PortName")[0] except WindowsError: continue # Bluetooth serial. for portInfo in hwPortUtils.listComPorts(onlyAvailable=True): try: btName = portInfo["bluetoothName"] except KeyError: continue if btName.startswith( "Brailliant B" ) or btName == "Brailliant 80" or "BrailleNote Touch" in btName: yield "Bluetooth serial", portInfo["port"]
def _getPorts(): # HID. for portInfo in hwPortUtils.listHidDevices(): if portInfo.get("usbID") in USB_IDS_HID: yield "USB HID", portInfo["devicePath"] # In Windows 10, the Bluetooth vendor and product ids don't get recognised. # Use strings instead. elif portInfo.get("manufacturer") == "Humanware" and portInfo.get("product") == "Brailliant HID": yield "Bluetooth HID", portInfo["devicePath"] # USB serial. for usbId in USB_IDS_SER: try: rootKey = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Enum\USB\%s" % usbId) except WindowsError: # A display with this id has never been connected via USB. continue with rootKey: for index in itertools.count(): try: keyName = _winreg.EnumKey(rootKey, index) except WindowsError: break # No more sub-keys. try: with _winreg.OpenKey(rootKey, os.path.join(keyName, "Device Parameters")) as paramsKey: yield "USB serial", _winreg.QueryValueEx(paramsKey, "PortName")[0] except WindowsError: continue # Bluetooth serial. for portInfo in hwPortUtils.listComPorts(onlyAvailable=True): try: btName = portInfo["bluetoothName"] except KeyError: continue if btName.startswith("Brailliant B") or btName == "Brailliant 80" or "BrailleNote Touch" in btName: yield "Bluetooth serial", portInfo["port"]
class BrailleDisplayDriver(braille.BrailleDisplayDriver): _dev: hwIo.IoBase name = SEIKA_NAME # Translators: Name of a braille display. description = _("Seika Notetaker") path = "" isThreadSafe = True for d in hwPortUtils.listHidDevices(): if d["hardwareID"].startswith(hidvidpid): path = d["devicePath"] @classmethod def getManualPorts(cls) -> typing.Iterator[typing.Tuple[str, str]]: """@return: An iterator containing the name and description for each port. """ return braille.getSerialPorts() def __init__(self, port="hid"): super().__init__() self.numCells = 0 self.numBtns = 0 self.numRoutingKeys = 0 self.handle = None self._hidBuffer = b"" self._command: typing.Optional[bytes] = None self._argsLen: typing.Optional[int] = None log.info(f"Seika Notetaker braille driver path: {self.path}") if self.path == "": raise RuntimeError("No MINI-SEIKA display found, no path found") self._dev = dev = hwIo.Hid(path=self.path, onReceive=self._onReceive) if dev._file == INVALID_HANDLE_VALUE: raise RuntimeError("No MINI-SEIKA display found, open error") dev.setFeature(SEIKA_CONFIG) # baud rate, stop bit usw dev.setFeature(SEIKA_CMD_ON) # device on dev.write(SEIKA_REQUEST_INFO) # Request the Info from the device # wait and try to get info from the Braille display for i in range(MAX_READ_ATTEMPTS): # the info-block is about dev.waitForRead(READ_TIMEOUT_SECS) if self.numCells: log.info(f"Seika notetaker on USB-HID," f" Cells {self.numCells}" f" Buttons {self.numBtns}") break if self.numCells == 0: dev.close() raise RuntimeError("No MINI-SEIKA display found, no response") def terminate(self): try: super().terminate() finally: self._dev.close() def display(self, cells: List[int]): # cells will already be padded up to numCells. cellBytes = SEIKA_SEND_TEXT + self.numCells.to_bytes( 1, 'little') + bytes(cells) self._dev.write(cellBytes) def _onReceive(self, data: bytes): """ Note: Further insight into this function would be greatly appreciated. This function is a very simple state machine, each stage represents the collection of a field, when all fields are collected the command they represent can be processed. On each call to _onReceive three bytes are read from the device. The first and third bytes are discarded, the second byte is appended to a buffer. The buffer is accumulated until the buffer has the required number of bytes for the field being collected. There are 3 fields to be collected before a command can be processed: 1: first 3 bytes: command 2: 1 byte: specify length of subsequent arguments in bytes 3: variable length: arguments for command type After accumulating enough bytes for each phase, the buffer is cleared and the next stage is entered. """ COMMAND_LEN = 3 stream = BytesIO(data) cmd = stream.read(3) # Note, first and third bytes are discarded newByte: bytes = cmd[1:2] # use range to return bytes self._hidBuffer += newByte hasCommandBeenCollected = self._command is not None hasArgLenBeenCollected = self._argsLen is not None if ( # still collecting command bytes not hasCommandBeenCollected and len(self._hidBuffer) == COMMAND_LEN): self._command = self._hidBuffer # command found reset and wait for args length self._hidBuffer = b"" elif ( # next byte gives the command + args length hasCommandBeenCollected and not hasArgLenBeenCollected # argsLen has not ): # the data is sent with the following structure # - command name (3 bytes) # - number of subsequent bytes to read (1 byte) # - Args (variable bytes) self._argsLen = ord(newByte) self._hidBuffer = b"" elif ( # now collect the args, hasCommandBeenCollected and hasArgLenBeenCollected and len(self._hidBuffer) == self._argsLen): arg = self._hidBuffer command = self._command # reset state variables self._command = None self._argsLen = None self._hidBuffer = b"" self._processCommand(command, arg) def _processCommand(self, command: bytes, arg: bytes) -> None: if command == SEIKA_INFO: self._handleInfo(arg) elif command == SEIKA_ROUTING: self._handleRouting(arg) elif command == SEIKA_KEYS: self._handleKeys(arg) elif command == SEIKA_KEYS_ROU: self._handleKeysRouting(arg) else: log.warning( f"Seika device has received an unknown command {command}") def _handleInfo(self, arg: bytes): """After sending a request for information from the braille device this data is returned to complete the handshake. """ self.numBtns = arg[0] self.numCells = arg[1] self.numRoutingKeys = arg[2] try: self._description = arg[3:].decode("ascii") except UnicodeDecodeError: log.debugWarning( f"Unable to decode Seika Notetaker description {arg[3:]}") def _handleRouting(self, arg: bytes): routingIndexes = _getRoutingIndexes(arg) for routingIndex in routingIndexes: gesture = InputGestureRouting(routingIndex) try: inputCore.manager.executeGesture(gesture) except inputCore.NoInputGestureAction: log.debug("No action for Seika Notetaker routing command") def _handleKeys(self, arg: bytes): brailleDots = arg[0] key = arg[1] | (arg[2] << 8) gestures = [] if key: gestures.append(InputGesture(keys=key)) if brailleDots: gestures.append(InputGesture(dots=brailleDots)) for gesture in gestures: try: inputCore.manager.executeGesture(gesture) except inputCore.NoInputGestureAction: log.debug("No action for Seika Notetaker keys.") def _handleKeysRouting(self, arg: bytes): self._handleRouting(arg[3:]) self._handleKeys(arg[:3]) gestureMap = inputCore.GlobalGestureMap({ "globalCommands.GlobalCommands": { "braille_routeTo": ("br(seikantk):routing", ), "braille_scrollBack": ("br(seikantk):LB", ), "braille_scrollForward": ("br(seikantk):RB", ), "braille_previousLine": ("br(seikantk):LJ_UP", ), "braille_nextLine": ("br(seikantk):LJ_DOWN", ), "braille_toggleTether": ("br(seikantk):LJ_CENTER", ), "sayAll": ("br(seikantk):SPACE+BACKSPACE", ), "showGui": ("br(seikantk):RB+LB", ), "kb:tab": ("br(seikantk):LJ_RIGHT", ), "kb:shift+tab": ("br(seikantk):LJ_LEFT", ), "kb:upArrow": ("br(seikantk):RJ_UP", ), "kb:downArrow": ("br(seikantk):RJ_DOWN", ), "kb:leftArrow": ("br(seikantk):RJ_LEFT", ), "kb:rightArrow": ("br(seikantk):RJ_RIGHT", ), "kb:shift+upArrow": ("br(seikantk):SPACE+RJ_UP", "br(seikantk):BACKSPACE+RJ_UP"), "kb:shift+downArrow": ("br(seikantk):SPACE+RJ_DOWN", "br(seikantk):BACKSPACE+RJ_DOWN"), "kb:shift+leftArrow": ("br(seikantk):SPACE+RJ_LEFT", "br(seikantk):BACKSPACE+RJ_LEFT"), "kb:shift+rightArrow": ("br(seikantk):SPACE+RJ_RIGHT", "br(seikantk):BACKSPACE+RJ_RIGHT"), "kb:escape": ("br(seikantk):SPACE+RJ_CENTER", ), "kb:windows": ("br(seikantk):BACKSPACE+RJ_CENTER", ), "kb:space": ( "br(seikantk):BACKSPACE", "br(seikantk):SPACE", ), "kb:backspace": ("br(seikantk):d7", ), "kb:pageup": ("br(seikantk):SPACE+LJ_RIGHT", ), "kb:pagedown": ("br(seikantk):SPACE+LJ_LEFT", ), "kb:home": ("br(seikantk):SPACE+LJ_UP", ), "kb:end": ("br(seikantk):SPACE+LJ_DOWN", ), "kb:control+home": ("br(seikantk):BACKSPACE+LJ_UP", ), "kb:control+end": ("br(seikantk):BACKSPACE+LJ_DOWN", ), "kb:enter": ("br(seikantk):RJ_CENTER", "br(seikantk):d8"), }, })
def _get_hidDevices(self) -> typing.List[typing.Dict]: return list(hwPortUtils.listHidDevices(onlyAvailable=True))
def _get_hidDevices(self): return list(hwPortUtils.listHidDevices(onlyAvailable=True))