A Python library for interfacing with XIAO SAMD21 CW paddle adapters, supporting both Vail firmware (hardware keyer) and custom firmware (software keyer).
- Dual firmware support: Works with both Vail adapter firmware and custom firmware
- Auto-detection: Automatically detects which firmware is installed
- MIDI configuration: Control Vail firmware settings (keyer mode, speed, sidetone)
- Backend abstraction: Clean API for different keyer implementations
- Async/sync compatible: Works in both asyncio and synchronous contexts
- USB HID reading: Generic HID device interface for paddle input
cd your-project
git submodule add https://github.com/tompatulpan/vail-adapter-lib
git submodule update --init --recursiveAdd to Python path in your code:
import sys
sys.path.insert(0, 'vail-adapter-lib')
from vail_adapter_lib import PaddleManagercd vail-adapter-lib
pip install -e .from vail_adapter_lib import PaddleManager
# Configuration
config = {
'usb_hid': {
'vendor_id': 0x2886,
'product_id': 0x802f,
'keyer_mode': 'straight' # or 'iambic_a', 'iambic_b'
},
'vail_adapter': {
'enabled': True,
'keyer_mode': 8, # Iambic B
'speed_wpm': 25,
'sidetone_note': 73, # ~600 Hz
'output_mode': 'keyboard'
}
}
# Initialize paddle manager
paddle = PaddleManager(config)
if paddle.initialize():
print(f"Detected firmware: {paddle.get_firmware_type()}")
# Read paddle state
left_pressed, right_pressed = paddle.read_paddles()
print(f"Left: {left_pressed}, Right: {right_pressed}")
# Check if Python keyer logic needed
if paddle.needs_python_keyer():
print("Using software iambic keyer")
else:
print("Using hardware keyer in firmware")
paddle.close()import asyncio
from vail_adapter_lib import PaddleManager
async def main():
paddle = PaddleManager(config)
paddle.initialize()
while True:
left, right = paddle.read_paddles()
if left or right:
print(f"Paddles: L={left} R={right}")
await asyncio.sleep(0.001) # 1ms polling
asyncio.run(main())Features:
- 9 keyer modes (passthrough, straight, bug, Ultimatic, Iambic A/B, keyahead)
- Hardware-precise timing (no jitter)
- MIDI configuration
- EEPROM settings storage
- USB HID keyboard output (Left/Right Ctrl)
Download: Vail Adapter Releases
Firmware file: xiao_basic_pcb_v2.uf2 (for PCB v2 with switched TRRS jack)
Configuration:
config = {
'vail_adapter': {
'enabled': True,
'keyer_mode': 8, # 0-9 (see modes below)
'speed_wpm': 25, # 5-60 WPM
'sidetone_note': 73, # MIDI note (0-127)
'output_mode': 'keyboard'
}
}Keyer Modes:
- 0: Passthrough (no keyer)
- 1: Straight key (either paddle = key down)
- 2: Bug (auto dits on one side, manual dah on other)
- 3: Ultimatic
- 4: Iambic A
- 5: Iambic A (reversed paddles)
- 6: Iambic B
- 7: Iambic B (reversed paddles)
- 8: Iambic B (Vail style) ← Recommended
- 9: Keyahead
Features:
- Simple USB HID output (dit/dah buttons)
- Python handles iambic keyer logic
- Lower latency (no firmware processing)
- More control over timing
Source: Your custom Arduino firmware
Configuration:
config = {
'usb_hid': {
'vendor_id': 0x2886,
'product_id': 0x802f,
'keyer_mode': 'iambic_b' # or 'straight', 'iambic_a'
},
'vail_adapter': {
'enabled': False # Disable Vail firmware features
}
}High-level manager that auto-detects firmware and provides unified interface.
Methods:
__init__(config: dict)- Create manager with configurationinitialize() -> bool- Initialize and detect firmware, returns successread_paddles() -> Tuple[bool, bool]- Get (left, right) paddle stateneeds_python_keyer() -> bool- Check if software keyer neededget_firmware_type() -> str- Get "vail" or "custom"close()- Clean up resources
Methods:
initialize(config: dict) -> bool- Setup backendread_paddles() -> Tuple[bool, bool]- Read paddle stateneeds_python_keyer() -> bool- Whether Python keyer neededclose()- Cleanup
Implementations:
VailFirmwareBackend- For Vail adapter firmwareCustomFirmwareBackend- For custom firmware
MIDI configuration for Vail adapter firmware.
Methods:
find_vail_adapter() -> Optional[str]- Find MIDI device nameopen() -> bool- Open MIDI portset_keyer_mode(mode: int)- Set keyer mode (0-9)set_speed(wpm: int)- Set speed (5-60 WPM)set_sidetone_note(note: int)- Set sidetone MIDI note (0-127)set_output_mode(mode: str)- Set "keyboard" or "midi" outputconfigure_adapter(config: dict)- Apply all settingsclose()- Close MIDI port
Helper Functions:
frequency_to_midi_note(freq: float) -> int- Convert Hz to MIDI notemidi_note_to_frequency(note: int) -> float- Convert MIDI note to Hz
Software iambic keyer for custom firmware.
Methods:
__init__(wpm: int, mode: str, on_element: callable, on_spacing: callable)process_paddles(dit: bool, dah: bool)- Process paddle inputset_speed(wpm: int)- Change speed
Modes:
"iambic_a"- Iambic mode A"iambic_b"- Iambic mode B (with memory)
Generic USB HID device reader.
Methods:
__init__(vendor_id: int, product_id: int, device_path: str)find_device() -> bool- Search for deviceopen() -> bool- Open deviceread(timeout_ms: int) -> Optional[bytes]- Read dataclose()- Close device
See examples/ directory:
basic_usage.py- Simple paddle readingasync_usage.py- Asyncio integrationmidi_config.py- Configure Vail firmwaresoftware_keyer.py- Using IambicKeyer
Required:
- Python 3.9+
Optional:
mido>=1.3.0- MIDI support (for Vail firmware configuration)python-rtmidi>=1.5.0- MIDI backend (recommended)
Install optional dependencies:
pip install mido python-rtmidiWithout MIDI support, the library still works but cannot configure Vail firmware settings (uses EEPROM defaults).
MIT License - See LICENSE file
- Vail adapter firmware: ropg/vail-adapter