Repository navigation
Releases: cvhviz/CAIRN
Release list
Cairn 115
Cairn 115 is mostly about audio and how things look. The L2 Pro's microphone works, the music player gets a five-band EQ, an edge-to-edge visualizer and its options as cards, and every chooser on the touch and key boards is now a page of cards instead of a list. Packet details are redesigned around the sender, the map draws a node's route, and nodes get an actions page. The clock no longer shows a stale time after a power cut (#18), older T-Deck keyboards type again (#17), and the T-Display SF32 no longer restarts when you switch emoji tabs in a message.
Highlights:
- L2 Pro microphone: the recorder works on the L2 Pro.
- EQ (L2 Pro, T-Deck Plus): five bands with Flat, Voice, Bright, Warm and Custom presets, shaped live over the music's spectrum.
- Now playing is a full-screen visualizer. Tap it for the controls.
- Choosers are cards: button actions, settings lists and action menus use the Settings card style.
- Packet details lead with who sent it: the name, its type, distance and key, then the signal and route as large values.
- Map routes and node actions: tap a node on the map for its route; a node's "…" opens Discover path, Reset route, Favorite, Share contact, Admin and Remove as cards.
- Clock: after a power cut the clock shows "--:--" until GPS, the app or Wi-Fi sets it, instead of the time the board was switched off.
- T-Display SF32: no more restart on the second
*in a message, a faster Nodes tab, and card titles in Title Case.
Update
- On Cairn 108 to 114 (L2 Pro, Heltec V4 touch, ThinkNode M9, T-Deck, T-Display SF32): Settings › System › Firmware update: Check, Download, Install. The new build starts on trial and goes back to the old one by itself if it fails to start. On the T-Display SF32, dock the core on the keyboard board first (it carries the Wi-Fi chip). A tester's 115beta build is offered this release too.
- Wio Tracker L1 Pro, DV1, L1 e-ink and the T096: double-tap Reset and copy the board's
.uf2onto the drive that appears (UF2 guide). Updating keeps your identity, contacts, channels and settings. - T-Display SF32 without Cairn, or to recover: unzip the SF32
-install.zip, plug the core in over USB-C and runInstall on Mac.command,Install on Windows.bator./install-linux.sh(SF32 install guide). - A board without Cairn: write the board's
-full.bin(ESP32 install guide), or copy the.uf2for the L1 Pro family and the T096 (UF2 guide).
Downloads
| Board | File | Use |
|---|---|---|
| ✅ Seeed Wio Tracker L2 Pro | MeshCore-WioL2Pro-Cairn115-2026-10-12-full.bin |
first install or recovery over USB, at 0x0 |
MeshCore-WioL2Pro-Cairn115-2026-10-12-update.bin |
update over USB at 0x10000, or over Wi-Fi |
|
| ✅ Heltec V4 + Expansion Kit (touch) | MeshCore-HeltecV4Touch-Cairn115-2026-10-12-full.bin |
first install or recovery over USB, at 0x0 |
MeshCore-HeltecV4Touch-Cairn115-2026-10-12-update.bin |
update over USB at 0x10000, or over Wi-Fi |
|
| ✅ Elecrow ThinkNode M9 (beta) | MeshCore-ThinkNodeM9-Cairn115-2026-10-12-full.bin |
first install or recovery over USB, at 0x0 |
MeshCore-ThinkNodeM9-Cairn115-2026-10-12-update.bin |
update over USB at 0x10000, or over Wi-Fi |
|
| ✅ LilyGO T-Deck / T-Deck Plus (beta) | MeshCore-TDeckPlus-Cairn115-2026-10-12-full.bin |
first install or recovery over USB, at 0x0 |
MeshCore-TDeckPlus-Cairn115-2026-10-12-update.bin |
update over USB at 0x10000, or over Wi-Fi |
|
| ✅ LilyGO T-Display SF32 + keypad | MeshCore-TDisplaySF32-Cairn115-2026-10-12-install.zip |
first install or recovery over USB, with the included installer |
MeshCore-TDisplaySF32-Cairn115-2026-10-12-update.bin |
update over Wi-Fi (what Firmware update fetches) | |
MeshCore-TDisplaySF32-Cairn115-2026-10-12-update.bin.sha256 |
its checksum, for the updater | |
| ✅ Seeed Wio Tracker L1 Pro | MeshCore-WioL1Pro-Cairn115-2026-10-12.uf2 |
companion, SH1106 OLED (the main L1 build) |
MeshCore-DV1-Cairn115-2026-10-12.uf2 |
companion, 2.42" SSD1309 OLED on the Grove port | |
MeshCore-WioL1Eink-Cairn115-2026-10-12.uf2 |
companion, 2.13" e-ink | |
MeshCore-WioL1RoomServer-Cairn115-2026-10-12.uf2 |
room server | |
| ✅ Heltec Mesh Node T096 (companion) | MeshCore-HeltecT096-Cairn115-2026-10-12-ble.uf2 |
the main build (phone app over Bluetooth) |
MeshCore-HeltecT096-Cairn115-2026-10-12-usb.uf2 |
the app over USB serial |
The compact repeater files are listed under Compact repeaters.
SHA256SUMS.txt covers every file: shasum -a 256 -c SHA256SUMS.txt. The preview boards' files are in Cairn 107 preview.
Touch and key boards: what's new
L2 Pro, Heltec V4 touch, ThinkNode M9 and T-Deck.
Audio (L2 Pro and T-Deck Plus)
- L2 Pro microphone: the recorder is back on and records. The mic needed the speaker chip's analogue side powered while recording.
- EQ: five bands (150 Hz, 400 Hz, 1 kHz, 2.5 kHz, 6 kHz, ±6 dB) and the presets Flat, Voice, Bright, Warm and Custom. The EQ page shows the live spectrum of the music after the EQ with a handle on each band: drag one to shape Custom. It applies to music and recordings.
- Now playing fills the screen: 24 bars with peak caps, the level, peak and loudest frequency, the EQ in use, and the time. Tap it and the controls slide in (title, Library, Options, volume, progress and transport) without covering the bars; tap the bars to put them away.
- Player options (⋯) are cards: Repeat, Shuffle, Volume, EQ, Screen off and Alerts.
- The Audio page has shorter Player and Recorder cards with Library and Options cards under them.
- Music no longer stutters when a message, contact or setting is saved while it plays, and no longer fails with "Not enough memory" after Wi-Fi has connected.
- Alert tones no longer interrupt music. Alerts in Player options turns them back on.
- L2 Pro: a louder speaker (tones about +9 dB, music about +6 dB) through a smoother limiter.
- The User button can now open the Player or the Recorder, or Play / pause.
Cards everywhere
- Every chooser is a page of cards in the Settings style: button actions (grouped General, Mesh, Tools, Audio), setting lists, and the action menus on Nodes, Chats, the map and the recorder.
- A node's "…" opens its actions as cards: Discover path, Reset route, Favorite, Share contact, Admin / Log in and Remove contact. Discover path finds a route to a contact (or logs in to a repeater or room to learn one).
- The Quick panel's Theme button is now SD Card, which opens Files.
Packet details
- Tap a packet in Traffic: the sender comes first with its name large, and under it who it was sent to, its type, how far away it is and its key. When it was heard sits at the top right.
- RSSI, SNR and margin, then route, size and airtime, are shown as large values with small labels, followed by the relay it came through and the packet type.
Map
- Tap a node on the GPS map to see its route: a line from you through each repeater on its path that has a position. A hop that is not on the map is drawn dashed, and a node with no known route gets a dashed straight line.
- The map remembers your last GPS fix across restarts.
Clock (#18)
- After a power cut or a reset, the clock shows "--:--" until GPS, the phone app or Wi-Fi gives it the real time. Before, it showed the time from the newest contact, which on the ThinkNode M9 was the moment it was switched off. Messages you send still carry timestamps that move forward.
- A soft restart or waking from sleep keeps a clock that was already set.
Other
- T-Deck (#17): keyboards with older firmware type again. They ignored the request for raw key mode that the Mic key needs; Cairn now notices and switches back.
- Heltec V4 touch: the LoRa / Wi-Fi / Bluetooth tabs are easier to tap.
- Settings › Connect › Wi-Fi shows the IP address once connected.
T-Display SF32: what's new
- No restart on the second
*in a message. The first*opens the symbol picker and the second switched to an emoji tab, which ran out of memory. Emoji tabs now take almost no memory, and all of each tab's emoji show. - Faster Nodes tab: it opens in about 0.2 s (was 0.5 s), and switching Recent and Contacts takes about 20 ms (was 0.4 s).
- Card titles in Title Case on every card ("Ping Last", "Time Sync", "IR Remote").
- The clock follows the same rule as the other boards when it has no trusted time.
- The firmware is about 58 KB smaller, leaving room for future updates in the Wi-Fi update slot.
Other boards
The Wio Tracker L1 Pro family, the T096 companion and the compact repeaters are rebuilt as Cairn 115 with no changes of their own.
Compact repeaters
| Board | File | Use |
|---|---|---|
| ✅ Heltec Mesh Node T096 (beta) | MeshCore-HeltecT096-Cairn115-2026-10-12-repeater.uf2 |
copy onto the board's USB drive |
| 🧪 Heltec T114 | MeshCore-HeltecT114-Cairn115-2026-10-12-repeater.uf2 |
copy onto the board's USB drive |
| 🧪 Heltec V2 | MeshCore-HeltecV2-Cairn115-2026-10-12-repeater-full.bin |
first install at 0x0 |
MeshCore-HeltecV2-Cairn115-2026-10-12-repeater-update.bin |
update at 0x10000 |
|
| 🧪 Heltec V3 | MeshCore-HeltecV3-Cairn115-2026-10-12-repeater-full.bin |
first install at 0x0 |
MeshCore-HeltecV3-Cairn115-2026-10-12-repeater-update.bin |
update at 0x10000 |
|
| 🧪 Heltec V4 | MeshCore-HeltecV4-Cairn115-2026-10-12-repeater-full.bin |
first install at 0x0 |
MeshCore-HeltecV4-Cairn115-2026-10-12-repeater-update.bin |
update at 0x10000 |
|
| 🧪 Heltec V4-R8 | `MeshCore-Helte... |
Cairn 114
Cairn 114 brings LoRa, Wi-Fi and Bluetooth together on one Radios page with tabs, and adds a full-screen Traffic log with a details window for every packet. The touch and key boards get a new Storage page with a file manager, and the T-Deck Plus and L2 Pro can now act as a USB drive for their microSD card. The card gets tidy top-level folders (Maps, Music, Recordings, Sounds, Pictures). Audio on the L2 Pro is cleaner and its volume matches the alert tones, and the T-Deck's Mic key opens the recorder. The T-Display SF32 gets the same Radios tabs and Traffic log, a Sensors card and a clean startup tone.
Highlights:
- Radios page with tabs: LoRa (with its frequency), Wi-Fi and Bluetooth side by side. The scanners moved here from the Network page. On the T-Deck and the SF32, keys 1, 2 and 3 pick a tab.
- Traffic log: tap the traffic card on the LoRa tab for a full-screen list of the last hour's packets (up to 2048), with a stats strip. Tap a packet for its details as tiles.
- Storage page and Files (Settings › Hardware › Storage): the card's status, a file manager, Eject, Re-scan and Format in one place.
- USB drive (T-Deck Plus and L2 Pro): the microSD card shows up as a disk on your computer, so you can copy maps and music without taking the card out.
- Card folders:
Maps,Music,Recordings,SoundsandPicturesat the top of the card. Maps in the oldcairn/mapsfolder move by themselves. - L2 Pro audio: music no longer distorts at high volume, and its loudness now matches the alert tones.
- T-Deck Plus: the Mic key opens the recorder.
Update
- On Cairn 108 to 113 (L2 Pro, Heltec V4 touch, ThinkNode M9, T-Deck, T-Display SF32): Settings › System › Firmware update: Check, Download, Install. The new build starts on trial and goes back to the old one by itself if it fails to start. On the T-Display SF32, dock the core on the keyboard board first (it carries the Wi-Fi chip).
- Wio Tracker L1 Pro, DV1, L1 e-ink and the T096: double-tap Reset and copy the board's
.uf2onto the drive that appears (UF2 guide). Updating keeps your identity, contacts, channels and settings. - T-Display SF32 without Cairn, or to recover: unzip the SF32
-install.zip, plug the core in over USB-C and runInstall on Mac.command,Install on Windows.bator./install-linux.sh(SF32 install guide). - A board without Cairn: write the board's
-full.bin(ESP32 install guide), or copy the.uf2for the L1 Pro family and the T096 (UF2 guide).
Downloads
| Board | File | Use |
|---|---|---|
| ✅ Seeed Wio Tracker L2 Pro | MeshCore-WioL2Pro-Cairn114-2026-10-12-full.bin |
first install or recovery over USB, at 0x0 |
MeshCore-WioL2Pro-Cairn114-2026-10-12-update.bin |
update over USB at 0x10000, or over Wi-Fi |
|
| ✅ Heltec V4 + Expansion Kit (touch) | MeshCore-HeltecV4Touch-Cairn114-2026-10-12-full.bin |
first install or recovery over USB, at 0x0 |
MeshCore-HeltecV4Touch-Cairn114-2026-10-12-update.bin |
update over USB at 0x10000, or over Wi-Fi |
|
| ✅ Elecrow ThinkNode M9 (beta) | MeshCore-ThinkNodeM9-Cairn114-2026-10-12-full.bin |
first install or recovery over USB, at 0x0 |
MeshCore-ThinkNodeM9-Cairn114-2026-10-12-update.bin |
update over USB at 0x10000, or over Wi-Fi |
|
| ✅ LilyGO T-Deck / T-Deck Plus (beta) | MeshCore-TDeckPlus-Cairn114-2026-10-12-full.bin |
first install or recovery over USB, at 0x0 |
MeshCore-TDeckPlus-Cairn114-2026-10-12-update.bin |
update over USB at 0x10000, or over Wi-Fi |
|
| ✅ LilyGO T-Display SF32 + keypad | MeshCore-TDisplaySF32-Cairn114-2026-10-12-install.zip |
first install or recovery over USB, with the included installer |
MeshCore-TDisplaySF32-Cairn114-2026-10-12-update.bin |
update over Wi-Fi (what Firmware update fetches) | |
MeshCore-TDisplaySF32-Cairn114-2026-10-12-update.bin.sha256 |
its checksum, for the updater | |
| ✅ Seeed Wio Tracker L1 Pro | MeshCore-WioL1Pro-Cairn114-2026-10-12.uf2 |
companion, SH1106 OLED (the main L1 build) |
MeshCore-DV1-Cairn114-2026-10-12.uf2 |
companion, 2.42" SSD1309 OLED on the Grove port | |
MeshCore-WioL1Eink-Cairn114-2026-10-12.uf2 |
companion, 2.13" e-ink | |
MeshCore-WioL1RoomServer-Cairn114-2026-10-12.uf2 |
room server | |
| ✅ Heltec Mesh Node T096 (companion) | MeshCore-HeltecT096-Cairn114-2026-10-12-ble.uf2 |
the main build (phone app over Bluetooth) |
MeshCore-HeltecT096-Cairn114-2026-10-12-usb.uf2 |
the app over USB serial |
The compact repeater files are listed under Compact repeaters.
SHA256SUMS.txt covers every file: shasum -a 256 -c SHA256SUMS.txt. The preview boards' files are in Cairn 107 preview.
Touch and key boards: what's new
L2 Pro, Heltec V4 touch, ThinkNode M9 and T-Deck.
Radios page with tabs
- The sidebar's Radio tab is now Radios, with three tabs across the top: LoRa (its label shows the frequency), Wi-Fi and Bluetooth. On the T-Deck's keyboard, 1, 2 and 3 pick a tab.
- The Wi-Fi and Bluetooth scanners moved here from Home › Network and Settings › Connect. Those pages keep the Wi-Fi and Bluetooth settings.
- Home's radio card is now titled LORA and opens the LoRa tab.
- The scanners' status ("Scanning", "Scan · 12", "N found", "BT off") moved into the status bar, so the plot and the bars are taller.
- The scanner's controls fit in one row: one button switches between Plot and List ('#' on the keys), Active and Passive sit next to it, and Start is the widest button.
Traffic log and packet details
- The LoRa tab is laid out again around a traffic card. Tap it for Traffic: a full-screen list of the packets heard over about the last hour (up to 2048), with a stats strip: packets, per minute, average RSSI and SNR.
- Tap a packet for a window with its fields as tiles: type, route and hops, signal, margin over the spreading factor's limit, from (or channel), to, heard via, size and when it was heard. Open node jumps to the sender's page.
Storage and Files
- Settings › Hardware › Storage is one page: the card's status first, then USB drive and Files, then Eject and Re-scan, then Format. While a delete runs, a Deleting tile shows its progress.
- Files is a file manager for the card: browse folders, see sizes, and delete a file or a whole folder. A folder delete moves it aside first, so it is quick and survives a restart.
- A card that fails to start at boot is tried again at the same speed before the slower ones.
USB drive (T-Deck Plus and L2 Pro)
- Storage › USB drive turns the board into a USB disk for your computer: the microSD card mounts on the Mac or PC, and you can copy files to it and from it. Eject it on the computer, then tap Done: the board restarts and goes back to normal.
- The radio and the app stay off while it is a drive. Measured speeds: about 0.7 MB/s on the L2 Pro and 0.5 MB/s on the T-Deck Plus (its card shares a bus with the screen and the radio).
- Not on the Heltec V4 touch or the ThinkNode M9: their USB ports cannot act as a drive.
Card folders
- The card now uses five folders at the top:
Maps,Music,Recordings,SoundsandPictures. The same layout is used on every Cairn board, so a card moves between them. - Offline maps in
cairn/mapsmove toMapsby themselves the first time the card is read. Only folder names change: nothing is copied or deleted. - The Library shows subfolders in
Music, up to two levels deep (for exampleMusic/Artist/Album).
Music player
- Now playing has a taller spectrum with its 24 bars. The volume buttons sit over the right side of the spectrum with the level in %. Tap the spectrum to hide or show them.
- Repeat and shuffle moved to the player's options sheet (⋯).
- L2 Pro: music no longer clips or sounds harsh at high volume (a low-cut filter, more headroom and a limiter), and the volume now follows the same scale as the alert tones, so they match.
T-Deck Plus
- The keyboard's Mic key opens the recorder. On the recorder it starts and stops a recording.
- A fix for crackling static in recordings (the microphone's data slipped by one bit now and then).
T-Display SF32: what's new
- Radios page with tabs: 1 LoRa, 2 Wi-Fi and 3 BLE, picked by touch or by keys 1, 2 and 3. The scanners moved here from the Network page.
- Traffic log and packet details, as on the touch boards.
- The scanners' status moved to the status bar, so the plot is taller. One button switches between Plot and List, Active and Passive have their own buttons, and Start is the widest. Keys: 4 Active, 5 Passive, 6 the view, 7 Start.
- Home: the GPS card is gone (the sidebar already shows the GPS). Climate and Steps are now one Sensors card, with the step count as its main value.
- Startup tone: it no longer stutters while the board starts up. The tone now plays at a higher priority than the start-up work.
Other boards
The Wio Tracker L1 Pro family, the T096 companion and the compact repeaters are rebuilt as Cairn 114 with no changes of their own.
Compact repeaters
| Board | File | Use |
|---|---|---|
| ✅ Heltec Mesh Node T096 (beta) | MeshCore-HeltecT096-Cairn114-2026-10-12-repeater.uf2 |
copy onto the board's USB drive |
| 🧪 Heltec T114 | MeshCore-HeltecT114-Cairn114-2026-10-12-repeater.uf2 |
copy onto the board's USB drive |
| 🧪 Heltec V2 | MeshCore-HeltecV2-Cairn114-2026-10-12-repeater-full.bin |
first install at 0x0 |
MeshCore-HeltecV2-Cairn114-2026-10-12-repeater-update.bin |
update at 0x10000 |
|
| 🧪 Heltec V3 | `MeshCore-HeltecV3-Cairn114-2026-10-12-re... |
Cairn 113
Cairn 113 brings Wi-Fi and Bluetooth scanners, with a full-screen radar and Find, to the Wio Tracker L2 Pro, Heltec V4 touch, ThinkNode M9, T-Deck and T-Display SF32. The T-Deck Plus gets a music player and a voice recorder, and the L2 Pro gets the music player. The touch and key boards also get a new Battery page and ask before Wi-Fi, Bluetooth or GPS switch. The Wio Tracker L1 Pro family moves up from Cairn 107 with sent-message sync, a GPS data view and Settings in categories. The T096 companion gets a regrouped Settings list, and the compact repeaters pick up fixes from upstream MeshCore.
Highlights:
- Wi-Fi and Bluetooth scanners (Home › Network): a live plot and a list of the networks or devices around you. The Bluetooth radar opens full screen, and Find helps you walk towards a device.
- Music player (T-Deck Plus and L2 Pro): play MP3 or WAV files from the microSD card. Voice recorder (T-Deck Plus): record voice notes with the built-in microphone. The L2 Pro's recorder is off for now.
- Battery page: time left, the rate, today's range and a graph over 1 hour to 7 days, with markers for plug-in, unplug and full.
- Asks before switching: turning Wi-Fi, Bluetooth or GPS on or off from the board asks first, so a stray touch can't drop the app.
- GPS icon in the sidebar is green while the GPS is on and red while it is off.
- Wio Tracker L1 Pro: messages sent from the phone show on the L1, plus a GPS data view, Path hash and Units settings, a TX/RX line and Settings in six categories.
- Heltec V4-R8: better reception (the receive amplifier is now switched on).
Update
- On Cairn 108 to 112 (L2 Pro, Heltec V4 touch, ThinkNode M9, T-Deck, T-Display SF32): Settings › System › Firmware update: Check, Download, Install. The new build starts on trial and goes back to the old one by itself if it fails to start. On the T-Display SF32, dock the core on the keyboard board first (it carries the Wi-Fi chip).
- Wio Tracker L1 Pro, DV1, L1 e-ink and the T096: double-tap Reset and copy the board's
.uf2onto the drive that appears (UF2 guide). Updating keeps your identity, contacts, channels and settings. - T-Display SF32 without Cairn, or to recover: unzip the SF32
-install.zip, plug the core in over USB-C and runInstall on Mac.command,Install on Windows.bator./install-linux.sh(SF32 install guide). - A board without Cairn: write the board's
-full.bin(ESP32 install guide), or copy the.uf2for the L1 Pro family and the T096 (UF2 guide).
Downloads
| Board | File | Use |
|---|---|---|
| ✅ Seeed Wio Tracker L2 Pro | MeshCore-WioL2Pro-Cairn113-2026-10-11-full.bin |
first install or recovery over USB, at 0x0 |
MeshCore-WioL2Pro-Cairn113-2026-10-11-update.bin |
update over USB at 0x10000, or over Wi-Fi |
|
| ✅ Heltec V4 + Expansion Kit (touch) | MeshCore-HeltecV4Touch-Cairn113-2026-10-11-full.bin |
first install or recovery over USB, at 0x0 |
MeshCore-HeltecV4Touch-Cairn113-2026-10-11-update.bin |
update over USB at 0x10000, or over Wi-Fi |
|
| ✅ Elecrow ThinkNode M9 (beta) | MeshCore-ThinkNodeM9-Cairn113-2026-10-11-full.bin |
first install or recovery over USB, at 0x0 |
MeshCore-ThinkNodeM9-Cairn113-2026-10-11-update.bin |
update over USB at 0x10000, or over Wi-Fi |
|
| ✅ LilyGO T-Deck / T-Deck Plus (beta) | MeshCore-TDeckPlus-Cairn113-2026-10-11-full.bin |
first install or recovery over USB, at 0x0 |
MeshCore-TDeckPlus-Cairn113-2026-10-11-update.bin |
update over USB at 0x10000, or over Wi-Fi |
|
| ✅ LilyGO T-Display SF32 + keypad | MeshCore-TDisplaySF32-Cairn113-2026-10-11-install.zip |
first install or recovery over USB, with the included installer |
MeshCore-TDisplaySF32-Cairn113-2026-10-11-update.bin |
update over Wi-Fi (what Firmware update fetches) | |
MeshCore-TDisplaySF32-Cairn113-2026-10-11-update.bin.sha256 |
its checksum, for the updater | |
| ✅ Seeed Wio Tracker L1 Pro | MeshCore-WioL1Pro-Cairn113-2026-10-11.uf2 |
companion, SH1106 OLED (the main L1 build) |
MeshCore-DV1-Cairn113-2026-10-11.uf2 |
companion, 2.42" SSD1309 OLED on the Grove port | |
MeshCore-WioL1Eink-Cairn113-2026-10-11.uf2 |
companion, 2.13" e-ink | |
MeshCore-WioL1RoomServer-Cairn113-2026-10-11.uf2 |
room server | |
| ✅ Heltec Mesh Node T096 (companion) | MeshCore-HeltecT096-Cairn113-2026-10-11-ble.uf2 |
the main build (phone app over Bluetooth) |
MeshCore-HeltecT096-Cairn113-2026-10-11-usb.uf2 |
the app over USB serial |
The compact repeater files are listed under Compact repeaters.
SHA256SUMS.txt covers every file: shasum -a 256 -c SHA256SUMS.txt. The preview boards' files are in Cairn 107 preview.
Touch and key boards: what's new
L2 Pro, Heltec V4 touch, ThinkNode M9 and T-Deck.
Wi-Fi and Bluetooth scanners
- Home › Network (and Settings › Connect) opens with two new tools at the top, Wi-Fi scan and BT scan, with the Wi-Fi and Bluetooth settings under them. Each works like the LoRa band scan: a Start button, a live plot and a List view.
- Wi-Fi scan shows the 2.4 GHz band, channels 1 to 14: one arc per network at its signal strength, each in its own colour, the four strongest named, and your own network's channel highlighted. Under the plot, a bar per channel shows how many networks reach it. Tap the plot or a bar for a readout ("ch 6 · 2437 MHz · HomeNet -62 dBm · WPA2 · 3 APs").
- The Wi-Fi list names every network, strongest first, hidden ones too, with its channel, security and a Connected or Saved tag. A tap offers Details, Show on plot or Join….
- BT scan shows the Bluetooth LE devices in range: a bar per device over a half radar with -60 / -80 / -100 dBm rings, MeshCore nodes marked and named in the accent colour. Tap a bar or a dot for a readout ("MeshCore-Chris · -58 dBm · pk -52 · Apple · 3 s ago"). The list gives each device's name, signal and peak, address, maker and when it was last heard, and keeps a steadier order, so neighbours a couple of dB apart no longer swap places every few seconds.
- Tap the radar (or press Enter on it) for a full-screen radar. It shows distance only, never a direction. The trackball or d-pad moves to the nearest dot in the direction you press, and a small chip follows the selected dot with its name and signal.
- Find: pick a device and walk towards it. The page shows its signal in big figures, one word for the last 10 s (Closer, Farther, Steady or Lost) and the last minute as a line. While you walk with a GPS course, a ring marks the way you were facing when the signal was strongest. An optional Tone ticks faster as the signal climbs.
- Details open as cards in the Settings style, with live values while the scan runs: for Wi-Fi the signal, channel, security, standards and BSSID; for Bluetooth the signal, maker, address, services and transmit power. A network or device that goes quiet says so.
- Active asks for names (Wi-Fi probe requests, about 2.5 s a scan; Bluetooth name requests). Passive only listens and sends nothing.
- A scan only starts when you tap Start. It stops by itself when you leave the page, when the screen goes dark, after 3 minutes without a touch, or when an update starts. One scanner runs at a time. Both use battery while they run. Nothing is sent on the mesh and LoRa keeps working. BT scan works with Bluetooth switched off and while the app is connected.
Music player (T-Deck Plus and L2 Pro) and voice recorder (T-Deck Plus)
- On these two boards an Audio card takes the GPS card's place on Home. The GPS tab in the sidebar still shows the satellites.
- Player: put MP3 or WAV files in a
Musicfolder on the microSD card. The Library lists Music and Recordings. Now playing has a live spectrum, a seek bar, previous / -10 s / play-pause / +10 s / next, volume, repeat (off, all, one) and shuffle. Music keeps playing with the screen dark, unless you set "Pause at screen off". A message alert pauses the music, and it carries on after the tone. Hold a file to delete it. - Plays MP3 (CBR or VBR) and WAV (8, 16 or 24-bit, mono or stereo), 8 to 48 kHz. Stereo plays as mono on the one speaker.
- Recorder (T-Deck Plus): records voice notes to a
Recordingsfolder as WAV at 16 or 8 kHz, with mic gain, a time limit, voice start (waits for sound and keeps the half second before it) and marks. Files are named by date and time once the clock is set. A recording keeps going with the screen dark and is kept safe if the power or the card goes. The microphone is only on while you record or the Record view is open, and the status bar shows MIC or REC whenever it is. - L2 Pro: the Audio card opens the Player. Its recorder is off for now: our unit's microphone gives only silence, and Seeed lists it as reserved. It stays off until we know more.
- The folders match the T-Display SF32's player, so a card moves between the devices.
- On the T-Deck the card shares its bus with the radio and the screen, so very large files (CD-quality WAV) are refused with "Too fast for this card: use MP3".
Battery page
- Home's Battery card opens a new page: a header with the percent, the voltage and one status line ("On battery · 9h 40m left", "Charging · ~45m to full", "USB power").
- Six cells: time left (or to full), the rate, how long it has been in this state, today's low and high, the last full charge and the screen-on share. On the M9 the last cell shows Battery saver while it is on.
- A graph over 1 hour, 6 hours, 24 hours or 7 days, in % or volts, with markers for plug-in, unplug, full, warnings, restarts and Battery saver changes, and...
Wio L1 Pro build 98 (archive)
Archived from the old WioL1Pro-CVHBuild repository (first published 2026-09-05). These are the Wio Tracker L1 Pro builds from before Cairn; current builds are in the latest Cairn release.
CVHBuild 98 — messages that survive a reboot, and a Nodes page
A Seeed Wio Tracker L1 Pro fork of MeshCore
(nRF52840, SH1106 128×64 OLED, 5-way joystick, buzzer, L76K GNSS), on upstream v1.17.1.
Six builds' worth of work since the last release, all cut the same day: messages now survive a
power cycle, messages you send from the device finally appear in its own list, the Recent page
grew into a Nodes page that also browses saved contacts, and the page indicator is legible.
Build 92 was a DV1-only diagnostic image and was never released, so every target moves from
91 straight to 98.
This fork versions by build number — CVHBuild N, shown on the splash screen — so a device can be
matched to a tag from its own display.
Downloads
| File | Target |
|---|---|
MeshCore-WioL1Pro-CVHBuild98-2026-09-05.uf2 |
Companion, SH1106 OLED — the main build |
MeshCore-WioL1Eink-CVHBuild98-2026-09-05.uf2 |
Companion, Waveshare 2.13" B/W e-ink |
MeshCore-DV1-CVHBuild98-2026-09-05.uf2 |
Companion, 2.42" SSD1309 OLED on the Grove connector |
MeshCore-WioL1RoomServer-CVHBuild98-2026-09-05.uf2 |
Room server, SH1106 OLED |
Flashing: double-tap reset to expose the TRACKER L1 volume and copy the .uf2 onto it. The
"could not copy extended attributes" error is normal, and the volume ejecting itself means the
flash succeeded.
Time the copy. A flash that finishes in about a second with "Device not configured" is a
truncated write and leaves the app bricked — a good flash takes several seconds (≈6 s for the
OLED images, ≈16 s for e-ink). If it was quick, double-tap reset and copy it again.
Added
The un-synced message queue now survives a power-off or reboot. (build 93)
The companion's offline queue holds up to 256 messages that have arrived but that the phone app has
not pulled yet. It lived only in RAM, so switching the node off discarded all of them — including
in the case the queue exists for, a node collecting messages unattended with no app connected.
It is now snapshotted to /offline_q on the way down and restored at the next boot. On the L1 that
file lands on the 2 MB QSPI flash alongside contacts and channels. Records are length-prefixed, so
a queue holding three messages writes a few hundred bytes rather than the whole 45 KB array, and a
write cut short by power loss reads back as fewer messages instead of garbage.
The snapshot is deliberately one-shot — the loader deletes the file after reading it. Queue reads
are destructive (the app takes each message into its own database), so a file left behind would
replay messages the app already had the next time power was lost ungracefully, showing up as
duplicates. Deleting at load means only never-synced messages ever come back.
Hooked into every path that ends in SYSTEM OFF or a reset: the menu power-off and the menu
restart, the app's reboot command, and the serial CLI reboot.
Scope: this survives a clean shutdown, not a battery pull, a brownout or a crash. Closing that
gap needs a debounced write-through, which would put blocking flash I/O in the packet-receive
callback. The device's own on-screen message history is a separate RAM-only list and is still lost
on reboot; only the app-sync queue is restored.
Fixed
A message sent from the device never appeared in its own Messages list. (build 94)
The message store only ever recorded received messages, so a message composed on the device went
out over the radio and left no trace on screen — the conversation showed only the other side of it.
Both send paths now record on success, keyed by the peer rather than by us. That is what threads
it correctly: an outgoing DM stores the recipient's key and name, so it joins that peer's
conversation instead of opening a new one titled with your own node name. Your own messages never
count toward the unread badge, thread rows mark them with >, and the read view titles them
To <peer> rather than showing the peer as if they had sent it.
Sent messages now share the 24-entry ring, so a busy conversation ages received messages out
somewhat sooner than before. That is inherent to showing a conversation rather than an inbox.
The Nodes page age column read 49707d for saved contacts. (build 97)
Caught on hardware the moment the contact list was first shown against a real mesh. The age is
now - last_advert_timestamp in unsigned arithmetic. A node with a drifting clock genuinely
advertises timestamps ahead of yours, and a contact carries whatever that node claimed — so the
subtraction underflowed and wrapped to just under 2^32 seconds, which divides out to about 49707
days. The contacts in question were roughly 3.3 days ahead, exactly the observed wrap.
It could not have shown up on the recently-heard list, where every entry is by definition something
this node received in its own past. Reaching the saved contact list is what made future timestamps
displayable for the first time.
Elapsed time is now clamped at zero, and anything more than a minute ahead of our clock renders as
-, the same dash a never-heard contact gets. Past clock jitter there is no age that can honestly
be stated, and now would have been a confident lie about a node last heard days ago.
Changed
The Recent page is now the Nodes page — recently-heard adverts or saved contacts. (build 95)
Same tab position and icon. The list gains a mode row at the top naming which list is on screen
and how long it is, with Enter switching between them:
> Recent 7 <- Enter switches to Contacts
BaseCamp 2m
KG7XYZ 14m
Repeater-N 1h
Back:exit Enter:ping
The cursor model extends the old one rather than replacing it. With no cursor, Enter still opens the
zero-hop/flood advert chooser exactly as before. Row 0 is the mode row. Rows 1 and beyond are nodes,
where Enter pings. Each position gets its own hint line — at 128px the annotation row holds about 21
characters, so a combined hint was never going to fit.
The mode row scrolls off once the cursor moves down into the list, so it does not permanently cost a
row of a four-row viewport. The cursor wraps through 1 + count rows, so the mode row stays
reachable and an empty Recent list can still be switched over to Contacts.
The contact list is windowed, not materialised — only the rows about to be drawn are fetched, so
350 contacts cost the same four rows of RAM the recently-heard table already used. Ping needed no
new logic: contacts render into the same row type the advert list uses, so both feed one render path
and one ping. Ages now roll over to days as well, since a contact can be weeks stale where a heard
advert never is.
The active page indicator is an inverted block instead of a 1px underline. (build 96)
The underline sat on the bottom row of the panel, where it was hard to tell from the panel edge and
near-impossible to place among six identical icons at a glance. The active icon is now knocked out
of a filled block — the same idiom as the inverted title bar, so the top and bottom of the frame
read alike.
The title bar names the page, not the node. (build 98)
It carried the node's own name on every page — the one thing that never changes — while the
sub-views, which you can get lost in, had no label at all. It now reads Messages, Recent,
Contacts, Radio, Traffic, Spectrum, GPS, Compass, Battery or Settings.
The clock face is the exception: the giant clock already says which page it is, so the bar carries
the node name there. That is also the page the device rests on, so the name stays glanceable
without changing pages.
The Nodes page loses its Recent/Contacts mode row. (build 98)
The title bar names the list now, so the row was saying it twice and costing a row of a four-row
viewport. With it gone the switch needed a home, and every gesture on that page was already spoken
for. The one that freed up cleanly was Enter — the send-advert chooser it used to open also lives
on the Radio page, reachable in the same two keypresses, so moving it off Nodes costs nothing:
- Not in the list — Up/Down switches Recent ↔ Contacts, the same gesture that switches the
sub-view on Clock, Radio and GPS. Enter steps into the list. - In the list — Up/Down moves the cursor, Enter pings, Back steps back out.
The page now behaves like every other page when idle, and like a list once you are in one.
Still open
- The SSD1309 back-button report — Back on the message-reading screen sleeps the display instead
of navigating out — is not fixed here. The input-latch mechanism that looked like the culprit
turned out to be inert on every OLED build, so that lead is closed. The symptom means the key
reaches the screen and is not consumed, so the reading screen's own cancel handling is where to
look next. - No add or remove action on contacts. Auto-add is on by default, so heard nodes are already
saved, and the recently-heard table keeps only a 7-byte key prefix — not enough to encrypt to
anyone. Manual promotion would need the full 32-byte key retained (~400 bytes).
Memory
| Build | Flash | RAM |
|---|---|---|
| Companion (SH1106 OLED) | 468 KB / 692 KB (67.6%) | 150 KB / 230 KB (65.4%) |
| Companion (e-ink) | 488 KB / 692 KB (70.6%) | 154 KB / 230 KB (67.1%) |
| Companion (DV1 / SSD1309) | 475 KB / 692 KB (68.6%) ... |
Wio L1 Pro build 97 (archive)
Archived from the old WioL1Pro-CVHBuild repository (first published 2026-09-05). These are the Wio Tracker L1 Pro builds from before Cairn; current builds are in the latest Cairn release.
CVHBuild 97 — messages that survive a reboot, and a Nodes page
A Seeed Wio Tracker L1 Pro fork of MeshCore
(nRF52840, SH1106 128×64 OLED, 5-way joystick, buzzer, L76K GNSS), on upstream v1.17.1.
Five builds' worth of work since the last release, all cut the same day: messages now survive a
power cycle, messages you send from the device finally appear in its own list, the Recent page
grew into a Nodes page that also browses saved contacts, and the page indicator is legible.
Build 92 was a DV1-only diagnostic image and was never released, so every target moves from
91 straight to 97.
This fork versions by build number — CVHBuild N, shown on the splash screen — so a device can be
matched to a tag from its own display.
Downloads
| File | Target |
|---|---|
MeshCore-WioL1Pro-CVHBuild97-2026-09-05.uf2 |
Companion, SH1106 OLED — the main build |
MeshCore-WioL1Eink-CVHBuild97-2026-09-05.uf2 |
Companion, Waveshare 2.13" B/W e-ink |
MeshCore-DV1-CVHBuild97-2026-09-05.uf2 |
Companion, 2.42" SSD1309 OLED on the Grove connector |
MeshCore-WioL1RoomServer-CVHBuild97-2026-09-05.uf2 |
Room server, SH1106 OLED |
Flashing: double-tap reset to expose the TRACKER L1 volume and copy the .uf2 onto it. The
"could not copy extended attributes" error is normal, and the volume ejecting itself means the
flash succeeded.
Time the copy. A flash that finishes in about a second with "Device not configured" is a
truncated write and leaves the app bricked — a good flash takes several seconds (≈6 s for the
OLED images, ≈16 s for e-ink). If it was quick, double-tap reset and copy it again.
Added
The un-synced message queue now survives a power-off or reboot. (build 93)
The companion's offline queue holds up to 256 messages that have arrived but that the phone app has
not pulled yet. It lived only in RAM, so switching the node off discarded all of them — including
in the case the queue exists for, a node collecting messages unattended with no app connected.
It is now snapshotted to /offline_q on the way down and restored at the next boot. On the L1 that
file lands on the 2 MB QSPI flash alongside contacts and channels. Records are length-prefixed, so
a queue holding three messages writes a few hundred bytes rather than the whole 45 KB array, and a
write cut short by power loss reads back as fewer messages instead of garbage.
The snapshot is deliberately one-shot — the loader deletes the file after reading it. Queue reads
are destructive (the app takes each message into its own database), so a file left behind would
replay messages the app already had the next time power was lost ungracefully, showing up as
duplicates. Deleting at load means only never-synced messages ever come back.
Hooked into every path that ends in SYSTEM OFF or a reset: the menu power-off and the menu
restart, the app's reboot command, and the serial CLI reboot.
Scope: this survives a clean shutdown, not a battery pull, a brownout or a crash. Closing that
gap needs a debounced write-through, which would put blocking flash I/O in the packet-receive
callback. The device's own on-screen message history is a separate RAM-only list and is still lost
on reboot; only the app-sync queue is restored.
Fixed
A message sent from the device never appeared in its own Messages list. (build 94)
The message store only ever recorded received messages, so a message composed on the device went
out over the radio and left no trace on screen — the conversation showed only the other side of it.
Both send paths now record on success, keyed by the peer rather than by us. That is what threads
it correctly: an outgoing DM stores the recipient's key and name, so it joins that peer's
conversation instead of opening a new one titled with your own node name. Your own messages never
count toward the unread badge, thread rows mark them with >, and the read view titles them
To <peer> rather than showing the peer as if they had sent it.
Sent messages now share the 24-entry ring, so a busy conversation ages received messages out
somewhat sooner than before. That is inherent to showing a conversation rather than an inbox.
The Nodes page age column read 49707d for saved contacts. (build 97)
Caught on hardware the moment the contact list was first shown against a real mesh. The age is
now - last_advert_timestamp in unsigned arithmetic. A node with a drifting clock genuinely
advertises timestamps ahead of yours, and a contact carries whatever that node claimed — so the
subtraction underflowed and wrapped to just under 2^32 seconds, which divides out to about 49707
days. The contacts in question were roughly 3.3 days ahead, exactly the observed wrap.
It could not have shown up on the recently-heard list, where every entry is by definition something
this node received in its own past. Reaching the saved contact list is what made future timestamps
displayable for the first time.
Elapsed time is now clamped at zero, and anything more than a minute ahead of our clock renders as
-, the same dash a never-heard contact gets. Past clock jitter there is no age that can honestly
be stated, and now would have been a confident lie about a node last heard days ago.
Changed
The Recent page is now the Nodes page — recently-heard adverts or saved contacts. (build 95)
Same tab position and icon. The list gains a mode row at the top naming which list is on screen
and how long it is, with Enter switching between them:
> Recent 7 <- Enter switches to Contacts
BaseCamp 2m
KG7XYZ 14m
Repeater-N 1h
Back:exit Enter:ping
The cursor model extends the old one rather than replacing it. With no cursor, Enter still opens the
zero-hop/flood advert chooser exactly as before. Row 0 is the mode row. Rows 1 and beyond are nodes,
where Enter pings. Each position gets its own hint line — at 128px the annotation row holds about 21
characters, so a combined hint was never going to fit.
The mode row scrolls off once the cursor moves down into the list, so it does not permanently cost a
row of a four-row viewport. The cursor wraps through 1 + count rows, so the mode row stays
reachable and an empty Recent list can still be switched over to Contacts.
The contact list is windowed, not materialised — only the rows about to be drawn are fetched, so
350 contacts cost the same four rows of RAM the recently-heard table already used. Ping needed no
new logic: contacts render into the same row type the advert list uses, so both feed one render path
and one ping. Ages now roll over to days as well, since a contact can be weeks stale where a heard
advert never is.
The active page indicator is an inverted block instead of a 1px underline. (build 96)
The underline sat on the bottom row of the panel, where it was hard to tell from the panel edge and
near-impossible to place among six identical icons at a glance. The active icon is now knocked out
of a filled block — the same idiom as the inverted title bar, so the top and bottom of the frame
read alike.
Still open
- The SSD1309 back-button report — Back on the message-reading screen sleeps the display instead
of navigating out — is not fixed here. The input-latch mechanism that looked like the culprit
turned out to be inert on every OLED build, so that lead is closed. The symptom means the key
reaches the screen and is not consumed, so the reading screen's own cancel handling is where to
look next. - No add or remove action on contacts. Auto-add is on by default, so heard nodes are already
saved, and the recently-heard table keeps only a 7-byte key prefix — not enough to encrypt to
anyone. Manual promotion would need the full 32-byte key retained (~400 bytes).
Memory
| Build | Flash | RAM |
|---|---|---|
| Companion (SH1106 OLED) | 468 KB / 692 KB (67.6%) | 150 KB / 230 KB (65.4%) |
| Companion (e-ink) | 488 KB / 692 KB (70.6%) | 154 KB / 230 KB (67.1%) |
| Companion (DV1 / SSD1309) | 475 KB / 692 KB (68.6%) | 150 KB / 230 KB (65.4%) |
| Room server | 417 KB / 792 KB (52.6%) | 37 KB / 230 KB (16.1%) |
Testing
All four environments build and the native suite passes 48/48 (40 core + 8 KISS modem). Every image
was verified to report CVHBuild 97 / 5 Sep 2026 against its firmware.elf.
Build 96 was run on a DV1, which is where the age-column wrap above was caught and fixed. Every
page renders correctly there.
Still unconfirmed: power the node off and back on with unread messages queued and check the app
still pulls them; send a message from the device and check it threads into the conversation; and
scroll a contact list longer than four rows on the Nodes page, since that is where the windowing
arithmetic lives.
Full changelog: see CHANGELOG.md, entries [cvhbuild-v93] through [cvhbuild-v97].
Wio L1 Pro build 91 (archive)
Archived from the old WioL1Pro-CVHBuild repository (first published 2026-08-28). These are the Wio Tracker L1 Pro builds from before Cairn; current builds are in the latest Cairn release.
CVHBuild 91 — the GPS Eco freeze
A Seeed Wio Tracker L1 Pro fork of MeshCore
(nRF52840, SH1106 128×64 OLED, 5-way joystick, buzzer, L76K GNSS), on upstream v1.17.1.
One fix, and it is the reason nodes were going dark: a node running GPS Eco froze for good at its
first ten-minute re-wake. Everything else is build 90.
This fork versions by build number — CVHBuild N, shown on the splash screen — so a device can be
matched to a tag from its own display.
Downloads
| File | Target |
|---|---|
MeshCore-WioL1Pro-CVHBuild91-2026-08-25.uf2 |
Companion, SH1106 OLED — the main build |
MeshCore-WioL1Eink-CVHBuild91-2026-08-25.uf2 |
Companion, Waveshare 2.13" B/W e-ink |
MeshCore-DV1-CVHBuild91-2026-08-25.uf2 |
Companion, 2.42" SSD1309 OLED on the Grove connector |
MeshCore-WioL1RoomServer-CVHBuild91-2026-08-25.uf2 |
Room server, SH1106 OLED |
All four images carry the fix — it is not specific to one panel.
Flashing: double-tap reset to expose the TRACKER L1 volume and copy the .uf2 onto it. The
"could not copy extended attributes" error is normal, and the volume ejecting itself means the
flash succeeded.
Time the copy. A flash that finishes in about a second with "Device not configured" is a
truncated write and leaves the app bricked — a good flash takes several seconds (≈6 s for the
OLED images, ≈16 s for e-ink). If it was quick, double-tap reset and copy it again.
Fixed
GPS Eco stopped killing the node ten minutes in.
EnvironmentSensorManager::start_gps() opened with an unconditional Serial1.end(), on the
reasoning that closing first is harmless whether or not the port was already open. It is not
harmless. This core's Uart::end() has no _begun guard: it clears EVENTS_TXSTOPPED and
EVENTS_RXTO, fires TASKS_STOPRX/TASKS_STOPTX, then spins on
while ( !(nrfUart->EVENTS_TXSTOPPED && nrfUart->EVENTS_RXTO) ) yield();
A disabled UARTE does not execute tasks, so on an already-closed port neither event ever arrives and
the call never returns. yield() is taskYIELD(), so the loop task spins at its own priority for
ever: display frozen or dark, joystick dead, node off the mesh — while the SoftDevice and TinyUSB
keep running on their own tasks, which is why the USB serial port stays enumerated through it and
the node looks asleep rather than crashed. Nothing recovers it; only a reset does.
Eco mode walks straight into it. stop_gps() closes the port at the end of each fix window, and
GPS_ECO_INTERVAL_MS (10 minutes) later the eco loop calls start_gps() — the first eco re-wake
is the hang. The same path is reachable by hand in any GPS mode: Settings → GPS On → Off → On
freezes on the second On, which is the thirty-second reproduction and the way to confirm the fix on
a flashed device.
Both Serial1.end() calls are now guarded with if (Serial1) — Uart::operator bool() returns
_begun, so the port is only closed when it is actually open.
Present since build 73 (the "GPS UART closes with the GPS" change, 2026-08-13) in every L1
image, not just the SH1106 one. It only fires on a device that is in Eco, or that has had GPS
toggled off and on again — which is why it read as intermittent.
Memory
| Build | Flash | RAM |
|---|---|---|
| Companion (SH1106 OLED) | 466 KB / 692 KB (67.3%) | 150 KB / 230 KB (65.4%) |
| Companion (e-ink) | 486 KB / 692 KB (70.3%) | 154 KB / 230 KB (67.1%) |
| Companion (DV1 / SSD1309) | 473 KB / 692 KB (68.3%) | 150 KB / 230 KB (65.4%) |
| Room server | 417 KB / 792 KB (52.6%) | 37 KB / 230 KB (16.1%) |
Testing
All four environments build and the native suite passes 48/48 (40 core + 8 KISS modem). The
mechanism is certain from the core's source rather than inferred from symptoms, but the fix has
not yet been confirmed on hardware — Settings → GPS On → Off → On is the check to run first.
Full changelog: see CHANGELOG.md, [cvhbuild-v91].
Wio L1 Pro build 83 (archive)
Archived from the old WioL1Pro-CVHBuild repository (first published 2026-08-15). These are the Wio Tracker L1 Pro builds from before Cairn; current builds are in the latest Cairn release.
CVHBuild 83 — an e-ink that rests, a third panel, and MeshCore v1.17.1
A Seeed Wio Tracker L1 Pro fork of MeshCore
(nRF52840, SH1106 128×64 OLED, 5-way joystick, buzzer, L76K GNSS).
Builds 68–83. The theme is e-paper: the panel now stops refreshing when nobody is reading it, and
it draws values and annotation in different faces so a 250×122 screen reads like one. There is a
third panel option, a 2.42" SSD1309 OLED. The base moves from upstream v1.17.0 to v1.17.1.
This fork versions by build number — CVHBuild N, shown on the splash screen — so a device can be
matched to a tag from its own display.
Downloads
| File | Target |
|---|---|
MeshCore-WioL1Pro-CVHBuild83.uf2 |
Companion, SH1106 OLED — the main build |
MeshCore-WioL1Eink-CVHBuild83.uf2 |
Companion, Waveshare 2.13" B/W e-ink |
MeshCore-WioL1-1309-CVHBuild83.uf2 |
Companion, 2.42" SSD1309 OLED |
MeshCore-WioL1RoomServer-CVHBuild83.uf2 |
Room server, SH1106 OLED |
Flashing: double-tap reset to expose the TRACKER L1 volume and copy the .uf2 onto it. The
"could not copy extended attributes" error is normal, and the volume ejecting itself means the
flash succeeded.
Upgrading from CVHBuild 67 keeps your settings — the preferences format is unchanged across this
span. One room-server nuance is noted under Upstream v1.17.1.
The tri-colour e-ink variant is no longer produced. Every refresh on a three-colour panel is a
blocking ~15 s full flash, which makes live pages snapshots rather than displays. The environment
and its EINK_3C paths remain in the tree and still build.
The e-ink panel finally rests
E-paper holds its image with no power, so the cost of a screen is not being on — it is
refreshing. Four separate things were refreshing an idle panel, each for its own reason, and none
of them was the screen-timeout setting:
- The Radio page redrew every 30 s forever. Spectrum sampling lives inside
render(), so one
column is one redraw. That changed the frame, which defeated the driver's CRC gate, which cost a
refresh on a page nobody was reading. Sampling is now gated on recent input and the readouts
hold their captured values, so the frame goes byte-identical and the refresh is skipped. - The clock asked for a redraw on every minute boundary before consulting the timeout — the
comment said "always". That was the periodic refresh that could not be switched off. - The message preview repainted every 10 seconds, ungated. It appears by itself when a message
lands and never dismisses itself, and drew a seconds-resolution age — six refreshes in the first
minute, then one a minute for as long as it stayed up. - The screen timeout was compiled out entirely on e-ink, on the reasoning that there is no
panel to blank. On e-paper that is the wrong question. One predicate,isScreenLive(), now
governs both builds: live when the timeout is Never, live while plugged in with keep-on-USB set,
otherwise live until the timeout expires since the last input. It replaced three hardcoded
60-second windows, so a bench node on USB keeps a live screen and a pocket node goes quiet.
The clock says SLEEPING rather than freezing. Simply stopping the clock would have been
worse than refreshing it: e-paper holds the last thing drawn, so a clock that stops ticking does
not go quiet, it goes wrong, and drifts further from the truth every minute it sits there.
SLEEPING costs one refresh to put up, after which every frame is identical and the CRC gate
suppresses all of them. Any key brings the real clock back on the next frame.
One honest caveat: the status bar is still drawn while asleep, so an arriving message can still
change the unread badge and cost a refresh. That is wanted — the count beside SLEEPING is
useful — but a busy mesh is not perfectly silent.
E-ink typography
The e-ink build draws the same 128×64 logical canvas as the OLED, so the bigger panel makes
everything larger, not roomier. What buys space is that its font is proportional and narrower
per glyph than the OLED's fixed 6 px advance.
- Values are drawn in bold, labels and units in a smaller face. Differentiating by weight
turned out to be the better lever:FreeSansBold9pt7bshares its metrics and its origin with the
regular face, so there is no second baseline to reconcile and no risk of a value overrunning its
column. There is no font one point above the built-in 6×8 cell — the whole bundled ladder below
9pt is Tiny3x3, TomThumb, Picopixel and Org_01, all smaller — so an intermediate size would
have meant generating and bundling a 7pt face. Bold sidesteps the question. - The treatment runs across the whole UI, not just the Radio page: clock and battery, GPS and
compass, the message list, thread, read view, quick reply and both compose stages, contacts,
settings and telemetry. Anything that names or qualifies a value — counters, ages, column
headers, keybind hints, units — drops to the small face, and column values right-justify onto a
shared rule. - Units get a reserved column the width of the widest unit on the page, rather than being
appended.MHz,kHz,dBmanddBare all different widths, so appending would push each
row's digits left by a different amount and unstack the column that reserving keeps aligned. - Header badge digits shrank. They annotate 7-row icons but were drawn in the value font,
standing a head taller than the glyph they belong to. - The read-view footer moving to the small cell is a clipping fix as well as a restyle: at 9pt
a glyph on row 56 descends past row 64.
Every one of these helpers carries a guarded contract — off e-ink, or on a board without a usable
small face, each is exactly the call it replaced, so the OLED build is byte-for-byte unaffected.
New: 2.42" SSD1309 build
WioTrackerL1_1309_companion_radio_ble drives a 2.42" SSD1309 OLED. It wears the e-ink
typography while behaving in every other respect like an OLED — isEink() stays false, so
blanking, brightness, the fast refresh cadence and every non-e-ink path apply unchanged.
That combination forced a real fix: the UI had been gating its layout on
UI_HAS_SMALL_FACE && isEink(), which conflates "this panel has a second, smaller face" with
"this panel is e-paper". Four defaulted DisplayDriver virtuals now separate them —
hasSmallFace(), smallFaceDrop(), smallFaceGap() and hasBoldFace(). Bold is a separate
capability from small precisely because this panel has one and not the other.
What the 128×64 panel would and would not take:
- TomThumb (3×5) for annotation, chosen over Picopixel and Org_01 by pixel-identity test across
all 62 alphanumerics: Picopixel collides0/O, and Org_01 collides0/O,1/l,2/Zand
5/S— on a page printingSF 5andCR 2, that matters. - No bold. Emulating weight by 1-px dilation lands on the right ink density but pushes 60 of 68
glyphs into their single inter-glyph gap —RSSIbecomes one continuous bar — and 20 lose their
counters. At 5×7 the stem, counter and sidebearing are each already one pixel wide. - Baseline drop is 2 here rather than the e-ink's 3, and the unit gap is 0 rather than 2, because
the fixed 6-px cell already carries a blank trailing column that a proportional face does not.
Upstream v1.17.1
Everything in upstream's release. What reaches this hardware:
- Fixed upstream: replies were dropped when
flood.max.unscopedwas low. A scoped reply was
being held to the unscoped hop limit, so on a mesh configured to damp unscoped flooding, the
answer to a request could die before reaching the asker. Reply-routing moved into a shared
RoutingPolicy.hwith 14 unit tests behind it — the native suite goes from 33 cases to 40. - The nRF52 hardware crypto engine is initialised once, at board startup, instead of on every
call;nRFCrypto.begin()/.end()used to bracket each individual hash, encrypt and decrypt. - The CC310 random number generator is mixed with the radio's entropy rather than trusted alone.
- FEM gain preferences arrive for boards with an external front end, and they land differently
in the twoNodePrefs. The companion's copy has them#if 0'd upstream until they can actually
be set, so it writes nothing new; the shared prefs used by the room server, repeater and sensor
carry both keys live. Youruisettings are untouched either way — the serialiser is named-key,
so added keys are additive. - Fixed upstream:
fem_rxgainwas aliased to the wrong field in the shared prefs. The key
claimed to describe the external front end while actually reading and writing the SX126x
boosted-gain setting. If you run the room server, note that it stops applying a stored
fem_rxgaintorx_boosted_gain— the correctly-namedrxgainkey still carries that, and both
had been written with the same value, so your setting survives.
On this hardware the new FEM calls are no-ops regardless — virtuals on mesh::MainBoard defaulting
to false, which WioTrackerL1Board overrides neither of.
The merge itself was cheap: one conflict, and not a disagreement — upstream added its two FEM gain
defaults at exactly the point this fork sets its five UI prefs defaults. Both blocks kept.
Security
Defects in the upstream v1.17.0 base, fixed here and still unfixed upstream:
Utils::decryptrejects a ciphertext length that is not a multiple of the...
Wio L1 Pro build 67 (archive)
Archived from the old WioL1Pro-CVHBuild repository (first published 2026-08-10). These are the Wio Tracker L1 Pro builds from before Cairn; current builds are in the latest Cairn release.
CVHBuild 67 — now on MeshCore v1.17.0, plus a room server build
A Seeed Wio Tracker L1 Pro fork of MeshCore
(nRF52840, SH1106 128×64 OLED, 5-way joystick, buzzer, L76K GNSS).
Rebased from upstream v1.16.0 onto v1.17.0 (320 upstream commits), and adds a room server
build that uses the same paged UI as the companion instead of a single static screen.
This fork versions by build number — CVHBuild N, shown on the splash screen — so a device can be
matched to a tag from its own display.
Downloads
| File | Target |
|---|---|
MeshCore-WioL1Pro-CVHBuild67.uf2 |
Companion, SH1106 OLED — the main build |
MeshCore-WioL1Eink-CVHBuild67.uf2 |
Companion, Waveshare 2.13" B/W e-ink |
MeshCore-WioL1RoomServer-CVHBuild67.uf2 |
Room server, SH1106 OLED |
Flashing: double-tap reset to expose the TRACKER L1 volume and copy the .uf2 onto it. The
"could not copy extended attributes" error is normal, and the volume ejecting itself means the
flash succeeded.
The tri-colour e-ink variant is no longer produced. Every refresh on a three-colour panel is a
blocking ~15 s full flash, which makes live pages snapshots rather than displays. The environment
and its EINK_3C paths remain in the tree and still build.
New: room server build
WioTrackerL1_room_server previously drew one static screen — node name, frequency, bandwidth —
with no pages and no joystick handling, even though the hardware inputs were already wired up for
any build with a display. It now has five pages, chosen for what a server that sits in one place
actually has rather than by copying the companion:
| Page | Contents |
|---|---|
| Clock | clock face, date, client count; sub-view for battery, uptime and post counters |
| Posts | the 32 stored posts, scrollable, Enter opens the full text word-wrapped |
| Clients | the ACL — key, admin flag, hops, unsynced posts, last seen; Enter opens a detail view |
| Radio | freq/BW/SF/CR grid, live RSSI spectrum, traffic monitor, fullscreen spectrum |
| Settings | advert, GPS, set clock, TX power, brightness, screen timeout, clock style |
Left/Right changes page, Up/Down scrolls or switches sub-view, Enter acts, hold Back locks.
Setting the clock. A room server has no companion app to push the time down, so previously the
only route was time <epoch> over the serial or admin CLI. There is now a GPS on/off setting —
the NMEA provider is constructed with the RTC, so a fix sets the clock — and a manual editor with
year/month/day/hour/minute cells for when there is no fix. Time is entered as local and stored as
UTC.
Identities are public-key prefixes (e.g. A3F2). The room server stores no names: neither
ClientInfo nor PostInfo has one, so that is the whole of what the device knows. The client
detail view shows eight bytes when you need to identify a specific node.
Upstream v1.17.0
Everything in upstream's release, plus these fork-specific consequences:
- Preferences moved to named-key JSON. Upstream replaced the byte-offset preferences struct
with a serialiser. This fork's eight UI settings moved into their ownuigroup, and the legacy
reader still parses them at their former offsets — so a device upgrading from CVHBuild 56
keeps its settings, and the first save migrates the file. - Colours come from a semantic palette rather than an abstract enum the driver reinterpreted.
No visible change on the OLED; on e-ink it removes the light/dark inversion hack, because the
palette carries the inversion instead. - Fixed: the contact list dropped its last eight entries. Upstream reserved eight slots at the
head of the contact array and madegetNumContacts()exclude them, whilegetContactByIdx()
still indexes the raw array. Three loops here paired the two, so they walked eight reserved slots
at the start and stopped eight short of the end — silently hiding the most recent contacts from
the compose picker, the recipient count and room re-login.
Security
- The destination bound on
Utils::decrypt()now also covers upstream's new CC310 hardware AES
path. Both write whole 16-byte blocks, so both can overrun a short destination; the check sits
above the branch rather than inside one arm of it. Upstream still carries no bound of its own. - Upstream independently fixed the BLE receive-queue race, which arrives with this merge.
Upstream defects found
Both break the build only on macOS/libc++, which is why upstream's Linux CI is green:
ConfigSerializer.cppcallsatoi/atol/atofwithout including<stdlib.h>.test/mocks/Utils.hdeclaresMACThenDecrypt()with the old unbounded signature.
E-ink companion
- The clock face is drawn, not typed. 24pt was the largest font available and pixel-doubling a
1-bit face stair-steps every edge, so the digits are now seven-segment shapes built from
rectangles: crisp at any size, and no flash cost. 46 panel rows tall, against 25 before. - The clock page became a resting screen. A reflective panel is on permanently and refreshes
about once a minute, so it now carries what you would otherwise change pages to read: date and
uptime with battery percentage and voltage, then unread and node counts with GPS state and die
temperature. Each row measures itself and drops its least important token rather than letting
two strings overlap. - Fixed: auto lock never engaged. It was implemented inside the screen-blanking path, and
e-paper never blanks — it holds its image with no power — so the whole block was compiled out
while the setting still readOn. Locking is about a device in a pocket, not about the panel. - The screen timeout and keep-awake-on-USB settings now read
n/awhere nothing blanks, instead
of accepting values that nothing acted on.
Memory
| Build | Flash | RAM |
|---|---|---|
| Companion (OLED) | 467 KB / 692 KB (67.5%) | 150 KB / 230 KB (65.3%) |
| Companion (e-ink) | 483 KB / 692 KB (69.9%) | 154 KB / 230 KB (67.1%) |
| Room server | 416 KB / 792 KB (52.5%) | 37 KB / 230 KB (16.1%) |
Testing
The OLED companion and the B/W e-ink companion are running on hardware. The room server has been
run on hardware and its Clock, Radio and Settings pages exercised; its Posts and Clients pages
cannot be fully validated until real clients log in and post, so those layouts are verified
arithmetically rather than visually. The tri-colour e-ink variant has never been run.
Upgrading from CVHBuild 56 migrates the preferences file on first save. The old file is left in
place, so reflashing 56 gets you back to a device that reads the original.
Full changelog: see CHANGELOG.md, builds 57–67.
Wio L1 Pro build 56 (archive)
Archived from the old WioL1Pro-CVHBuild repository (first published 2026-08-09). These are the Wio Tracker L1 Pro builds from before Cairn; current builds are in the latest Cairn release.
Changelog
Notable changes in this fork of MeshCore, targeting the
Seeed Wio Tracker L1 Pro (nRF52840, SH1106 128×64 OLED, joystick, buzzer, L76K GNSS).
Based on upstream MeshCore v1.16.0 (companion). Format follows
Keep a Changelog. This fork versions by build number —
CVHBuild N, shown on the splash screen — so a device can be matched to a tag from its own display.
Build environments:
| Env | Target |
|---|---|
WioTrackerL1_companion_radio_ble |
SH1106 OLED (primary) |
WioTrackerL1Eink_companion_radio_ble |
Waveshare 2.13" B/W e-ink |
WioTrackerL1Eink3C_companion_radio_ble |
Waveshare 2.13" tri-colour e-ink |
[cvhbuild-v56] — 2026-08-09
Builds 48–56. Only build 48 was flashed and tested on hardware; 49–56 are staged.
Added
Bluetooth
- The BLE pairing pin can be set on the device. The Settings row was previously hardcoded
read-only, so the pin could only be changed from the phone app. Six digit cells: Left/Right
walks them, Up/Down cycles the one under the caret through 0–9.000000means auto, where
the device picks a pin each session. - Pin changes apply without a reboot, via a new
BaseSerialInterface::setPin()implemented
for nRF52 and ESP32. Devices already bonded keep their keys and stay paired — the pin gates
new pairings only. - The app's
CMD_SET_DEVICE_PINand theset pinserial command now route through the same
MyMesh::setBLEPin(), so all three paths validate, persist and apply identically.
Display
- Brightness setting, 10/25/50/75/100 %, persisted at
NodePrefsbyte 146. The SH1106 has
no backlight, so this drives the contrast register (0x81) that sets segment current — a
real power lever, not just a comfort one. Applied live while stepping; Back restores.
Percentages are perceived brightness, squared on the way to the register (10 % → 2,
50 % → 63, 100 % → 255), because eye response to luminance is a power law and a linear map
leaves every visible step crowded into the bottom fifth of the range. - New
DisplayDriver::hasBrightness()/setBrightness()virtuals, defaulting to no-ops. The
row readsn/aon e-ink, which is reflective and has nothing to turn up.
Radio page
- Third sub-view: fullscreen spectrum. Up/Down now cycles settings+spectrum → traffic →
fullscreen. 24 px graph with a dotted peak-hold rule, live channel RSSI and noise floor, and
the peak/low of the window in dBm. - Enter sends an advert from any Radio sub-view, using the Recent page's existing zero-hop vs.
flood chooser. Nothing transmits until the choice is confirmed.
Battery view
- Discharge graph replacing the fill-bar, with a dotted 20 % reference line and an
estimated time remaining. The bar only restated a percentage already printed twice on the
same page; the trend is the part a number cannot give you. - The buffer is self-scaling: 63 slots, 126 bytes, allocated once. Sampling starts at 20 s
spacing and each time the buffer fills it discards every other sample in place and doubles
the spacing — 21 min of window at first, ~11 h after five compactions, ~90 h after eight. It
never grows, wraps or resets.
Changed
- Key lock is two gestures instead of one toggle: hold Back to lock, double-click Back to
unlock. Double-click no longer locks.UITask::toggleLock()was removed so nothing can
reintroduce the toggle. Triple-click still unlocks, for a fumbled double. - Radio page values are laid out on a real column grid — labels on a left rule, values
right-aligned onto a common rule, split by a vertical divider — so digits stack vertically.
Units were dropped from the values, which is what makes the numbers align rather than merely
the fields. Previously the second column was padded inside a singlesprintf(), so
BW 250.00versusBW 7.80shiftedCRsideways. - Spectrum samples are captured at the fullscreen graph's resolution and divided down for the
compact band, instead of being captured at the compact 7 px resolution and blown up. setBLEPin()accepts0–999999rather than0or100000–999999. A BLE passkey is a
six-character field, so012345is a real pin; both backends already format to six digits.
Fixed
- The GPS was left running through hibernate. nRF52 System OFF retains GPIO levels, so a
GPS enabled at power-off kept acquiring at tens of milliamps on a device that looked off.
It is now stopped beforepowerOff(), without touching prefs, so the chosen mode returns on
the next boot. start_gps()/stop_gps()are idempotent. Re-selecting the mode already active leaked a
ref-counted claim on the shared peripheral rail that the matching stop could never undo, and
re-ran_location->reset(), forcing a cold start on boards with a GPS reset pin.- E-ink layout occupied roughly two thirds of the panel.
EINK_SCALE_Ywas 1.28 for a
250×122 panel showing a 128×64 canvas, mapping 64 rows onto 82 pixels and leaving the bottom
third blank, while crushing the 9 px row pitch to 11.5 physical px against a font needing 18.
Now 1.90625 (122 ÷ 64). - E-ink text baselines are derived from each font's ascent (
EINK_TEXT_BASELINE_AUTO) instead
of a hand-tunedEINK_Y_OFFSETthat could only line up for one text size. Adafruit_GFX
places custom fonts on the baseline while every caller passes the top-left corner. - New
EINK_LOGICAL_W/EINK_LOGICAL_Hreport the canvas the UI was drawn for, so anything
sizing itself offheight()— the alert popup — is no longer given 128 on a 64-row layout.
[cvhbuild-v47] — 2026-08-06
First tagged release. Everything below is relative to upstream v1.16.0.
Added
Display & text
- UTF-8 text folding for the CP437 bitmap fonts, replacing the previous
"every non-ASCII byte becomes a solid block" behaviour. Typographic punctuation folds to
ASCII (don’t→don't,…→..., en/em dashes →-), accented Latin folds to base
letters (café→cafe,straße→strasse), and ~40 emoji map to real font glyphs
(❤ → ♥, 🙂 → ☺, 🎵 → ♫, 👍 →+1, arrows →← → ↑ ↓). - Cyrillic and Greek romanisation —
Привет→Privet,Ретранслятор→Retranslyator,
Αθήνα→Athina. Covers the main Cyrillic block, the Ukrainian/Serbian/Macedonian
extensions, and Greek including final sigma and accented vowels. The table is Russian-style,
so Ukrainianиromanises as i rather than y. - Context-dependent handling for emoji with no font glyph: dropped from names (a marker is
just noise in front of the word), shown as a bullet in message bodies (silently dropping
what a sender wrote would change the message). Names fall back to blocks only when a name
would otherwise render empty.
UI pages
- Clock page with 12h / 24h / analogue styles and a persisted UTC offset, plus a battery
sub-view (percentage, charge bar, millivolts, power source, uptime, CPU die temperature). - Recent page listing heard nodes, with per-node ping, a zero-hop/flood advert chooser, and a
confirm-guarded clear. - Radio page with a live channel-RSSI spectrum anchored to the noise floor, and a traffic
monitor sub-view tabulating the last frames by type, route, hops, RSSI, SNR and age. - GPS page with satellite bar, speed, altitude, and a course-over-ground compass sub-view.
- Settings page consolidating Bluetooth, GPS, tones, screen, clock, region, TX power, BLE pin,
auto-lock and hibernate, with in-place editing and confirm-on-commit for destructive changes. - Node-to-node ping from the UI, over telemetry request for known chat contacts and trace
otherwise, reporting round-trip time and SNR.
GPS
- Eco mode — a third GPS state that duty-cycles the receiver (10 min interval, 90 s fix window,
15 s settle) instead of running it continuously. No preferences are written per cycle. - Cached last-known fix, so the GPS page shows coordinates and the fix time while the receiver
is powered down between eco cycles, rather than a countdown. - Speed and course over ground added to
LocationProvider(getSpeedKnotsX1000(),
getCourseDegX1000()), defaulting to unsupported for providers that cannot supply them.
Messaging
- Conversation threading, a message read view with scrolling, and quick-reply presets.
Wake on msgsetting — whether an incoming message may light the screen. Messages never
take over the display during active use, and a screen switched off deliberately with Back
stays off.
Power & display
- Configurable screen timeout, keep-awake-while-USB-powered, and auto-lock on timeout.
E-ink
- Two e-ink variants (B/W and tri-colour) with activity-aware refresh pacing and periodic
full refreshes to clear ghosting.
Changed
- Settings item
Screen offis nowScreen, a submenu covering timeout, USB behaviour and
message wake. - Altitude in feet, speed in mph, temperature in °F, battery as a percentage.
Fixed
- Contact names in the compose recipient picker were rendered without UTF-8 filtering, sending
raw bytes to a CP437 font and producing mojibake. Every other name path was already filtered.
Security
These harden upstream code and are not specific to this board.
- Decryption wrote past the destination buffer.
Utils::decrypt()always writes whole
16-byte blocks, but neither it norMACThenDecrypt()knew how large the destination was.
Both now take adest_sizeand refuse to write when the blo...
Wio L1 Pro build 40 (archive)
Archived from the old WioL1Pro-CVHBuild repository (first published 2026-08-06). These are the Wio Tracker L1 Pro builds from before Cairn; current builds are in the latest Cairn release.
Initial Release. Please view the ReadMe