mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
Fix buzzer notifications, unread tracking, OLED dimming; remove dead code
- 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>
This commit is contained in:
@@ -465,9 +465,7 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
|
||||
bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN;
|
||||
if (should_display && _ui) {
|
||||
_ui->newMsg(path_len, from.name, text, offline_queue_len, from.type);
|
||||
if (!_serial->isConnected()) {
|
||||
_ui->notify(UIEventType::contactMessage);
|
||||
}
|
||||
_ui->notify(UIEventType::contactMessage);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -561,13 +559,10 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
|
||||
uint8_t frame[1];
|
||||
frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle'
|
||||
_serial->writeFrame(frame, 1);
|
||||
} else {
|
||||
#ifdef DISPLAY_CLASS
|
||||
if (_ui) _ui->notify(UIEventType::channelMessage);
|
||||
#endif
|
||||
}
|
||||
#ifdef DISPLAY_CLASS
|
||||
if (_ui) _ui->addChannelMsg(channel_idx, text);
|
||||
if (_ui) _ui->notify(UIEventType::channelMessage);
|
||||
const char *channel_name = "Unknown";
|
||||
ChannelDetails channel_details;
|
||||
if (getChannel(channel_idx, channel_details)) {
|
||||
|
||||
@@ -692,8 +692,8 @@ class QuickMsgScreen : public UIScreen {
|
||||
int _hist_sel, _hist_scroll;
|
||||
bool _hist_fullscreen;
|
||||
int _hist_fs_scroll;
|
||||
int _viewing_unread_start; // index of first "unread" message when entering CHANNEL_HIST
|
||||
int _viewing_max_seen; // highest _hist_sel reached in current session
|
||||
int _unread_at_entry; // _ch_unread value when entering CHANNEL_HIST
|
||||
int _viewing_max_seen; // highest _hist_sel reached in current session
|
||||
static const int CH_HIST_MAX = 32;
|
||||
|
||||
static const int FS_CHARS = 21;
|
||||
@@ -843,6 +843,11 @@ class QuickMsgScreen : public UIScreen {
|
||||
char entry[sizeof(ChHistEntry::text)];
|
||||
snprintf(entry, sizeof(entry), "Me: %s", msg);
|
||||
addChannelMsg(_sel_channel_idx, entry);
|
||||
// After inserting sent msg at index 0, the unread index range is stale.
|
||||
// User is active in this channel — treat as fully read.
|
||||
_ch_unread[_sel_channel_idx] = 0;
|
||||
_unread_at_entry = 0;
|
||||
_viewing_max_seen = 0;
|
||||
_task->showAlert("Sent!", 600);
|
||||
} else {
|
||||
_task->showAlert(ok ? "Sent!" : "Send failed", 1500);
|
||||
@@ -929,7 +934,7 @@ public:
|
||||
_msg_sel(0), _msg_scroll(0), _active_msg_count(0),
|
||||
_hist_sel(0), _hist_scroll(0),
|
||||
_hist_fullscreen(false), _hist_fs_scroll(0),
|
||||
_viewing_unread_start(0), _viewing_max_seen(0),
|
||||
_unread_at_entry(0), _viewing_max_seen(0),
|
||||
_hist_head(0), _hist_count(0),
|
||||
_kb_row(0), _kb_col(0), _kb_len(0),
|
||||
_kb_caps(false), _kb_ph_mode(false), _kb_ph_sel(0),
|
||||
@@ -966,10 +971,9 @@ public:
|
||||
void updateChannelUnread() {
|
||||
if (_hist_sel < 0 || _sel_channel_idx < 0 || _sel_channel_idx >= MAX_GROUP_CHANNELS) return;
|
||||
if (_hist_sel > _viewing_max_seen) _viewing_max_seen = _hist_sel;
|
||||
int hist_count = histCountForChannel(_sel_channel_idx);
|
||||
int watermark = _viewing_max_seen > (_viewing_unread_start - 1)
|
||||
? _viewing_max_seen : (_viewing_unread_start - 1);
|
||||
int remaining = (hist_count - 1) - watermark;
|
||||
// histEntryForChannel is newest-first: index 0 = newest (unread), higher = older.
|
||||
// Each step down from 0 sees one more message; seen count = max_seen + 1.
|
||||
int remaining = _unread_at_entry - (_viewing_max_seen + 1);
|
||||
_ch_unread[_sel_channel_idx] = (uint8_t)(remaining > 0 ? remaining : 0);
|
||||
}
|
||||
|
||||
@@ -991,7 +995,7 @@ public:
|
||||
_ctx_open = false;
|
||||
_ctx_dirty = false;
|
||||
_ctx_sel = 0;
|
||||
_viewing_unread_start = 0;
|
||||
_unread_at_entry = 0;
|
||||
_viewing_max_seen = 0;
|
||||
}
|
||||
|
||||
@@ -1476,8 +1480,7 @@ public:
|
||||
if (c == KEY_ENTER && _num_channels > 0) {
|
||||
_sel_channel_idx = _channel_indices[_channel_sel];
|
||||
int hc = histCountForChannel(_sel_channel_idx);
|
||||
_viewing_unread_start = hc - (int)_ch_unread[_sel_channel_idx];
|
||||
if (_viewing_unread_start < 0) _viewing_unread_start = 0;
|
||||
_unread_at_entry = (int)_ch_unread[_sel_channel_idx];
|
||||
_hist_scroll = 0;
|
||||
_hist_sel = hc > 0 ? 0 : -1;
|
||||
_viewing_max_seen = _hist_sel >= 0 ? _hist_sel : 0;
|
||||
@@ -2133,186 +2136,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class MsgPreviewScreen : public UIScreen {
|
||||
UITask* _task;
|
||||
mesh::RTCClock* _rtc;
|
||||
|
||||
struct MsgEntry {
|
||||
uint32_t timestamp;
|
||||
char origin[62];
|
||||
char msg[78];
|
||||
};
|
||||
#define MAX_UNREAD_MSGS 32
|
||||
int num_unread;
|
||||
int head;
|
||||
int _view_offset; // 0=newest, num_unread-1=oldest
|
||||
bool _fullscreen;
|
||||
int _fs_scroll; // line scroll offset in fullscreen
|
||||
MsgEntry unread[MAX_UNREAD_MSGS];
|
||||
|
||||
static const int FS_CHARS = 21; // chars per line at text size 1, 128px wide
|
||||
static const int FS_LINE_H = 9;
|
||||
static const int FS_START_Y = 12;
|
||||
static const int FS_VISIBLE = (64 - FS_START_Y) / FS_LINE_H; // 5 lines
|
||||
|
||||
// word-wrap msg into lines; returns line count
|
||||
int wrapLines(const char* text, char out[][FS_CHARS + 1], int max_lines) const {
|
||||
int count = 0;
|
||||
const char* p = text;
|
||||
while (*p && count < max_lines) {
|
||||
int len = strlen(p);
|
||||
if (len <= FS_CHARS) {
|
||||
strncpy(out[count++], p, FS_CHARS);
|
||||
out[count-1][len] = '\0';
|
||||
break;
|
||||
}
|
||||
int brk = FS_CHARS;
|
||||
for (int i = FS_CHARS - 1; i > 0; i--) {
|
||||
if (p[i] == ' ') { brk = i; break; }
|
||||
}
|
||||
strncpy(out[count], p, brk);
|
||||
out[count++][brk] = '\0';
|
||||
p += brk + (p[brk] == ' ' ? 1 : 0);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public:
|
||||
MsgPreviewScreen(UITask* task, mesh::RTCClock* rtc)
|
||||
: _task(task), _rtc(rtc), num_unread(0), head(MAX_UNREAD_MSGS - 1),
|
||||
_view_offset(0), _fullscreen(false), _fs_scroll(0) {}
|
||||
|
||||
void addPreview(uint8_t path_len, const char* from_name, const char* msg) {
|
||||
head = (head + 1) % MAX_UNREAD_MSGS;
|
||||
if (num_unread < MAX_UNREAD_MSGS) num_unread++;
|
||||
_view_offset = 0;
|
||||
_fullscreen = false;
|
||||
|
||||
auto p = &unread[head];
|
||||
p->timestamp = _rtc->getCurrentTime();
|
||||
if (path_len == 0xFF) {
|
||||
sprintf(p->origin, "(D) %s:", from_name);
|
||||
} else {
|
||||
sprintf(p->origin, "(%d) %s:", (uint32_t)path_len, from_name);
|
||||
}
|
||||
StrHelper::strncpy(p->msg, msg, sizeof(p->msg));
|
||||
}
|
||||
|
||||
int render(DisplayDriver& display) override {
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
|
||||
int msg_idx = (head - _view_offset + MAX_UNREAD_MSGS) % MAX_UNREAD_MSGS;
|
||||
auto p = &unread[msg_idx];
|
||||
|
||||
char filtered_origin[sizeof(p->origin)];
|
||||
display.translateUTF8ToBlocks(filtered_origin, p->origin, sizeof(filtered_origin));
|
||||
char filtered_msg[sizeof(p->msg)];
|
||||
display.translateUTF8ToBlocks(filtered_msg, p->msg, sizeof(filtered_msg));
|
||||
|
||||
// header: origin + counter, tekst + separator
|
||||
char counter[8];
|
||||
snprintf(counter, sizeof(counter), "%d/%d", _view_offset + 1, num_unread);
|
||||
int counter_w = display.getTextWidth(counter);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawTextEllipsized(2, 1, display.width() - counter_w - 6, filtered_origin);
|
||||
display.setCursor(display.width() - counter_w - 2, 1);
|
||||
display.print(counter);
|
||||
display.fillRect(0, 10, display.width(), 1);
|
||||
|
||||
if (_fullscreen) {
|
||||
char lines[10][FS_CHARS + 1];
|
||||
int line_count = wrapLines(filtered_msg, lines, 10);
|
||||
int max_scroll = (line_count > FS_VISIBLE) ? line_count - FS_VISIBLE : 0;
|
||||
if (_fs_scroll > max_scroll) _fs_scroll = max_scroll;
|
||||
|
||||
for (int i = 0; i < FS_VISIBLE && (_fs_scroll + i) < line_count; i++) {
|
||||
display.setCursor(0, FS_START_Y + i * FS_LINE_H);
|
||||
display.print(lines[_fs_scroll + i]);
|
||||
}
|
||||
if (_fs_scroll > 0) {
|
||||
display.setCursor(display.width() - 6, FS_START_Y);
|
||||
display.print("^");
|
||||
}
|
||||
if (_fs_scroll < max_scroll) {
|
||||
display.setCursor(display.width() - 6, FS_START_Y + (FS_VISIBLE - 1) * FS_LINE_H);
|
||||
display.print("v");
|
||||
}
|
||||
if (_view_offset < num_unread - 1) {
|
||||
display.setCursor(0, 56);
|
||||
display.print("<");
|
||||
}
|
||||
if (_view_offset > 0) {
|
||||
display.setCursor(display.width() - 6, 56);
|
||||
display.print(">");
|
||||
}
|
||||
} else {
|
||||
display.setCursor(0, 13);
|
||||
display.printWordWrap(filtered_msg, display.width());
|
||||
|
||||
// nav hints
|
||||
if (_view_offset < num_unread - 1) {
|
||||
display.setCursor(display.width() - 6, 13);
|
||||
display.print("^");
|
||||
}
|
||||
if (_view_offset > 0) {
|
||||
display.setCursor(display.width() - 6, 55);
|
||||
display.print("v");
|
||||
}
|
||||
|
||||
// timestamp
|
||||
char tmp[10];
|
||||
int secs = _rtc->getCurrentTime() - p->timestamp;
|
||||
if (secs < 60) snprintf(tmp, sizeof(tmp), "%ds", secs);
|
||||
else if (secs < 3600) snprintf(tmp, sizeof(tmp), "%dm", secs / 60);
|
||||
else snprintf(tmp, sizeof(tmp), "%dh", secs / 3600);
|
||||
display.setCursor(0, 56);
|
||||
display.print(tmp);
|
||||
}
|
||||
|
||||
#if AUTO_OFF_MILLIS==0
|
||||
return 10000;
|
||||
#else
|
||||
return 1000;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool handleInput(char c) override {
|
||||
if (_fullscreen) {
|
||||
if (c == KEY_UP) { if (_fs_scroll > 0) _fs_scroll--; return true; }
|
||||
if (c == KEY_DOWN) { _fs_scroll++; return true; }
|
||||
if (c == KEY_LEFT) {
|
||||
if (_view_offset < num_unread - 1) { _view_offset++; _fs_scroll = 0; }
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_RIGHT) {
|
||||
if (_view_offset > 0) { _view_offset--; _fs_scroll = 0; }
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_ENTER || c == KEY_CANCEL) { _fullscreen = false; return true; }
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_UP || c == KEY_LEFT) {
|
||||
if (_view_offset < num_unread - 1) _view_offset++;
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_DOWN || c == KEY_RIGHT) {
|
||||
if (_view_offset > 0) _view_offset--;
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_ENTER) {
|
||||
_fullscreen = true;
|
||||
_fs_scroll = 0;
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_CANCEL) {
|
||||
num_unread = 0;
|
||||
_task->gotoHomeScreen();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs) {
|
||||
_display = display;
|
||||
@@ -2352,7 +2175,6 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
|
||||
splash = new SplashScreen(this);
|
||||
home = new HomeScreen(this, &rtc_clock, sensors, node_prefs);
|
||||
msg_preview = new MsgPreviewScreen(this, &rtc_clock);
|
||||
settings = new SettingsScreen(this);
|
||||
quick_msg = new QuickMsgScreen(this);
|
||||
setCurrScreen(splash);
|
||||
@@ -2380,7 +2202,7 @@ int UITask::getChannelUnreadCount() const {
|
||||
}
|
||||
|
||||
void UITask::showAlert(const char* text, int duration_millis) {
|
||||
strcpy(_alert, text);
|
||||
snprintf(_alert, sizeof(_alert), "%s", text);
|
||||
_alert_expiry = millis() + duration_millis;
|
||||
}
|
||||
|
||||
@@ -2436,7 +2258,6 @@ void UITask::msgRead(int msgcount) {
|
||||
_msgcount = msgcount;
|
||||
if (msgcount == 0) {
|
||||
_room_unread = 0;
|
||||
if (curr == msg_preview) gotoHomeScreen();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ class UITask : public AbstractUITask {
|
||||
|
||||
UIScreen* splash;
|
||||
UIScreen* home;
|
||||
UIScreen* msg_preview;
|
||||
UIScreen* settings;
|
||||
UIScreen* quick_msg;
|
||||
UIScreen* curr;
|
||||
|
||||
@@ -17,6 +17,9 @@ bool SH1106Display::begin()
|
||||
void SH1106Display::turnOn()
|
||||
{
|
||||
display.oled_command(SH110X_DISPLAYON);
|
||||
uint8_t pre[] = { 0xD9, _precharge };
|
||||
display.oled_commandList(pre, 2);
|
||||
display.setContrast(_contrast);
|
||||
_isOn = true;
|
||||
}
|
||||
|
||||
@@ -87,11 +90,17 @@ uint16_t SH1106Display::getTextWidth(const char *str)
|
||||
|
||||
void SH1106Display::setBrightness(uint8_t level)
|
||||
{
|
||||
// OLED contrast is highly non-linear: most perceptible change is in the low range.
|
||||
// Values are tuned so each step looks visually distinct.
|
||||
static const uint8_t contrast_values[] = { 8, 30, 80, 160, 255 };
|
||||
uint8_t contrast = contrast_values[level < 5 ? level : 4];
|
||||
display.setContrast(contrast);
|
||||
// Contrast alone has limited effect on some OLED panels; combining with
|
||||
// pre-charge period (0xD9) gives a wider perceptible dimming range.
|
||||
// Pre-charge 0x11 = phase1=1,phase2=1 (minimum drive); 0x1F = default.
|
||||
static const uint8_t contrast_values[] = { 0, 25, 60, 150, 255 };
|
||||
static const uint8_t precharge_values[] = { 0x11, 0x15, 0x1F, 0x1F, 0x1F };
|
||||
uint8_t idx = level < 5 ? level : 4;
|
||||
_contrast = contrast_values[idx];
|
||||
_precharge = precharge_values[idx];
|
||||
uint8_t pre[] = { 0xD9, _precharge };
|
||||
display.oled_commandList(pre, 2);
|
||||
display.setContrast(_contrast);
|
||||
}
|
||||
|
||||
void SH1106Display::endFrame()
|
||||
|
||||
@@ -19,11 +19,13 @@ class SH1106Display : public DisplayDriver
|
||||
Adafruit_SH1106G display;
|
||||
bool _isOn;
|
||||
uint8_t _color;
|
||||
uint8_t _contrast;
|
||||
uint8_t _precharge;
|
||||
|
||||
bool i2c_probe(TwoWire &wire, uint8_t addr);
|
||||
|
||||
public:
|
||||
SH1106Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; }
|
||||
SH1106Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; _contrast = 255; _precharge = 0x1F; }
|
||||
bool begin();
|
||||
|
||||
bool isOn() override { return _isOn; }
|
||||
|
||||
Reference in New Issue
Block a user