> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/nvaccess/nvda/llms.txt
> Use this file to discover all available pages before exploring further.

# Braille Display Drivers

> Learn how to develop braille display drivers for NVDA

## 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

1. **Device Communication**: Uses `hwIo` module for hardware I/O operations
2. **Protocol Implementation**: Packet-based communication with displays
3. **Input Mapping**: Translates hardware inputs to NVDA gestures
4. **Cell Translation**: Converts Unicode braille to device-specific formats

## Creating a Basic Driver

### Minimal Driver Structure

Here's the simplest possible braille driver:

```python brailleDisplayDrivers/noBraille.py theme={null}
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

<ParamField path="name" type="str" required>
  Unique identifier for the driver (should match the module filename)
</ParamField>

<ParamField path="description" type="str" required>
  Human-readable name shown in NVDA's braille display settings
</ParamField>

<ParamField path="isThreadSafe" type="bool" default="False">
  Whether the driver can be used safely from background threads
</ParamField>

<ParamField path="supportsAutomaticDetection" type="bool" default="False">
  Whether the driver supports automatic USB/Bluetooth detection
</ParamField>

## Full Driver Implementation

### Freedom Scientific Example

The Freedom Scientific driver demonstrates a complete implementation:

```python theme={null}
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

```python theme={null}
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:

```python theme={null}
# 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

```python theme={null}
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:

```python theme={null}
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 = [
    0x01, 0x02, 0x04, 0x10,
    0x20, 0x40, 0x08, 0x80,
]
FOCUS_1_TRANSLATION_TABLE = _makeTranslationTable(FOCUS_1_DOTS_TABLE)
```

## Input Handling

### Key Gestures

```python theme={null}
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

```python theme={null}
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

```python theme={null}
@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:

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

<Steps>
  <Step title="Install the driver">
    Place your driver file in `source/brailleDisplayDrivers/yourdriver.py`
  </Step>

  <Step title="Restart NVDA">
    Restart NVDA or reload plugins (NVDA+Control+F3)
  </Step>

  <Step title="Select your driver">
    Open NVDA Settings > Braille and select your driver from the list
  </Step>

  <Step title="Test functionality">
    * Verify cell output displays correctly
    * Test all buttons and gestures
    * Check routing buttons work
    * Verify automatic detection (if supported)
  </Step>
</Steps>

## Best Practices

<CardGroup cols={2}>
  <Card title="Thread Safety" icon="lock">
    Mark `isThreadSafe = True` if your driver uses proper locking and can handle concurrent calls
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation">
    Always handle connection errors gracefully and clean up resources in `terminate()`
  </Card>

  <Card title="Timeout Management" icon="clock">
    Use appropriate timeouts to prevent NVDA from freezing if the device becomes unresponsive
  </Card>

  <Card title="Translation Tables" icon="language">
    Cache translation tables - don't regenerate them for every cell update
  </Card>
</CardGroup>

<Warning>
  Always test your driver with the actual hardware. Emulators may not accurately represent device behavior.
</Warning>

## 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

### Input Not Working

* 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

<Card title="Developer Guide" icon="book" href="/development/guides/developer-guide">
  See the NVDA Developer Guide for more information on plugin development
</Card>
