Skip to content

fix(cpn):board hardware definition not loaded for firmware objects with empty hwdefnId - #7744

Draft
pfeerick wants to merge 2 commits into
EdgeTX:mainfrom
pfeerick:fix/companion-firmware-hwdefn-fallback
Draft

fix(cpn):board hardware definition not loaded for firmware objects with empty hwdefnId#7744
pfeerick wants to merge 2 commits into
EdgeTX:mainfrom
pfeerick:fix/companion-firmware-hwdefn-fallback

Conversation

@pfeerick

@pfeerick pfeerick commented Sep 2, 2026

Copy link
Copy Markdown
Member

Note

Might be fixed in #7623

Summary

Companion silently drops model input sources (expoData/mixData srcRaw, e.g. Thr/Ail/Ele/Rud) to SOURCE_TYPE_NONE when loading a model YAML, for any profile whose firmware type resolves to a "bare" (non-variant) Firmware object - most commonly a profile whose fwType was never explicitly saved via Preferences and so defaulted to Firmware::getDefaultVariant().

Root cause

Firmware::Firmware() registers the board's hardware definition like this:

gBoardFactories->registerBoard(board, hwdefnId);

registerOpenTxFirmwares() constructs every base firmware object with an empty hwdefnId - it relies on Firmware::getFlavour() as a fallback, the same way Firmware::getHwDefnId() already does:

const QString getHwDefnId() { return getFirmwareBase()->hwdefnId.isEmpty() ? getFlavour() : getFirmwareBase()->hwdefnId; }

But the constructor's direct call to registerBoard() bypassed that fallback and passed the raw, empty field. BoardFactory::loadFile() bails out immediately when the hwdefn name is empty:

if (hwdefn.isEmpty()) return false;

...with no warning. That board's BoardJson data (sticks, switches, trims, etc.) never loads, so every capability query for it returns 0/empty for the lifetime of the app.

A "variant" Firmware object (created via the (id, parent) constructor, e.g. for language variants such as edgetx-tx16s-en) resolves hwdefnId through parent->getHwDefnId(), which does apply the fallback, so it registers correctly. Since AppPreferencesDialog::getFirmwareVariant() always appends a language suffix, any profile saved fresh through Preferences ends up with a decorated id and never hits the bug.

However, companion.cpp defaults a profile with an empty fwType() straight to the bare id:

if (profile.fwType().isEmpty()) {
  profile.fwType(Firmware::getDefaultVariant()->getId());   // e.g. "edgetx-tx16s", no language suffix
}

Any profile that ends up with that bare id resolves to the broken Firmware object. With no stick data loaded, Boards::getInputYamlIndex("Thr", ...) etc. fail to resolve, and YamlRawSourceDecode() collapses the source to SOURCE_TYPE_NONE - silently dropping the input source on every model load for that profile.

Regression origin

This is a regression from #4406 ("Support JSON radio hardware definitions"), which introduced registerBoard()/JSON-based hardware definitions and made an empty hwdefnId significant for the first time. Before that PR, board capability came from hardcoded C++ tables and the firmware object's hwdefnId field was never used to look anything up, so this code path was inert. The defect has been present, unchanged, in both the 2.12 branch and main since #4406 merged.

Steps to reproduce

  1. Build/run a Companion whose commit includes refactor(cpn): Support JSON radio hardware definitions #4406 (any recent release or main qualifies).
  2. Create a new profile (Settings → Radio Profiles → Add Radio Profile), but cancel out of / skip the Preferences dialog it opens rather than clicking OK - or, simpler: manually edit the profile's stored fwType value in Companion's settings back to a bare id (e.g. edgetx-tx16s, with no trailing -<lang> suffix), simulating a profile whose fwType was never explicitly saved via Preferences.
  3. Restart Companion with that profile selected. On startup, companion.cpp sees fwType() is empty/unset in some cases and defaults it to Firmware::getDefaultVariant()->getId() - a bare id - if it wasn't already bare from step 2.
  4. Open (or create) a model that sets stick sources on an input, e.g. YAML containing:
    expoData:
      - srcRaw: Thr
        ...
  5. Observe that after loading, expoData[i].srcRaw decodes to SOURCE_TYPE_NONE instead of the expected stick source - visible in the Inputs/Mixer UI as the source field being blank, and confirmed by inspecting the decoded RawSource (type == SOURCE_TYPE_NONE).
  6. Compare against a profile whose fwType carries a language-decorated id (e.g. edgetx-tx16s-en) saved via Preferences → General → Firmware - the same model loads correctly, because that path already applies the getFlavour() fallback via getHwDefnId().

