Skip to content

engine: a frame the receiver locked onto and lost reaches the chip - #757

Open
A13xB0 wants to merge 5 commits into
mainfrom
failed-frames-reach-the-chip
Open

A13xB0 wants to merge 5 commits into
mainfrom
failed-frames-reach-the-chip

Conversation

@A13xB0

@A13xB0 A13xB0 commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

What

A frame a receiver locked onto and then lost now reaches that node's firmware as the SX1262 reports it, instead of not at all.

  • Calculated mode: a collision says where it landed. More than one destroyed symbol inside the eight-symbol header block (coded at CR 4/8 whatever the payload uses) is a header error. Anything else that is lost is a CRC error. The miss's detail now says which.
  • Waveform mode: the demodulator already knew: no lock is nothing, a lock with an unreadable header is a header error, a header that read and a payload that did not is a CRC error.
  • The bridge: a new host-to-node kind, 0x0A: [failure][frame...], numbered as vsx_deliver_frame_failed numbers it (1 CRC, 2 header). The chip raises HeaderErr alone, or RxDone beside CrcErr with the corrupt bytes in the buffer.
  • A frame never locked onto (failure 3): a frame swamped by interference the receiver could not capture over, arriving while the preamble was still to be found, or in waveform mode a frame with no preamble lock. The part raises nothing for it. Under strict receive (Strict receive: a frame is received only if the receiver listened for it virtual-sx1262#7) the chip model takes back the HeaderValid it raised from the carrier's length and leaves PreambleDetected latched, as the part leaves it. Interference arriving after the lock is a header or CRC error, as above.
  • Only to a node that asks for it: a node says it takes 0x0A with bit 0 of byte 47 of its radio stats record, which every engine reads on length. A native bridge built before this ends its connection on any kind it does not know (default: goto done), so a node that does not say so is sent nothing. Failure 3 needs bit 1 as well (Receive only what the receiver was listening for meshcore-native#30), because a chip older than ABI 1.7 reads 3 as a CRC error.

Why

Before this, every failure was withheld, so a radio driver's error paths never ran in the simulator. MeshCore's CustomSX1262::isReceiving() treats any HeaderErr as "not receiving" and clears HeaderValid with it, even when a newer frame's HeaderValid sits beside a stale error. That can make a node transmit over a frame it is in the middle of receiving, and it could not be tested here, because nothing ever raised HeaderErr.

Compatibility

Today's published native builds, Renode and QEMU boards never set the bit, so they are never sent 0x0A and behave exactly as before. A native node built with MeshBench/meshcore-native#29 sets it. Renode and QEMU boards come later: they skip unknown kinds safely, but they cannot say they take this one until their stats records grow to byte 47.

The first push of this PR sent the kind to every firmware node, and CI caught it: today's native bridge hung up on it and the experiment and fixture checks failed. That is what the capability bit fixes.

Who loses a frame is unchanged: only what the losing receiver's chip is told.

Still kinder than the part

Written up as docs/shortcomings.md 3.2b:

  • only interference fails a frame, not weak signals near the floor;
  • in calculated mode HeaderErr arrives when the packet ends, not when its header does;
  • HeaderValid is raised from the carrier's length, not from a decoded header, and is only taken back when the frame ends without a lock;
  • a native node's processor takes no time, so firmware that is slow to collect a packet loses nothing to the delay.

On native builds with strict receive, the detection flags now stay set as the datasheet says. Emulated boards still clear them with the carrier.

Tests

  • TestACollisionOnTheHeaderIsAHeaderError: a collision across the header block is reported as a header error, and one after it as a CRC failure. Fails on main.
  • TestTheFirmwareIsToldWhereAFrameFailed: over a real bridge, the firmware is handed 0x0A with 2 for the header case and 1 for the payload case.
  • TestAFailedFrameReachesOnlyANodeThatTakesIt: a node that has not set the bit is sent nothing; once it has, the wire format.
  • TestANeverLockedFrameReachesOnlyAChipThatReadsIt: failure 3 is withheld from a node that sets only bit 0, and sent once it sets bit 1.
  • TestAFrameSwampedBeforeLockIsOneTheChipNeverLockedOnto: a stronger interferer arriving during the preamble is failure 3; arriving after the header it is a CRC failure.
  • go test for the engine, firmware and layout packages, go vet, gofmt, the file-length check, golangci-lint v2.12.2 on the changed packages and the lint ratchet all pass.

A collision now says where it landed. More than one destroyed symbol inside
the eight-symbol header block, coded at CR 4/8, is a header the receiver could
not read; anything else lost is a payload that failed its CRC. The firmware's
chip is told the same through a new bridge kind, 0x0A, and raises HeaderErr,
or RxDone beside CrcErr, as the part does. Waveform mode takes the answer from
the demodulator.

Before, a lost frame reached the firmware as nothing at all, so every error
path in a radio driver went unexercised. A node that does not know the kind
skips it, as it skips any kind, and behaves as it always has.
A native bridge built before kind 0x0A ends its connection on any kind it does
not know, so sending it to today's published builds stopped those nodes and
failed the run. A node now says it takes the kind with bit 0 of byte 47 of its
radio stats record, which every engine reads on length; one that does not say
so is sent nothing, as before.

The Renode peripheral's handler is withdrawn until Renode can say so too.
…ilure 3

A frame swamped by interference the receiver could not capture over is told
to the chip by when the interference arrived. Arriving while the preamble was
still to be found, it left nothing to lock onto, which the part reports by
raising nothing; the chip model takes back the HeaderValid it raised from the
carrier's length and leaves PreambleDetected latched. Arriving later, it found
a receiver that had locked, and is a header or CRC error as before. In
waveform mode a frame with no preamble lock is the same third case.

Sent only to a node whose radio stats record sets bit 1 of byte 47, because a
chip older than virtual-sx1262 ABI 1.7 reads failure 3 as a CRC error.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants