Overview
Braille display drivers enable NVDA to communicate with braille displays, allowing blind users to read screen content in braille. Each driver handles the specific protocol and hardware interface for a particular manufacturer’s displays.
Driver Architecture
Base Class
All braille drivers inherit from braille.BrailleDisplayDriver. This provides the core framework for:
Device detection and connection
Braille cell rendering
Input handling (keys, routing buttons, wheels)
Configuration management
Key Components
Device Communication : Uses hwIo module for hardware I/O operations
Protocol Implementation : Packet-based communication with displays
Input Mapping : Translates hardware inputs to NVDA gestures
Cell Translation : Converts Unicode braille to device-specific formats
Creating a Basic Driver
Minimal Driver Structure
Here’s the simplest possible braille driver:
brailleDisplayDrivers/noBraille.py
import braille
class BrailleDisplayDriver ( braille . BrailleDisplayDriver ):
"""A dummy braille display driver used to disable braille in NVDA."""
name = "noBraille"
# Translators: Is used to indicate that braille support will be disabled.
description = _( "No braille" )
@ classmethod
def check ( cls ):
return True
Required Attributes
Unique identifier for the driver (should match the module filename)
Human-readable name shown in NVDA’s braille display settings
Whether the driver can be used safely from background threads
supportsAutomaticDetection
Whether the driver supports automatic USB/Bluetooth detection
Full Driver Implementation
Freedom Scientific Example
The Freedom Scientific driver demonstrates a complete implementation:
import braille
import inputCore
import hwIo
from hwIo import intToByte
import serial
import bdDetect
BAUD_RATE = 57600
PARITY = serial. PARITY_NONE
MODELS = {
"Focus 14" : 14 ,
"Focus 40" : 40 ,
"Focus 80" : 80 ,
}
class BrailleDisplayDriver ( braille . BrailleDisplayDriver ):
name = "freedomScientific"
description = _( "Freedom Scientific Focus/PAC Mate series" )
isThreadSafe = True
supportsAutomaticDetection = True
receivesAckPackets = True
timeout = 0.2
@ classmethod
def registerAutomaticDetection ( cls , driverRegistrar ):
"""Register USB/Bluetooth IDs for automatic detection."""
driverRegistrar.addUsbDevices(bdDetect.DeviceType. USB , {
"VID_0F4E&PID_0100" , # Focus 1
"VID_0F4E&PID_0111" , # Focus 2
"VID_0F4E&PID_0112" , # Focus Blue
})
Device Initialization
def __init__ ( self , port = "auto" ):
super (). __init__ ()
self .numCells = 0
self ._model = None
# Connect to device
for match in self ._getAutoPorts( usb = True , bluetooth = True ):
try :
self ._dev = hwIo.Serial(
match[ "port" ],
baudrate = BAUD_RATE ,
parity = PARITY ,
timeout = self .timeout,
writeTimeout = self .timeout,
onReceive = self ._onReceive,
)
except Exception :
continue
# Query device info
self ._sendPacket( FS_PKT_QUERY )
# Wait for response with device info
for _i in range ( 3 ):
self ._dev.waitForRead( self .timeout)
if self .numCells:
break
if self .numCells:
log.info( f "Found { self ._model } with { self .numCells } cells" )
break
else :
raise RuntimeError ( "No display found" )
Protocol Implementation
Packet Structure
Most braille displays use packet-based protocols:
# Packet types
FS_PKT_QUERY = b " \x00 " # Query device info
FS_PKT_ACK = b " \x01 " # Acknowledgment
FS_PKT_KEY = b " \x03 " # Key press/release
FS_PKT_BUTTON = b " \x04 " # Routing button
FS_PKT_WHEEL = b " \x05 " # Wheel turn
FS_PKT_WRITE = b " \x81 " # Write cells
FS_PKT_EXT_KEY = b " \x82 " # Extended keys
def _sendPacket ( self , packetType , data = b "" ):
"""Send a packet to the display."""
packet = packetType + intToByte( len (data)) + data
self ._dev.write(packet)
def _onReceive ( self , data ):
"""Handle incoming data from the display."""
if not data:
return
packetType = data[ 0 : 1 ]
payload = data[ 2 :]
if packetType == FS_PKT_KEY :
self ._handleKeyPacket(payload)
elif packetType == FS_PKT_BUTTON :
self ._handleButtonPacket(payload)
elif packetType == FS_PKT_INFO :
self ._handleInfoPacket(payload)
Writing to Display
def display ( self , cells ):
"""Display braille cells on the display.
@param cells: List of cell values (0-255) in Unicode braille format
"""
# Translate cells if needed
if self ._useTranslationTable:
cells = self ._translate(cells, FOCUS_1_TRANSLATION_TABLE )
# Send to display
data = bytes (cells)
self ._sendPacket( FS_PKT_WRITE , data)
Cell Translation
Some displays use non-standard dot mappings:
def _makeTranslationTable ( dotsTable ):
"""Create a translation table for braille dot combinations.
@param dotsTable: List of 8 bitmasks for each dot (dot 1-8)
@return: Translation table with 256 entries
"""
def isoDot ( number ):
"""Returns ISO 11548-1 formatted braille dot."""
return 1 << (number - 1 )
outputTable = [ 0 ] * 256
for byte in range ( 256 ):
cell = 0
for dot in range ( 8 ):
if byte & isoDot(dot + 1 ):
cell |= dotsTable[dot]
outputTable[byte] = cell
return outputTable
# Example: Focus 1 uses different dot ordering
FOCUS_1_DOTS_TABLE = [
0x 01 , 0x 02 , 0x 04 , 0x 10 ,
0x 20 , 0x 40 , 0x 08 , 0x 80 ,
]
FOCUS_1_TRANSLATION_TABLE = _makeTranslationTable( FOCUS_1_DOTS_TABLE )
Key Gestures
from inputCore import InputGesture
class InputGesture ( InputGesture ):
source = "freedomScientific.braille"
def __init__ ( self , keys = None , routing = None ):
super (). __init__ ()
self .keys = keys
self .routingIndex = routing
self .id = self ._makeId()
def _makeId ( self ):
if self .routingIndex is not None :
return f "routing { self .routingIndex } "
elif self .keys:
return "+" .join( self .keys)
return "unknown"
def _handleKeyPacket ( self , payload ):
"""Process key press/release events."""
keys = self ._parseKeys(payload)
if keys:
gesture = InputGesture( keys = keys)
try :
inputCore.manager.executeGesture(gesture)
except inputCore.NoInputGestureAction:
pass
def _handleButtonPacket ( self , payload ):
"""Process routing button presses."""
routingIndex = payload[ 0 ]
gesture = InputGesture( routing = routingIndex)
try :
inputCore.manager.executeGesture(gesture)
except inputCore.NoInputGestureAction:
pass
Gesture Mapping
gestureMap = inputCore.GlobalGestureMap({
"globalCommands.GlobalCommands" : {
"braille_scrollBack" : ( "br(freedomScientific):leftAdvanceBar" ,),
"braille_scrollForward" : ( "br(freedomScientific):rightAdvanceBar" ,),
"braille_previousLine" : ( "br(freedomScientific):leftRockerBarUp" ,),
"braille_nextLine" : ( "br(freedomScientific):leftRockerBarDown" ,),
"braille_routeTo" : ( "br(freedomScientific):routing" ,),
},
})
Automatic Detection
USB Detection
@ classmethod
def registerAutomaticDetection ( cls , driverRegistrar : bdDetect.DriverRegistrar):
"""Register device IDs for automatic detection."""
# USB devices
driverRegistrar.addUsbDevices(bdDetect.DeviceType. USB , {
"VID_0F4E&PID_0100" , # Focus 1
"VID_0F4E&PID_0111" , # Focus 2
"VID_0F4E&PID_0112" , # Focus Blue
})
# Bluetooth devices
driverRegistrar.addBluetoothDevices( lambda m :
m.type == bdDetect.DeviceType. SERIAL and
m.id.startswith( "Bluetooth_" ) and
( "Focus" in m.deviceInfo.get( "friendlyName" , "" ))
)
Advanced Features
Status Cells
Some displays have dedicated status cells:
def display ( self , cells ):
"""Display cells with status cells support."""
# Focus 1 has 3 status cells on each side
if self .numCells in FOCUS_1_CELL_COUNTS :
# Pad with status cells
statusCells = [ 0 ] * 3 # Left status
statusCells += [ 0 ] # Separator
statusCells += cells # Main content
statusCells += [ 0 ] # Separator
statusCells += [ 0 ] * 3 # Right status
cells = statusCells
self ._sendCells(cells)
Configuration Settings
from autoSettingsUtils.driverSetting import BooleanDriverSetting
class BrailleDisplayDriver ( braille . BrailleDisplayDriver ):
# ...
supportedSettings = [
BooleanDriverSetting(
"wordWrap" ,
_( "Word wrap" ),
defaultVal = True ,
),
]
def _get_wordWrap ( self ):
return self ._wordWrap
def _set_wordWrap ( self , value ):
self ._wordWrap = value
# Update display behavior
Testing Your Driver
Debug Logging
from logHandler import log
class BrailleDisplayDriver ( braille . BrailleDisplayDriver ):
# ...
def _onReceive ( self , data ):
log.debug( f "Received { len (data) } bytes: { data.hex() } " )
# Process data...
def display ( self , cells ):
log.debug( f "Displaying { len (cells) } cells" )
# Send to display...
Manual Testing
Install the driver
Place your driver file in source/brailleDisplayDrivers/yourdriver.py
Restart NVDA
Restart NVDA or reload plugins (NVDA+Control+F3)
Select your driver
Open NVDA Settings > Braille and select your driver from the list
Test functionality
Verify cell output displays correctly
Test all buttons and gestures
Check routing buttons work
Verify automatic detection (if supported)
Best Practices
Thread Safety Mark isThreadSafe = True if your driver uses proper locking and can handle concurrent calls
Error Handling Always handle connection errors gracefully and clean up resources in terminate()
Timeout Management Use appropriate timeouts to prevent NVDA from freezing if the device becomes unresponsive
Translation Tables Cache translation tables - don’t regenerate them for every cell update
Always test your driver with the actual hardware. Emulators may not accurately represent device behavior.
Common Issues
Device Not Detected
Verify USB/Bluetooth IDs are correct
Check device drivers are installed
Ensure registerAutomaticDetection() is implemented
Test with manual port selection first
Garbled Braille Output
Check if device uses non-standard dot mapping
Verify cell translation table is correct
Ensure byte order matches device expectations
Verify packet parsing is correct
Check gesture IDs match NVDA’s expectations
Ensure InputGesture.source is set correctly
Test with NVDA input logging enabled
Reference
Key Modules
braille: Base braille display driver framework
hwIo: Hardware I/O for serial, USB, HID communication
bdDetect: Braille display automatic detection
inputCore: Input gesture handling
brailleInput: Braille keyboard input support
Example Drivers
Study these drivers in source/brailleDisplayDrivers/ for reference:
freedomScientific.py: Full-featured driver with complex protocol
alva.py: Driver with automatic detection
noBraille.py: Minimal driver structure
hidBrailleStandard.py: HID-based braille standard protocol
Developer Guide See the NVDA Developer Guide for more information on plugin development