mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-20 20:42:03 +00:00
feat(ui-core,ui-lvgl): sound in the Core, L2 speaker via ES8311, Sound settings, melody editor
- genericBuzzer I2S path (BUZZER_I2S): an audio task synthesises the notes with eased edges and keeps the DMA queue fed with silence between sounds; ES8311 setup; amp powered through the board only around sounds - ui-core/SoundControl.h (mode, volume, built-in sounds, melody model), SoundNotifier moved to the Core, chanctl::melody, schema Sound page - L2: notifications, alarm / timer ring, locator sounds, Settings > Sound, melody editor with the playing note lit, per-chat sound, mute icon - noteIndex() on every buzzer backend; scrolling popups past screen height Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -835,32 +835,6 @@ class MessagesScreen : public UIScreen {
|
||||
return p && (p->ch_fav_bitmask & (1ULL << ch_idx)) != 0;
|
||||
}
|
||||
|
||||
// Returns per-channel notification state: 0=follow global, 1=muted, 2=force-on
|
||||
// Per-contact/channel notification + melody overrides share two storage
|
||||
// shapes, so the eight accessors below are thin wrappers over two primitives:
|
||||
//
|
||||
// • Channels — a 3-state packed into a pair of channel-index bitmasks: a
|
||||
// "presence" mask (is there an override at all) + a "variant" mask. The two
|
||||
// uses disagree on which state the variant bit means, so the caller passes
|
||||
// the state value that corresponds to variant-set (v_set).
|
||||
// • DMs — a small {prefix[4], value} table: find by 4-byte prefix, update or
|
||||
// clear (clear frees the slot), else insert into the first free slot, else
|
||||
// overwrite slot 0. value 0 == "no override" == empty slot.
|
||||
|
||||
static uint8_t maskPairGet(uint64_t presence, uint64_t variant, uint8_t idx,
|
||||
uint8_t v_set, uint8_t v_clr) {
|
||||
uint64_t m = 1ULL << idx;
|
||||
if (!(presence & m)) return 0;
|
||||
return (variant & m) ? v_set : v_clr;
|
||||
}
|
||||
static void maskPairSet(uint64_t& presence, uint64_t& variant, uint8_t idx,
|
||||
uint8_t state, uint8_t v_set) {
|
||||
uint64_t m = 1ULL << idx;
|
||||
if (state == 0) { presence &= ~m; variant &= ~m; return; }
|
||||
presence |= m;
|
||||
if (state == v_set) variant |= m; else variant &= ~m;
|
||||
}
|
||||
|
||||
// Channel notif (ui-core/ChannelControl.h): 0 = default, 1 = muted, 2 = force-on.
|
||||
uint8_t chNotifState(uint8_t ch_idx) const { return chanctl::notif(_task->getNodePrefs(), ch_idx); }
|
||||
void setChNotifState(uint8_t ch_idx, uint8_t state) { chanctl::setNotif(_task->getNodePrefs(), ch_idx, state); }
|
||||
@@ -869,15 +843,9 @@ class MessagesScreen : public UIScreen {
|
||||
uint8_t dmNotifState(const uint8_t* pub_key) const { return contactctl::notif(_task->getNodePrefs(), pub_key); }
|
||||
void setDmNotifState(const uint8_t* pub_key, uint8_t state) { contactctl::setNotif(_task->getNodePrefs(), pub_key, state); }
|
||||
|
||||
// Channel melody: slot 1 = melody 1 (variant bit clear), 2 = melody 2 (set).
|
||||
uint8_t chNotifMelody(uint8_t ch_idx) const {
|
||||
NodePrefs* p = _task->getNodePrefs();
|
||||
return p ? maskPairGet(p->ch_notif_melody_set, p->ch_notif_melody_2, ch_idx, 2, 1) : 0;
|
||||
}
|
||||
void setChNotifMelody(uint8_t ch_idx, uint8_t slot) {
|
||||
NodePrefs* p = _task->getNodePrefs();
|
||||
if (p) maskPairSet(p->ch_notif_melody_set, p->ch_notif_melody_2, ch_idx, slot, 2);
|
||||
}
|
||||
// Channel melody (ui-core/ChannelControl.h): 0 = global, 1 / 2 = melody 1 / 2.
|
||||
uint8_t chNotifMelody(uint8_t ch_idx) const { return chanctl::melody(_task->getNodePrefs(), ch_idx); }
|
||||
void setChNotifMelody(uint8_t ch_idx, uint8_t slot) { chanctl::setMelody(_task->getNodePrefs(), ch_idx, slot); }
|
||||
|
||||
uint8_t dmMelodySlot(const uint8_t* pub_key) const { return contactctl::melody(_task->getNodePrefs(), pub_key); }
|
||||
void setDmMelody(const uint8_t* pub_key, uint8_t slot) { contactctl::setMelody(_task->getNodePrefs(), pub_key, slot); }
|
||||
|
||||
@@ -6,20 +6,17 @@ class RingtoneEditorScreen : public UIScreen {
|
||||
UITask* _task;
|
||||
NodePrefs* _prefs;
|
||||
|
||||
static const int MAX_NOTES = 32;
|
||||
static const int MAX_NOTES = soundctl::MAX_NOTES; // melody model: ui-core/SoundControl.h
|
||||
|
||||
// Menu item indices
|
||||
enum MenuIdx { MI_PLAY=0, MI_SWITCH, MI_DURATION, MI_BPM, MI_INSERT, MI_DELETE, MI_SAVE, MI_DISCARD, MI_COUNT };
|
||||
|
||||
int _visible_notes = 7; // updated in render(); used by clampScroll()
|
||||
|
||||
static const uint16_t BPM_OPTS[5];
|
||||
static const char* DUR_LABELS[4];
|
||||
static const char PITCH_NAMES[8]; // lowercase rtttl names
|
||||
|
||||
uint8_t _notes[MAX_NOTES];
|
||||
uint8_t _len;
|
||||
uint8_t _bpm_idx;
|
||||
soundctl::Melody _mel;
|
||||
uint8_t* const _notes = _mel.notes;
|
||||
uint8_t& _len = _mel.len;
|
||||
uint8_t& _bpm_idx = _mel.bpm_idx;
|
||||
int _slot; // 0=melody1, 1=melody2
|
||||
int _cursor;
|
||||
int _scroll;
|
||||
@@ -30,12 +27,10 @@ class RingtoneEditorScreen : public UIScreen {
|
||||
char _menu_dur_label[16];
|
||||
char _menu_bpm_label[10];
|
||||
|
||||
static uint8_t notePitch(uint8_t b) { return b & 0x07; }
|
||||
static uint8_t noteOctave(uint8_t b) { return ((b >> 3) & 0x03) + 4; }
|
||||
static uint8_t noteDurIdx(uint8_t b) { return (b >> 5) & 0x03; }
|
||||
static uint8_t packNote(uint8_t pitch, uint8_t octave, uint8_t dur_idx) {
|
||||
return (pitch & 0x07) | (((octave - 4) & 0x03) << 3) | ((dur_idx & 0x03) << 5);
|
||||
}
|
||||
static uint8_t notePitch(uint8_t b) { return soundctl::notePitch(b); }
|
||||
static uint8_t noteOctave(uint8_t b) { return soundctl::noteOctave(b); }
|
||||
static uint8_t noteDurIdx(uint8_t b) { return soundctl::noteDur(b); }
|
||||
static uint8_t packNote(uint8_t pitch, uint8_t octave, uint8_t dur_idx) { return soundctl::packNote(pitch, octave, dur_idx); }
|
||||
|
||||
void clampScroll() {
|
||||
if (_cursor < _scroll) _scroll = _cursor;
|
||||
@@ -43,14 +38,11 @@ class RingtoneEditorScreen : public UIScreen {
|
||||
if (_scroll < 0) _scroll = 0;
|
||||
}
|
||||
|
||||
void buildRTTTL() {
|
||||
NodePrefs::buildRTTTLString(_notes, _len, _bpm_idx, _play_buf, sizeof(_play_buf));
|
||||
}
|
||||
void buildRTTTL() { soundctl::toRtttl(_mel, _play_buf, sizeof(_play_buf)); }
|
||||
|
||||
void previewNote(uint8_t note_byte) {
|
||||
uint8_t pitch = notePitch(note_byte);
|
||||
if (pitch == 0) { _task->stopMelody(); return; }
|
||||
snprintf(_play_buf, sizeof(_play_buf), "P:d=16,o=5,b=240:%c%d", PITCH_NAMES[pitch], noteOctave(note_byte));
|
||||
soundctl::notePreview(note_byte, _play_buf, sizeof(_play_buf));
|
||||
if (!_play_buf[0]) { _task->stopMelody(); return; }
|
||||
_task->playMelody(_play_buf);
|
||||
}
|
||||
|
||||
@@ -61,12 +53,7 @@ public:
|
||||
// setCurrScreen() (whose onShow() can't carry the slot argument).
|
||||
void selectSlot(int slot = 0) {
|
||||
_slot = (slot == 1) ? 1 : 0;
|
||||
bool s2 = (_slot == 1);
|
||||
_bpm_idx = (_prefs && (s2 ? _prefs->ringtone2_bpm_idx : _prefs->ringtone_bpm_idx) < 5)
|
||||
? (s2 ? _prefs->ringtone2_bpm_idx : _prefs->ringtone_bpm_idx) : 2;
|
||||
uint8_t rlen = _prefs ? (s2 ? _prefs->ringtone2_len : _prefs->ringtone_len) : 0;
|
||||
_len = (rlen <= (uint8_t)MAX_NOTES) ? rlen : 0;
|
||||
if (_prefs) memcpy(_notes, s2 ? _prefs->ringtone2_notes : _prefs->ringtone_notes, sizeof(_notes));
|
||||
soundctl::load(_prefs, _slot, _mel);
|
||||
_cursor = 0;
|
||||
_scroll = 0;
|
||||
_menu.active = false;
|
||||
@@ -76,8 +63,8 @@ public:
|
||||
snprintf(_menu_play_label, sizeof(_menu_play_label), "%s", _task->isMelodyPlaying() ? "Stop" : "Play");
|
||||
snprintf(_menu_slot_label, sizeof(_menu_slot_label), "Melody %d", (_slot == 0) ? 2 : 1);
|
||||
uint8_t di = (_cursor < _len) ? noteDurIdx(_notes[_cursor]) : 0;
|
||||
snprintf(_menu_dur_label, sizeof(_menu_dur_label), "Duration: %s", DUR_LABELS[di]);
|
||||
snprintf(_menu_bpm_label, sizeof(_menu_bpm_label), "BPM: %u", BPM_OPTS[_bpm_idx]);
|
||||
snprintf(_menu_dur_label, sizeof(_menu_dur_label), "Duration: %s", soundctl::durLabel(di));
|
||||
snprintf(_menu_bpm_label, sizeof(_menu_bpm_label), "BPM: %u", soundctl::bpm(_bpm_idx));
|
||||
_menu.begin("Options", 5);
|
||||
_menu.addItem(_menu_play_label);
|
||||
_menu.addItem(_menu_slot_label);
|
||||
@@ -101,7 +88,7 @@ public:
|
||||
_visible_notes = display.width() / cell_w;
|
||||
|
||||
char hdr[32];
|
||||
snprintf(hdr, sizeof(hdr), "M%d BPM:%u %d/%d", _slot + 1, BPM_OPTS[_bpm_idx], _len, MAX_NOTES);
|
||||
snprintf(hdr, sizeof(hdr), "M%d BPM:%u %d/%d", _slot + 1, soundctl::bpm(_bpm_idx), _len, MAX_NOTES);
|
||||
display.setCursor(0, 0);
|
||||
display.print(hdr);
|
||||
display.fillRect(0, display.headerH() - 1, display.width(), display.sepH());
|
||||
@@ -117,7 +104,7 @@ public:
|
||||
if (pitch == 0) {
|
||||
label[0] = '-'; label[1] = '-'; label[2] = '\0';
|
||||
} else {
|
||||
label[0] = PITCH_NAMES[pitch] - 32; // uppercase
|
||||
label[0] = soundctl::pitchChar(pitch) - 32; // uppercase
|
||||
label[1] = '0' + octave;
|
||||
label[2] = '\0';
|
||||
}
|
||||
@@ -147,7 +134,7 @@ public:
|
||||
if (_cursor < _len) {
|
||||
char info[24];
|
||||
snprintf(info, sizeof(info), "oct:%d dur:%s",
|
||||
noteOctave(_notes[_cursor]), DUR_LABELS[noteDurIdx(_notes[_cursor])]);
|
||||
noteOctave(_notes[_cursor]), soundctl::durLabel(noteDurIdx(_notes[_cursor])));
|
||||
display.setCursor(display.getCharWidth() + 2, info_y);
|
||||
display.print(info);
|
||||
} else if (_cursor == _len) {
|
||||
@@ -170,11 +157,11 @@ public:
|
||||
uint8_t di = noteDurIdx(_notes[_cursor]);
|
||||
di = (dir > 0) ? (di + 1) & 0x03 : (di + 3) & 0x03;
|
||||
_notes[_cursor] = packNote(p, o, di);
|
||||
snprintf(_menu_dur_label, sizeof(_menu_dur_label), "Duration: %s", DUR_LABELS[di]);
|
||||
snprintf(_menu_dur_label, sizeof(_menu_dur_label), "Duration: %s", soundctl::durLabel(di));
|
||||
} else if (sel == MI_BPM) {
|
||||
if (dir > 0) { if (_bpm_idx < 4) _bpm_idx++; }
|
||||
else { if (_bpm_idx > 0) _bpm_idx--; }
|
||||
snprintf(_menu_bpm_label, sizeof(_menu_bpm_label), "BPM: %u", BPM_OPTS[_bpm_idx]);
|
||||
snprintf(_menu_bpm_label, sizeof(_menu_bpm_label), "BPM: %u", soundctl::bpm(_bpm_idx));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,15 +213,7 @@ public:
|
||||
break;
|
||||
case MI_SAVE:
|
||||
if (_prefs) {
|
||||
if (_slot == 1) {
|
||||
_prefs->ringtone2_bpm_idx = _bpm_idx;
|
||||
_prefs->ringtone2_len = _len;
|
||||
memcpy(_prefs->ringtone2_notes, _notes, sizeof(_notes));
|
||||
} else {
|
||||
_prefs->ringtone_bpm_idx = _bpm_idx;
|
||||
_prefs->ringtone_len = _len;
|
||||
memcpy(_prefs->ringtone_notes, _notes, sizeof(_notes));
|
||||
}
|
||||
soundctl::store(_prefs, _slot, _mel);
|
||||
the_mesh.savePrefs();
|
||||
}
|
||||
_task->stopMelody();
|
||||
@@ -294,7 +273,3 @@ public:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const uint16_t RingtoneEditorScreen::BPM_OPTS[5] = { 60, 90, 120, 150, 180 };
|
||||
const char* RingtoneEditorScreen::DUR_LABELS[4] = { "1/4", "1/8", "1/16", "1/32" };
|
||||
const char RingtoneEditorScreen::PITCH_NAMES[8] = { 'p', 'c', 'd', 'e', 'f', 'g', 'a', 'b' };
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
#pragma once
|
||||
// Fork-specific buzzer/melody dispatch — isolated here so upstream changes to
|
||||
// UITask::notify() don't create merge conflicts.
|
||||
#ifdef PIN_BUZZER
|
||||
#include <helpers/ui/buzzer.h>
|
||||
#include "../NodePrefs.h"
|
||||
|
||||
class SoundNotifier {
|
||||
genericBuzzer& _buz;
|
||||
const NodePrefs* _prefs;
|
||||
char* _mel_buf;
|
||||
int _mel_buf_sz;
|
||||
|
||||
static void buildMelody(const NodePrefs* p, int slot, char* buf, int size) {
|
||||
const uint8_t* notes = (slot == 2) ? p->ringtone2_notes : p->ringtone_notes;
|
||||
uint8_t len = (slot == 2) ? p->ringtone2_len : p->ringtone_len;
|
||||
uint8_t bpm_i = (slot == 2) ? p->ringtone2_bpm_idx : p->ringtone_bpm_idx;
|
||||
NodePrefs::buildRTTTLString(notes, len, bpm_i, buf, size);
|
||||
}
|
||||
|
||||
// Play custom slot (1/2) or fall back to built-in RTTTL. Slot 3 = explicit silence.
|
||||
void playSlot(int slot, bool force, const char* fallback) {
|
||||
if (slot == 3) return;
|
||||
if (slot > 0 && _prefs) {
|
||||
if (_buz.isPlaying()) _buz.stop();
|
||||
buildMelody(_prefs, slot, _mel_buf, _mel_buf_sz);
|
||||
if (_mel_buf[0]) {
|
||||
if (force) _buz.playForced(_mel_buf); else _buz.play(_mel_buf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (force) _buz.playForced(fallback); else _buz.play(fallback);
|
||||
}
|
||||
|
||||
public:
|
||||
SoundNotifier(genericBuzzer& buz, const NodePrefs* prefs, char* buf, int sz)
|
||||
: _buz(buz), _prefs(prefs), _mel_buf(buf), _mel_buf_sz(sz) {}
|
||||
|
||||
void playDM(bool dm_valid, const uint8_t* dm_prefix) {
|
||||
bool play = false, force = false;
|
||||
if (dm_valid && _prefs) {
|
||||
uint8_t state = 0;
|
||||
for (int i = 0; i < NodePrefs::DM_NOTIF_TABLE_MAX; i++) {
|
||||
if (_prefs->dm_notif[i].state &&
|
||||
memcmp(_prefs->dm_notif[i].prefix, dm_prefix, 4) == 0)
|
||||
{ state = _prefs->dm_notif[i].state; break; }
|
||||
}
|
||||
if (state == 2) { play = true; force = true; }
|
||||
else if (state == 1) { /* muted */ }
|
||||
else { play = !_buz.isQuiet(); }
|
||||
} else {
|
||||
play = !_buz.isQuiet();
|
||||
}
|
||||
if (!play) return;
|
||||
|
||||
int slot = _prefs ? (int)_prefs->notif_melody_dm : 0;
|
||||
if (dm_valid && _prefs) {
|
||||
for (int i = 0; i < NodePrefs::DM_MELODY_TABLE_MAX; i++)
|
||||
if (_prefs->dm_melody[i].slot &&
|
||||
memcmp(_prefs->dm_melody[i].prefix, dm_prefix, 4) == 0)
|
||||
{ slot = _prefs->dm_melody[i].slot; break; }
|
||||
}
|
||||
playSlot(slot, force, "MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
|
||||
}
|
||||
|
||||
void playCH(int ch_idx) {
|
||||
bool play = false, force = false;
|
||||
if (ch_idx >= 0 && ch_idx < 64 && _prefs) {
|
||||
uint64_t mask = 1ULL << ch_idx;
|
||||
if (_prefs->ch_notif_override & mask) {
|
||||
if (!(_prefs->ch_notif_muted & mask)) { play = true; force = true; }
|
||||
} else {
|
||||
play = !_buz.isQuiet();
|
||||
}
|
||||
} else {
|
||||
play = !_buz.isQuiet();
|
||||
}
|
||||
if (!play) return;
|
||||
|
||||
int slot = _prefs ? (int)_prefs->notif_melody_ch : 0;
|
||||
if (ch_idx >= 0 && ch_idx < 64 && _prefs) {
|
||||
uint64_t mask = 1ULL << ch_idx;
|
||||
if (_prefs->ch_notif_melody_set & mask)
|
||||
slot = (_prefs->ch_notif_melody_2 & mask) ? 2 : 1;
|
||||
}
|
||||
playSlot(slot, force, "kerplop:d=16,o=6,b=120:32g#,32c#");
|
||||
}
|
||||
|
||||
void playAD(bool is_flood) {
|
||||
if (_prefs && _prefs->advert_sound_scope == ADVERT_SOUND_SCOPE_ZERO_HOP && is_flood) return;
|
||||
if (_buz.isQuiet()) return;
|
||||
int slot = _prefs ? (int)_prefs->notif_melody_ad : 0;
|
||||
playSlot(slot, false, "MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
|
||||
}
|
||||
};
|
||||
#endif
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "UITask.h"
|
||||
#include "SoundNotifier.h"
|
||||
#include "../ui-core/SoundNotifier.h"
|
||||
#include "../ui-core/SoundControl.h"
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include "../MyMesh.h"
|
||||
#include "../MsgExpand.h"
|
||||
@@ -1667,9 +1668,9 @@ void UITask::gotoGpioScreen() {
|
||||
void UITask::gotoLiveShareScreen() { setCurrScreen(live_share_screen); }
|
||||
|
||||
// ── Clock tools (engine in ui-core/ClockEngine.h) ───────────────────────────
|
||||
// The melody overrides mute (playMelody → buzzer.playForced); the ring
|
||||
// auto-stops after ClockEngine::RING_MS if no key dismisses it (see loop()).
|
||||
static const char* CLOCK_ALARM_MELODY = "alarm:d=8,o=6,b=125:c,c,c,c,p,c,c,c,c,p";
|
||||
// The melody (soundctl::MEL_ALARM) overrides mute (playMelody →
|
||||
// buzzer.playForced); the ring auto-stops after ClockEngine::RING_MS if no key
|
||||
// dismisses it (see loop()).
|
||||
|
||||
void UITask::wakeForAlarm() {
|
||||
if (_display != NULL) _display->turnOn();
|
||||
@@ -1693,7 +1694,7 @@ void UITask::tickCore() {
|
||||
_core->loop();
|
||||
drainCoreEvents();
|
||||
// Repeat the ring melody until dismissed or the ring window elapses.
|
||||
if (_core->clock.isRinging() && !isMelodyPlaying()) playMelody(CLOCK_ALARM_MELODY);
|
||||
if (_core->clock.isRinging() && !isMelodyPlaying()) playMelody(soundctl::MEL_ALARM);
|
||||
}
|
||||
|
||||
void UITask::drainCoreEvents() {
|
||||
@@ -1703,7 +1704,7 @@ void UITask::drainCoreEvents() {
|
||||
case UiEventType::ClockAlert:
|
||||
wakeForAlarm();
|
||||
showAlert(ev.text, ClockEngine::RING_MS);
|
||||
playMelody(CLOCK_ALARM_MELODY);
|
||||
playMelody(soundctl::MEL_ALARM);
|
||||
break;
|
||||
case UiEventType::ClockRingEnded:
|
||||
stopMelody();
|
||||
@@ -1715,10 +1716,10 @@ void UITask::drainCoreEvents() {
|
||||
case UiEventType::LocatorCrossed:
|
||||
showAlert(ev.text, 3000);
|
||||
if (!isBuzzerQuiet())
|
||||
playMelody(ev.flag ? "locarr:d=8,o=6,b=140:c,e,g" : "loclv:d=8,o=6,b=140:g,e,c");
|
||||
playMelody(ev.flag ? soundctl::MEL_ARRIVE : soundctl::MEL_LEAVE);
|
||||
break;
|
||||
case UiEventType::LocatorBeep:
|
||||
playMelody("locp:d=32,o=7,b=200:c");
|
||||
playMelody(soundctl::MEL_TICK);
|
||||
break;
|
||||
case UiEventType::MessageArrived:
|
||||
onMessageArrived(ev);
|
||||
@@ -2681,13 +2682,8 @@ void UITask::loop() {
|
||||
userLedHandler();
|
||||
|
||||
#ifdef PIN_BUZZER
|
||||
if (_node_prefs && _node_prefs->buzzer_auto) {
|
||||
bool should_quiet = isClientConnected(); // BLE bonded or an open USB port
|
||||
if (buzzer.isQuiet() != should_quiet) {
|
||||
buzzer.quiet(should_quiet);
|
||||
_next_refresh = 0;
|
||||
}
|
||||
}
|
||||
if (soundctl::autoTick(_node_prefs, buzzer, isClientConnected())) // BLE bonded or an open USB port
|
||||
_next_refresh = 0;
|
||||
if (buzzer.isPlaying()) buzzer.loop();
|
||||
#endif
|
||||
|
||||
@@ -3257,10 +3253,7 @@ void UITask::setBrightnessLevel(uint8_t level) {
|
||||
void UITask::setBuzzerVolumeLevel(uint8_t level) {
|
||||
#ifdef PIN_BUZZER
|
||||
if (_node_prefs == NULL) return;
|
||||
if (level > 4) level = 4;
|
||||
_node_prefs->buzzer_volume = level;
|
||||
buzzer.setVolume(level);
|
||||
if (level > 0) buzzer.playForced("Vol:d=16,o=6,b=120:c");
|
||||
soundctl::setVolume(_node_prefs, buzzer, level);
|
||||
_next_refresh = 0;
|
||||
#endif
|
||||
}
|
||||
@@ -3283,8 +3276,8 @@ void UITask::toggleBuzzer() {
|
||||
|
||||
int UITask::getBuzzerMode() {
|
||||
#ifdef PIN_BUZZER
|
||||
if (_node_prefs && _node_prefs->buzzer_auto) return 2;
|
||||
return buzzer.isQuiet() ? 1 : 0;
|
||||
if (_node_prefs && _node_prefs->buzzer_auto) return soundctl::MODE_AUTO;
|
||||
return buzzer.isQuiet() ? soundctl::MODE_OFF : soundctl::MODE_ON;
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
@@ -3294,11 +3287,9 @@ void UITask::cycleBuzzerMode() {
|
||||
#ifdef PIN_BUZZER
|
||||
if (!_node_prefs) return;
|
||||
int mode = getBuzzerMode();
|
||||
mode = (mode + 1) % 3; // ON(0) → OFF(1) → Auto(2) → ON
|
||||
_node_prefs->buzzer_auto = (mode == 2) ? 1 : 0;
|
||||
if (mode == 0) { buzzer.quiet(false); _node_prefs->buzzer_quiet = 0; notify(UIEventType::ack); }
|
||||
if (mode == 1) { buzzer.quiet(true); _node_prefs->buzzer_quiet = 1; }
|
||||
if (mode == 2) { buzzer.quiet(isClientConnected()); }
|
||||
mode = (mode + 1) % soundctl::MODE_COUNT; // ON → OFF → Auto → ON
|
||||
soundctl::setMode(_node_prefs, buzzer, (uint8_t)mode, isClientConnected());
|
||||
if (mode == soundctl::MODE_ON) notify(UIEventType::ack);
|
||||
static const char* labels[] = { "Buzzer: ON", "Buzzer: OFF", "Buzzer: Auto" };
|
||||
showAlert(labels[mode], 800);
|
||||
_next_refresh = 0;
|
||||
|
||||
Reference in New Issue
Block a user