- SensorManager::getAvailableLPPTypes() reports which LPP types will be
present based on initialized sensor flags (temp, humidity, pressure, etc.)
- Sensors page renders one row per available type; shows "--" for any sensor
that hasn't produced a reading yet instead of hiding the row entirely
- LPPReader: add readLuminosity, readPercentage, readConcentration, readDistance
- All known LPP types now have proper names and units in the sensor display
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
buzzer.play() was returning early when _is_quiet=true, so channels with
explicit notification=ON were silent even though the logic correctly set
play=true. playForced() bypasses the quiet check for these overrides.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Unread badge now clears gradually as the user navigates through messages
instead of instantly on channel entry; counter tracks the highest-seen
message watermark so navigating back doesn't re-add unread count
- Fullscreen view: LEFT/RIGHT switch between messages in the thread (resets
scroll); < > arrows shown at bottom corners when adjacent messages exist
- Navigating in list (UP/DOWN) or entering fullscreen also marks messages read
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- msgRead(0) now only navigates to home when curr == msg_preview, preventing
unexpected redirects while the user is in the chat selection screen
- Fullscreen message view: LEFT/RIGHT navigate between unread messages (resets
scroll to top); < > hints shown at bottom corners when more messages available
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DataStore: persist custom_msgs, ch_notif_override/muted, dm_show_all,
room_fav_only — none were being saved/loaded, all settings lost on reboot.
Load uses file.available() guard for backward compat with old saves.
- QuickMsgScreen reset(): clear _ctx_open/_ctx_dirty/_ctx_sel so context
menu state is clean when re-entering the messaging screen.
- Remove unused _ctx_ch_idx field (code always uses _channel_indices[_channel_sel]).
- _room_unread: cap at _msgcount before incrementing to prevent badge
going negative (getMsgCount() - getRoomUnreadCount()).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
savePrefs() is now called once when the user closes the menu with
Back/Cancel, only if the notification state was actually changed
(_ctx_dirty flag). Toggling multiple times before closing still
results in a single write.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- newMsg() now takes contact_type; room server messages (ADV_TYPE_ROOM)
tracked separately in _room_unread; DM badge = msgCount - roomUnread
- MODE_SELECT shows unread badges for all 3 rows: DM, Channels, Rooms
- Room unread cleared when user opens Room Servers list or msgRead(0)
- Channel context menu: ENTER performs action but stays open; only
CANCEL (Back button) closes it so user can see the updated state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds SECTION_CONTACTS with two items under Settings:
- DM: "fav" (default) / "all" — controlled by dm_show_all in NodePrefs
- Rooms: "all" (default) / "fav" — controlled by room_fav_only in NodePrefs
Both fields default to 0, preserving current behaviour on firmware upgrade.
buildContactList() reads the prefs to filter accordingly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds ADV_TYPE_ROOM contacts as a selectable category in the MODE_SELECT
screen. Reuses the existing CONTACT_PICK → MSG_PICK → send flow via a
_room_mode flag; sendMessage() works identically for room servers.
All room server contacts are listed (no favourites filter).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sums getMsgCount() (DM unreads) and getTotalChannelUnread() (sum of
_ch_unread[] across all channels) and displays "N unread" under the
"Messages" heading when non-zero.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The contact picker now lists only contacts of type ADV_TYPE_CHAT
that have the favourite flag set. Shows "No favourites" when none
are starred. Asterisk prefix removed since all entries are favourites.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Long-pressing ENTER on a channel in the channel picker opens a
2-item context menu: "Mark all read" and "Notif: default/OFF/ON".
Muted channels still increment the unread counter silently; force-ON
overrides global buzzer mute. Settings persist to flash via NodePrefs
(ch_notif_override / ch_notif_muted bitmasks).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix placeholder picker colors: dark box + white border, light fill
for selected item (consistent with rest of UI)
- Fix placeholders not expanded when sending from keyboard (expandMsg
was only called in MSG_PICK path, not KEYBOARD path)
- Fix sent messages incrementing unread counter: set _phase=CHANNEL_HIST
before addChannelMsg so viewing=true
- Fix keyboard column inverse mapping on UP from special row
- Remove old_row dead code from QuickMsgScreen keyboard handler
- Alert banner shown only on home screen, suppressed in settings/messages
- Bump FIRMWARE_VERSION to v1.15.1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Settings → Messages section: 10 user-editable message slots (M1–M10)
with on-screen keyboard editor; slot M1 defaults to "OK" on first boot
- Keyboard: add Caps Lock [^] button — stays highlighted when active,
all letters display and insert as uppercase until toggled off
- Keyboard: add [{}] placeholder picker overlay — inserts {loc} or {time}
into the message at cursor position; available in both compose and
settings slot editor
- Max message length raised to 139 chars to match the companion app;
ChHistEntry text buffer extended to 140 chars
- NodePrefs: custom_msgs slot size extended to 140 chars per slot
- KB constants (KB_MAX_LEN, KB_CHARS, etc.) moved before SettingsScreen
so both SettingsScreen and QuickMsgScreen can reference them
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add FIRMWARE_PLUS_BUILD compile flag to both companion sections in
platformio.ini. When set, SplashScreen shows "+ unofficial build" in
yellow below the build date. Avoids runtime string parsing — the
existing version stripping logic (cuts at first dash) is unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Track dirty state per-field in handleInput. savePrefs() on exit only
if at least one value was actually modified. Buzzer is excluded — it
already saves immediately via toggleBuzzer(). markClean() resets the
flag each time the settings screen is opened.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lowBatIndex(), autoOffIndex(), gpsIntervalIndex() each called
getNodePrefs() independently without null check, while renderItem()
had its own guarded copy. Added null checks consistent with the rest
of the class. Also initialize _node_prefs to NULL in UITask constructor.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace linear voltage-to-percent formula with a LiPo discharge curve
lookup table and linear interpolation. The curve is then rescaled so
that low_batt_mv (configurable shutdown threshold) maps to 0% and
4200mV maps to 100%, preserving the non-linear shape regardless of
the chosen threshold.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Filter contact list to ADV_TYPE_CHAT only — excludes repeaters, rooms,
and sensors which cannot receive direct chat messages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Favourite contacts (flags & 0x01 set by companion app) appear at the
top of the contact picker, marked with '*'. Non-favourites appear
below. The favourite bit is already synced by the existing protocol
via CMD_ADD_UPDATE_CONTACT — no protocol changes needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Watchdog: nRF52840 hardware WDT with 30s timeout, runs during sleep.
Resets device if main loop stalls for >30s. Pets in every loop()
iteration so normal operation never triggers it.
QuickMsgScreen: new UI screen accessible from home screen pages.
Two-phase UI: contact picker (shows all contacts with hop count),
then predefined message picker (location, I'm OK, need help, on my
way, ETA 10min, ETA 30min). Location message uses live GPS fix if
available. Sends via the_mesh.sendMessage() and shows sent/error alert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Save last known unix timestamp to /rtc_save on shutdown and restore it
on startup, so the clock shows correct time immediately after restart
without waiting for GPS fix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CPU sleep: board.sleep(0) in main loop puts NRF52 into WFE/sd_app_evt_wait
between iterations; wakes on any interrupt (radio, BLE, timer). Largest
power saving of the three changes.
- VBAT_ENABLE gating: voltage divider now powered only during ADC read
(HIGH for 10ms, then LOW). Previously stayed HIGH continuously.
Fixed in both variant.cpp (init) and WioTrackerL1Board::getBattMilliVolts().
- LED off with display: PIN_LED is cleared when auto-off triggers and
restored (handed back to userLedHandler) when display wakes. Avoids
idle LED current draw during screen-off periods.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* room server: added RegionMap, and new CommonCLI wiring, default_scope handling
* sensor: only minimal RegionMap wiring. Still needs work to handle default-scope
The companion_radio example was not restoring the GPS enabled/disabled
preference from flash after reboot. The preference was being saved
correctly when toggled via the mobile app, but on boot,
sensors.begin() -> initBasicGPS() unconditionally sets gps_active=false
and nothing subsequently restored the persisted state.
Added applyGpsPrefs() (matching the pattern in simple_repeater,
simple_sensor, and simple_room_server) and call it from main.cpp
after sensors.begin() to ensure the GPS hardware is initialized
before the saved preference is applied.