Inspiration
CHRP's pouch turns a phone into a LoRa mesh radio. The radio was built for tiny text messages: one packet holds about 230 bytes, the default preset moves 1 kbps, and every hop can lose the packet. The brief asked for voice notes and photos over that link, with a status screen that never lies. We wanted to see how much real media fits through a straw, and how honest an app can be about what actually arrived.
What it does
Phone A records a voice note and picks a photo. Phone E receives them across a simulated five-node mesh (A, B, C, D, E) that follows Meshtastic rules: 229-byte packets, radio presets from LongFast at 1.07 kbps to ShortTurbo at 21.88 kbps, hop limit 3, a 20 percent loss coin on every hop, hop-by-hop acknowledgments and resends.
Media goes out cheapest first:
| Tier | Form | Bytes for the 8.7 s test clip | Packets |
|---|---|---|---|
| words | transcript, brotli | 210 | 1 |
| photo | 42x42 JPEG, header stripped, plus caption | 131 + caption | 1 |
| voice | Codec2 700, 100 bytes per second | 872 | 4 |
Raw phone audio for the same clip is 278,292 bytes. Codec2 cuts it 319 times.
The far phone shows only bytes that arrived: words when the words tier is complete, the tiny real photo when its packet lands, voice chunk by chunk with a play button that plays what is there and leaves silence in the holes. A mesh log under the graph prints every DROP, RESEND and COMPLETE line as it happens. The sender card shows two numbers per tier: what the next hop took custody of, and what the far end has. A tier reads "complete" only when the destination sent the acknowledgment for its last chunk. The word "delivered" does not appear anywhere.
How we built it
- Python 3.11, FastAPI, one process, no database. The mesh is an event heap in simulated LoRa seconds; the page can run it at 1x to 10x and every number on screen stays in real seconds.
- Codec2 mode 700 through pycodec2 for voice, brotli for text, Pillow for the 42x42 JPEG. Both ends keep the 623-byte JPEG header; only 131 bytes fly.
- A 4-byte packet header: message id, chunk index, chunk count, tier and ack flag. 225 payload bytes, 229 total.
- Every AI step runs through the Nicrron router with one key: Whisper for the transcript (segment log-probabilities become the confidence shown under the words), GPT-4o for a photo caption that rides with the photo, Nano Banana to redraw the caption on the far phone at zero bytes on air, and Claude Haiku to explain each message's status in two sentences.
- The explainer is fenced by code. A validator rejects any sentence that says "delivered", that claims a tier is complete when it is not, or that contains a number not present in the measured data. In testing it rejected the model once for adding bytes up on its own.
- Block Convey PRISM gets one trace per message: transcribe, caption, compress, chunk, route, send, ack, mark_complete, regenerate, explain. The mark_complete span records whether the ack rule held.
- A vanilla JavaScript page, no build step: two phones, a prism-shaped graph where each hop lights an edge and chevrons travel with the chunk number, the mesh log, a trust legend (measured, derived, AI) on every number.
- 65 pytest tests, including: complete never fires before the destination's final ack; a tier fails honestly when both relays are down; a speed change never jumps the clock.
Challenges we ran into
- Nicrron's image endpoint returned 410 on the first download after the job said "succeeded". A retry three seconds later worked; the code now does that.
- Flooding is hard to watch. B relays to C and D at the same time, so "one edge at a time" is a lie. We let both light and dim the older one.
- Hosting: Ninoo imports Node web apps only, so the demo runs from a laptop.
Accomplishments that we're proud of
- Real voice crosses a 1 kbps link with 20 percent loss per hop and plays on the other side, holes and all.
- An AI model explains the status and cannot say more than the data does.
- A judge can read the trust boundary off the page: measured, derived, AI.
What we learned
- The transcript is the trick. Words for 8.7 seconds of speech cost 210 bytes; the voice costs 872; the raw audio costs 278,292.
- Honest status is a data-flow rule, not a UI rule: only the destination's ack may set "complete", and the far phone may only render bytes it holds.
- A model is useful at the edge of a trust boundary only when code checks every word it writes.
What's next
- A neural codec at 20 bytes per second to keep the speaker's voice.
- Keyframes every 10 seconds as 42x42 photos, to carry video.
- Swap the simulator for the Meshtastic Python API on real radios; the packet format already fits the 237-byte payload limit.
Built With
- block-convey
- brotli
- claude-haiku
- codec2
- fastapi
- ffmpeg
- gpt-4o
- javascript
- lora
- meshtastic
- nano-banana
- nicrron
- numpy
- openai-whisper
- pillow
- prism
- pycodec2
- python
- svg
- uvicorn
- websockets
Log in or sign up for Devpost to join the conversation.