Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
0628b0b
fix incorrectly assigned combination keys.
EdKweon Feb 1, 2024
f4f560f
Update user_docs/en/changes.t2t
EdKweon Feb 1, 2024
13d6f5b
Update user_docs/en/changes.t2t
EdKweon Feb 1, 2024
1416648
Update user_docs/en/userGuide.t2t
EdKweon Feb 1, 2024
b80c46b
update userGuide and changes.t2t
EdKweon Feb 1, 2024
7582481
Update user_docs/en/changes.t2t
EdKweon Feb 1, 2024
0a35e9c
Apply suggestions from code review
seanbudd Feb 1, 2024
633e900
Merge branch 'master' of https://github.com/nvaccess/nvda
EdKweon Feb 2, 2024
5f42d7a
Merge branch 'master' of https://github.com/EdKweon/nvda
EdKweon Feb 29, 2024
d5ebb31
Merge branch 'master' of https://github.com/nvaccess/nvda
EdKweon Mar 8, 2024
5ae0e35
Merge branch 'master' of https://github.com/nvaccess/nvda
EdKweon Mar 15, 2024
1dade92
Merge branch 'master' of https://github.com/nvaccess/nvda
EdKweon Mar 15, 2024
92f8d7e
fix serial connection error
EdKweon Mar 22, 2024
d2cbe88
Merge branch 'nvaccess:master' into FIX_HIMS_BRAILLE_OPTION
EdKweon Mar 28, 2024
ab80c9b
delete unneccessary line
EdKweon Mar 29, 2024
fc444f7
fix Lint error
EdKweon Mar 29, 2024
f953b28
Apply suggestions from code review
seanbudd Apr 15, 2024
cd36c04
Merge branch 'master' of https://github.com/nvaccess/nvda into FIX_HI…
EdKweon May 3, 2024
05377c5
Merge branch 'FIX_HIMS_BRAILLE_OPTION' of https://github.com/EdKweon/…
EdKweon May 3, 2024
a8f360d
Loew HID priority for specific devices
EdKweon May 3, 2024
536954e
fix lint error
EdKweon May 3, 2024
084ac4e
Update source/bdDetect.py
EdKweon May 10, 2024
ed3629a
Update source/bdDetect.py
EdKweon May 10, 2024
0295ce8
Update source/brailleDisplayDrivers/hims.py
EdKweon May 10, 2024
f8534ed
split parameter
EdKweon May 10, 2024
69a6f24
Update source/bdDetect.py
EdKweon May 10, 2024
6a3038b
Update source/bdDetect.py
EdKweon May 10, 2024
73f9f17
update parameter docs
EdKweon May 10, 2024
6c05691
add return type
EdKweon May 10, 2024
f8a1c1d
Merge branch 'FIX_HIMS_BRAILLE_OPTION' of https://github.com/EdKweon/…
EdKweon May 10, 2024
5c379eb
change class name, add member variable type
EdKweon May 10, 2024
5ea3031
fix infinite iteration
EdKweon May 10, 2024
7cf2b57
rollback
EdKweon May 10, 2024
8040fb7
fix list growing error
EdKweon May 10, 2024
b475b6f
rollback fallback
EdKweon May 20, 2024
1900c71
delete empty line
EdKweon May 20, 2024
e4241ad
rollback match order hims.py
EdKweon May 20, 2024
5b4c5b2
rollback match order hims.py v2
EdKweon May 20, 2024
06502ce
roll back fallback code
EdKweon May 20, 2024
12351bf
fix parameter type
EdKweon May 20, 2024
1109352
resolve comment
EdKweon May 20, 2024
07d007a
add fallback
EdKweon May 20, 2024
b2d4714
fix lint
EdKweon May 20, 2024
cf0ed60
fix override function name
EdKweon May 20, 2024
c4409ab
fix lint
EdKweon May 20, 2024
0c6eb29
fix settingsDialog portsList error
EdKweon May 20, 2024
1e0985e
Update source/bdDetect.py
EdKweon May 20, 2024
26dc8ab
Update source/bdDetect.py
EdKweon May 20, 2024
040493e
Merge branch 'FIX_HIMS_BRAILLE_OPTION' of https://github.com/EdKweon/…
EdKweon May 21, 2024
3e37873
rollback DeviceMatch class
EdKweon May 21, 2024
4d91c25
associate driver with fallback
EdKweon May 21, 2024
a026142
add fallback in getDriversForconnectedUsbDevices
EdKweon May 22, 2024
088e436
fix variable name
EdKweon May 22, 2024
8826001
Update source/bdDetect.py
EdKweon Jun 5, 2024
95da774
clear fallbackDevices in terminate function
EdKweon Jun 5, 2024
ce39b8b
Update source/bdDetect.py
seanbudd Jun 6, 2024
a1587a9
Update source/bdDetect.py
EdKweon Jun 7, 2024
bfa7af9
split serial accumulate data
EdKweon Jun 7, 2024
84af07e
change registerAutomaticDetection function structure
EdKweon Jun 18, 2024
2d88d8d
delete redundant brace(set)
EdKweon Jun 18, 2024
e09c5d1
add type hint
EdKweon Jun 18, 2024
3346248
resolve feedback
EdKweon Jun 18, 2024
6877379
change [import typing] to [from typing]
EdKweon Jun 18, 2024
9e17619
make f-string
EdKweon Jun 18, 2024
be24784
specify exception
EdKweon Jun 18, 2024
4f28277
add log in unmatched portType
EdKweon Jun 18, 2024
76b5f96
rollback userGuide
EdKweon Jun 18, 2024
8079058
Update source/bdDetect.py
seanbudd Jun 19, 2024
aca155c
Apply suggestions from code review
seanbudd Jun 19, 2024
92bbd9b
Update source/brailleDisplayDrivers/hims.py
EdKweon Jun 19, 2024
c8134e5
Update source/brailleDisplayDrivers/hims.py
EdKweon Jun 19, 2024
04ce4c8
change variable name
EdKweon Jun 19, 2024
dbff32f
add comment
EdKweon Jun 19, 2024
55ab288
add docstring FallbackDevicesStore class
EdKweon Jun 19, 2024
5ac7305
modify docstring of FallbackDevicesStore
EdKweon Jun 19, 2024
9dc15a1
remove redundant brace
EdKweon Jun 25, 2024
f1aa7bb
simplify the return statement
EdKweon Jun 25, 2024
3dd5f70
change fallbackDevices to module variable
EdKweon Jun 25, 2024
11c5dbc
fixups
seanbudd Jun 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 46 additions & 5 deletions source/bdDetect.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ class DeviceMatch(NamedTuple):

_driverDevices = OrderedDict[str, DriverDictT]()

fallBackDevices: set[tuple[str, DeviceType, str]] = set()
"""
Used to store fallback devices.
When registered as a fallback device, it will be yielded last among the connected USB devices.
"""

scanForDevices = extensionPoints.Chain[Tuple[str, DeviceMatch]]()
"""
A Chain that can be iterated to scan for devices.
Expand Down Expand Up @@ -153,13 +159,18 @@ def getDriversForConnectedUsbDevices(
for port in deviceInfoFetcher.hidDevices
if port["provider"] == "usb"
))

fallbackDriversAndMatches: list[set[str, DeviceMatch]] = []
for match in itertools.chain(usbCustomDeviceMatches, usbHidDeviceMatchesForCustom, usbComDeviceMatches):
for driver, devs in _driverDevices.items():
if limitToDevices and driver not in limitToDevices:
continue
for type, ids in devs.items():
if match.type == type and match.id in ids:
yield driver, match
if (driver, match.type, match.id) in fallBackDevices:
fallbackDriversAndMatches.append({driver, match})
else:
yield driver, match

hidName = _getStandardHidDriverName()
if limitToDevices and hidName not in limitToDevices:
Expand All @@ -169,7 +180,13 @@ def getDriversForConnectedUsbDevices(
# This ensures that a vendor specific driver is preferred over the braille HID protocol.
# This preference may change in the future.
if _isHIDBrailleMatch(match):
yield (hidName, match)
if (driver, match.type, match.id) in fallBackDevices:
fallbackDriversAndMatches.append({hidName, match})
else:
yield hidName, match

for driver, match in fallbackDriversAndMatches:
yield driver, match


def _getStandardHidDriverName() -> str:
Expand Down Expand Up @@ -446,6 +463,8 @@ def terminate(self):
appModuleHandler.post_appSwitch.unregister(self.pollBluetoothDevices)
messageWindow.pre_handleWindowMessage.unregister(self.handleWindowMessage)
self._stopBgScan()
# Clear the fallback devices
fallBackDevices.clear()
# Clear the cache of bluetooth devices so new devices can be picked up with a new instance.
deviceInfoFetcher.btDevsCache = None
self._executor.shutdown(wait=False)
Expand All @@ -471,15 +490,27 @@ def getConnectedUsbDevicesForDriver(driver: str) -> Iterator[DeviceMatch]:
for port in deviceInfoFetcher.usbComPorts
)
)

fallbackMatches: list[DeviceMatch] = []

for match in usbDevs:
if driver == _getStandardHidDriverName():
if _isHIDBrailleMatch(match):
yield match
if (driver, match.type, match.id) in fallBackDevices:
fallbackMatches.append(match)
else:
yield match
else:
devs = _driverDevices[driver]
for type, ids in devs.items():
if match.type == type and match.id in ids:
yield match
if (driver, match.type, match.id) in fallBackDevices:
fallbackMatches.append(match)
else:
yield match

for match in fallbackMatches:
yield match


def getPossibleBluetoothDevicesForDriver(driver: str) -> Iterator[DeviceMatch]:
Expand Down Expand Up @@ -605,11 +636,18 @@ def _getDriverDict(self) -> DriverDictT:
ret = _driverDevices[self._driver] = DriverDictT(set)
return ret

def addUsbDevices(self, type: DeviceType, ids: Set[str]):
def addUsbDevices(self, type: DeviceType, ids: set[str], useAsFallBack: bool = False):
"""Associate USB devices with the driver on this instance.
:param type: The type of the driver.
:param ids: A set of USB IDs in the form C{"VID_xxxx&PID_XXXX"}.
Note that alphabetical characters in hexadecimal numbers should be uppercase.
:param useAsFallBack: A boolean flag to determine how USB devices are associated with the driver.

If False (default), the devices are added directly to the primary driver list for the specified type,
meaning they are immediately available for use with the driver.
If True, the devices are added to a fallback list and are used only if the primary driver cannot use
the initial devices, serving as a backup option in case of compatibility issues.
This provides flexibility and robustness in managing driver-device connections.
:raise ValueError: When one of the provided IDs is malformed.
"""
malformedIds = [id for id in ids if not isinstance(id, str) or not USB_ID_REGEX.match(id)]
Expand All @@ -618,6 +656,9 @@ def addUsbDevices(self, type: DeviceType, ids: Set[str]):
f"Invalid IDs provided for driver {self._driver!r}, type {type!r}: "
f"{', '.join(malformedIds)}"
)
if useAsFallBack:
fallBackDevices.update((self._driver, type, id) for id in ids)

devs = self._getDriverDict()
driverUsb = devs[type]
driverUsb.update(ids)
Expand Down
110 changes: 73 additions & 37 deletions source/brailleDisplayDrivers/hims.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Copyright (C) 2010-2023 Gianluca Casalino, NV Access Limited, Babbage B.V., Leonard de Ruijter,
# Bram Duvigneau

from typing import List
from typing import List, Iterator

import serial
from io import BytesIO
Expand Down Expand Up @@ -247,22 +247,31 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver):

@classmethod
def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar):
# Hid device
driverRegistrar.addUsbDevices(bdDetect.DeviceType.HID, {
"VID_045E&PID_940A", # Braille Edge3S 40
})

# Bulk devices
driverRegistrar.addUsbDevices(bdDetect.DeviceType.CUSTOM, {
"VID_045E&PID_930A", # Braille Sense & Smart Beetle
"VID_045E&PID_930B", # Braille EDGE 40
})
deviceTypes = {
bdDetect.DeviceType.HID: (
{
"VID_045E&PID_940A" # Braille Edge3S 40
},
True
),
bdDetect.DeviceType.CUSTOM: (
{
"VID_045E&PID_930A", # Braille Sense & Smart Beetle
"VID_045E&PID_930B" # Braille EDGE 40
},
False
),
bdDetect.DeviceType.SERIAL: (
{
"VID_0403&PID_6001",
"VID_1A86&PID_55D3" # Braille Edge2S 40
},
False
)
}

# Sync Braille, serial device
driverRegistrar.addUsbDevices(bdDetect.DeviceType.SERIAL, {
"VID_0403&PID_6001",
"VID_1A86&PID_55D3", # Braille Edge2S 40
})
for deviceType, (ids, useAsFallback) in deviceTypes.items():
driverRegistrar.addUsbDevices(deviceType, ids, useAsFallback)

driverRegistrar.addBluetoothDevices(lambda m: any(m.id.startswith(prefix) for prefix in (
"BrailleSense",
Expand All @@ -271,13 +280,14 @@ def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar):
)))

@classmethod
def getManualPorts(cls):
return braille.getSerialPorts(filterFunc=lambda info: "bluetoothName" in info)
def getManualPorts(cls) -> Iterator[tuple[str, str]]:
return braille.getSerialPorts()

def __init__(self, port="auto"):
super(BrailleDisplayDriver, self).__init__()
self.numCells = 0
self._model = None
self._serialData = b''

for match in self._getTryPorts(port):
portType, portId, port, portInfo = match
Expand All @@ -291,7 +301,7 @@ def __init__(self, port="auto"):
case bdDetect.DeviceType.CUSTOM:
# onReceiveSize based on max packet size according to USB endpoint information.
self._dev = hwIo.Bulk(port, 0, 1, self._onReceive, onReceiveSize=64)
case _:
case bdDetect.DeviceType.SERIAL:
self._dev = hwIo.Serial(
port,
baudrate=BAUD_RATE,
Expand All @@ -300,6 +310,8 @@ def __init__(self, port="auto"):
writeTimeout=self.timeout,
onReceive=self._onReceive
)
case _:
log.error(f"No matching case for portType found: {portType}")
except EnvironmentError:
log.debugWarning("", exc_info=True)
continue
Expand Down Expand Up @@ -489,37 +501,61 @@ def _hidOnReceive(self, data: bytes):

def _onReceive(self, data: bytes):
if self.isBulk:
# data contains the entire packet.
stream = BytesIO(data)
firstByte:bytes = data[0:1]
firstByte: bytes = data[0:1]
stream.seek(1)
else:
firstByte = data
# data only contained the first byte. Read the rest from the device.
stream = self._dev
if firstByte == b"\x1c":

# sometimes serial data is received in fragments.
# so accumulate data until it reaches 10 bytes.
if not self._accumulateSerialData(data):
return

if firstByte == b"\xfa":
self._processSerialData(firstByte, stream)
elif firstByte == b"\x1c":
# A device is identifying itself
deviceId: bytes = stream.read(2)
# When a device identifies itself, the packets ends with 0x1f
assert stream.read(1) == b"\x1f"
self._handleIdentification(deviceId)
elif firstByte == b"\xfa":
# Command packets are ten bytes long
packet = firstByte + stream.read(9)
assert packet[2] == 0x01 # Fixed value
CHECKSUM_INDEX = 8
checksum: int = packet[CHECKSUM_INDEX]
assert packet[9] == 0xfb # Command End
calcCheckSum: int = 0xff & sum(
c for index, c in enumerate(packet) if(
index != CHECKSUM_INDEX)
)
assert(calcCheckSum == checksum)
self._handlePacket(packet)
else:
log.debug("Unknown first byte received: 0x%x"%ord(firstByte))
log.debug(f"Unknown first byte received: 0x{ord(firstByte):x}")
return

def _accumulateSerialData(self, data: bytes) -> bool:
if self._serialData:
self._serialData += data
return len(self._serialData) == 10

return True

def _processSerialData(self, firstByte: bytes, stream):
# serial data first received
if not self._serialData:
try:
# Command packets are ten bytes long
packet = firstByte + stream.read(9)
except IOError:
# remaining data will be received next onReceive
self._serialData = firstByte
return
else:
packet = self._serialData
self._serialData = b""

assert packet[2] == 0x01 # Fixed value
CHECKSUM_INDEX = 8
checksum: int = packet[CHECKSUM_INDEX]
assert packet[9] == 0xfb # Command End
calcCheckSum: int = 0xff & sum(
c for index, c in enumerate(packet) if index != CHECKSUM_INDEX
)
assert calcCheckSum == checksum
self._handlePacket(packet)

def _sendPacket(
self,
packetType: bytes,
Expand Down