Fix

Apply the same getFlavour() fallback in the Firmware constructor before calling registerBoard(), so a "bare" firmware object registers its hardware definition correctly regardless of which code path constructed it:

gBoardFactories->registerBoard(board, this->hwdefnId.isEmpty() ? getFlavour() : this->hwdefnId);

This mirrors existing logic already used by getHwDefnId(). Verified that every current call site passes an empty hwdefnId for base board registration (only downloadId is ever overridden, for the 4 boards needing alternate download filenames), so this fallback is exercised universally and consistently for all boards, and that the later "variant" registration for the same board always resolves to the identical hwdefn string (idempotent no-op via BoardFactories::registerBoard's existing equality check) - so there's no risk of the duplicate-registration conflict path being triggered by this change.

Test plan

  • Confirm a profile with a bare fwType (e.g. edgetx-tx16s) now correctly decodes stick-based RawSource values (Thr/Ail/Ele/Rud) on model load.
  • Confirm a profile with a decorated fwType (e.g. edgetx-tx16s-en) is unaffected (unchanged behavior).
  • Confirm Preferences board list, firmware download, and simulator launch still work for a representative sample of boards.

🤖 Generated with Claude Code

…registration

Firmware::Firmware() passed hwdefnId to gBoardFactories->registerBoard()
as-is, without the getFlavour() fallback that Firmware::getHwDefnId()
already applies. Since registerOpenTxFirmwares() constructs every base
firmware object without an explicit hwdefnId, BoardJson::loadFile()
bailed out immediately (hwdefn.isEmpty()) and that board's hardware
definition (sticks, switches, trims, etc.) never loaded.

Any profile whose stored fwType() resolves to one of these "bare"
firmware objects - e.g. a profile whose fwType was never explicitly
set via Preferences and so defaulted to Firmware::getDefaultVariant()
in companion.cpp - hits this broken object. With no stick data loaded,
Boards::getInputYamlIndex() fails to resolve names like "Thr"/"Ail",
and YAML source decoding collapses them to SOURCE_TYPE_NONE, silently
dropping expoData/mixData srcRaw sources on model load.

This is a regression from EdgeTX#4406, which introduced JSON-based hardware
definitions and made an empty hwdefnId significant for the first time;
before that, board capability came from hardcoded tables and hwdefnId
was unused. It affects both the 2.12 branch and main identically.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@pfeerick pfeerick added bug/regression ↩️ A new version of EdgeTX broke something companion (cpn) Related to the companion software labels Sep 2, 2026
@pfeerick
pfeerick marked this pull request as draft September 2, 2026 02:52
registerOpenTxFirmwares() now actually needs the embedded hwdefs.qrc
resource (previously every board's hwdefnId was empty, so
BoardJson::loadFile() bailed out before ever touching it). That
resource is bundled into the "common" static library, so per Qt's
resource-in-static-lib rules it must be force-initialized with
Q_INIT_RESOURCE(hwdefs) - companion.cpp and simulator.cpp already do
this at startup, but the gtest entry point never did, since it never
needed to.

Without it, gtests-companion crashes: the first board loaded via
registerOpenTxFirmwares() can't find its resource, BoardJson::loadFile()
calls QMessageBox::critical(), and that aborts under the QCoreApplication
(no QApplication) the test harness constructs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@pfeerick pfeerick changed the title fix(companion): regression - board hardware definition not loaded for firmware objects with empty hwdefnId fix(cpn):board hardware definition not loaded for firmware objects with empty hwdefnId Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug/regression ↩️ A new version of EdgeTX broke something companion (cpn) Related to the companion software

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant