Switch the fast-GPS beacon to the firmware's canonical 16-byte wire format
(magic, key6, lat/lon microdegrees, speed km/h) — no on-wire timestamp; the
receiver stamps its own RX time. Drops compatibility with the older 19-byte app
format.
Mirror the firmware's adaptive send cadence in the app-fallback path
(LocationTrackingService): anchor + dwell motion hysteresis, EMA ground speed,
9-min stationary keepalive, fix-quality (accuracy) gate, and a post-RX hold-off
to avoid channel collisions. Parked nodes now beacon the stable anchor instead
of GPS wander.
Add the fast_gps_region config: region-scope picker in settings backed by the
firmware's fast_gps_region / fast_gps_regions custom vars.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CMD_SEND_RAW_DATA (0x19) was passed the encoded MeshCore route descriptor
(e.g. 0x41 for a 1-hop 2-byte-hash route), but the v1.15 companion firmware
raw-data handler treats the path byte as a legacy literal hop count, not an
encoded descriptor. It misread 0x41 as "65 path bytes" and rejected the
command (ERROR: Unsupported command), so media fetch always failed while
normal messaging worked.
- Add ContactRouteCodec.toLegacyRawPath() converting encoded descriptor +
path into legacy format (literal hop count + first byte of each hop hash).
- Apply it in ConnectionProvider.sendRawVoicePacket, the single chokepoint
all raw media (voice/image fragments, swarm, route probes) flows through.
- Pace served fragments 350ms apart to avoid firmware "ERROR: Table full"
when bursting many raw packets.
- Tests for the descriptor conversion.
Fixes#43
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- New MapLayerType.mapyOutdoor with Referer header support
(tile server rejects requests without mapy.com Referer)
- Headered layers get a dedicated NetworkTileProvider sharing
the offline tile cache
- Offline region downloads pass layer headers through
TileDownloadService
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- i18n: translate connection screen + chat tab keys into 13 locales;
add "Send my contact" (advert) strings in all locales
- feat: move self-advert from home header into composer "+" action menu
(new lib/utils/advert_helper.dart, always shows Flood/Direct sheet)
- fix: hide-repeaters map toggle was bypassed in simple mode
- fix: simple mode map now shows only favourite chat contacts
- fix: wrap ListTiles in Material (contact_tile secondary actions,
inferred contact group card) to satisfy Flutter ink assertions
- device-authoritative contact/channel sync and UX review fixes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tapping signal/battery in header now also shows device's own telemetry
(temperature, humidity, pressure, GPS) when available. Triggers a self
telemetry request on sheet open. Data comes from the device contact's
telemetry field.
Room servers forward messages with the original author's 4-byte public
key prefix (roomPostAuthorPrefix). Was ignoring this — all room messages
showed the room server's name as sender.
Now resolves the actual poster's name from roomPostAuthorPrefix by
matching against the contacts list. Falls back to room server name if
the author isn't a known contact.
Also fixes findContactByPrefix to accept 4-byte prefixes (was rejecting
anything shorter than 6 bytes).
Fixes#22
When firmware sends telemetry via both pushTelemetryResponse (0x8B)
and pushBinaryResponse (0x8C), the same LPP data was parsed and
applied twice, causing duplicate sensor entries.
Added hash-based dedup: same payload for same contact within 2s is
skipped. Also added minimum length check for binary responses.
Received message path: Sender → hop1 (near sender) → ... → Us
Outbound path: Us → hop1 (near us) → ... → Contact
Was using outbound path which placed contacts near OUR repeater.
Now uses first hop of the received message path — the repeater
nearest to the sender. Only triggers on received messages, not sends.
When a contact has no GPS, estimate their position from RSSI
observations through multiple repeaters:
- 1 repeater: offset by RSSI distance in deterministic direction
- 2 repeaters: weighted midpoint between circle intersections
- 3+ repeaters: weighted centroid with inverse-square weighting
(closer repeater observations dominate)
Observations stored per-repeater (latest wins), max 8 per contact,
expire after 30 minutes. Each incoming message updates the estimate.
For contacts without GPS, estimate their position by:
1. Finding the first-hop repeater from their routing path
2. Estimating distance from RSSI using log-distance path loss model
3. Placing them at a deterministic offset from the repeater
Uses LoRa outdoor path loss exponent (n=3.0) with reference RSSI
-30dBm at 1m. Distance clamped to 10m-50km range. Bearing derived
from contact public key hash for stable positioning.
- maxRetryAttemptsFloodOnly: 1→0 (was causing an extra retry for
flood contacts that the official app doesn't do)
- isLastAttempt: fix off-by-one (retryAttempt already incremented
when called from _scheduleRetry)
- Reduce ACK timeout jitter from 1-8s to 0.5-2s to avoid long waits
that make the UI appear stuck
- Contact.isFavourite reads bit 0 of firmware flags field
- Toggle favourite via contact action sheet (writes to device)
- Favourites section shown first in contacts tab
- Path size options corrected to [1, 2, 3] bytes