mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-22 15:41:28 +00:00
joystick UI: lock-screen info, BLE-aware unread, mirror local sends
- Lock overlay: show battery % + unread count between title and unlock
sequence; ui_invalidate_battery_cache() on screen wake forces a fresh
ADC sample so the user sees current data immediately
- UnreadScreen::addPreview gains initially_read; received msgs pass
_ble_connected (don't count unread when phone is syncing); sent msgs
pass true (you sent it, you know it)
- CompanionMesh::queueLocalSent{Contact,Channel}Message + PUSH_CODE_MSG_WAITING
on wio-originated sends so a connected phone app sees them via the
normal offline-queue flow (path_len = OUT_PATH_SENT marker)
- OUT_PATH_SENT moved back from joystick_defs.h to ContactInfo.h
(now a wire-format value, not UI-only)
This commit is contained in:
1 parent
18a67e679d
commit
3de179f9bc
9 files changed
+163
-15
No files matched your search
@@ -767,6 +767,71 @@ void CompanionMesh::queueContactMessage(const ContactInfo &contact, mesh::Packet
|
||||
queueOfflineMessage(frame, i);
|
||||
}
|
||||
|
||||
void CompanionMesh::queueLocalSentContactMessage(const ContactInfo &contact,
|
||||
uint32_t timestamp, const char *text)
|
||||
{
|
||||
if (!text) return;
|
||||
uint8_t frame[MAX_FRAME_SIZE];
|
||||
int i = 0;
|
||||
|
||||
if (_app_target_ver >= 3) {
|
||||
frame[i++] = PACKET_CONTACT_MSG_V3;
|
||||
frame[i++] = 0; // SNR (local origin, no incoming packet)
|
||||
frame[i++] = 0; // reserved1
|
||||
frame[i++] = 0; // reserved2
|
||||
} else {
|
||||
frame[i++] = PACKET_CONTACT_MSG_RECV;
|
||||
}
|
||||
memcpy(&frame[i], contact.id.pub_key, 6);
|
||||
i += 6;
|
||||
frame[i++] = OUT_PATH_SENT; // mark as locally-originated
|
||||
frame[i++] = TXT_TYPE_PLAIN;
|
||||
put_le32(&frame[i], timestamp);
|
||||
i += 4;
|
||||
size_t text_len = strlen(text);
|
||||
if (i + text_len > sizeof(frame)) {
|
||||
text_len = sizeof(frame) - i;
|
||||
}
|
||||
memcpy(&frame[i], text, text_len);
|
||||
i += text_len;
|
||||
|
||||
LOG_DBG("queueLocalSentContactMessage: frame_len=%d", i);
|
||||
queueOfflineMessage(frame, i);
|
||||
sendPush(PUSH_CODE_MSG_WAITING);
|
||||
}
|
||||
|
||||
void CompanionMesh::queueLocalSentChannelMessage(uint8_t channel_idx,
|
||||
uint32_t timestamp, const char *text)
|
||||
{
|
||||
if (!text) return;
|
||||
uint8_t frame[MAX_FRAME_SIZE];
|
||||
int i = 0;
|
||||
|
||||
if (_app_target_ver >= 3) {
|
||||
frame[i++] = PACKET_CHANNEL_MSG_V3;
|
||||
frame[i++] = 0; // SNR
|
||||
frame[i++] = 0; // reserved1
|
||||
frame[i++] = 0; // reserved2
|
||||
} else {
|
||||
frame[i++] = PACKET_CHANNEL_MSG_RECV;
|
||||
}
|
||||
frame[i++] = channel_idx;
|
||||
frame[i++] = OUT_PATH_SENT;
|
||||
frame[i++] = TXT_TYPE_PLAIN;
|
||||
put_le32(&frame[i], timestamp);
|
||||
i += 4;
|
||||
size_t text_len = strlen(text);
|
||||
if (i + text_len > sizeof(frame)) {
|
||||
text_len = sizeof(frame) - i;
|
||||
}
|
||||
memcpy(&frame[i], text, text_len);
|
||||
i += text_len;
|
||||
|
||||
LOG_DBG("queueLocalSentChannelMessage: frame_len=%d channel_idx=%d", i, channel_idx);
|
||||
queueOfflineMessage(frame, i);
|
||||
sendPush(PUSH_CODE_MSG_WAITING);
|
||||
}
|
||||
|
||||
void CompanionMesh::onCommandDataRecv(const ContactInfo &contact, mesh::Packet *pkt,
|
||||
uint32_t sender_timestamp, const char *text)
|
||||
{
|
||||
|
||||
@@ -191,6 +191,23 @@ public:
|
||||
*/
|
||||
const AdvertPath *findAdvertPath(const uint8_t *pubkey_prefix, int prefix_len);
|
||||
|
||||
/**
|
||||
* Queue a locally-originated DM into the BLE offline queue and signal
|
||||
* MSG_WAITING. The frame uses path_len = OUT_PATH_SENT (0xFE) so an
|
||||
* updated phone app can render it as outbound. Older apps still see a
|
||||
* message text in their inbox (possibly mis-rendered as "0xFE hops"),
|
||||
* but the text content is correct so no info is lost.
|
||||
*/
|
||||
void queueLocalSentContactMessage(const ContactInfo &contact, uint32_t timestamp,
|
||||
const char *text);
|
||||
|
||||
/**
|
||||
* Queue a locally-originated channel message into the BLE offline queue
|
||||
* and signal MSG_WAITING. See queueLocalSentContactMessage().
|
||||
*/
|
||||
void queueLocalSentChannelMessage(uint8_t channel_idx, uint32_t timestamp,
|
||||
const char *text);
|
||||
|
||||
/* DataStoreHost interface */
|
||||
bool onContactLoaded(const ContactInfo &c) override;
|
||||
bool getContactForSave(uint32_t idx, ContactInfo &c) override;
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
#include <mesh/MeshCore.h>
|
||||
|
||||
#define OUT_PATH_UNKNOWN 0xFF
|
||||
/* Marker used in BLE offline-queue frames to indicate the message was sent
|
||||
* by THIS device (not received). Also reused by the joystick UI's local
|
||||
* message-preview structs for the same purpose. Not a routing value —
|
||||
* never appears in over-the-air packet path_len fields. */
|
||||
#define OUT_PATH_SENT 0xFE
|
||||
|
||||
struct ContactInfo {
|
||||
mesh::Identity id;
|
||||
|
||||
@@ -47,9 +47,3 @@
|
||||
|
||||
/* List view constants */
|
||||
#define UI_RECENT_LIST_SIZE 4 /* max items visible in a scrollable list */
|
||||
|
||||
/* UI display marker: this MsgEntry/preview slot represents a message
|
||||
* WE SENT (vs received). Used only inside the joystick UI's stored-message
|
||||
* structs — never assigned to ContactInfo::out_path_len, never serialized,
|
||||
* never sent over the air. */
|
||||
#define OUT_PATH_SENT 0xFE
|
||||
@@ -402,7 +402,11 @@ public:
|
||||
void activatePreview(bool transient, uint32_t timeout_ms = 10000);
|
||||
int getUnreadCount();
|
||||
int getStoredMsgCount() const;
|
||||
void addPreview(uint8_t path_len, const char *from_name, const char *msg);
|
||||
/* @param initially_read true → store in history without counting as unread
|
||||
* (used for sent messages, and for received messages
|
||||
* when a BLE phone is connected and will sync them). */
|
||||
void addPreview(uint8_t path_len, const char *from_name, const char *msg,
|
||||
bool initially_read = false);
|
||||
bool getStoredMessageForSourceAt(const char *source, uint32_t ts, const char *&out_msg) const;
|
||||
bool getLatestStoredMessageForSource(const char *source, const char *&out_msg, uint32_t *out_ts = nullptr) const;
|
||||
int getContactMsgCount(const char *contact_name) const;
|
||||
|
||||
@@ -388,6 +388,27 @@ void JoystickUITask::renderLockOverlay()
|
||||
mc_display_text((w - title_w) / 2, 6, title, false);
|
||||
mc_display_hline(2, 6 + fh + 2, w - 4);
|
||||
|
||||
/* Battery + unread (cached values; refreshed on screen wake via the
|
||||
* normal render-path ui_refresh_battery() gate, so locked-and-asleep
|
||||
* doesn't burn power). */
|
||||
char batt_buf[16];
|
||||
if (_cached_batt_mv > 0) {
|
||||
int pct = ((int)_cached_batt_mv - kBattMinMv) * 100 / (kBattMaxMv - kBattMinMv);
|
||||
if (pct < 0) pct = 0;
|
||||
if (pct > 100) pct = 100;
|
||||
snprintf(batt_buf, sizeof(batt_buf), "Batt: %d%%", pct);
|
||||
} else {
|
||||
snprintf(batt_buf, sizeof(batt_buf), "Batt: --");
|
||||
}
|
||||
int batt_w = (int)strlen(batt_buf) * fw;
|
||||
mc_display_text((w - batt_w) / 2, 6 + fh + 6, batt_buf, false);
|
||||
|
||||
char unread_buf[16];
|
||||
int unread = (_unread) ? static_cast<UnreadScreen *>(_unread)->getUnreadCount() : 0;
|
||||
snprintf(unread_buf, sizeof(unread_buf), "Unread: %d", unread);
|
||||
int unread_w = (int)strlen(unread_buf) * fw;
|
||||
mc_display_text((w - unread_w) / 2, 6 + fh + 6 + fh + 2, unread_buf, false);
|
||||
|
||||
/* Three step unlock sequence */
|
||||
const char *toks[3] = { "Back", "OK", "Back" };
|
||||
const int gap = 6;
|
||||
@@ -435,7 +456,10 @@ void JoystickUITask::loop()
|
||||
char key = 0;
|
||||
if (k_msgq_get(&_key_queue, &key, K_NO_WAIT) == 0) {
|
||||
if (!_display.isOn()) {
|
||||
/* Wake display, consume the key press */
|
||||
/* Wake display, consume the key press. Invalidate the battery
|
||||
* cache so the upcoming render samples a fresh value — matters
|
||||
* for the lock screen, which the user is about to see. */
|
||||
ui_invalidate_battery_cache();
|
||||
_display.turnOn(); /* mc_display_on() already calls mc_display_reset_auto_off() */
|
||||
_auto_off = now + _screen_off_ms;
|
||||
_next_refresh = 0;
|
||||
@@ -628,7 +652,10 @@ void JoystickUITask::newMsg(uint8_t path_len, const char *from_name, const char
|
||||
{
|
||||
_msgcount = msgcount;
|
||||
if (_unread) {
|
||||
static_cast<UnreadScreen *>(_unread)->addPreview(path_len, from_name, text);
|
||||
/* When BLE phone is connected it pulls offline queue and marks read;
|
||||
* keep the message in our local history but don't count as unread. */
|
||||
static_cast<UnreadScreen *>(_unread)->addPreview(path_len, from_name, text,
|
||||
/*initially_read=*/_ble_connected);
|
||||
}
|
||||
/* Wake display if wake on msg is enabled and no BLE peer is connected */
|
||||
if (_wake_on_msg && !_ble_connected && !_display.isOn()) {
|
||||
@@ -661,7 +688,9 @@ void JoystickUITask::newChannelMsg(const char *channel_name, const char *text,
|
||||
if (_unread) {
|
||||
char ch_from[48];
|
||||
snprintf(ch_from, sizeof(ch_from), "#%s", channel_name ? channel_name : "?");
|
||||
static_cast<UnreadScreen *>(_unread)->addPreview(path_len, ch_from, text);
|
||||
/* BLE-connected → phone reads via offline queue, don't count as unread. */
|
||||
static_cast<UnreadScreen *>(_unread)->addPreview(path_len, ch_from, text,
|
||||
/*initially_read=*/_ble_connected);
|
||||
}
|
||||
if (_wake_on_msg && !_ble_connected && !_display.isOn()) {
|
||||
_display.turnOn();
|
||||
@@ -784,7 +813,13 @@ bool JoystickUITask::sendComposedMessage(const char *text)
|
||||
if (result == 0) return false;
|
||||
ui_signal_tx();
|
||||
if (_unread) {
|
||||
static_cast<UnreadScreen *>(_unread)->addPreview(OUT_PATH_SENT, _compose_contact_name, text);
|
||||
/* Sent message: in history but not "unread" (user sent it). */
|
||||
static_cast<UnreadScreen *>(_unread)->addPreview(OUT_PATH_SENT, _compose_contact_name, text,
|
||||
/*initially_read=*/true);
|
||||
}
|
||||
/* Notify BLE app so it can mirror the sent message in its UI. */
|
||||
if (CompanionMesh *cm = static_cast<CompanionMesh *>(_mesh)) {
|
||||
cm->queueLocalSentContactMessage(*recipient, ts, text);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -816,6 +851,10 @@ bool JoystickUITask::sendComposedMessage(const char *text)
|
||||
p.timestamp = ts;
|
||||
p.path_len = OUT_PATH_SENT;
|
||||
if (_ch_preview_count < JOYSTICK_OFFLINE_QUEUE_SIZE) _ch_preview_count++;
|
||||
/* Notify BLE app so it can mirror the sent message in its UI. */
|
||||
if (CompanionMesh *cm = static_cast<CompanionMesh *>(_mesh)) {
|
||||
cm->queueLocalSentChannelMessage((uint8_t)_compose_channel_idx, ts, text);
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
@@ -831,7 +870,13 @@ bool JoystickUITask::sendChannelMessage(const char *text)
|
||||
uint32_t ts = _rtc ? _rtc->getCurrentTimeUnique() : k_uptime_get_32();
|
||||
bool ok = _mesh->sendGroupMessage(ts, ch.channel,
|
||||
_prefs ? _prefs->node_name : "", text, (int)strlen(text));
|
||||
if (ok) ui_signal_tx();
|
||||
if (ok) {
|
||||
ui_signal_tx();
|
||||
/* Notify BLE app so it can mirror the sent message in its UI. */
|
||||
if (CompanionMesh *cm = static_cast<CompanionMesh *>(_mesh)) {
|
||||
cm->queueLocalSentChannelMessage((uint8_t)_compose_channel_idx, ts, text);
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,8 @@ const UnreadScreen::MsgEntry *UnreadScreen::getByListIndex(int idx) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UnreadScreen::addPreview(uint8_t path_len, const char *from_name, const char *msg)
|
||||
void UnreadScreen::addPreview(uint8_t path_len, const char *from_name, const char *msg,
|
||||
bool initially_read)
|
||||
{
|
||||
normalizeUnreadState();
|
||||
_head_index = (_head_index + 1) % MAX_UNREAD_MSGS;
|
||||
@@ -92,8 +93,8 @@ void UnreadScreen::addPreview(uint8_t path_len, const char *from_name, const cha
|
||||
}
|
||||
MsgEntry &entry = _entries[_head_index];
|
||||
entry.timestamp = _rtc->getCurrentTime();
|
||||
entry.read = false;
|
||||
if (_visible_unread_count < MAX_UNREAD_MSGS) _visible_unread_count++;
|
||||
entry.read = initially_read;
|
||||
if (!initially_read && _visible_unread_count < MAX_UNREAD_MSGS) _visible_unread_count++;
|
||||
|
||||
const char *safe_from = (from_name && from_name[0]) ? from_name : "unknown";
|
||||
const char *safe_msg = msg ? msg : "";
|
||||
|
||||
@@ -224,3 +224,12 @@ void ui_refresh_battery(void)
|
||||
s_batt_last_read_ms = k_uptime_get_32();
|
||||
s_batt_ever_read = true;
|
||||
}
|
||||
|
||||
/* Forget the freshness timestamp so the next ui_refresh_battery() call is
|
||||
* guaranteed to sample the ADC. Use when entering a state where a fresh
|
||||
* reading matters (e.g. just woke the screen from sleep). */
|
||||
void ui_invalidate_battery_cache(void)
|
||||
{
|
||||
s_batt_ever_read = false;
|
||||
s_batt_last_read_ms = 0;
|
||||
}
|
||||
@@ -182,6 +182,14 @@ void ui_set_battery_provider(uint16_t (*provider)(void));
|
||||
*/
|
||||
void ui_refresh_battery(void);
|
||||
|
||||
/**
|
||||
* Drop the battery-refresh freshness timestamp. The next
|
||||
* ui_refresh_battery() call is guaranteed to sample the ADC.
|
||||
* Use when waking the display from sleep so the user sees a current
|
||||
* reading immediately instead of a possibly-stale cached value.
|
||||
*/
|
||||
void ui_invalidate_battery_cache(void);
|
||||
|
||||
/**
|
||||
* Notify UI of a received contact message.
|
||||
* Rich UIs display the text and sender; simpler ones forward to
|
||||
|
||||
Reference in new issue
Block a user