feat(hid): recognize aether-pad RC-28 emulator (Arduino VID/PID alias)#3171
Conversation
Adds the Arduino composite VID/PID 0x2341:0x0266 to HidDeviceParser's supported-devices table, routing it to IcomRC28Parser. This lets the aether-pad reference implementation (github.com/nigelfenton/aether-pad) plug in as a hardware-in-the-loop test bench for the RC-28 wire protocol — most notably PR aethersdr#2870's report-layout fix, which was validated end-to-end against this bench on 2026-05-20. Why an alias rather than spoofing Icom's VID/PID: The Giga R1's mbed PluggableUSB stack hardcodes the device-level VID/PID at the core level, so the emulator firmware can't claim 0x0C26:0x001E even if it wanted to (and we wouldn't want to anyway — driver INF pain on Windows + the obvious ethical issue). Routing the Arduino-default composite VID/PID to the same parser is the clean way in. The wire protocol is identical to the real RC-28 — same 32-byte report layout, same seq/dir/button encoding documented above IcomRC28Parser. The alias just gives community hardware a supported route into the existing parser. Test plan: - Builds clean (incremental, 698/698 targets). - Binary launches + stays alive 6 s, closes cleanly. - Live RC-28 wire validation already on file from the 2026-05-20 bench session (with the alias on a feature branch); see aethersdr-20260520-220037.log in that bench's evidence trail. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Thanks Nigel — this is a clean, well-scoped change.
What I checked:
- Scope matches description. Two entries in
kSupportedDevicesand a matching factory branch inHidDeviceParser::create(). Existing devices route identically (the new branch is below all the originals); no behavior change for FlexControl, Griffin, or Contour paths. - Parser reuse is sound.
IcomRC28Parser::parsedoes its own length/dispatch handling (thelen >= 33Windows/Linux vs macOS branch and thelen < 5early return at HidDeviceParser.cpp:41), so any device misclaimed as RC-28 would safely produce no events rather than crash. Length-based report-ID detection means the emulator just needs to match the wire layout, which the PR body confirms. - No RAII / null-pointer / resource concerns —
std::make_uniquematches the surrounding pattern. - Out-of-scope work correctly held back. The FlexControl persona path is appropriately deferred to #3111; this PR is HID-only.
The comment block is verbose but it captures the why (Arduino VID/PID being hardcoded by mbed PluggableUSB, and the deliberate decision not to spoof FlexRadio identifiers), which is the right call for a community-hardware alias that future maintainers will need to understand.
Nothing blocking from me. Nice work on the upstream PR after carrying the local patch — and good documentation in the body of the bench setup and the ethical reasoning around VID/PID spoofing.
🤖 aethersdr-agent · cost: $3.0577 · model: claude-opus-4-7
|
Claude here — really nice work on this, Nigel. Solid PR shape: pattern-consistent with the existing 4 entries, well-commented, hardware-validated against #2870, and a thoughtful explanation of why you're aliasing rather than spoofing Icom's VID/PID. One safety addition before we land it, since the collision risk is more concrete than I'd want to ship: The concern: Proposed fix — match on a USB string descriptor too: We'd extend struct HidDeviceId {
uint16_t vid;
uint16_t pid;
const char* name;
const wchar_t* productStringMatch = nullptr; // nullptr = match VID/PID only
};Then in HidEncoderManager::detectDevice() the iteration would check the auto* info = hid_enumerate(devices[i].vid, devices[i].pid);
for (auto* cur = info; cur; cur = cur->next) {
if (devices[i].productStringMatch &&
wcscmp(cur->product_string, devices[i].productStringMatch) != 0)
continue; // VID/PID hit but product string doesn't match — skip
// ...match...
}Existing four entries (real RC-28, PowerMate, ShuttleXpress, ShuttlePro v2) leave {0x2341, 0x0266, "AetherPad RC-28 emulator", L"AetherPad RC-28"},Firmware side — set the iProduct string: On the Giga's mbed PluggableUSB stack, override const uint8_t* AetherPadUSB::stringIproductDesc() {
static const uint8_t desc[] = {
2 + 2*15, // bLength: 2 header + 15 chars × 2 bytes
STRING_DESCRIPTOR,
'A',0,'e',0,'t',0,'h',0,'e',0,'r',0,'P',0,'a',0,'d',0,' ',0,
'R',0,'C',0,'-',0,'2',0,'8',0
};
return desc;
}(Or whatever the equivalent shape is in your mbed version — you know that stack better than I do.) Happy to do the AetherSDR-side patch myself if you want to focus on the firmware string-descriptor update — easier split than having you flip back and forth between repos. Just let me know the exact iProduct string you end up shipping and we'll hardcode that on the AE side, then merge both PRs in lockstep. 73, Jeremy KK7GWY & Claude (AI dev partner) |
jensenpat
left a comment
There was a problem hiding this comment.
Thanks Nigel, dedicated VID/PID is the right approach for a new class of devices.
…urator (#3238) First-platform delivery of #3232 (cross-platform BT-HID encoder support). Linux side complete; Windows/macOS land in follow-up PRs. Backend (EvdevEncoderManager, Linux-only): - Scans /dev/input/event* for "Ulanzi Dial Keyboard" sub-device - Opens with EVIOCGRAB to claim it exclusively so the dial's media-key emissions (KEY_PREVIOUSSONG/NEXTSONG/PLAYPAUSE/MUTE/Ctrl+chords) don't leak to the focused window - Event-driven via QSocketNotifier; no polling - Chord assembly state machine decodes Ctrl+key + compound chord events - Hot-plug via QFileSystemWatcher on /dev/input/ UI (UlanziDialMapperDialog, fixed 640x640): - Product image of the dial in the centre, action-combo "pills" positioned around it at each physical control - Each pill is a QComboBox listing every registered ShortcutManager action + every discrete-type MIDI param (shortcut:<id> / midi:<id> tagged so MainWindow dispatcher can route correctly) - Signature-to-pill mapping is firmware-property (hardcoded in kPillSpecs); the function assigned to each pill is user-configurable via the combo and persists to AppSettings under UlanziDial/action/<pillId> MainWindow integration: - Instantiates the manager on the existing ExtControllers thread - Rotary tuneSteps coalesces into the active slice tune step (same pattern as HidEncoderManager from #3171) - buttonEvent dispatcher parses the prefix and invokes the matching ShortcutManager handler or MIDI param setter; falls back to the per-pill defaultAction so first-launch bindings work without the user ever opening the mapper Files: - src/core/EvdevEncoderManager.{h,cpp} - new, Linux-only - src/gui/UlanziDialMapperDialog.{h,cpp} - new, Linux-only - resources/images/ulanzi-dial.png - product image - src/gui/MainWindow.{h,cpp} - wiring + menu entry - CMakeLists.txt - gate new sources on UNIX AND NOT APPLE Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
aethersdr#3171) ## Summary Adds the Arduino composite VID/PID `0x2341:0x0266` to `HidDeviceParser`'s supported-devices table, routing it to `IcomRC28Parser`. Twelve lines, no behaviour change for any existing device. ## Motivation The aether-pad reference implementation ([github.com/nigelfenton/aether-pad](https://github.com/nigelfenton/aether-pad)) is a small Arduino Giga R1-based RC-28 emulator I've been using as a hardware-in-the-loop test bench for the RC-28 wire protocol. End-to-end validation against PR aethersdr#2870's report-layout fix was done with this bench on 2026-05-20 (parse-side DIAG trace + frequency display tracking each detent). The bench has been carrying this alias as a local-only patch since then. Filing it upstream so the bench works against unmodified `main` going forward, and so other folks who want to build community RC-28-compatible hardware on Arduino have a path in. ## Why an alias rather than spoofing Icom's VID/PID The Giga R1's mbed PluggableUSB stack hardcodes the device-level VID/PID at the core level — the emulator firmware can't claim `0x0C26:0x001E` even if it wanted to. And spoofing FlexRadio-class hardware identifiers in community firmware is something I'd rather not do for the obvious driver-INF / ethical reasons. Routing the Arduino-default composite VID/PID to the same parser is the clean way in. The wire protocol is identical to the real RC-28 — same 32-byte report layout, same seq/dir/button encoding documented above `IcomRC28Parser`. The alias just gives community hardware a supported route into the existing parser. ## Test plan - [x] Compiles clean (incremental Windows build, 698/698 targets) - [x] Binary launches + stays alive, closes cleanly - [x] Live wire validation already on file from the 2026-05-20 bench (with this exact patch on a feature branch); aether-pad firmware on [nigelfenton/aether-pad@feat/rc28-test-mode](https://github.com/nigelfenton/aether-pad/tree/feat/rc28-test-mode) drives the protocol bytes ## Out of scope The aether-pad also has an AetherControl-style FlexControl-protocol persona in flight — that needs separate plumbing on the AE side (`FlexControlManager::detectPort()`'s VID/PID filter is the blocker there). I'll file that as its own PR once the firmware-side persona work lands. This PR is just the HID alias. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…urator (aethersdr#3238) First-platform delivery of aethersdr#3232 (cross-platform BT-HID encoder support). Linux side complete; Windows/macOS land in follow-up PRs. Backend (EvdevEncoderManager, Linux-only): - Scans /dev/input/event* for "Ulanzi Dial Keyboard" sub-device - Opens with EVIOCGRAB to claim it exclusively so the dial's media-key emissions (KEY_PREVIOUSSONG/NEXTSONG/PLAYPAUSE/MUTE/Ctrl+chords) don't leak to the focused window - Event-driven via QSocketNotifier; no polling - Chord assembly state machine decodes Ctrl+key + compound chord events - Hot-plug via QFileSystemWatcher on /dev/input/ UI (UlanziDialMapperDialog, fixed 640x640): - Product image of the dial in the centre, action-combo "pills" positioned around it at each physical control - Each pill is a QComboBox listing every registered ShortcutManager action + every discrete-type MIDI param (shortcut:<id> / midi:<id> tagged so MainWindow dispatcher can route correctly) - Signature-to-pill mapping is firmware-property (hardcoded in kPillSpecs); the function assigned to each pill is user-configurable via the combo and persists to AppSettings under UlanziDial/action/<pillId> MainWindow integration: - Instantiates the manager on the existing ExtControllers thread - Rotary tuneSteps coalesces into the active slice tune step (same pattern as HidEncoderManager from aethersdr#3171) - buttonEvent dispatcher parses the prefix and invokes the matching ShortcutManager handler or MIDI param setter; falls back to the per-pill defaultAction so first-launch bindings work without the user ever opening the mapper Files: - src/core/EvdevEncoderManager.{h,cpp} - new, Linux-only - src/gui/UlanziDialMapperDialog.{h,cpp} - new, Linux-only - resources/images/ulanzi-dial.png - product image - src/gui/MainWindow.{h,cpp} - wiring + menu entry - CMakeLists.txt - gate new sources on UNIX AND NOT APPLE Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Adds the Arduino composite VID/PID
0x2341:0x0266toHidDeviceParser's supported-devices table, routing it toIcomRC28Parser. Twelve lines, no behaviour change for any existing device.Motivation
The aether-pad reference implementation (github.com/nigelfenton/aether-pad) is a small Arduino Giga R1-based RC-28 emulator I've been using as a hardware-in-the-loop test bench for the RC-28 wire protocol. End-to-end validation against PR #2870's report-layout fix was done with this bench on 2026-05-20 (parse-side DIAG trace + frequency display tracking each detent).
The bench has been carrying this alias as a local-only patch since then. Filing it upstream so the bench works against unmodified
maingoing forward, and so other folks who want to build community RC-28-compatible hardware on Arduino have a path in.Why an alias rather than spoofing Icom's VID/PID
The Giga R1's mbed PluggableUSB stack hardcodes the device-level VID/PID at the core level — the emulator firmware can't claim
0x0C26:0x001Eeven if it wanted to. And spoofing FlexRadio-class hardware identifiers in community firmware is something I'd rather not do for the obvious driver-INF / ethical reasons. Routing the Arduino-default composite VID/PID to the same parser is the clean way in.The wire protocol is identical to the real RC-28 — same 32-byte report layout, same seq/dir/button encoding documented above
IcomRC28Parser. The alias just gives community hardware a supported route into the existing parser.Test plan
Out of scope
The aether-pad also has an AetherControl-style FlexControl-protocol persona in flight — that needs separate plumbing on the AE side (
FlexControlManager::detectPort()'s VID/PID filter is the blocker there). I'll file that as its own PR once the firmware-side persona work lands. This PR is just the HID alias.🤖 Generated with Claude Code