From 493731e4e4fca553bd89ec216c7ba2c4c34ee3a5 Mon Sep 17 00:00:00 2001 From: Peter Feerick Date: Mon, 31 Aug 2026 11:15:17 +0000 Subject: [PATCH 1/3] fix(radio): stop antennaMode aliasing subType on boards without an external antenna #7113 made the ModuleData bit layout conditional on EXTERNAL_ANTENNA, taking antennaMode's two bits out of subType. The generated YAML descriptors are shared between boards that differ on that flag - tx16s, t16, t18, v16 include the x10 table, and t8, t12, tlite, lr3pro, commando8 include the xlite one - so those boards describe an antennaMode field they do not compile, overlaying the top two bits of their live subType. They emit a phantom antennaMode derived from subType, and writing one back corrupts it. The same change also narrowed subType from 4 bits to 2 on x10, x10express, x12s, xlite, xlites and v12, where MPM sub-protocols above 3 then truncate. Make antennaMode unconditional so every board compiles the layout its table describes, borrowing the bits from type (18 of 64 values used) since check_yaml_funcs() pins the header to 4 bytes, and restore subType to 4 bits. Co-Authored-By: Claude Opus 5 --- radio/src/datastructs_private.h | 14 ++++++-------- radio/src/storage/yaml/yaml_datastructs_128x64.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_c14.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_f16.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_gx12.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_gx15.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_nb4p.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_nv14.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_pa01.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_pl18.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_pl18u.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_st16.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_t15.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_t15pro.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_t20.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_t22.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_tpro.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_tx15.cpp | 3 ++- .../src/storage/yaml/yaml_datastructs_tx16smk3.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_v12.cpp | 6 +++--- radio/src/storage/yaml/yaml_datastructs_x10.cpp | 6 +++--- radio/src/storage/yaml/yaml_datastructs_x9d.cpp | 3 ++- .../src/storage/yaml/yaml_datastructs_x9dp2019.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_x9e.cpp | 3 ++- radio/src/storage/yaml/yaml_datastructs_xlite.cpp | 6 +++--- radio/src/storage/yaml/yaml_datastructs_xlites.cpp | 6 +++--- 26 files changed, 60 insertions(+), 41 deletions(-) diff --git a/radio/src/datastructs_private.h b/radio/src/datastructs_private.h index fc4e2c828d4..85fce1e7e80 100644 --- a/radio/src/datastructs_private.h +++ b/radio/src/datastructs_private.h @@ -284,7 +284,7 @@ PACK(struct TimerData { uint32_t minuteBeep:1; uint32_t persistent:2; int32_t countdownStart:2; - uint8_t showElapsed:1; + uint8_t showElapsed:1; uint8_t extraHaptic:1; uint8_t spare:6 SKIP; NOBACKUP(char name[LEN_TIMER_NAME]); @@ -499,17 +499,15 @@ PACK(struct PpmModule { }); PACK(struct ModuleData { - uint8_t type ENUM(ModuleType) CUST(r_moduleType, w_moduleType); + // antennaMode stays unconditional as boards differing on EXTERNAL_ANTENNA share + // generated YAML descriptors. + uint8_t type:6 ENUM(ModuleType) CUST(r_moduleType, w_moduleType); + int8_t antennaMode:2 ENUM(AntennaModes); CUST_ATTR(subType,r_modSubtype,w_modSubtype); uint8_t channelsStart; int8_t channelsCount CUST(r_channelsCount,w_channelsCount); // 0=8 channels uint8_t failsafeMode:4 ENUM(FailsafeModes); // only 3 bits used - #if defined(EXTERNAL_ANTENNA) - uint8_t subType:2 SKIP; - int8_t antennaMode:2 ENUM(AntennaModes); - #else uint8_t subType:4 SKIP; - #endif union { uint8_t raw[PXX2_MAX_RECEIVERS_PER_MODULE * PXX2_LEN_RX_NAME + 1]; @@ -888,7 +886,7 @@ PACK(struct ModelData { NOBACKUP(uint8_t usbJoystickIfMode:3 ENUM(USBJoystickIfMode)); NOBACKUP(uint8_t usbJoystickCircularCut:4); NOBACKUP(USBJoystickChData usbJoystickCh[USBJ_MAX_JOYSTICK_CHANNELS]); - + // Radio level tabs control (model settings) #if defined(COLORLCD) uint8_t radioThemesDisabled:2 ENUM(ModelOverridableEnable); diff --git a/radio/src/storage/yaml/yaml_datastructs_128x64.cpp b/radio/src/storage/yaml/yaml_datastructs_128x64.cpp index b3bb957b709..60c9239a9b6 100644 --- a/radio/src/storage/yaml/yaml_datastructs_128x64.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_128x64.cpp @@ -655,7 +655,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_c14.cpp b/radio/src/storage/yaml/yaml_datastructs_c14.cpp index c0edf57f67b..8f254d4b506 100644 --- a/radio/src/storage/yaml/yaml_datastructs_c14.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_c14.cpp @@ -731,7 +731,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_f16.cpp b/radio/src/storage/yaml/yaml_datastructs_f16.cpp index 6ae2ad4931c..a5cacdad285 100644 --- a/radio/src/storage/yaml/yaml_datastructs_f16.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_f16.cpp @@ -732,7 +732,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_gx12.cpp b/radio/src/storage/yaml/yaml_datastructs_gx12.cpp index 073cee189ac..770e5385e16 100644 --- a/radio/src/storage/yaml/yaml_datastructs_gx12.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_gx12.cpp @@ -679,7 +679,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_gx15.cpp b/radio/src/storage/yaml/yaml_datastructs_gx15.cpp index 274ab03338c..d65ed4ff998 100644 --- a/radio/src/storage/yaml/yaml_datastructs_gx15.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_gx15.cpp @@ -756,7 +756,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_nb4p.cpp b/radio/src/storage/yaml/yaml_datastructs_nb4p.cpp index 13244fbd515..e2a9f71ccb0 100644 --- a/radio/src/storage/yaml/yaml_datastructs_nb4p.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_nb4p.cpp @@ -722,7 +722,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_nv14.cpp b/radio/src/storage/yaml/yaml_datastructs_nv14.cpp index 3183ba72784..5898226bd2c 100644 --- a/radio/src/storage/yaml/yaml_datastructs_nv14.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_nv14.cpp @@ -729,7 +729,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_pa01.cpp b/radio/src/storage/yaml/yaml_datastructs_pa01.cpp index 517ab5ddf4c..24448d1a65a 100644 --- a/radio/src/storage/yaml/yaml_datastructs_pa01.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_pa01.cpp @@ -755,7 +755,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp index 66bedbcec33..eb78350b476 100644 --- a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp @@ -729,7 +729,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_pl18u.cpp b/radio/src/storage/yaml/yaml_datastructs_pl18u.cpp index 17e0ed75c40..21caf352bef 100644 --- a/radio/src/storage/yaml/yaml_datastructs_pl18u.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_pl18u.cpp @@ -722,7 +722,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_st16.cpp b/radio/src/storage/yaml/yaml_datastructs_st16.cpp index 6d8a62570a8..ee4452565d4 100644 --- a/radio/src/storage/yaml/yaml_datastructs_st16.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_st16.cpp @@ -755,7 +755,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_t15.cpp b/radio/src/storage/yaml/yaml_datastructs_t15.cpp index bc3331313fb..0d28102e70d 100644 --- a/radio/src/storage/yaml/yaml_datastructs_t15.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_t15.cpp @@ -740,7 +740,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_t15pro.cpp b/radio/src/storage/yaml/yaml_datastructs_t15pro.cpp index e739e85febd..39846e84b9c 100644 --- a/radio/src/storage/yaml/yaml_datastructs_t15pro.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_t15pro.cpp @@ -755,7 +755,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_t20.cpp b/radio/src/storage/yaml/yaml_datastructs_t20.cpp index 7eb523e795a..b98db8de59b 100644 --- a/radio/src/storage/yaml/yaml_datastructs_t20.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_t20.cpp @@ -664,7 +664,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_t22.cpp b/radio/src/storage/yaml/yaml_datastructs_t22.cpp index e739e85febd..39846e84b9c 100644 --- a/radio/src/storage/yaml/yaml_datastructs_t22.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_t22.cpp @@ -755,7 +755,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_tpro.cpp b/radio/src/storage/yaml/yaml_datastructs_tpro.cpp index 5628d781b15..96d9ec6473a 100644 --- a/radio/src/storage/yaml/yaml_datastructs_tpro.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_tpro.cpp @@ -664,7 +664,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_tx15.cpp b/radio/src/storage/yaml/yaml_datastructs_tx15.cpp index 274ab03338c..d65ed4ff998 100644 --- a/radio/src/storage/yaml/yaml_datastructs_tx15.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_tx15.cpp @@ -756,7 +756,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_tx16smk3.cpp b/radio/src/storage/yaml/yaml_datastructs_tx16smk3.cpp index 0e16f08cea4..e972cd5042c 100644 --- a/radio/src/storage/yaml/yaml_datastructs_tx16smk3.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_tx16smk3.cpp @@ -757,7 +757,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_v12.cpp b/radio/src/storage/yaml/yaml_datastructs_v12.cpp index 56f73b7bd93..506ade98cf2 100644 --- a/radio/src/storage/yaml/yaml_datastructs_v12.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_v12.cpp @@ -750,13 +750,13 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), YAML_ENUM("failsafeMode", 4, enum_FailsafeModes, NULL), - YAML_PADDING( 2 ), - YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), + YAML_PADDING( 4 ), YAML_UNION("mod", 200, union_anonymous_4_elmts, select_mod_type), YAML_END }; diff --git a/radio/src/storage/yaml/yaml_datastructs_x10.cpp b/radio/src/storage/yaml/yaml_datastructs_x10.cpp index 31b8131cc0c..d1ba656a6ec 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x10.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x10.cpp @@ -731,13 +731,13 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), YAML_ENUM("failsafeMode", 4, enum_FailsafeModes, NULL), - YAML_PADDING( 2 ), - YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), + YAML_PADDING( 4 ), YAML_UNION("mod", 200, union_anonymous_4_elmts, select_mod_type), YAML_END }; diff --git a/radio/src/storage/yaml/yaml_datastructs_x9d.cpp b/radio/src/storage/yaml/yaml_datastructs_x9d.cpp index 3f5d786143f..6fa673905a2 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x9d.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x9d.cpp @@ -655,7 +655,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_x9dp2019.cpp b/radio/src/storage/yaml/yaml_datastructs_x9dp2019.cpp index 3f5d786143f..6fa673905a2 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x9dp2019.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x9dp2019.cpp @@ -655,7 +655,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_x9e.cpp b/radio/src/storage/yaml/yaml_datastructs_x9e.cpp index 774550d85a4..b29b5c3a70d 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x9e.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x9e.cpp @@ -655,7 +655,8 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), diff --git a/radio/src/storage/yaml/yaml_datastructs_xlite.cpp b/radio/src/storage/yaml/yaml_datastructs_xlite.cpp index c86d9498d2e..60c9239a9b6 100644 --- a/radio/src/storage/yaml/yaml_datastructs_xlite.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_xlite.cpp @@ -655,13 +655,13 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), YAML_ENUM("failsafeMode", 4, enum_FailsafeModes, NULL), - YAML_PADDING( 2 ), - YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), + YAML_PADDING( 4 ), YAML_UNION("mod", 200, union_anonymous_4_elmts, select_mod_type), YAML_END }; diff --git a/radio/src/storage/yaml/yaml_datastructs_xlites.cpp b/radio/src/storage/yaml/yaml_datastructs_xlites.cpp index 1351124a697..4e95395f303 100644 --- a/radio/src/storage/yaml/yaml_datastructs_xlites.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_xlites.cpp @@ -660,13 +660,13 @@ static const struct YamlNode union_anonymous_4_elmts[] = { }; static const struct YamlNode struct_ModuleData[] = { YAML_IDX, - YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_UNSIGNED_CUST( "type", 6, r_moduleType, w_moduleType ), + YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), YAML_UNSIGNED( "channelsStart", 8 ), YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), YAML_ENUM("failsafeMode", 4, enum_FailsafeModes, NULL), - YAML_PADDING( 2 ), - YAML_ENUM("antennaMode", 2, enum_AntennaModes, NULL), + YAML_PADDING( 4 ), YAML_UNION("mod", 200, union_anonymous_4_elmts, select_mod_type), YAML_END }; From 09aa4474984c9bac70d4e5d8a03f07b50414bd2e Mon Sep 17 00:00:00 2001 From: Peter Feerick Date: Mon, 31 Aug 2026 11:15:17 +0000 Subject: [PATCH 2/3] fix(cpn): do not read or write moduleData antennaMode on boards without one Firmware released before antennaMode was made unconditional emitted the field on boards with no external antenna, where it aliased the top two bits of subType. Companion hides the UI for those boards but still round-tripped the value, and writes antennaMode after subType, so firmware applied it second and it overwrote subType's high bits - changing an MPM sub-protocol in Companion could silently change it again on load. Gate encode, decode and the legacy pxx migration on HasExternalAntenna, matching the predicate the model setup panel already uses. Co-Authored-By: Claude Opus 5 --- companion/src/firmwares/edgetx/yaml_moduledata.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/companion/src/firmwares/edgetx/yaml_moduledata.cpp b/companion/src/firmwares/edgetx/yaml_moduledata.cpp index 2fcbdd7c789..225199dd352 100644 --- a/companion/src/firmwares/edgetx/yaml_moduledata.cpp +++ b/companion/src/firmwares/edgetx/yaml_moduledata.cpp @@ -145,6 +145,13 @@ static const YamlLookupTable moduleAntennaModeLut = { { GeneralSettings::ANTENNA_MODE_EXTERNAL, "MODE_EXTERNAL" }, }; +// Firmware built before the field was made unconditional emitted antennaMode on +// boards without an external antenna, where it aliased the top bits of subType. +static bool hasModuleAntennaMode() +{ + return Boards::getCapability(getCurrentBoard(), Board::HasExternalAntenna); +} + static int exportPpmDelay(int delay) { return (delay - 300) / 50; } static int importPpmDelay(int delay) { return 300 + 50 * delay; } @@ -204,7 +211,7 @@ Node convert::encode(const ModuleData& rhs) node["channelsStart"] = rhs.channelsStart; node["channelsCount"] = rhs.channelsCount; node["failsafeMode"] = LookupValue(failsafeLut, rhs.failsafeMode); - if (rhs.antennaMode) + if (rhs.antennaMode && hasModuleAntennaMode()) node["antennaMode"] = LookupValue(moduleAntennaModeLut, rhs.antennaMode); Node mod; @@ -370,7 +377,7 @@ bool convert::decode(const Node& node, ModuleData& rhs) node["channelsStart"] >> rhs.channelsStart; node["channelsCount"] >> rhs.channelsCount; node["failsafeMode"] >> failsafeLut >> rhs.failsafeMode; - if (node["antennaMode"]) + if (node["antennaMode"] && hasModuleAntennaMode()) node["antennaMode"] >> moduleAntennaModeLut >> rhs.antennaMode; if (node["mod"]) { @@ -397,7 +404,7 @@ bool convert::decode(const Node& node, ModuleData& rhs) // pxx["receiverTelemetryOff"] >> rhs.pxx.receiverTelemetryOff; // pxx["receiverHigherChannels"] >> rhs.pxx.receiverHigherChannels; // Migration: legacy raw-int antennaMode, only if not already set - if (!node["antennaMode"] && pxx["antennaMode"]) { + if (!node["antennaMode"] && pxx["antennaMode"] && hasModuleAntennaMode()) { pxx["antennaMode"] >> rhs.antennaMode; } } else if (mod["sbus"]) { From 9bc42987b4da12abde700957227bbf59d4e0eb47 Mon Sep 17 00:00:00 2001 From: Peter Feerick Date: Mon, 31 Aug 2026 11:39:39 +0000 Subject: [PATCH 3/3] test(cpn): cover moduleData antennaMode decode and board gating Guards the two Companion-side failures: a module carrying a string antennaMode must decode at all, and the key must not be written back for a board with no external antenna, where the radio parses it over subType's top bits. The fixture already selects tx16s, which is one of the affected boards. The decode case works on a ModuleData node rather than a whole model - a throw out of the ModelData decode is not catchable here and aborts the binary instead of failing. Co-Authored-By: Claude Opus 5 --- .../src/tests/model_yaml_roundtrip_test.cpp | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/companion/src/tests/model_yaml_roundtrip_test.cpp b/companion/src/tests/model_yaml_roundtrip_test.cpp index 594fedcff0a..87234b8a5fc 100644 --- a/companion/src/tests/model_yaml_roundtrip_test.cpp +++ b/companion/src/tests/model_yaml_roundtrip_test.cpp @@ -39,6 +39,7 @@ #include "firmwares/eeprominterface.h" #include "firmwares/edgetx/edgetxinterface.h" +#include "firmwares/edgetx/yaml_moduledata.h" namespace { @@ -124,3 +125,54 @@ TEST_F(ModelYamlRoundTrip, OverlongNameTruncatesAndRoundTrips) ModelData m3 = roundTrip(m2, y2); EXPECT_EQ(m2.name.str(), m3.name.str()); } + +// A module written by a radio that emitted antennaMode must decode. The field is +// a string enum firmware side; read as a raw int, yaml-cpp throws +// TypedBadConversion and the whole model fails to open. +TEST_F(ModelYamlRoundTrip, RadioWrittenAntennaModeDecodes) +{ + YAML::Node node = YAML::Load( + "type: TYPE_MULTIMODULE\n" + "subType: 6,7\n" + "channelsStart: 0\n" + "channelsCount: 16\n" + "failsafeMode: NOT_SET\n" + "antennaMode: MODE_PER_MODEL\n"); + + ModuleData md; + bool threw = false; + std::string err; + try { + node >> md; + } catch (const std::exception& e) { + threw = true; + err = e.what(); + } catch (...) { + threw = true; + err = "non-std exception"; + } + + ASSERT_FALSE(threw) << "decoding threw: " << err; + EXPECT_EQ(md.subType, 7u) << "subType lost alongside antennaMode"; +} + +// The fixture selects tx16s, which has no external antenna. Such radios parse +// antennaMode over the top two bits of subType, so writing the key back would +// change the sub-protocol on load. +TEST_F(ModelYamlRoundTrip, AntennaModeNotWrittenWithoutExternalAntenna) +{ + ASSERT_FALSE(Boards::getCapability(getCurrentBoard(), Board::HasExternalAntenna)) + << "fixture board is expected to have no external antenna"; + + ModelData m; + m.clear(); + m.used = true; + // only modules with a protocol are serialised at all + m.moduleData[1].protocol = PULSES_MULTIMODULE; + m.moduleData[1].antennaMode = GeneralSettings::ANTENNA_MODE_EXTERNAL; + + QByteArray y; + writeModelToYaml(m, y); + ASSERT_TRUE(y.contains("moduleData")) << "module was not serialised"; + EXPECT_FALSE(y.contains("antennaMode")); +}