Skip to content
Merged
69 changes: 64 additions & 5 deletions source/brailleDisplayDrivers/handyTech.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ def windowProc(self, hwnd: int, msg: int, wParam: int, lParam: int):
MODEL_MODULAR_80 = b"\x88"
MODEL_MODULAR_40 = b"\x89"
MODEL_ACTIVATOR = b"\xA4"
MODEL_ACTIVATOR_PRO_64 = b"\xA6"
MODEL_ACTIVATOR_PRO_80 = b"\xA8"

# Key constants
KEY_B1 = 0x03
Expand Down Expand Up @@ -373,6 +375,15 @@ def display(self, cells: List[int]):
super(StatusCellMixin, self).display(cells)


class ActiveSplitMixin:
"""Mixin for displays supporting ActiveSplit, i.e. dynamic adjustment of number of cells"""

def postInit(self):
super().postInit()
log.debug("Prevent disconnect/reconnect activity for dynamic length adjustment")
self._display.sendExtendedPacket(HT_EXTPKT_NO_RECONNECT)


class ModularConnect88(TripleActionKeysMixin, Model):
deviceId = MODEL_MODULAR_CONNECT_88
genericName = "Modular Connect"
Expand Down Expand Up @@ -531,7 +542,14 @@ class Modular80(Modular):
numCells = 80


class Activator(TimeSyncFirmnessMixin, AtcMixin, JoystickMixin, TripleActionKeysMixin, Model):
class Activator(
ActiveSplitMixin,
TimeSyncFirmnessMixin,
AtcMixin,
JoystickMixin,
TripleActionKeysMixin,
Model
):
deviceId = MODEL_ACTIVATOR
numCells = 40
genericName = name = 'Activator'
Expand All @@ -545,6 +563,37 @@ def _get_keys(self) -> Dict[int, str]:
return keys


class ActivatorPro(
ActiveSplitMixin,
TimeSyncFirmnessMixin,
AtcMixin,
TripleActionKeysMixin,
Model
):
genericName = 'Activator Pro'

def _get_name(self):
return '{name} {cells}'.format(name=self.genericName, cells=self.numCells)

def _get_keys(self) -> Dict[int, str]:
keys = super().keys
keys.update({
0x7A: "escape",
0x7B: "return",
})
return keys


class ActivatorPro64(ActivatorPro):
deviceId = MODEL_ACTIVATOR_PRO_64
numCells = 64


class ActivatorPro80(ActivatorPro):
deviceId = MODEL_ACTIVATOR_PRO_80
numCells = 80


def _allSubclasses(cls):
"""List all direct and indirect subclasses of cls

Expand All @@ -569,6 +618,7 @@ def _allSubclasses(cls):
HT_PKT_NAK = b"\x7D"
HT_PKT_ACK = b"\x7E"
HT_PKT_OK = b"\xFE"
HT_PKT_OK_WITH_LENGTH = b"\xFD"
HT_PKT_RESET = b"\xFF"
HT_EXTPKT_BRAILLE = HT_PKT_BRAILLE
HT_EXTPKT_KEY = b"\x04"
Expand All @@ -589,6 +639,7 @@ def _allSubclasses(cls):
HT_EXTPKT_GET_FIRMNESS = b"\x61"
HT_EXTPKT_GET_PROTOCOL_PROPERTIES = b"\xC1"
HT_EXTPKT_GET_FIRMWARE_VERSION = b"\xC2"
HT_EXTPKT_NO_RECONNECT = b"\xAE"

# HID specific constants
HT_HID_RPT_OutData = b"\x01" # receive data from device
Expand Down Expand Up @@ -637,6 +688,8 @@ def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar):
"VID_1FE4&PID_0093", # Basic Braille Plus 32
"VID_1FE4&PID_0094", # Basic Braille Plus 40
"VID_1FE4&PID_00A4", # Activator
"VID_1FE4&PID_00A6", # Activator Pro 64
"VID_1FE4&PID_00A8", # Activator Pro 80
})

# Some older HT displays use a HID converter and an internal serial interface
Expand All @@ -656,7 +709,7 @@ def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar):
"Braillino BL",
"Braille Wave BW",
"Easy Braille EBR",
"Activator AC",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this removal intentional?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The intent is to match Activator AC4/serial, Activator Pro 64/serial and Activator Pro 80/serial with a single condition.

"Activator",
)))

@classmethod
Expand Down Expand Up @@ -712,6 +765,9 @@ def __init__(self, port="auto"):

if self.numCells:
# A display responded.
if not isinstance(self._model, OldProtocolMixin):
self.sendExtendedPacket(HT_EXTPKT_GET_PROTOCOL_PROPERTIES)
self._dev.waitForRead(self.timeout)
self._model.postInit()
log.info("Found {device} connected via {type} ({port})".format(
device=self._model.name, type=portType, port=port))
Expand Down Expand Up @@ -923,15 +979,18 @@ def _serialOnReceive(self, data: bytes):
self._handleInputStream(data, self._dev)

def _handleInputStream(self, htPacketType: bytes, stream):
if htPacketType in (HT_PKT_OK, HT_PKT_EXTENDED):
if htPacketType in (HT_PKT_OK, HT_PKT_EXTENDED, HT_PKT_OK_WITH_LENGTH):
modelId: bytes = stream.read(1)
if not self._model:
if modelId not in MODELS:
log.debugWarning("Unknown model: %r" % modelId)
raise RuntimeError(
"The model with ID %r is not supported by this driver" % modelId)
self._model = MODELS.get(modelId)(self)
self.numCells = self._model.numCells
if htPacketType == HT_PKT_OK_WITH_LENGTH:
self.numCells = ord(stream.read(1))
else:
self.numCells = self._model.numCells
elif self._model.deviceId != modelId:
# Somehow the model ID of this display changed, probably another display
# plugged in the same (already open) serial port.
Expand Down Expand Up @@ -962,7 +1021,7 @@ def _handleInputStream(self, htPacketType: bytes, stream):
# Ignore ATC packets for now
pass
elif extPacketType == HT_EXTPKT_GET_PROTOCOL_PROPERTIES:
pass
self.numCells = packet[3]
elif isinstance(self._model, TimeSyncFirmnessMixin):
if extPacketType == HT_EXTPKT_GET_RTC:
self._model.handleTime(packet[1:])
Expand Down
2 changes: 2 additions & 0 deletions user_docs/en/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Unicode CLDR has been updated.
* This can be useful when reading characters that are unknown to a particular speech synthesizer or braille table and which have a compatible alternative, like the bold and italic characters commonly used on social media.
* It also allows reading of equations in the Microsoft Word equation editor. (#4631)
* You can enable this functionality for both speech and braille in their respective settings categories in the NVDA Settings dialog.
* New Braille features:
* Added support for the Help Tech Activator Pro displays. (#16668)
* By default, after NVDA startup, you will be notified if any add-on updates are available. (#15035)
* This can be disabled in the "Add-on Store" category of settings.
* NVDA checks daily for add-on updates.
Expand Down