- Move "A" (auto-advert) indicator left of BT "B" in status bar
- Ellipsize node name to fit available width instead of overflowing
- Shrink A and B background rectangles by 1px right, 2px bottom
- Change A blink to 2s on / 2s off at 1000ms refresh rate
- Auto-advert sends GPS position directly via createSelfAdvert/sendZeroHop
- Align Settings and Tools home page titles to y=22, same as Messages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds configurable periodic 0-hop self-advert that includes GPS coordinates,
making the device's position visible in other nodes' Nearby Nodes screen.
Options: off / 1min / 2min / 5min / 10min / 30min / 1h.
Accessible via Tools → Auto-Advert. Timer runs in MyMesh::loop().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Long-press ENTER in the DM contact list opens a context menu with:
- "Mark as read": clears unread counter for that contact
- "Notif: default/OFF/ON": cycles notification state (same as channels)
Notification state is persisted in NodePrefs (dm_notif[16] table,
keyed by 4-byte pub_key prefix). When muted, the buzzer is silenced
for that contact's messages; when force-on, buzzer plays even in
quiet mode. Default follows the global buzzer setting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add DM_HIST phase in QuickMsgScreen: 32-entry ring buffer tracks incoming
and outgoing DMs per contact; selecting a contact now opens history view
before compose
- Per-contact DM unread badge in contact list (replaces hop count display)
- Bot replies (DM and channel) stored in history so they appear on-screen
- Fix contact index truncation: _sorted was uint8_t (max 255), causing
contacts at indices 256-349 to map to wrong entries; changed to uint16_t
- Add addDMMsg() virtual to AbstractUITask; wire through MyMesh.cpp
- Incoming chat DMs call addDMMsg(); outgoing after successful send too
- msgRead(0) from companion app clears per-contact unread table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bot redesign: DM bot active for all private messages (no pubkey filter),
channel bot optional with separate enable flag; both can run simultaneously
- Bot: one shared trigger, separate reply texts for DM and channel
- Buzzer: add Auto mode (mutes when BT connected, unmutes on disconnect);
per-channel force-on overrides still work as intended
- Settings: defer flash writes to menu exit (only when dirty)
- Settings: add Seconds toggle (hide clock seconds, refresh drops 1s→60s)
- Fullscreen msg preview: fix truncation (fmsg buf 79→140 chars)
- RingtoneEditor: fix melody playback cut after first note (RTTTL lib holds
pointer — moved play buffer to member variable)
- BotScreen: remove key hint footer
- .gitattributes: protect README.md from upstream merge conflicts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move tryBotReplyDM() and tryBotReplyChannel() implementations to
MyMeshBot.h (included at end of MyMesh.cpp). onMessageRecv and
onChannelMessageRecv now contain only a single-line call each,
minimising the diff against upstream in those methods.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace BotScreen's custom embedded keyboard with the shared global
keyboard layout (KB_* constants): char rows at KB_CHARS_Y, special row
at KB_SPECIAL_Y=48 with 5 buttons ([^][Sp][Del][{}][OK]), fixing the
previous layout that rendered below the 64 px display boundary.
Proportional column scaling between char rows (10 cols) and special row
(5 cols). [{}] opens the placeholder picker overlay.
- Add MsgExpand.h: standalone inline expandMsg() with no framework
dependencies, expanding {loc} and {time} placeholders. Used by both
MyMesh (bot channel/DM replies) and QuickMsgScreen, replacing the
duplicate expandBotMsg() static and the inline expandMsg() method.
- Bump FIRMWARE_VERSION to v1.15.3.
- ToolsScreen: remove key-hint footer line.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The bot can now reply to a specific DM contact instead of a channel.
UI (BotScreen): item 1 cycles through all channels followed by all DM
contacts (ADV_TYPE_CHAT). The label switches between "Channel" / "Contact"
and the value shows the channel or contact name. Left/right/enter navigate
the combined list.
Logic (MyMesh::onMessageRecv): when bot_target_type==1, match the trigger
against incoming DM text and reply via sendMessage(). A 4-byte pubkey
prefix identifies the target; all-zero prefix matches any DM sender.
Persistence: bot_target_type and bot_dm_pubkey[4] appended to prefs with
backward-compat read guard (old firmware defaults to channel mode).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- BotScreen keyboard: add explicit null-terminator after strncpy when
trigger/reply text reaches max length (strncpy skips it in that case)
- BotScreen keyboard: clamp _kb_col to 3 when moving DOWN to special row,
so the cursor doesn't become invisible (col > 3 has no special-row item)
- MyMesh: initialise _bot_last_reply_ms = 0 in constructor (was relying on
BSS zero-init, which is technically UB for a non-trivial class member)
- RingtoneEditor: exit via CANCEL/Save/Discard goes to ToolsScreen instead
of HomeScreen — consistent with BotScreen and expected navigation flow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New 'Tools' page on the home screen carousel with Ringtone Editor and Auto-Reply Bot items
- Bot settings: enable/disable toggle, channel picker, trigger phrase, reply text (keyboard with pre-fill)
- Bot logic in MyMesh: case-insensitive substring match, 10 s cooldown to prevent reply loops
- Settings: Home Pages visibility control (which carousel pages are shown; Settings and Messages always visible)
- DataStore: persist home_pages_mask and bot fields with backward-compat guards on read
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New "Home Pages" section in Settings lets the user toggle which pages
appear in the home screen carousel. Settings and Messages are always
shown; Clock, Recent, Radio, Bluetooth, Advert, GPS, Sensors, Tools
and Shutdown can each be hidden individually.
- NodePrefs: home_pages_mask uint16_t (bit per page, 0=all visible)
- HomeScreen: isPageVisible/navPage skip hidden pages; dots indicator
counts only visible pages; enforces visible page on re-entry
- SettingsScreen: SECTION_HOME_PAGES with per-page ON/OFF toggles
- DataStore: persisted at offset 1598
- Default: all pages visible (0x01FF)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- RingtoneEditorScreen: 32-note step sequencer accessible via new Tools page on home screen. U/D=pitch, ENTER=octave cycle, L/R=cursor, MENU=options (play, duration, BPM, insert/delete, save). Notes stored packed in NodePrefs and persisted via DataStore.
- ToolsScreen: new "Tools" home page entry launching the editor
- Buzzer volume: 0-4 bar control in Settings > Sound, persisted to prefs
- Buzzer notifications: fixed notify() firing inside wrong serial-guard branch and before addChannelMsg set the channel index
- Unread counter: fixed newest-first index direction; watermark formula now correct
- OLED brightness: wider range via pre-charge register 0xD9 combined with contrast
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- NodePrefs: add buzzer_volume field (0=min..4=max, default 4)
- DataStore: persist buzzer_volume at end of prefs file (backward-compat)
- buzzer: setVolume()/getVolume(); applyVolume() sets PWM duty cycle via
analogWrite() after each rtttl::play() call to control output amplitude
- Settings UI: new BzrVol bar item in Sound section (left/right to adjust)
- UITask: apply saved volume on startup alongside brightness
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MyMesh: notify() now always fires for channel/contact messages regardless
of serial connection; called after addChannelMsg so channel index is set
- ChannelHist: fix unread watermark direction (histEntry is newest-first,
index 0=newest); counter now decrements correctly as user reads messages
- ChannelHist: reset unread to 0 after sending (user is active in channel)
- SH1106Display: add pre-charge register (0xD9) to setBrightness for wider
dimming range; re-apply contrast+precharge in turnOn(); tune curve
- UITask: remove MsgPreviewScreen dead code (~180 lines, never shown)
- UITask: fix strcpy→snprintf in showAlert()
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>
- 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>
- Change > to >= so stored value 1 means direct/0-hop only (liamcottle)
- Clamp max_hops to 63 on write since getPathHashCount() caps at 63 (robekl)
- Update comments to reflect encoding: 0=no limit, 1=direct only, N=up to N-1 hops
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Filter auto-add of new contacts by hop count (issues #1533, #1546).
Setting is configurable from the companion app via extended
CMD_SET/GET_AUTOADD_CONFIG protocol (0 = no limit, 1-63 = max hops).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of overloading getOutboundCount() with a magic sentinel value,
add a dedicated getOutboundTotal() method to the PacketManager interface
that returns the total queue size without time filtering.
This eliminates the fragile convention that caused the regression and
makes the two operations — time-filtered count vs total count —
explicitly separate in the API.
* companion: new field in CMD_SET_OTHER_PARAMS, path_hash_mode
* companion: CMD_SEND_SELF_ADVERT, cmd_frame[1] now holds the path hash size (0 = zero hop, 1..3 = flood path hash size)