mirror of
https://github.com/meshtastic/firmware.git
synced 2026-09-20 21:39:01 -04:00
* fix(portduino): recognise the LR2021 power ceilings in --check loadConfig() has read Lora.LR2021_MAX_POWER and Lora.LR2021_MAX_POWER_HF since LR2021 support landed, but neither was listed in the config checker's schema. --check therefore reported both as "unknown key ... ignored by meshtasticd" -- false, and actively misleading: it tells the user to delete a key that is doing exactly what they wanted. This breaks the contract stated above schema(), that a key taught to loadConfig() is added there too. CI enforces that by running --check over bin/config.d/**, but no shipped config sets either key -- or mentions lr2021 at all -- so nothing ever tripped over the omission. It could only surface for someone hand-writing an LR2021 config. LR20x0 is the only module with two power ceilings, one per band, selected at runtime by region; every other family expresses the split as separate module names and needs a single key. That is the likely reason the pair was missed while every other *_MAX_POWER key was added. Also adds both to valueSpecs(), so a wrong-typed value is reported rather than silently replaced by the default. * feat(portduino): configurable IRQ DIO and a chip-neutral RF switch table for LR20x0 Two gaps found bringing an LR2021 up under meshtasticd on a Luckfox Lyra Zero W. Both sit in the LR20x0 support added in #11252, and they interact: the switch table has to be written slightly wrong to pass validation, and the interrupt lands on a pin that table is driving. Every symptom is silent, because begin() only exercises SPI and BUSY -- the radio reports init success and then receives nothing. IRQ DIO could not be set on Portduino ------------------------------------- LR20x0Interface picked the IRQ DIO purely at compile time, and neither LR2021_IRQ_DIO_NUM nor IRQ_DIO_NUM exists for a Portduino target, so meshtasticd always fell through to RadioLib's default of DIO5 -- which is also the first RF switch line on carriers using the DIO5-DIO8 table. A variant says this with a #define (the pro-micro DIY board uses DIO9); a carrier has only the YAML, and had no way to say it. Adds Lora.IRQ_DIO_NUM, and an ARCH_PORTDUINO branch after the two existing #define branches, so a variant that already sets one still wins. The switch table was parsed as LR11xx-only ------------------------------------------ Pin names resolved to RADIOLIB_LR11X0_DIOn whatever the radio, and the mode set was the LR11xx's, so MODE_RX_HF -- a mode the LR20x0 really has -- was rejected as an unknown key and had to be omitted. The two families are not interchangeable: an LR11xx has no DIO9, so its fifth switch slot is DIO10, while an LR20x0's fifth slot is DIO9 and DIO10 is its sixth. A table naming DIO10 was therefore driving the wrong pin on an LR20x0. The YAML layer now stores what was written -- a DIO number and a neutral mode id -- and each interface supplies its own DIO constants and OpMode_t map to a shared builder. Neither family's constants are assumed to coincide with the other's. This also fixes a round trip in the config writer, which decoded pins by comparing against RADIOLIB_LR11X0_* and always emitted five values per mode row: for a four-pin table it produced YAML that --check would reject for mismatched row lengths. --check ------- Findings are now judged against the resolved module rather than a fixed list, so a mode or pin the part does have can no longer be rejected, and one it does not have is named instead of silently accepted. The claim that the table "is only applied to LR11xx radios" was stale and is corrected, and the missing-table warning now covers both families. "auto" is excluded throughout: the module has not been probed yet, so absence cannot be judged. The IRQ/switch-pin collision is reported in both directions, including the harder case where no key is set and the radio default collides -- nothing in the file looks wrong. Note that listing a pin is what breaks it, not driving it: setRfSwitchTable() reassigns the DIO function for every pin in the list whatever the levels say, so an all-LOW column is still a collision. Seven fixtures cover these, including a false-positive guard: DIO5 as the interrupt is normal, and must stay silent when the table is elsewhere. * feat(portduino): let the YAML ask for a TCXO probe, across every family that has one A variant declares "a TCXO may or may not be fitted" at compile time with TCXO_OPTIONAL, because the board is known when the image is built. A Portduino carrier cannot: the same meshtasticd binary runs on hardware populated either way, so the statement has to arrive as YAML and be answered at runtime. Adds Lora.TCXO_OPTIONAL, and TCXO_OPTIONAL_ENABLED in RadioLibInterface.h to unify the two, so each driver asks the question once rather than growing a second, Portduino-shaped code path. On an embedded target it stays a compile-time constant, so `if (TCXO_OPTIONAL_ENABLED)` folds away exactly as the old `#if` did: the nrf52_promicro_diy_tcxo image, which defines TCXO_OPTIONAL and so exercises the converted branches, still ends at 0xDF1D0 -- the same address as before this change. It is defined there rather than in a header of its own because InterfacesTemplates.cpp includes all three interface .cpp files into one translation unit, where a per-file definition would collide. Covers every family that has a TCXO reference to probe for: SX126x (sx1262/sx1268/LLCC68), LR11xx and LR20x0. With no DIO3_TCXO_VOLTAGE given, the TCXO attempt uses RadioLib's own 1.6 V default rather than being skipped -- otherwise there is nothing to fall back FROM and the flag would silently do nothing. This is also the FIXME that sat on the Portduino branch in LR20x0Interface: an unset voltage now means "no TCXO" explicitly. Two things are deliberately left alone: Each family keeps its own probe order. LR11xx tries XTAL first, because a TCXO-first attempt hangs RadioLib's unbounded calibration wait on a module with no TCXO fitted, whereas XTAL fails fast and cleanly on a module that has one; LR20x0 and SX126x try the TCXO first. A carrier therefore behaves the same way in a Portduino build as in an embedded one, and changing an order stays a hardware-behaviour decision rather than a tidying-up one. The SX126x retry is Portduino-only. An embedded TCXO_OPTIONAL board already gets this from initLoRa(), which constructs a second SX126x interface with no Vref when the first fails; retrying inside init() as well would leave that ladder step unreachable and change how every existing t-echo-class board reports its oscillator. A Portduino build has no ladder to fall through, because the module is named in YAML rather than probed. --check learns the key, reports which Vref will actually be tried, and warns when it is set on a radio with no TCXO reference, where it is read, stored and inert. * docs(portduino): condense the comments on this branch The repo asks for one or two lines and no multi-paragraph blocks, on the grounds that the diff and the commit message carry the rationale while the code carries the behaviour. What landed here was well past that: 163 added comment lines, including a 26-line block above a single macro. Removes the rhetoric, the issue numbers and the before-and-after asides, and the notes on where a thing used to live. No added block is longer than three lines now. Two facts needed stating and are stated once each rather than repeated at every use: the slot/DIO divergence between the families, in PortduinoGlue.h, and the per-family TCXO probe order, in RadioLibInterface.h. The longest surviving explanation is why an all-LOW switch column still collides with the interrupt, which sits in the fixtures README because without it that pair of fixtures reads as contradictory. Comments only; no functional change. * address CodeRabbit review on #11382 - SX126xInterface: distinguish an explicit DIO3_TCXO_VOLTAGE from the TCXO_OPTIONAL probing default in the debug log instead of always claiming the config field was set. - ConfigCheck: modesFor() now reports an unresolved use_autoconf against the union of both radio families' modes, not the LR11xx subset - fixes a false "not a mode this part has" warning for valid LR2021-only modes (e.g. RFSW_RX_HF) before autodetection resolves the module. - PortduinoGlue loadConfig: build rfswitch_mode_high[m] as a fresh per-row bitmask instead of OR-accumulating onto a stale value, so a config re-parse can clear a slot back to LOW. - PortduinoGlue YAML serialization: gate rfswitch_table emission on has_rfswitch_table rather than rfswitch_dio_num[0] >= 0 (missed sparse pin lists), and track each emitted pin's original slot so row values line up correctly instead of shifting when a low slot is absent. - config-dist.yaml: document the per-family TCXO/XTAL probe order (SX126x/LR20x0 TCXO-first, LR11x0 XTAL-first). - Trim three overlong comments per the coding-guideline nitpicks. Left the SX126x XTAL-retry-on-oscillator-failure nitpick alone - RadioLib's begin() already does its own XOSC_START_ERR recovery internally, and narrowing our wrapper's retry condition on top of that needs hardware to verify it doesn't regress a real failure path. Verified: bin/test-config-check.sh GREEN 69/69 against an isolated native build; pio test -e native -f test_rtc PASSED. * fix CI: cppcheck duplicateValueTernary, harden kRfSwitchModes init LR11x0Interface::init(): work around cppcheck's duplicateValueTernary on `TCXO_OPTIONAL_ENABLED ? 0 : tcxoVoltage` (both branches fold to 0 on a board with no ARCH_PORTDUINO, no TCXO_OPTIONAL, and no explicit Vref, since tcxoVoltage already reduces to 0 via the same macro chain) by splitting it into a plain assignment + if, rather than suppressing the warning. The other TCXO_OPTIONAL_ENABLED ternaries in this PR (LR11x0Interface.cpp:75, SX126xInterface.cpp:76, LR20x0Interface.cpp: 86,240) pick between TCXO_OPTIONAL_DEFAULT_VOLTAGE (1.6f) and 0, which can never coincide, so they're unaffected and left as-is. ConfigCheck.cpp: kRfSwitchModes was a namespace-scope global with dynamic initialization (a lambda IIFE) reading kRfSwitchModeNames, which is defined in a different translation unit (PortduinoGlue.cpp). Currently safe only because kRfSwitchModeNames's initializer is constant-expression-only (string literals + enum constants), which the standard guarantees completes before any TU's dynamic initializers - but that safety is silent and would break if PortduinoGlue.cpp's array initializer ever stopped being a constant expression, with nothing to warn a future editor. Converted to a function-local static (Meyers' singleton), which is correct by construction regardless of the other TU's initializer, updating all 4 call sites (definition + 3 uses) from kRfSwitchModes to kRfSwitchModes(). Verified: pio test -e native -f test_radio PASSED; bin/test-config- check.sh GREEN 69/69 against an isolated native build. * refactor: simplify TCXO voltage handling across interfaces and improve comments * fix rfswitch_table cross-file merge; drop now-stale checker warning Three CodeRabbit findings on09e0d390c, addressed together since #2 and #3 are the same root cause: 1. PortduinoGlue.cpp: require an exact "DIO<n>" match when parsing rfswitch_table.pins. sscanf's %d stops at the first non-digit, so "DIO5invalid" silently parsed as DIO5 at runtime even though ConfigCheck.cpp's static validator (exact match against kRfSwitchPins) already rejected it - checker and loader disagreed. 2. PortduinoGlue.cpp: reset all 5 pin slots and all 8 mode rows before applying a table, rather than only overwriting what the new table mentions. A later config.d file that omitted a mode a prior file had set (e.g. only redefining MODE_TX) let the earlier file's MODE_RX leak through, contradicting "last file wins" - the rule every other Lora: key already follows. 3. ConfigCheck.cpp: with #2 fixed, rfswitch_table behaves like any other cross-file key, so removed the special-cased ERROR in checkCrossFileOverlap ("These do NOT override each other... OR of every table") - it described the pre-fix OR-accumulation bug and is no longer accurate. Falls through to the generic "last file wins" INFO now. Renamed/repurposed the rfswitch-sticky fixture to rfswitch-last-wins and updated its assertion (was rc=1 asserting the old error text, now rc=0 asserting the generic info) and the fixtures README. Verified: bin/test-config-check.sh GREEN 69/69 against an isolated native build, including the renamed assertion. * Assert the effective rfswitch table, not just the overlap diagnostic The "last one wins" case checked that the cross-file info fires and that the result is clean. Neither observes the table the loader actually ended up with, so the merge bugee9b5b81efixed - a later table leaving an earlier file's pins and mode rows as carryover - would still have passed it. Raised by CodeRabbit. Asserting the value needs two things the existing case cannot supply. The winner has to be deterministic. Both files in rfswitch-last-wins/ sit in config.d/, which is walked with a bare directory_iterator and no sort, so which one lands last is up to the filesystem - the point configd-conflict/ exists to make, and the reason the checker warns rather than assuming alphabetical order. rfswitch-replace/ puts the losing table in config.yaml instead, which is always loaded before config.d/. The loser is the wider of the two, four pins and three all-HIGH mode rows against the winner's two pins and one all-LOW row, so carryover shows up as a surviving pin, a surviving mode row, or a HIGH that should be LOW. The table has to be observable. The check report says no more than "RF switch table : set", and check-yaml cannot help: it is --check --output-yaml, and --check wins and exits before the dump - which the case just below it asserts. emit_yaml() does serialise the effective table, so the assert helper grows a yaml mode that passes --output-yaml alone. Confirmed non-vacuous: with the reset loop in loadConfig() removed, the new assertion fails and the old one still passes. Config-check suite GREEN 70/70. Native suite GREEN 44/44, 968 cases. * Say nothing about rows the radio will never read Two of the RF-switch diagnostics judged a table against a family's mode list without first asking whether the module reads a table at all. modesFor() treated every module that was not an LR20x0 as LR11xx-like, so an sx1262 carrying a table was told which of its rows were "not a mode sx1262 has" and which modes it had omitted - alongside the correct warning that the whole table is inert. pinsFor() already returned an empty set for these parts and its caller already guarded on that; the mode path now matches. The missing-mode advice is dropped under "auto" as well. The module has not been probed, so the union of both families is all there is to compare against, and naming its absent modes would advise adding MODE_TX_HP, MODE_GNSS and MODE_WIFI rows to what may turn out to be an LR20x0. Fixtures for both silences, and an assert() needle prefixed with '!' to hold them: a line that is merely absent today is otherwise nobody's regression. Also corrects the kLr11x0SwitchDios/kLr20x0SwitchDios comments. They describe a slot mapping, but buildRfSwitchTable() searches them by value to find the parallel pin constant - and with 7 DIOs against 5 YAML pin slots, the LR20x0 array could not be positional. * Warn when the two TCXO keys ask for opposite things DIO3_TCXO_VOLTAGE written out as false or 0 asks for DIO3 to be left alone, and stores identically to the key being absent - so TCXO_OPTIONAL then probes DIO3 at the radio default anyway. Both keys behave exactly as documented; only together are they wrong, which is what makes the outcome surprising. loadConfig() now keeps the distinction that the store loses, and --check reports the contradiction and which key to drop. The flag is diagnostic only and is not serialized: an explicit false and an absent key both round-trip as absent, as they did before. Also fixes the Portduino TCXO log lines, which named the variant define SX126X_DIO3_TCXO_VOLTAGE on a path where the knob is the YAML key, and adds a TODO over the SX126x XTAL retry. RadioLib has autocorrected that case itself since 7.5.0 - SX126x::modSetup() retries config() on the XTAL when begin() fails with SPI_CMD_FAILED and XOSC_START_ERR - so the ordinary case never reaches our retry and what does is mostly invalid settings, logged as a TCXO fault. * feat(portduino): bound Lora.IRQ_DIO_NUM, accept the older spelling, document both An LR20x0 raises its interrupt on DIO5 through DIO11. Anything else was read straight out of the YAML and programmed into RadioLib, where it routes the IRQ nowhere: begin() touches only SPI and BUSY, so the radio reports init success and then never receives a packet - the same failure the switch-pin collision check already covers, reached by a typo instead. Refuse it in loadConfig() and again in the driver before it reaches RadioLib, warning both times. Because loadConfig() discards the value, the merged config cannot tell a rejected number from an absent key, so --check judges the range in its per-file pass where the offending line is still known. LR2021_IRQ_DIO_NUM, the spelling carried by the two earlier LR2021 branches, is read when IRQ_DIO_NUM is absent and reported as shadowed when it is not. Both keys, the DIO range and the collision that makes the setting matter are now described in config-dist.yaml. * fix(portduino): a rejected IRQ_DIO_NUM returns to the radio default loadConfig() runs once per file - the main config, then each file in config.d/ - and they all write the same portduino_config. An out-of-range Lora.IRQ_DIO_NUM warned that it was falling back to the radio default but left any valid value an earlier file had set, so LR20x0Interface went on programming that stale DIO. Reset it to -1, the unset sentinel every other reader already tests for. Pinned by a new fixture: DIO9 in the main config, out of range in config.d/. The main config is always read first, so the ordering is deterministic, unlike two files in config.d/ (see rfswitch-last-wins). The assertion requires the summary to name the radio default and not DIO9; with the reset removed and rebuilt, it is the only assertion that fails.
222 lines
17 KiB
Markdown
222 lines
17 KiB
Markdown
# Portduino config fixtures
|
|
|
|
Input files for `bin/test-config-check.sh`, which drives a built `meshtasticd`
|
|
binary and asserts what `meshtasticd --check` reports about each one. Every file
|
|
here is referenced by name from that script, so renaming one means editing it too.
|
|
|
|
The theme is configuration that is accepted by the YAML parser but does not mean
|
|
what it looks like it means - the failures that otherwise only show up as a radio
|
|
that never transmits.
|
|
|
|
Each file carries a comment header naming its planted fault and what the checker is
|
|
expected to say about it, so a fixture can be read on its own. One assertion,
|
|
`malformed-indent.yaml`'s reported line number, counts those header lines - editing
|
|
that file's comments means updating the expected line in the script.
|
|
|
|
These files are exempt from `trunk fmt` (see `.trunk/trunk.yaml`): prettier rejects
|
|
the duplicate keys and bad indentation that are the entire point of them.
|
|
|
|
## Valid, one per radio module family
|
|
|
|
These must each report zero errors and zero warnings, and must resolve to the
|
|
module named in the file. A silent fallback to `sim` would otherwise pass.
|
|
|
|
| File | Module | Family |
|
|
| -------------------- | -------- | ---------- |
|
|
| `module-rf95.yaml` | `RF95` | SX127x |
|
|
| `module-sx1262.yaml` | `sx1262` | SX126x |
|
|
| `module-sx1268.yaml` | `sx1268` | SX126x |
|
|
| `module-llcc68.yaml` | `LLCC68` | SX126x |
|
|
| `module-sx1280.yaml` | `sx1280` | SX128x |
|
|
| `module-lr1110.yaml` | `lr1110` | LR11xx |
|
|
| `module-lr1120.yaml` | `lr1120` | LR11xx |
|
|
| `module-lr1121.yaml` | `lr1121` | LR11xx |
|
|
| `module-sim.yaml` | `sim` | simulated |
|
|
| `module-auto.yaml` | `auto` | autodetect |
|
|
|
|
`valid.yaml` is a minimal SX126x config, and `empty-sections.yaml` (`Lora:` with
|
|
no body) exists to prove the checker does not invent a finding for it.
|
|
|
|
## Module naming
|
|
|
|
Names are matched exactly and are not consistently cased - `RF95` and `LLCC68`
|
|
are upper, `sx1262` and `lr1121` lower.
|
|
|
|
| File | Expected |
|
|
| ------------------------ | --------------------------------------------------- |
|
|
| `module-unknown.yaml` | `sx1263` is refused, and the valid set is listed. |
|
|
| `module-wrong-case.yaml` | `llcc68` is refused with a "did you mean" for case. |
|
|
|
|
## LR11xx rfswitch table
|
|
|
|
| File | Expected |
|
|
| ------------------------------ | ----------------------------------------------------------------- |
|
|
| `rfswitch-valid.yaml` | A full seven-mode table on an `lr1121` is clean. |
|
|
| `rfswitch-partial.yaml` | Legal, but the omitted modes are named - they are driven all-LOW. |
|
|
| `rfswitch-bad-pin.yaml` | `DIO9` is a real pin name but not a switch pin on an `lr1121`. |
|
|
| `rfswitch-row-length.yaml` | Rows shorter and longer than the declared pin count. |
|
|
| `rfswitch-bad-level.yaml` | `high` and `On`: anything not exactly `HIGH` is silently LOW. |
|
|
| `rfswitch-no-pins.yaml` | No `pins` list, so no switch pin is ever driven. |
|
|
| `rfswitch-too-many-pins.yaml` | Six pins declared; only the first five are read. |
|
|
| `rfswitch-not-a-map.yaml` | `rfswitch_table` given a scalar. |
|
|
| `rfswitch-unknown-mode.yaml` | `MODE_TRANSMIT` is not a mode. |
|
|
| `rfswitch-stranded-modes.yaml` | A `MODE_` row one level out, sitting under `Lora:` doing nothing. |
|
|
| `rfswitch-auto-partial.yaml` | **Silence guard** - under `auto` the omitted modes are not named. |
|
|
| `rfswitch-inert-table.yaml` | **Silence guard** - an sx1262's rows are not judged individually. |
|
|
|
|
`rfswitch-partial.yaml` is legal but noted: the omitted modes are driven all-LOW.
|
|
`module-mismatch-lr11xx.yaml` (LR11xx with no table - cannot transmit) and
|
|
`module-mismatch-sx126x.yaml` (a table on a radio that never applies one) cover
|
|
the module/table disagreement in both directions.
|
|
|
|
Both silence guards exist because a mode list is only meaningful once the radio is known.
|
|
Under `Module: auto` the part has not been probed, so naming the omitted modes against the
|
|
union of both families would advise adding `MODE_TX_HP`, `MODE_GNSS` and `MODE_WIFI` rows to
|
|
what may turn out to be an LR20x0. On a module that applies no table at all, singling out one
|
|
row as ignored would imply the others are used, when the single `module-mismatch-sx126x.yaml`
|
|
warning already says the whole table is inert.
|
|
|
|
## LR20x0 rfswitch table and interrupt DIO
|
|
|
|
The table is handed to an LR20x0 as well as an LR11xx, and the two parts have neither the
|
|
same modes nor the same switch pins, so which findings are correct depends on the module.
|
|
|
|
| File | Expected |
|
|
| --------------------------------------- | ------------------------------------------------------------------------------- |
|
|
| `rfswitch-lr2021.yaml` | Clean. `MODE_RX_HF` is a real mode here, and `IRQ_DIO_NUM` keeps the IRQ clear. |
|
|
| `rfswitch-lr2021-irq-collision.yaml` | `IRQ_DIO_NUM: 5` names a pin the table also drives as a switch line. |
|
|
| `rfswitch-lr2021-irq-default.yaml` | The same collision reached by omitting the key: the radio default is DIO5. |
|
|
| `rfswitch-lr2021-irq-clear.yaml` | **False-positive guard** - DIO5 as the IRQ, table on DIO6/7/8, is clean. |
|
|
| `rfswitch-lr2021-irq-all-low.yaml` | DIO5 listed in `pins` but driven LOW everywhere: still a collision. |
|
|
| `rfswitch-lr2021-irq-out-of-range.yaml` | `IRQ_DIO_NUM: 3` is outside DIO5-DIO11, so it is discarded and DIO5 is used. |
|
|
| `rfswitch-lr2021-irq-alias.yaml` | `LR2021_IRQ_DIO_NUM` alongside `IRQ_DIO_NUM`: the older spelling does nothing. |
|
|
| `rfswitch-lr2021-no-table.yaml` | An LR20x0 with no table cannot transmit, same as an LR11xx without one. |
|
|
| `rfswitch-lr2021-wrong-mode.yaml` | `MODE_TX_HP` and `MODE_GNSS` are LR11xx modes an LR20x0 does not have. |
|
|
|
|
`begin()` needs only SPI and BUSY, so a radio whose interrupt lands on a switch pin still
|
|
reports init success and then never receives a packet.
|
|
|
|
An out-of-range `IRQ_DIO_NUM` is refused twice, in `loadConfig()` and again in the driver before
|
|
it reaches RadioLib, so the checker judges it per file: by the time the merged config is built a
|
|
rejected value and an absent key look the same.
|
|
|
|
Why `-irq-all-low` is a fault and `-irq-clear` is not: `LR2021::config()` (from `begin()`)
|
|
points the IRQ DIO at `FUNCTION_IRQ`, then `setRfSwitchTable()` calls `setDioFunction(...,
|
|
FUNCTION_RF_SWITCH)` for every non-NC pin in the list whatever the levels are, and nothing
|
|
re-asserts the IRQ function afterwards. Listing the pin is what breaks it, not driving it.
|
|
|
|
`rfswitch-lr2021.yaml` also carries `LR2021_MAX_POWER` and `LR2021_MAX_POWER_HF` in a case
|
|
that must stay at zero warnings, so dropping either from the checker's schema fails it.
|
|
|
|
## TCXO probing (`Lora.TCXO_OPTIONAL`)
|
|
|
|
A variant declares "a TCXO may or may not be fitted" at compile time with `TCXO_OPTIONAL`.
|
|
A Portduino carrier cannot: the same `meshtasticd` binary runs on hardware populated either
|
|
way, so the statement arrives as YAML and is answered at runtime.
|
|
|
|
| File | Expected |
|
|
| ---------------------------------- | -------------------------------------------------------------------------------------- |
|
|
| `tcxo-optional.yaml` | Clean. No Vref given, so the TCXO attempt uses the 1.6 V radio default. |
|
|
| `tcxo-optional-sx1262.yaml` | Clean. Another family, explicit Vref, which is the one reported. |
|
|
| `tcxo-optional-unsupported.yaml` | An SX128x has no TCXO reference to probe for, so the key is inert. |
|
|
| `tcxo-optional-contradiction.yaml` | `DIO3_TCXO_VOLTAGE: false` asks for DIO3 to be left alone; the probe drives it anyway. |
|
|
|
|
With the probe asked for and no voltage given, the driver tries the radio default rather
|
|
than skipping the TCXO attempt - otherwise there is nothing to fall back _from_.
|
|
|
|
## PA gain table (`TX_GAIN_LORA`)
|
|
|
|
Two shapes are accepted and they fail differently. A list is read element-by-element
|
|
with `.as<int>()` and NO fallback, so one bad entry throws and meshtasticd will not
|
|
start. A bare scalar is read as `.as<int>(0)` and merely falls back to 0. The table
|
|
is `uint16_t[22]`, so extra points are dropped and out-of-range values wrap.
|
|
|
|
| File | Expected |
|
|
| ---------------------------- | ------------------------------------------------------------------------- |
|
|
| `txgain-scalar.yaml` | **Clean, and a regression guard** - an earlier checker called this fatal. |
|
|
| `value-type-fatal-list.yaml` | A non-numeric list entry: throws, so meshtasticd will not start. |
|
|
| `txgain-out-of-range.yaml` | `-5` and `70000` wrap to a different gain than written. |
|
|
| `txgain-too-many.yaml` | 25 points; everything past the 22nd is dropped. |
|
|
|
|
## Value types, ranges and units
|
|
|
|
| File | Expected |
|
|
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `value-type-fatal.yaml` | `Logging.AsciiLogs` is the other no-fallback read: a bad value stops meshtasticd starting. |
|
|
| `value-type-silent.yaml` | Wrong-typed values where the read has a default: silently replaced, so the setting does nothing. |
|
|
| `tcxo-millivolts.yaml` | `DIO3_TCXO_VOLTAGE` is in VOLTS and multiplied by 1000, so `1800` silently asks for 1800V. Write `1.8`. |
|
|
| `port-out-of-range.yaml` | `APIPort` outside 1024-65535 is silently ignored; `Webserver.Port` has no guard at all. |
|
|
| `statusmessage-long.yaml` | Copied into a `char[80]`, so it is safe but silently shortened to 79 characters. |
|
|
| `configdir-missing.yaml` | **Crash regression guard** - an unreadable `ConfigDirectory` used to abort meshtasticd (and `--check`) with SIGABRT via an uncaught `filesystem_error`. |
|
|
|
|
## MAC address
|
|
|
|
The MAC no longer determines NodeNum - that comes from the public key - but a MAC
|
|
that fails to apply still falls through to the BlueZ and LoRa-serial fallbacks, and
|
|
if those yield nothing meshtasticd exits with "Blank MAC Address not allowed!".
|
|
|
|
| File | Expected |
|
|
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `mac-conflict.yaml` | Both `MACAddress` and `MACAddressSource`; meshtasticd refuses. |
|
|
| `mac-malformed.yaml` | `AA:BB:CC` is under 12 hex digits, so it is silently dropped. |
|
|
| `mac-source-missing.yaml` | Names an interface with no `/sys/class/net/<n>/address`. Warning, not an error: it is machine-dependent and may be checked on another host. |
|
|
|
|
## CH341 USB-SPI adapters
|
|
|
|
`spidev: ch341` is a different hardware model, not a variant of the same one. The Lora
|
|
pins become indexes on the adapter and are driven by the usermode USB driver -
|
|
`portduinoSetup()` skips `initGPIOPin()` for every one of them - so nothing is claimed
|
|
from a gpiochip. This is also the only shape that works on Windows and macOS, which have
|
|
no gpiochip, `gpiodetect` or `gpioinfo` to check anything against.
|
|
|
|
| File | Expected |
|
|
| --------------------- | ----------------------------------------------------------------------------- |
|
|
| `usb-ch341.yaml` | Clean, and the report lists adapter pins rather than resolved gpiochip lines. |
|
|
| `ch341-gpiochip.yaml` | A gpiochip and line mapping alongside `ch341`: read, stored, and never used. |
|
|
|
|
## Structure
|
|
|
|
| File | Expected |
|
|
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
| `duplicate-key.yaml` | yaml-cpp keeps the FIRST duplicate, so the later value is lost. |
|
|
| `nonmap-section.yaml` | `Lora: invalid` - a known section whose body cannot be read. |
|
|
| `unknown-section.yaml` | A top-level section meshtasticd never reads. |
|
|
| `stranded-key.yaml` | `spidev` left at the top level instead of inside a section. |
|
|
| `top-level-list.yaml` | Document root is a sequence. |
|
|
| `empty-file.yaml` | No content: comments only, which parse to a null document. A warning, not an error. |
|
|
| `malformed-indent.yaml` | Will not parse; the report must still name the file and line. |
|
|
| `pin-unknown-subkey.yaml` | A pin mapping accepts only `pin`, `gpiochip` and `line`. |
|
|
| `pin-unreadable.yaml` | A non-numeric pin resolves to -1 and trips an assertion at startup. |
|
|
| `hub75-unknown-key.yaml` | An unknown `Display.HUB75` option. On a build without rgbmatrix this also reports the missing HUB75 support, so the test asserts only the unknown key. |
|
|
|
|
## Across a config directory
|
|
|
|
`configd-conflict/` is a whole tree: a `config.yaml` pointing at a `config.d/`
|
|
holding two more `Lora:` sections. It covers the trap that a key not repeated in
|
|
the last-loaded file is reset to its default - here `config.yaml` sets
|
|
`DIO3_TCXO_VOLTAGE: 1800` and the effective configuration ends up without it.
|
|
The load order within `config.d/` comes from the filesystem, so the report warns
|
|
rather than assuming alphabetical order.
|
|
|
|
`rfswitch-last-wins/` covers `Lora.rfswitch_table` across two `config.d/` files. It
|
|
follows the same "last file loaded wins" rule as every other `Lora:` key - the loader
|
|
resets a table's pins and mode rows before applying a replacement, so an earlier
|
|
file's `MODE_RX` setting cannot leak through a later file that omits it. `--check`
|
|
reports this as the standard cross-file-overlap info, not a special-cased error.
|
|
|
|
`rfswitch-replace/` pins the replacement itself rather than the diagnostic. Which of
|
|
two `config.d/` files wins is up to the filesystem, so the fixture above cannot assert
|
|
the effective table by value; here the losing table sits in `config.yaml`, which is
|
|
always loaded before `config.d/`, and the winner is therefore deterministic. The loser
|
|
is the wider of the two - four pins and three mode rows, all `HIGH` - so any carryover
|
|
appears as a surviving pin, a surviving mode row, or a `HIGH` that should be `LOW`.
|
|
`--output-yaml` is what reports it: the `--check` report says no more than `set`.
|
|
|
|
## Running these as a normal boot
|
|
|
|
`malformed-indent.yaml`, `nonmap-section.yaml`, `module-unknown.yaml`,
|
|
`mac-conflict.yaml` and `hub75-unknown-key.yaml` are also run _without_ `--check`,
|
|
where each must be rejected with a non-zero exit. That is the guard on `--check`
|
|
mode not having quietly made the normal path permissive. No other fixture is run
|
|
that way: a config meshtasticd accepts makes it boot a node and block.
|