mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-09-19 17:46:38 +00:00
fix(ui): keep a gutter beside chat bubbles; docs + release notes catch-up for v1.28
- Message bubbles are capped at the row width minus a gutter (2 char widths, min 8px) so a long message no longer fills the whole row and loses the left/right anchoring that shows who sent it. Body wrapping uses the capped width. DM and channel history. - Docs: shared scope list (Settings, Repeater Extra scopes, channel Scope row), contact Expire / Prune now, history hop count / wrap / scope tag, external PA TX curve build flags. - release-notes.md: v1.28 gains GAT562 30S TX power, Heltec V3/V4 joystick Enter fix, prefs-migration removal note, and the bubble gutter fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -91,6 +91,23 @@ layered on top of GPS support, not a requirement for it.
|
||||
|
||||
---
|
||||
|
||||
### External PA / TX power curve
|
||||
|
||||
For a board with an always-on external power amplifier after the SX1262. Both
|
||||
flags are needed together; leave them unset on a bare-SX1262 board.
|
||||
|
||||
| Flag | Meaning |
|
||||
| --- | --- |
|
||||
| `LORA_TX_POWER=<dBm>` / `MAX_LORA_TX_POWER=<dBm>` | Default and ceiling for the TX power the app/CLI may request. The SX1262 alone tops out at 22; a board with a PA sets these to what the PA really delivers (the GAT562 30S sets 30). |
|
||||
| `NUM_PA_POINTS=<n>` + `TX_GAIN_LORA=<g0,g1,…>` | The PA's measured gain in dB for each SX1262 register setting `0 … n-1`. Requesting *X* dBm picks the lowest register setting whose `setting + gain` reaches *X*, clamped at the last entry once the PA saturates — so the reported power matches the radiated one. A request below the PA's floor gain still radiates at that floor, and the value stored/reported back is the power actually applied. |
|
||||
|
||||
The GAT562 30S uses the vendor-measured 869 MHz curve from
|
||||
[meshtastic/firmware#11212](https://github.com/meshtastic/firmware/pull/11212).
|
||||
Note the on-device **Settings › Radio › TX Pwr** row is still capped at 22 dBm;
|
||||
values above that are set from the phone app or the CLI.
|
||||
|
||||
---
|
||||
|
||||
### Misc
|
||||
|
||||
| Flag | Meaning |
|
||||
|
||||
@@ -62,7 +62,9 @@ Posting to a **room server** needs a login handshake — the device does this on
|
||||
| :-----------------------: | :-----------------------: |
|
||||
|  |  |
|
||||
|
||||
Messages appear as chat bubbles sized to their content — **right**-anchored for outgoing, **left** for incoming — with sender name and a compact age indicator (`3m`, `2h`, `>1d`) in the top-right corner. List runs **newest at the bottom**; opening a history starts at the latest message, scrolling up goes further back.
|
||||
Messages appear as chat bubbles sized to their content — **right**-anchored for outgoing, **left** for incoming — with sender name and a compact age indicator (`3m`, `2h`, `>1d`) in the top-right corner. List runs **newest at the bottom**; opening a history starts at the latest message, scrolling up goes further back. The list wraps at both ends like every other list: **UP** at the oldest message jumps to the newest, and **DOWN** past the newest lands on the compose row and then wraps to the oldest.
|
||||
|
||||
A tiny digit icon on a bubble is its hop count: on your own messages, how many repeaters echoed them back; on a **received** DM or channel post, how many hops it took to reach you. A received message always shows a time — if its timestamp is unknown or reads slightly ahead of this device's clock (sender/receiver clock skew, or the clock isn't synced yet), the receipt time is shown instead. In a channel history the title carries the channel's scope in brackets (`name [scope]`) whenever it is set to anything but `*`.
|
||||
|
||||
**Short Enter** on a message opens it in fullscreen. **Hold Enter** — on a history row or in fullscreen — opens the same options menu: Reply, plus **Navigate** / **Save waypoint** / **Set as target** when the message contains a location, and **Path** / **Relayed by** when hop data is available (see Fullscreen message view). You don't need to open the message first.
|
||||
|
||||
@@ -146,6 +148,7 @@ In the **Rooms** list the context menu instead offers:
|
||||
| Notif: Default / OFF / ON | Per-channel notification override — **LEFT/RIGHT** or **Enter** to cycle |
|
||||
| Melody: Global / M1 / M2 | Per-channel melody override — **LEFT/RIGHT** or **Enter** to cycle |
|
||||
| Fav: ON / OFF | Add or remove this channel from favourites — **LEFT/RIGHT** or **Enter** to toggle |
|
||||
| Scope: <name> | **Enter** opens a picker over the shared scope list (Settings › Radio › Scope) — `*` sends this channel unscoped, any named scope tags its flood traffic with that region. Each channel keeps its own pick, matching the phone app's per-channel region picker. |
|
||||
| Pin to dial / Unpin (slot N) | Pin this channel to a [Favourites Dial](../favourites_dial/favourites_dial.md) slot |
|
||||
| Edit | Opens the Add/Edit form below, pre-filled with the channel's name |
|
||||
| Delete | Removes the channel — confirms first (defaults to Cancel) |
|
||||
|
||||
@@ -65,7 +65,7 @@ Lists all available home screen pages. For each entry:
|
||||
|
||||
| Setting | Options | Notes |
|
||||
| --------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| TX Pwr | 2–22 dBm | LEFT/RIGHT. With **Auto pwr** on this is the *ceiling* — the radio may transmit lower. |
|
||||
| TX Pwr | 2–22 dBm | LEFT/RIGHT. With **Auto pwr** on this is the *ceiling* — the radio may transmit lower. On the **GAT562 30S** (external 30 dBm PA) the requested power maps through the PA's measured gain curve, so the stored value matches what is radiated; this row stays capped at 22, while the phone app / CLI can request up to 30 dBm — see [Build Flags › External PA](../build_flags.md#external-pa--tx-power-curve). |
|
||||
| Preset | named presets | LEFT/RIGHT cycles community RF presets (region frequency + bandwidth/SF/CR). **Enter** opens a popup to pick one, save the current settings as a named preset, or delete a saved one — deleting confirms first (defaults to Cancel). Applies frequency, bandwidth, SF and CR together. |
|
||||
| Freq | chip range | **Enter** opens a digit-by-digit editor: LEFT/RIGHT moves between decimal places, UP/DOWN steps that digit. Bounds come from the radio chip's own validated range, so a value the radio would reject can't be entered. |
|
||||
| SF | 5–12 | LEFT/RIGHT. Spreading factor. |
|
||||
@@ -73,7 +73,7 @@ Lists all available home screen pages. For each entry:
|
||||
| CR | 5–8 | LEFT/RIGHT. Coding rate (4/5–4/8). |
|
||||
| Pwr save | ON / OFF | **Battery saver.** Hardware duty-cycle receive (SX126x only): cycles RX↔sleep, wakes on preamble, cuts average RX current at the cost of some latency. **Forced off (`--`) while the repeater is on** — restored once it's switched off. A background watchdog auto-recovers if the sequencer gets stuck (soft re-arm, then a full reset) — see Tools › Diagnostics for the counts. |
|
||||
| Auto pwr | ON / OFF | **Adaptive Power Control.** Lowers TX power on strong links, ramps back to the **TX Pwr** ceiling on weak/lost ones. Link quality from DM ACK SNR, or — for channels (no ACK) — a repeater's rebroadcast. Live power shown on the radio page/name bar. Default OFF. **Suppressed (`--`) while the repeater is on** — restored once it's switched off. |
|
||||
| Scope | text | Names your device's radio "community" (e.g. `pl`) — typing a name derives a shared key the same way on every device, so any device that types the same name lands on the same scope automatically, no key exchange needed. Tags this device's own DM/channel sends so repeaters can tell your community's flood traffic apart from others sharing the same frequency; paired with **Tools › Repeater › Scope only**, it's also what this device relays for in repeater mode. Not encryption — message content is unaffected either way. **Enter** opens the keyboard; empty clears it (unscoped, the previous default). |
|
||||
| Scope | list | Shows the list's **default** scope. **Enter** opens the **SCOPE** list: `*` (wildcard = unscoped, always first, can't be renamed or deleted) plus up to 8 named scopes of your own (e.g. `pl`). Typing a name derives a shared key the same way on every device, so any device that types the same name lands on the same scope automatically, no key exchange needed. **Enter** on a row opens **Set as default** / **Rename** / **Delete** (delete confirms first); **+ Add scope** at the bottom opens the keyboard. The **default** scope (marked `[default]`) governs **DMs** and the **repeater's own relay slot**; each **channel** carries its own pick — set from the channel's context menu (see [Message Screen](../message_screen/message_screen.md)) — and a channel left on `*` sends unscoped. Scopes tag flood traffic so repeaters can tell your community's messages apart from others sharing the same frequency; paired with **Tools › Repeater › Scope only** it's also what this device relays for in repeater mode. The default also syncs both ways with the phone app's default-scope setting. Not encryption — message content is unaffected either way. Upgrading from a build with the old single Scope field carries it over as the default entry and seeds every existing channel with it, so nothing changes on the air. |
|
||||
|
||||
| OLED | E-Ink |
|
||||
| :-----------------------: | :-----------------------: |
|
||||
@@ -121,11 +121,15 @@ European Latin-diacritic letters (Polish, Czech, Slovak, German, French, Spanish
|
||||
| Channels | All / Fav | Show all channels or only favourited ones |
|
||||
| Rooms | All / Fav | Show all room servers or only favourited ones |
|
||||
| Favs top | ON / OFF | Sort favourites to the top of every list (default ON) |
|
||||
| Expire | Off / 7d / 30d / 90d | Age after which a contact with no advert/update counts as inactive (default Off). Only used by **Prune now** — nothing is ever deleted automatically. |
|
||||
| Prune now | action (**Enter**) | Counts the contacts older than **Expire**, then asks `Remove N contacts?` (defaults to Cancel) before deleting anything. Shows `Expire is Off` / `No inactive contacts` instead when there is nothing to do. |
|
||||
|
||||
Favourites are set per item in its context menu (**Hold Enter** › **Fav: ON / OFF**) — see [Message Screen](../message_screen/message_screen.md), and the same row exists in Tools › Nodes. A contact's or room's favourite flag is the same one the companion app shows as a starred contact, so it syncs both ways; a channel's is device-only.
|
||||
|
||||
A favourite is marked with a ★ on its row wherever it is listed, and — unless **Favs top** is off — sorted above everything else. The three filters above are independent of that: they control what's *listed at all*, the sort only controls the order.
|
||||
|
||||
**Pruning.** A contact is inactive when its last advert/update is older than **Expire**. **Favourites are never pruned**, and neither is a contact with no timestamp or one that reads ahead of the device's own clock (e.g. the clock isn't set yet) — the rule only ever errs on the side of keeping data.
|
||||
|
||||
---
|
||||
|
||||
### Messages
|
||||
|
||||
@@ -542,8 +542,8 @@ Navigate with **UP/DOWN**; change a value with **LEFT/RIGHT** (or **Enter** for
|
||||
| Yield | OFF / x2–x9 | Scales the retransmit delay for **forwarded** floods only (your own sends are unaffected), so a mobile companion defers to better-sited fixed repeaters. Widens the window for **Suppress dup**. |
|
||||
| Min SNR | OFF / −20…10 dB | Drop a flood copy received below this signal-to-noise threshold, so marginal fringe traffic isn't re-flooded. |
|
||||
| Suppress dup | ON / OFF | If the same flood packet is overheard from another node while still queued to retransmit, cancel our copy — a peer already relayed it. Cuts redundant airtime in dense meshes; pairs with **Yield**. |
|
||||
| Scope only | ON / OFF | Only forward flood traffic matching this device's own **Scope** (Settings › Radio) or one of the **Extra scopes** below — drops unscoped floods and floods tagged for a different community. No-op while neither is set, so switching this on with no scope configured can't silently stop all relaying. |
|
||||
| Extra scopes | text | Comma-separated extra region names this repeater also relays for (e.g. `eu,de`), on top of its own Settings › Radio › **Scope** — relay-only, never changes what scope this device's own messages send under. **Enter** opens the keyboard. |
|
||||
| Scope only | ON / OFF | Only forward flood traffic matching this device's own default **Scope** (Settings › Radio) or one of the **Extra scopes** below — drops unscoped floods and floods tagged for a different community. No-op while neither is set, so switching this on with no scope configured can't silently stop all relaying. |
|
||||
| Extra scopes | pick list | Extra scopes this repeater also relays for, on top of its own Settings › Radio default **Scope** — relay-only, never changes what scope this device's own messages send under. Shows `N picked`. **Enter** opens a checklist of the Settings › Radio scope list (**Enter** toggles a row, **Back** closes); at most 4 are active. Shows `No scopes defined` until at least one named scope exists. |
|
||||
|
||||
The flood filters (**Skip advert** through **Scope only**) are **opt-in** (default OFF, so a plain repeater is unaffected) and act on **flood** traffic only — on a direct route this node is the named next hop, so it never drops those.
|
||||
|
||||
|
||||
@@ -505,10 +505,20 @@ class MessagesScreen : public UIScreen {
|
||||
// bubble sits on shows who sent it, the same convention as a typical
|
||||
// messenger. Everything else in the row (sender, age, ack glyph, body) is
|
||||
// then drawn relative to this box's own x instead of the screen edge.
|
||||
//
|
||||
// A bubble never fills the whole row: max_w leaves a gutter on the side the
|
||||
// sender's bubble is anchored away from (see bubbleMaxW), so even a long
|
||||
// message still shows which side it came from.
|
||||
struct BubbleBox { int x, w; };
|
||||
static BubbleBox computeBubbleBox(int full_avail, bool outgoing, int header_w, int body_w) {
|
||||
static int bubbleMaxW(DisplayDriver& d, int full_avail) {
|
||||
int gutter = d.getCharWidth() * 2;
|
||||
if (gutter < 8) gutter = 8;
|
||||
int w = full_avail - gutter;
|
||||
return w < 1 ? 1 : w;
|
||||
}
|
||||
static BubbleBox computeBubbleBox(int full_avail, int max_w, bool outgoing, int header_w, int body_w) {
|
||||
int w = header_w > body_w ? header_w : body_w;
|
||||
if (w > full_avail) w = full_avail;
|
||||
if (w > max_w) w = max_w;
|
||||
if (w < 1) w = 1;
|
||||
return { outgoing ? (full_avail - w) : 0, w };
|
||||
}
|
||||
@@ -1582,6 +1592,7 @@ public:
|
||||
// computeBubbleBox): the header (sender+ack+age) vs the body, measured
|
||||
// once here and reused below instead of re-wrapping.
|
||||
int full_avail = display.width() - reserve;
|
||||
int max_w = bubbleMaxW(display, full_avail);
|
||||
// Incoming: the hop count the DM actually took to reach us, shown as
|
||||
// the same tiny digit icon an outgoing send uses for its relay/echo
|
||||
// count -- no ack glyph exists for incoming (there's nothing to
|
||||
@@ -1593,15 +1604,15 @@ public:
|
||||
int body_w, nl = 0;
|
||||
if (portrait_expand) {
|
||||
display.translateUTF8ToBlocks(s_wrap_trans, body, sizeof(s_wrap_trans));
|
||||
nl = FullscreenMsgView::wrapLines(display, s_wrap_trans, full_avail - 6, s_wrap_lines, 8);
|
||||
nl = FullscreenMsgView::wrapLines(display, s_wrap_trans, max_w - 6, s_wrap_lines, 8);
|
||||
body_w = 0;
|
||||
for (int li = 0; li < nl; li++) { int w = display.getTextWidth(s_wrap_lines[li]); if (w > body_w) body_w = w; }
|
||||
body_w += 6;
|
||||
} else {
|
||||
int raw_w = display.getTextWidth(body);
|
||||
body_w = (raw_w > full_avail - 6 ? full_avail - 6 : raw_w) + 6;
|
||||
body_w = (raw_w > max_w - 6 ? max_w - 6 : raw_w) + 6;
|
||||
}
|
||||
BubbleBox box = computeBubbleBox(full_avail, e.outgoing, header_w, body_w);
|
||||
BubbleBox box = computeBubbleBox(full_avail, max_w, e.outgoing, header_w, body_w);
|
||||
|
||||
drawHistRowFrame(display, box.x, box.w, y, bh, lh, sel);
|
||||
// Only the body marquees, not the sender too: both share the single
|
||||
@@ -1805,21 +1816,22 @@ public:
|
||||
// the same way an outgoing post's repeater-echo count is.
|
||||
int in_hop_count = !outgoing ? (_history.chAtPos(ring_pos).path_len & 63) : 0;
|
||||
int full_avail = display.width() - reserve;
|
||||
int max_w = bubbleMaxW(display, full_avail);
|
||||
int ack_w = show_ack ? (3 + ackGlyphWidth(display, ACK_OK, 1, relay_count))
|
||||
: (in_hop_count > 0 ? (3 + miniIconNumberWidth(display, in_hop_count)) : 0);
|
||||
int header_w = 3 + display.getTextWidth(sender) + ack_w + age_w + 3;
|
||||
int body_w, nl = 0;
|
||||
if (portrait_expand) {
|
||||
display.translateUTF8ToBlocks(s_wrap_trans, body, sizeof(s_wrap_trans));
|
||||
nl = FullscreenMsgView::wrapLines(display, s_wrap_trans, full_avail - 6, s_wrap_lines, 8);
|
||||
nl = FullscreenMsgView::wrapLines(display, s_wrap_trans, max_w - 6, s_wrap_lines, 8);
|
||||
body_w = 0;
|
||||
for (int li = 0; li < nl; li++) { int w = display.getTextWidth(s_wrap_lines[li]); if (w > body_w) body_w = w; }
|
||||
body_w += 6;
|
||||
} else {
|
||||
int raw_w = display.getTextWidth(body);
|
||||
body_w = (raw_w > full_avail - 6 ? full_avail - 6 : raw_w) + 6;
|
||||
body_w = (raw_w > max_w - 6 ? max_w - 6 : raw_w) + 6;
|
||||
}
|
||||
BubbleBox box = computeBubbleBox(full_avail, outgoing, header_w, body_w);
|
||||
BubbleBox box = computeBubbleBox(full_avail, max_w, outgoing, header_w, body_w);
|
||||
|
||||
drawHistRowFrame(display, box.x, box.w, y, bh, lh, sel);
|
||||
// Only the body marquees, not the sender — see the DM history block above.
|
||||
|
||||
@@ -5,10 +5,14 @@
|
||||
- **The Clock/Lock dashboard gets a separate "Altitude (GPS)" field**, alongside the existing barometric one (now labelled "Altitude (Baro)") — the original single Altitude field only ever read a barometric sensor's telemetry, showing `--` on any board without one even with a perfectly good GPS fix.
|
||||
- **Received messages now show how many hops they actually took to reach you**, right in the message list — the same tiny digit-icon a sent message already uses for its repeater/echo count, now shown for incoming DMs and channel posts too, using the hop path the mesh already records for them.
|
||||
- **Scope is now a shared, freely-definable list, not one device-wide text field.** Settings › Radio › Scope manages a small named list (`*`/wildcard always first, plus a movable default) instead of a single free-typed name. Each channel picks exactly one scope of its own — a `Scope: <name>` row in the channel's context menu, matching the phone app's own per-channel region picker — and the channel's history title shows the tag when it's set to anything but `*`. Tools › Repeater's "Extra scopes" now multi-selects from the same list instead of comma-typing region names, capped at the same 4 active relay scopes as before. A channel set to `*` sends unscoped; DMs and the repeater's primary relay slot follow the list's default entry. Upgrading carries an existing single-scope setup over unchanged: the old Scope becomes the default entry and every channel you already had is seeded with it, so nothing changes on the air.
|
||||
- **GAT562 30S reports and uses its real TX power.** The board's always-on external PA is rated at 30 dBm, but the firmware wrote the request straight to the SX1262 register (max 22), so the reported power never matched the radiated one and couldn't ask for more. TX power now goes through the PA's measured gain curve (up to 30 dBm from the app/CLI), and every setter stores and reports the power actually applied. The other GAT562 boards and every non-PA build are unchanged.
|
||||
- **Settings › Contacts can now clean out contacts you haven't heard from in a while.** New "Expire" option (Off/7d/30d/90d) plus a "Prune now" action that shows exactly how many contacts would be removed before you confirm. Favourites are always kept regardless of age.
|
||||
|
||||
### Fixes
|
||||
|
||||
- **Long messages no longer stretch their chat bubble across the whole row**, which hid which side a message came from. A bubble now always leaves a gutter on the side away from its sender — on the left for your own messages, on the right for received ones — and long text wraps a little earlier to fit.
|
||||
- **Upgrading from a very old firmware** (one that predates several past settings-layout changes) no longer runs a chain of one-time migrations on first boot: the few settings they used to backfill now start at today's plain defaults, the same as a fresh device. Current-version upgrades are unaffected.
|
||||
- **Heltec V3/V4 with a wired joystick: Enter felt laggy and unreliable.** The Enter contact had no internal pull-up and buffered every click for ~280 ms waiting for a double/triple click that the joystick code ignores, so a quick double-tap was silently dropped. It's now wired like the direction and Back contacts. Boards without a joystick are unaffected.
|
||||
- **Every altitude reading (baro + GPS, Clock and Lock screen) and the GPS home page's "alt" row now respect Settings › System › Units**, same as every other distance in the UI — none of them checked metric/imperial before. Locator's Radius row had the same gap and is fixed the same way.
|
||||
- **The Radio home page's noise floor showed "n/a" whenever Pwr save (RX duty-cycle) was on**, on the assumption duty-cycle RX can't sample it. That's stale — the radio already recalibrates it periodically even under duty-cycle, which is why Diagnostics' own reading was already showing something real. Radio home page now matches.
|
||||
- **"Msg wake" moved from Settings › Sound to Settings › Display** — a more logical home for a display-wake toggle than notifications. Menu order only; the setting itself is unaffected.
|
||||
|
||||
Reference in New Issue
Block a user