Skip to content

feat(traffic): decode the Pangolin protocols, and watch BEYOND's live control - #112

Merged
pyramation merged 1 commit into
mainfrom
feat/pangolin-protocol-decode
Aug 19, 2026
Merged

feat(traffic): decode the Pangolin protocols, and watch BEYOND's live control#112
pyramation merged 1 commit into
mainfrom
feat/pangolin-protocol-decode

Conversation

@pyramation

Copy link
Copy Markdown
Contributor

Summary

Two captures from the show machine turned out to hold three unrelated protocols, two of them readable. bin/analyze could only show them as flows and bytes, so this adds bin/decode, which reads a capture as Pangolin, and bin/rgba, which watches the readable one live. Still strictly passive — both only read.

tools/traffic/PROTOCOL.md is the writeup: what the captures show, and where the evidence stops.

port readable what
UDP 9022 yes FB4 announce (model tag, device id = the serial BEYOND lists) + ~223 unnamed (u32 tag, u32 value) settings pairs per device
UDP 16062 yes, ASCII BEYOND broadcasting what its live control holds
TCP 3348 header only frame stream, 32-byte plaintext header + encrypted body

The find worth acting on is UDP 16062. With its RGBA panel open BEYOND broadcasts every live-control change as text:

ControlZone 3\r\nRGBA 0, 229\r\n    →  zone 3, red = 229   (0=red 1=green 2=blue 3=alpha, plus Brightness)

OSC acknowledges nothing, so "did BEYOND receive that?" has been unanswerable all session. This is the answer: bin/rgba prints values as they change, so you send and watch. Silence means nothing is arriving. bin/decode --timeline does the same after the fact, to line a capture up against what you were doing.

TCP 3348 is framed by a length field, since TCP gives no boundaries (a 2392B frame arrives as 1460 + 932), so reading it needs reassembly:

Header = magic 40fb0000 | u32 type | u32 total_length | u32 sequence | 2×u64 clock   # 32B
split_messages(stream)  # walks by header.length; stops on a message the capture cut in half

Frame bodies measure ~8.00 bits/byte and don't compress, and consecutive frames of static content share almost no bytes — point data for 25 lasers would be neither. So the decoder reports framing, type mix, rate and sequence continuity, and states the bodies are encrypted rather than inventing a point parser. That also settles the bigger question: the frame path can't be driven from what a capture gives us.

Everything the decoder can't justify is kept raw — settings tags are counted and reported unnamed, unknown RGBA channel numbers pass through as their number, unknown stream types print as unknown with their hex. Naming them needs bin/experiment + bin/compare (change one thing, capture, diff), not guesswork.

27 unit tests over framing, reassembly, both UDP parsers, entropy and the empty-capture paths, run in CI (python3 -m unittest discover -s tools/traffic/lib -p '*_test.py') — the header test parses a real captured frame header, so a framing regression fails there instead of silently mis-reporting a capture.

Link to Devin session: https://app.devin.ai/sessions/ec43152136134467a853ec0bbf783ea9
Requested by: @pyramation

…live control

decode reads a capture as protocols rather than bytes: FB4 announces on UDP
9022, BEYOND's live-control broadcast on UDP 16062, and the framing/rate of the
frame stream on TCP 3348. rgba watches the same broadcast live, which is the
only confirmation an OSC send ever gets.

The frame bodies are encrypted (~8.0 bits/byte, incompressible), so the decoder
reports the framing and says so instead of guessing at points. PROTOCOL.md
records what the captures show and where the evidence stops.
@pyramation pyramation self-assigned this Aug 19, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@pyramation
pyramation merged commit c5a5e79 into main Aug 19, 2026
5 checks passed
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.

1 participant