mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-09 11:16:39 +00:00
feat(l2): Arduino-ESP32 3.3.12 / IDF 5.5 env, NimBLE and new I2S driver ports, large UI buffers in PSRAM
- New env Wio_Tracker_L2_companion_solo_lvgl_v3 on pioarduino 55.03.312-1; the 2.0.17 env stays until the new one passes the hardware checklist - SerialBLEInterface: NimBLE variant (PIN pairing through encrypted, authenticated characteristic properties; setAccessPermissions is a no-op) - buzzer: i2s_std driver on IDF 5 (legacy driver kept for 2.0.17) - ESPNOWRadio: IDF 5 callback signatures - ui-lvgl: psramBuf() for trail drawing, tile cache, map marks, list rows, keyboard maps and message metadata; internal heap back to 2.0.17 level Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,8 @@ static lv_obj_t* s_lock_unread = nullptr;
|
|||||||
static lv_obj_t* s_lock_slider = nullptr;
|
static lv_obj_t* s_lock_slider = nullptr;
|
||||||
|
|
||||||
static int s_fav_slot = -1; // slot the hold / pick popup is about
|
static int s_fav_slot = -1; // slot the hold / pick popup is about
|
||||||
static uint8_t s_pick_keys[64][PUB_KEY_SIZE];
|
static const int PICK_MAX = 64;
|
||||||
|
static uint8_t (*s_pick_keys)[PUB_KEY_SIZE] = psramBuf<uint8_t[PUB_KEY_SIZE]>(PICK_MAX);
|
||||||
enum : int { PICK_CONTACT = 100 }; // pick codes: channel index, or PICK_CONTACT + row
|
enum : int { PICK_CONTACT = 100 }; // pick codes: channel index, or PICK_CONTACT + row
|
||||||
|
|
||||||
// "Pin" from an options popup: what gets pinned.
|
// "Pin" from an options popup: what gets pinned.
|
||||||
@@ -313,7 +314,7 @@ void UITask::favPickPopup(int slot) {
|
|||||||
}
|
}
|
||||||
sectionTitle(list, "CONTACTS AND ROOMS");
|
sectionTitle(list, "CONTACTS AND ROOMS");
|
||||||
int rows = 0, total = the_mesh.getNumContacts();
|
int rows = 0, total = the_mesh.getNumContacts();
|
||||||
const int MAX_ROWS = (int)(sizeof(s_pick_keys) / sizeof(s_pick_keys[0]));
|
const int MAX_ROWS = PICK_MAX;
|
||||||
for (int pass = 0; pass < 2; pass++) {
|
for (int pass = 0; pass < 2; pass++) {
|
||||||
for (int i = 0; i < total && rows < MAX_ROWS; i++) {
|
for (int i = 0; i < total && rows < MAX_ROWS; i++) {
|
||||||
ContactInfo c;
|
ContactInfo c;
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ static const char* const* const ROWS[L_COUNT] = { LAT, LAT_UP, CYR, CYR_UP, GRK,
|
|||||||
// Full lv_keyboard maps: the rows above + a bottom row assembled in
|
// Full lv_keyboard maps: the rows above + a bottom row assembled in
|
||||||
// buildMaps() (globe only when a second script is enabled).
|
// buildMaps() (globe only when a second script is enabled).
|
||||||
static const int MAX_KEYS = 40;
|
static const int MAX_KEYS = 40;
|
||||||
static const char* s_maps[L_COUNT][MAX_KEYS + 8];
|
static const char* (*s_maps)[MAX_KEYS + 8] = psramBuf<const char*[MAX_KEYS + 8]>(L_COUNT); // in PSRAM
|
||||||
static lv_buttonmatrix_ctrl_t s_ctrl[L_COUNT][MAX_KEYS];
|
static lv_buttonmatrix_ctrl_t (*s_ctrl)[MAX_KEYS] = psramBuf<lv_buttonmatrix_ctrl_t[MAX_KEYS]>(L_COUNT);
|
||||||
static uint8_t s_layout = L_LAT;
|
static uint8_t s_layout = L_LAT;
|
||||||
static uint8_t s_script = 0; // current script (NodePrefs KB_ALPHABET_*)
|
static uint8_t s_script = 0; // current script (NodePrefs KB_ALPHABET_*)
|
||||||
static NodePrefs* s_prefs = nullptr;
|
static NodePrefs* s_prefs = nullptr;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace mapview {
|
|||||||
|
|
||||||
static RasterTileProvider s_raster("/sdcard/maps");
|
static RasterTileProvider s_raster("/sdcard/maps");
|
||||||
static TileProvider* s_provider = &s_raster; // the one place to swap in a vector renderer
|
static TileProvider* s_provider = &s_raster; // the one place to swap in a vector renderer
|
||||||
static TileCache s_cache;
|
static TileCache& s_cache = *new (psramBuf<TileCache>(1)) TileCache(); // decoded tiles, in PSRAM
|
||||||
static TileDownloader s_dl("/sdcard/maps");
|
static TileDownloader s_dl("/sdcard/maps");
|
||||||
static const uint32_t AVG_TILE_BYTES = 22 * 1024; // OpenTopoMap-ish, for the size estimate
|
static const uint32_t AVG_TILE_BYTES = 22 * 1024; // OpenTopoMap-ish, for the size estimate
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ static double latToTileY(double lat, int z) {
|
|||||||
enum : uint8_t { MK_NODE, MK_WAYPOINT, MK_LIVE };
|
enum : uint8_t { MK_NODE, MK_WAYPOINT, MK_LIVE };
|
||||||
struct Mark { int32_t lat_e6, lon_e6; int idx; uint8_t kind; lv_obj_t* obj; lv_obj_t* dot; };
|
struct Mark { int32_t lat_e6, lon_e6; int idx; uint8_t kind; lv_obj_t* obj; lv_obj_t* dot; };
|
||||||
static const int MAX_MARKS = 48; // >= MAX_NEARBY, and >= waypoints + live shares
|
static const int MAX_MARKS = 48; // >= MAX_NEARBY, and >= waypoints + live shares
|
||||||
static Mark s_marks[MAX_MARKS];
|
static Mark* s_marks = psramBuf<Mark>(MAX_MARKS);
|
||||||
static int s_mark_count = 0;
|
static int s_mark_count = 0;
|
||||||
static int s_drag = 0; // px moved in the current press: a drag isn't a tap
|
static int s_drag = 0; // px moved in the current press: a drag isn't a tap
|
||||||
static bool s_available = false; // provider has data; checked when the map opens, not per frame
|
static bool s_available = false; // provider has data; checked when the map opens, not per frame
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ static const int TRAIL_SEGS = 8;
|
|||||||
// Stored as float offsets from the first point (s_ox/s_oy): a float holds a
|
// Stored as float offsets from the first point (s_ox/s_oy): a float holds a
|
||||||
// small offset exactly enough, but not a whole 0..1 coordinate at street zoom
|
// small offset exactly enough, but not a whole 0..1 coordinate at street zoom
|
||||||
// (2^18 * 256 px across -- a float is off by pixels there).
|
// (2^18 * 256 px across -- a float is off by pixels there).
|
||||||
static float s_nx[TrailStore::CAPACITY], s_ny[TrailStore::CAPACITY];
|
static float* s_nx = nullptr; // TrailStore::CAPACITY each, in PSRAM (first trail drawn)
|
||||||
|
static float* s_ny = nullptr;
|
||||||
static double s_ox = 0, s_oy = 0;
|
static double s_ox = 0, s_oy = 0;
|
||||||
static lv_point_precise_t s_pts[TrailStore::CAPACITY];
|
static lv_point_precise_t* s_pts = nullptr;
|
||||||
static int s_seg_first[TRAIL_SEGS], s_seg_len[TRAIL_SEGS], s_segs = 0;
|
static int s_seg_first[TRAIL_SEGS], s_seg_len[TRAIL_SEGS], s_segs = 0;
|
||||||
static lv_obj_t* s_trail[TRAIL_SEGS];
|
static lv_obj_t* s_trail[TRAIL_SEGS];
|
||||||
static lv_obj_t* s_target_line = nullptr;
|
static lv_obj_t* s_target_line = nullptr;
|
||||||
@@ -191,6 +192,12 @@ void UITask::rebuildNavMarkers() {
|
|||||||
TrailStore& ts = _core->trail.store();
|
TrailStore& ts = _core->trail.store();
|
||||||
navmap::s_segs = 0;
|
navmap::s_segs = 0;
|
||||||
int n = ts.count();
|
int n = ts.count();
|
||||||
|
if (!navmap::s_pts) {
|
||||||
|
navmap::s_nx = psramBuf<float>(TrailStore::CAPACITY);
|
||||||
|
navmap::s_ny = psramBuf<float>(TrailStore::CAPACITY);
|
||||||
|
navmap::s_pts = psramBuf<lv_point_precise_t>(TrailStore::CAPACITY);
|
||||||
|
}
|
||||||
|
if (!navmap::s_nx || !navmap::s_ny || !navmap::s_pts) n = 0;
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
const TrailPoint& p = ts.at(i);
|
const TrailPoint& p = ts.at(i);
|
||||||
if (i == 0) { navmap::s_ox = navmap::normX(p.lon_1e6); navmap::s_oy = navmap::normY(p.lat_1e6); }
|
if (i == 0) { navmap::s_ox = navmap::normX(p.lon_1e6); navmap::s_oy = navmap::normY(p.lat_1e6); }
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ static const int TX_MAX = 22;
|
|||||||
static lv_obj_t* s_overlay = nullptr; // frequency entry
|
static lv_obj_t* s_overlay = nullptr; // frequency entry
|
||||||
static lv_obj_t* s_ta = nullptr;
|
static lv_obj_t* s_ta = nullptr;
|
||||||
static bool s_freq_rpt = false; // the entry is for the repeater profile (RepeaterScreen.h)
|
static bool s_freq_rpt = false; // the entry is for the repeater profile (RepeaterScreen.h)
|
||||||
static char s_opts[1024];
|
static const int OPTS_LEN = 1024;
|
||||||
|
static char* s_opts = psramBuf<char>(OPTS_LEN); // dropdown options (LVGL copies them)
|
||||||
|
|
||||||
// Label + optional hint on the left of a settings row; the control goes on the right.
|
// Label + optional hint on the left of a settings row; the control goes on the right.
|
||||||
static lv_obj_t* settingRow(lv_obj_t* parent, const char* text, const char* hint) {
|
static lv_obj_t* settingRow(lv_obj_t* parent, const char* text, const char* hint) {
|
||||||
@@ -83,10 +84,10 @@ void UITask::buildRadio() {
|
|||||||
NodePrefs* p = _prefs;
|
NodePrefs* p = _prefs;
|
||||||
|
|
||||||
// Preset: "Custom" first (current params match none), then the list.
|
// Preset: "Custom" first (current params match none), then the list.
|
||||||
int o = snprintf(s_opts, sizeof(s_opts), "Custom");
|
int o = snprintf(s_opts, OPTS_LEN, "Custom");
|
||||||
const char* name; float f, b; uint8_t sf, cr;
|
const char* name; float f, b; uint8_t sf, cr;
|
||||||
for (int i = 0; radioctl::presetAt(p, i, name, f, b, sf, cr) && o < (int)sizeof(s_opts) - 24; i++)
|
for (int i = 0; radioctl::presetAt(p, i, name, f, b, sf, cr) && o < OPTS_LEN - 24; i++)
|
||||||
o += snprintf(s_opts + o, sizeof(s_opts) - o, "\n%s", name);
|
o += snprintf(s_opts + o, OPTS_LEN - o, "\n%s", name);
|
||||||
lv_obj_t* row = settingRow(body, "Preset", nullptr);
|
lv_obj_t* row = settingRow(body, "Preset", nullptr);
|
||||||
lv_obj_t* dd = rowDropdown(row, s_opts, radioctl::currentPreset(p) + 1, 200, onRadioDropdown, R_PRESET);
|
lv_obj_t* dd = rowDropdown(row, s_opts, radioctl::currentPreset(p) + 1, 200, onRadioDropdown, R_PRESET);
|
||||||
lv_dropdown_set_dir(dd, LV_DIR_BOTTOM);
|
lv_dropdown_set_dir(dd, LV_DIR_BOTTOM);
|
||||||
@@ -105,21 +106,21 @@ void UITask::buildRadio() {
|
|||||||
lv_obj_center(label(fb, fs, THEME_FONT_BODY, theme::TEXT));
|
lv_obj_center(label(fb, fs, THEME_FONT_BODY, theme::TEXT));
|
||||||
|
|
||||||
o = 0;
|
o = 0;
|
||||||
for (int v = 5; v <= 12; v++) o += snprintf(s_opts + o, sizeof(s_opts) - o, v > 5 ? "\n%d" : "%d", v);
|
for (int v = 5; v <= 12; v++) o += snprintf(s_opts + o, OPTS_LEN - o, v > 5 ? "\n%d" : "%d", v);
|
||||||
rowDropdown(settingRow(body, "Spreading factor", "Higher = longer range"), s_opts,
|
rowDropdown(settingRow(body, "Spreading factor", "Higher = longer range"), s_opts,
|
||||||
p->sf >= 5 && p->sf <= 12 ? p->sf - 5 : 0, 90, onRadioDropdown, R_SF);
|
p->sf >= 5 && p->sf <= 12 ? p->sf - 5 : 0, 90, onRadioDropdown, R_SF);
|
||||||
o = 0;
|
o = 0;
|
||||||
for (int i = 0; i < LORA_BW_OPT_COUNT; i++)
|
for (int i = 0; i < LORA_BW_OPT_COUNT; i++)
|
||||||
o += snprintf(s_opts + o, sizeof(s_opts) - o, "%s%g kHz", i ? "\n" : "", (double)LORA_BW_OPTS[i]);
|
o += snprintf(s_opts + o, OPTS_LEN - o, "%s%g kHz", i ? "\n" : "", (double)LORA_BW_OPTS[i]);
|
||||||
rowDropdown(settingRow(body, "Bandwidth", nullptr), s_opts, nearestBwIndex(p->bw), 130, onRadioDropdown, R_BW);
|
rowDropdown(settingRow(body, "Bandwidth", nullptr), s_opts, nearestBwIndex(p->bw), 130, onRadioDropdown, R_BW);
|
||||||
o = 0;
|
o = 0;
|
||||||
for (int v = 5; v <= 8; v++) o += snprintf(s_opts + o, sizeof(s_opts) - o, v > 5 ? "\n4/%d" : "4/%d", v);
|
for (int v = 5; v <= 8; v++) o += snprintf(s_opts + o, OPTS_LEN - o, v > 5 ? "\n4/%d" : "4/%d", v);
|
||||||
rowDropdown(settingRow(body, "Coding rate", nullptr), s_opts, p->cr >= 5 && p->cr <= 8 ? p->cr - 5 : 0, 90,
|
rowDropdown(settingRow(body, "Coding rate", nullptr), s_opts, p->cr >= 5 && p->cr <= 8 ? p->cr - 5 : 0, 90,
|
||||||
onRadioDropdown, R_CR);
|
onRadioDropdown, R_CR);
|
||||||
|
|
||||||
sectionTitle(body, "TRANSMIT");
|
sectionTitle(body, "TRANSMIT");
|
||||||
o = 0;
|
o = 0;
|
||||||
for (int v = TX_MIN; v <= TX_MAX; v++) o += snprintf(s_opts + o, sizeof(s_opts) - o, v > TX_MIN ? "\n%d dBm" : "%d dBm", v);
|
for (int v = TX_MIN; v <= TX_MAX; v++) o += snprintf(s_opts + o, OPTS_LEN - o, v > TX_MIN ? "\n%d dBm" : "%d dBm", v);
|
||||||
int tx = p->tx_power_dbm < TX_MIN ? TX_MIN : p->tx_power_dbm > TX_MAX ? TX_MAX : p->tx_power_dbm;
|
int tx = p->tx_power_dbm < TX_MIN ? TX_MIN : p->tx_power_dbm > TX_MAX ? TX_MAX : p->tx_power_dbm;
|
||||||
rowDropdown(settingRow(body, "TX power", p->tx_apc ? "Ceiling for auto power" : nullptr), s_opts, tx - TX_MIN, 110,
|
rowDropdown(settingRow(body, "TX power", p->tx_apc ? "Ceiling for auto power" : nullptr), s_opts, tx - TX_MIN, 110,
|
||||||
onRadioDropdown, R_TX);
|
onRadioDropdown, R_TX);
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
namespace rptview {
|
namespace rptview {
|
||||||
|
|
||||||
enum : uint8_t { RP_ON, RP_NETWORK, RP_PRESET, RP_SF, RP_BW, RP_CR, RP_HOPS, RP_YIELD, RP_SNR };
|
enum : uint8_t { RP_ON, RP_NETWORK, RP_PRESET, RP_SF, RP_BW, RP_CR, RP_HOPS, RP_YIELD, RP_SNR };
|
||||||
static char s_opts[768];
|
static const int OPTS_LEN = 768;
|
||||||
|
static char* s_opts = psramBuf<char>(OPTS_LEN); // dropdown options (LVGL copies them)
|
||||||
static lv_obj_t* s_scopes_sub = nullptr; // "Extra scopes" row's count, updated from the popup
|
static lv_obj_t* s_scopes_sub = nullptr; // "Extra scopes" row's count, updated from the popup
|
||||||
|
|
||||||
} // namespace rptview
|
} // namespace rptview
|
||||||
@@ -77,10 +78,10 @@ void UITask::buildRepeater() {
|
|||||||
lv_obj_align(seg, LV_ALIGN_RIGHT_MID, -4, 0);
|
lv_obj_align(seg, LV_ALIGN_RIGHT_MID, -4, 0);
|
||||||
lv_obj_add_event_cb(seg, onRptNetwork, LV_EVENT_VALUE_CHANGED, NULL);
|
lv_obj_add_event_cb(seg, onRptNetwork, LV_EVENT_VALUE_CHANGED, NULL);
|
||||||
if (p->repeater_use_profile) {
|
if (p->repeater_use_profile) {
|
||||||
int o = snprintf(s_opts, sizeof(s_opts), "Custom");
|
int o = snprintf(s_opts, OPTS_LEN, "Custom");
|
||||||
const char* name; float f, b; uint8_t sf, cr;
|
const char* name; float f, b; uint8_t sf, cr;
|
||||||
for (int i = 0; radioctl::presetAt(p, i, name, f, b, sf, cr) && o < (int)sizeof(s_opts) - 24; i++)
|
for (int i = 0; radioctl::presetAt(p, i, name, f, b, sf, cr) && o < OPTS_LEN - 24; i++)
|
||||||
o += snprintf(s_opts + o, sizeof(s_opts) - o, "\n%s", name);
|
o += snprintf(s_opts + o, OPTS_LEN - o, "\n%s", name);
|
||||||
lv_obj_t* dd = rowDropdown(settingRow(body, "Preset", nullptr), s_opts, rptctl::currentPreset(p) + 1, 200,
|
lv_obj_t* dd = rowDropdown(settingRow(body, "Preset", nullptr), s_opts, rptctl::currentPreset(p) + 1, 200,
|
||||||
onRptDropdown, RP_PRESET);
|
onRptDropdown, RP_PRESET);
|
||||||
lv_dropdown_set_dir(dd, LV_DIR_BOTTOM);
|
lv_dropdown_set_dir(dd, LV_DIR_BOTTOM);
|
||||||
@@ -98,15 +99,15 @@ void UITask::buildRepeater() {
|
|||||||
lv_obj_center(label(fb, fs, THEME_FONT_BODY, theme::TEXT));
|
lv_obj_center(label(fb, fs, THEME_FONT_BODY, theme::TEXT));
|
||||||
|
|
||||||
o = 0;
|
o = 0;
|
||||||
for (int v = 5; v <= 12; v++) o += snprintf(s_opts + o, sizeof(s_opts) - o, v > 5 ? "\n%d" : "%d", v);
|
for (int v = 5; v <= 12; v++) o += snprintf(s_opts + o, OPTS_LEN - o, v > 5 ? "\n%d" : "%d", v);
|
||||||
rowDropdown(settingRow(body, "Spreading factor", nullptr), s_opts,
|
rowDropdown(settingRow(body, "Spreading factor", nullptr), s_opts,
|
||||||
p->repeater_sf >= 5 && p->repeater_sf <= 12 ? p->repeater_sf - 5 : 0, 90, onRptDropdown, RP_SF);
|
p->repeater_sf >= 5 && p->repeater_sf <= 12 ? p->repeater_sf - 5 : 0, 90, onRptDropdown, RP_SF);
|
||||||
o = 0;
|
o = 0;
|
||||||
for (int i = 0; i < LORA_BW_OPT_COUNT; i++)
|
for (int i = 0; i < LORA_BW_OPT_COUNT; i++)
|
||||||
o += snprintf(s_opts + o, sizeof(s_opts) - o, "%s%g kHz", i ? "\n" : "", (double)LORA_BW_OPTS[i]);
|
o += snprintf(s_opts + o, OPTS_LEN - o, "%s%g kHz", i ? "\n" : "", (double)LORA_BW_OPTS[i]);
|
||||||
rowDropdown(settingRow(body, "Bandwidth", nullptr), s_opts, nearestBwIndex(p->repeater_bw), 130, onRptDropdown, RP_BW);
|
rowDropdown(settingRow(body, "Bandwidth", nullptr), s_opts, nearestBwIndex(p->repeater_bw), 130, onRptDropdown, RP_BW);
|
||||||
o = 0;
|
o = 0;
|
||||||
for (int v = 5; v <= 8; v++) o += snprintf(s_opts + o, sizeof(s_opts) - o, v > 5 ? "\n4/%d" : "4/%d", v);
|
for (int v = 5; v <= 8; v++) o += snprintf(s_opts + o, OPTS_LEN - o, v > 5 ? "\n4/%d" : "4/%d", v);
|
||||||
rowDropdown(settingRow(body, "Coding rate", nullptr), s_opts,
|
rowDropdown(settingRow(body, "Coding rate", nullptr), s_opts,
|
||||||
p->repeater_cr >= 5 && p->repeater_cr <= 8 ? p->repeater_cr - 5 : 0, 90, onRptDropdown, RP_CR);
|
p->repeater_cr >= 5 && p->repeater_cr <= 8 ? p->repeater_cr - 5 : 0, 90, onRptDropdown, RP_CR);
|
||||||
}
|
}
|
||||||
@@ -117,21 +118,21 @@ void UITask::buildRepeater() {
|
|||||||
int o = 0;
|
int o = 0;
|
||||||
for (int i = 0; i <= rptctl::MAX_HOPS; i++) {
|
for (int i = 0; i <= rptctl::MAX_HOPS; i++) {
|
||||||
rptctl::fmtHops(v, sizeof(v), (uint8_t)i);
|
rptctl::fmtHops(v, sizeof(v), (uint8_t)i);
|
||||||
o += snprintf(s_opts + o, sizeof(s_opts) - o, "%s%s", i ? "\n" : "", v);
|
o += snprintf(s_opts + o, OPTS_LEN - o, "%s%s", i ? "\n" : "", v);
|
||||||
}
|
}
|
||||||
rowDropdown(settingRow(body, "Max hops", "Hop limit for floods"), s_opts, p->repeat_max_hops, 90,
|
rowDropdown(settingRow(body, "Max hops", "Hop limit for floods"), s_opts, p->repeat_max_hops, 90,
|
||||||
onRptDropdown, RP_HOPS);
|
onRptDropdown, RP_HOPS);
|
||||||
o = 0;
|
o = 0;
|
||||||
for (int i = 0; i <= rptctl::MAX_YIELD; i++) {
|
for (int i = 0; i <= rptctl::MAX_YIELD; i++) {
|
||||||
rptctl::fmtYield(v, sizeof(v), (uint8_t)i);
|
rptctl::fmtYield(v, sizeof(v), (uint8_t)i);
|
||||||
o += snprintf(s_opts + o, sizeof(s_opts) - o, "%s%s", i ? "\n" : "", v);
|
o += snprintf(s_opts + o, OPTS_LEN - o, "%s%s", i ? "\n" : "", v);
|
||||||
}
|
}
|
||||||
rowDropdown(settingRow(body, "Yield", "Let fixed repeaters go first"), s_opts, p->repeat_delay_boost, 90,
|
rowDropdown(settingRow(body, "Yield", "Let fixed repeaters go first"), s_opts, p->repeat_delay_boost, 90,
|
||||||
onRptDropdown, RP_YIELD);
|
onRptDropdown, RP_YIELD);
|
||||||
o = 0;
|
o = 0;
|
||||||
for (int i = 0; i < rptctl::snrChoiceCount(); i++) {
|
for (int i = 0; i < rptctl::snrChoiceCount(); i++) {
|
||||||
rptctl::fmtSnr(v, sizeof(v), rptctl::snrFromChoice(i));
|
rptctl::fmtSnr(v, sizeof(v), rptctl::snrFromChoice(i));
|
||||||
o += snprintf(s_opts + o, sizeof(s_opts) - o, "%s%s", i ? "\n" : "", v);
|
o += snprintf(s_opts + o, OPTS_LEN - o, "%s%s", i ? "\n" : "", v);
|
||||||
}
|
}
|
||||||
rowDropdown(settingRow(body, "Min SNR", "Ignore weaker packets"), s_opts, rptctl::snrToChoice(p->repeat_min_snr), 100,
|
rowDropdown(settingRow(body, "Min SNR", "Ignore weaker packets"), s_opts, rptctl::snrToChoice(p->repeat_min_snr), 100,
|
||||||
onRptDropdown, RP_SNR);
|
onRptDropdown, RP_SNR);
|
||||||
|
|||||||
@@ -5,6 +5,16 @@
|
|||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
#include <esp_heap_caps.h>
|
#include <esp_heap_caps.h>
|
||||||
#endif
|
#endif
|
||||||
|
#include <new>
|
||||||
|
|
||||||
|
// A zeroed buffer of `n` T in PSRAM when the board has it: internal RAM is what
|
||||||
|
// BLE, WiFi and TLS need. Allocated once (at startup or first use), never freed.
|
||||||
|
template <class T> static T* psramBuf(size_t n) {
|
||||||
|
#if defined(ESP32)
|
||||||
|
if (void* p = heap_caps_calloc(n, sizeof(T), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)) return (T*)p;
|
||||||
|
#endif
|
||||||
|
return (T*)calloc(n, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
#include "../ui-core/UiCore.h" // shared UI Core (header-only, this TU)
|
#include "../ui-core/UiCore.h" // shared UI Core (header-only, this TU)
|
||||||
#include "../ui-core/NearbyModel.h"
|
#include "../ui-core/NearbyModel.h"
|
||||||
@@ -254,6 +264,19 @@ void UITask::loop() {
|
|||||||
|
|
||||||
_core->loop();
|
_core->loop();
|
||||||
drainCoreEvents();
|
drainCoreEvents();
|
||||||
|
#if defined(UI_HEAP_REPORT) && defined(ESP32)
|
||||||
|
// -D UI_HEAP_REPORT: internal / PSRAM heap once, 20 s after boot (the
|
||||||
|
// framework comparison in docs/development/l2-roadmap.md).
|
||||||
|
static bool heap_done = false;
|
||||||
|
if (!heap_done && millis() > 20000) {
|
||||||
|
heap_done = true;
|
||||||
|
Serial.printf("HEAP internal free %u largest %u min %u | psram free %u | idf %s\n",
|
||||||
|
(unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
|
||||||
|
(unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),
|
||||||
|
(unsigned)heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
|
||||||
|
(unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM), esp_get_idf_version());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#ifdef PIN_BUZZER
|
#ifdef PIN_BUZZER
|
||||||
_buzzer.loop();
|
_buzzer.loop();
|
||||||
if (soundctl::autoTick(_prefs, _buzzer, isClientConnected()) && !_asleep) refreshStatusBar();
|
if (soundctl::autoTick(_prefs, _buzzer, isClientConnected()) && !_asleep) refreshStatusBar();
|
||||||
@@ -853,7 +876,8 @@ static void onOpenChannel(lv_event_t* e) {
|
|||||||
|
|
||||||
// DM rows carry a 4-byte prefix; kept in a static table the rows point into.
|
// DM rows carry a 4-byte prefix; kept in a static table the rows point into.
|
||||||
static uint8_t s_dm_rows[MessageHistory::DM_HIST_MAX][4];
|
static uint8_t s_dm_rows[MessageHistory::DM_HIST_MAX][4];
|
||||||
static uint8_t s_contact_rows[64][PUB_KEY_SIZE];
|
static const int CONTACT_ROWS_MAX = 64;
|
||||||
|
static uint8_t (*s_contact_rows)[PUB_KEY_SIZE] = psramBuf<uint8_t[PUB_KEY_SIZE]>(CONTACT_ROWS_MAX);
|
||||||
|
|
||||||
static void onOpenDMRow(lv_event_t* e) {
|
static void onOpenDMRow(lv_event_t* e) {
|
||||||
s_ui->openDM(s_dm_rows[(uintptr_t)lv_event_get_user_data(e)]);
|
s_ui->openDM(s_dm_rows[(uintptr_t)lv_event_get_user_data(e)]);
|
||||||
@@ -865,7 +889,8 @@ static void onNewChat(lv_event_t* e) { (void)e; s_ui->showContacts(); }
|
|||||||
static void onChanRowHold(lv_event_t* e); // ChannelScreen.h
|
static void onChanRowHold(lv_event_t* e); // ChannelScreen.h
|
||||||
static void onChanAdd(lv_event_t* e);
|
static void onChanAdd(lv_event_t* e);
|
||||||
static void onChanThreadMenu(lv_event_t* e);
|
static void onChanThreadMenu(lv_event_t* e);
|
||||||
static uint8_t s_room_rows[16][PUB_KEY_SIZE];
|
static const int ROOM_ROWS_MAX = 16;
|
||||||
|
static uint8_t (*s_room_rows)[PUB_KEY_SIZE] = psramBuf<uint8_t[PUB_KEY_SIZE]>(ROOM_ROWS_MAX);
|
||||||
static void onDMRowHold(lv_event_t* e); // ConversationScreen.h
|
static void onDMRowHold(lv_event_t* e); // ConversationScreen.h
|
||||||
static void onRoomRow(lv_event_t* e);
|
static void onRoomRow(lv_event_t* e);
|
||||||
static void onRoomRowHold(lv_event_t* e);
|
static void onRoomRowHold(lv_event_t* e);
|
||||||
@@ -1007,7 +1032,7 @@ void UITask::buildChats() {
|
|||||||
else snprintf(rt, sizeof(rt), "ROOMS");
|
else snprintf(rt, sizeof(rt), "ROOMS");
|
||||||
sectionWithFilter(body, rt, room_fav_only, CF_ROOMS);
|
sectionWithFilter(body, rt, room_fav_only, CF_ROOMS);
|
||||||
int nrooms = 0, total = the_mesh.getNumContacts();
|
int nrooms = 0, total = the_mesh.getNumContacts();
|
||||||
const int MAX_ROOMS = (int)(sizeof(s_room_rows) / sizeof(s_room_rows[0]));
|
const int MAX_ROOMS = ROOM_ROWS_MAX;
|
||||||
for (int pass = fav_first ? 0 : 1; pass < 2; pass++) {
|
for (int pass = fav_first ? 0 : 1; pass < 2; pass++) {
|
||||||
for (int i = 0; i < total && nrooms < MAX_ROOMS; i++) {
|
for (int i = 0; i < total && nrooms < MAX_ROOMS; i++) {
|
||||||
ContactInfo c;
|
ContactInfo c;
|
||||||
@@ -1055,7 +1080,7 @@ void UITask::buildContacts() {
|
|||||||
int rows = 0;
|
int rows = 0;
|
||||||
// +MAX_ANON_CONTACTS: getContactByIdx() takes the raw table index (see
|
// +MAX_ANON_CONTACTS: getContactByIdx() takes the raw table index (see
|
||||||
// MessageHistory::contactByPrefix()).
|
// MessageHistory::contactByPrefix()).
|
||||||
for (int i = 0; i < total && rows < (int)(sizeof(s_contact_rows) / sizeof(s_contact_rows[0])); i++) {
|
for (int i = 0; i < total && rows < CONTACT_ROWS_MAX; i++) {
|
||||||
ContactInfo c;
|
ContactInfo c;
|
||||||
if (!the_mesh.getContactByIdx(MAX_ANON_CONTACTS + i, c)) continue;
|
if (!the_mesh.getContactByIdx(MAX_ANON_CONTACTS + i, c)) continue;
|
||||||
if (c.type != ADV_TYPE_CHAT) continue;
|
if (c.type != ADV_TYPE_CHAT) continue;
|
||||||
@@ -1723,12 +1748,12 @@ uint32_t UITask::threadSignature() const {
|
|||||||
// for the Go / Save buttons under such a bubble.
|
// for the Go / Save buttons under such a bubble.
|
||||||
struct MsgLoc { int32_t lat, lon; char label[WAYPOINT_LABEL_LEN * 2]; };
|
struct MsgLoc { int32_t lat, lon; char label[WAYPOINT_LABEL_LEN * 2]; };
|
||||||
static const int THREAD_MAX_SHOWN = 30;
|
static const int THREAD_MAX_SHOWN = 30;
|
||||||
static MsgLoc s_msg_locs[THREAD_MAX_SHOWN];
|
static MsgLoc* s_msg_locs = psramBuf<MsgLoc>(THREAD_MAX_SHOWN);
|
||||||
static int s_msg_loc_n = 0;
|
static int s_msg_loc_n = 0;
|
||||||
|
|
||||||
// What a held bubble is about: its history ring entry, sender, position slot.
|
// What a held bubble is about: its history ring entry, sender, position slot.
|
||||||
struct MsgMeta { int pos; bool channel; bool own; int loc; char from[32]; };
|
struct MsgMeta { int pos; bool channel; bool own; int loc; char from[32]; };
|
||||||
static MsgMeta s_msg_meta[THREAD_MAX_SHOWN];
|
static MsgMeta* s_msg_meta = psramBuf<MsgMeta>(THREAD_MAX_SHOWN);
|
||||||
static int s_msg_meta_n = 0;
|
static int s_msg_meta_n = 0;
|
||||||
|
|
||||||
static int noteMsgMeta(int pos, bool channel, bool own, int loc, const char* from) {
|
static int noteMsgMeta(int pos, bool channel, bool own, int loc, const char* from) {
|
||||||
|
|||||||
@@ -75,3 +75,20 @@ lib_deps =
|
|||||||
HTTPClient
|
HTTPClient
|
||||||
WiFiClientSecure
|
WiFiClientSecure
|
||||||
Preferences
|
Preferences
|
||||||
|
|
||||||
|
; The same firmware on Arduino-ESP32 3.x (ESP-IDF 5.5, pioarduino). Runs beside
|
||||||
|
; the 2.0.17 env above until it passes docs/development/l2-hardware-checklist.md
|
||||||
|
; (roadmap stage 2), then replaces it.
|
||||||
|
[env:Wio_Tracker_L2_companion_solo_lvgl_v3]
|
||||||
|
extends = env:Wio_Tracker_L2_companion_solo_lvgl
|
||||||
|
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.312-1/platform-espressif32.zip
|
||||||
|
lib_deps =
|
||||||
|
${Wio_Tracker_L2.lib_deps}
|
||||||
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
lvgl/lvgl @ 9.2.2
|
||||||
|
SD_MMC
|
||||||
|
FS
|
||||||
|
WiFi
|
||||||
|
HTTPClient
|
||||||
|
NetworkClientSecure ; 3.x name of WiFiClientSecure (the header is still there)
|
||||||
|
Preferences
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <esp_now.h>
|
#include <esp_now.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <esp_wifi.h>
|
#include <esp_wifi.h>
|
||||||
|
#include <esp_mac.h>
|
||||||
|
|
||||||
static uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
static uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||||
static esp_now_peer_info_t peerInfo;
|
static esp_now_peer_info_t peerInfo;
|
||||||
@@ -10,13 +11,21 @@ static esp_err_t last_send_result;
|
|||||||
static uint8_t rx_buf[256];
|
static uint8_t rx_buf[256];
|
||||||
static uint8_t last_rx_len = 0;
|
static uint8_t last_rx_len = 0;
|
||||||
|
|
||||||
// callback when data is sent
|
// callback when data is sent (IDF 5.5 passes tx info / rx info instead of MACs)
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
static void OnDataSent(const wifi_tx_info_t *info, esp_now_send_status_t status) {
|
||||||
|
#else
|
||||||
static void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
|
static void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
|
||||||
|
#endif
|
||||||
is_send_complete = true;
|
is_send_complete = true;
|
||||||
ESPNOW_DEBUG_PRINTLN("Send Status: %d", (int)status);
|
ESPNOW_DEBUG_PRINTLN("Send Status: %d", (int)status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
static void OnDataRecv(const esp_now_recv_info_t *info, const uint8_t *data, int len) {
|
||||||
|
#else
|
||||||
static void OnDataRecv(const uint8_t *mac, const uint8_t *data, int len) {
|
static void OnDataRecv(const uint8_t *mac, const uint8_t *data, int len) {
|
||||||
|
#endif
|
||||||
ESPNOW_DEBUG_PRINTLN("Recv: len = %d", len);
|
ESPNOW_DEBUG_PRINTLN("Recv: len = %d", len);
|
||||||
memcpy(rx_buf, data, len);
|
memcpy(rx_buf, data, len);
|
||||||
last_rx_len = len;
|
last_rx_len = len;
|
||||||
|
|||||||
@@ -28,9 +28,15 @@ void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code
|
|||||||
BLEDevice::setSecurityCallbacks(this);
|
BLEDevice::setSecurityCallbacks(this);
|
||||||
BLEDevice::setMTU(MAX_FRAME_SIZE);
|
BLEDevice::setMTU(MAX_FRAME_SIZE);
|
||||||
|
|
||||||
|
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||||
|
BLESecurity::setPassKey(true, pin_code); // static PIN, shown by this device
|
||||||
|
BLESecurity::setCapability(ESP_IO_CAP_OUT);
|
||||||
|
BLESecurity::setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM_BOND);
|
||||||
|
#else
|
||||||
BLESecurity sec;
|
BLESecurity sec;
|
||||||
sec.setStaticPIN(pin_code);
|
sec.setStaticPIN(pin_code);
|
||||||
sec.setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM_BOND);
|
sec.setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM_BOND);
|
||||||
|
#endif
|
||||||
|
|
||||||
//BLEDevice::setPower(ESP_PWR_LVL_N8);
|
//BLEDevice::setPower(ESP_PWR_LVL_N8);
|
||||||
|
|
||||||
@@ -42,12 +48,22 @@ void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code
|
|||||||
pService = pServer->createService(SERVICE_UUID);
|
pService = pServer->createService(SERVICE_UUID);
|
||||||
|
|
||||||
// Create a BLE Characteristic
|
// Create a BLE Characteristic
|
||||||
|
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||||
|
// NimBLE takes the encryption / MITM requirement as properties (its
|
||||||
|
// setAccessPermissions() is a no-op) and adds the 2902 descriptor itself.
|
||||||
|
pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX,
|
||||||
|
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY |
|
||||||
|
BLECharacteristic::PROPERTY_READ_ENC | BLECharacteristic::PROPERTY_READ_AUTHEN);
|
||||||
|
BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX,
|
||||||
|
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_ENC | BLECharacteristic::PROPERTY_WRITE_AUTHEN);
|
||||||
|
#else
|
||||||
pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
|
pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
|
||||||
pTxCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM);
|
pTxCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM);
|
||||||
pTxCharacteristic->addDescriptor(new BLE2902());
|
pTxCharacteristic->addDescriptor(new BLE2902());
|
||||||
|
|
||||||
BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
|
BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
|
||||||
pRxCharacteristic->setAccessPermissions(ESP_GATT_PERM_WRITE_ENC_MITM);
|
pRxCharacteristic->setAccessPermissions(ESP_GATT_PERM_WRITE_ENC_MITM);
|
||||||
|
#endif
|
||||||
pRxCharacteristic->setCallbacks(this);
|
pRxCharacteristic->setCallbacks(this);
|
||||||
|
|
||||||
pServer->getAdvertising()->addServiceUUID(SERVICE_UUID);
|
pServer->getAdvertising()->addServiceUUID(SERVICE_UUID);
|
||||||
@@ -74,8 +90,19 @@ bool SerialBLEInterface::onSecurityRequest() {
|
|||||||
return true; // allow
|
return true; // allow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||||
|
void SerialBLEInterface::onAuthenticationComplete(ble_gap_conn_desc* desc, int status) {
|
||||||
|
(void)desc;
|
||||||
|
authDone(status == 0);
|
||||||
|
}
|
||||||
|
#else
|
||||||
void SerialBLEInterface::onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) {
|
void SerialBLEInterface::onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) {
|
||||||
if (cmpl.success) {
|
authDone(cmpl.success);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void SerialBLEInterface::authDone(bool ok) {
|
||||||
|
if (ok) {
|
||||||
BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Success");
|
BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Success");
|
||||||
deviceConnected = true;
|
deviceConnected = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -92,6 +119,16 @@ void SerialBLEInterface::onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) {
|
|||||||
void SerialBLEInterface::onConnect(BLEServer* pServer) {
|
void SerialBLEInterface::onConnect(BLEServer* pServer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||||
|
void SerialBLEInterface::onConnect(BLEServer* pServer, ble_gap_conn_desc* desc) {
|
||||||
|
BLE_DEBUG_PRINTLN("onConnect(), conn_handle=%d", desc->conn_handle);
|
||||||
|
last_conn_id = desc->conn_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerialBLEInterface::onMtuChanged(BLEServer* pServer, ble_gap_conn_desc* desc, uint16_t mtu) {
|
||||||
|
BLE_DEBUG_PRINTLN("onMtuChanged(), mtu=%d", mtu);
|
||||||
|
}
|
||||||
|
#else
|
||||||
void SerialBLEInterface::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) {
|
void SerialBLEInterface::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) {
|
||||||
BLE_DEBUG_PRINTLN("onConnect(), conn_id=%d, mtu=%d", param->connect.conn_id, pServer->getPeerMTU(param->connect.conn_id));
|
BLE_DEBUG_PRINTLN("onConnect(), conn_id=%d, mtu=%d", param->connect.conn_id, pServer->getPeerMTU(param->connect.conn_id));
|
||||||
last_conn_id = param->connect.conn_id;
|
last_conn_id = param->connect.conn_id;
|
||||||
@@ -100,6 +137,7 @@ void SerialBLEInterface::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t
|
|||||||
void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) {
|
void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) {
|
||||||
BLE_DEBUG_PRINTLN("onMtuChanged(), mtu=%d", pServer->getPeerMTU(param->mtu.conn_id));
|
BLE_DEBUG_PRINTLN("onMtuChanged(), mtu=%d", pServer->getPeerMTU(param->mtu.conn_id));
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void SerialBLEInterface::onDisconnect(BLEServer* pServer) {
|
void SerialBLEInterface::onDisconnect(BLEServer* pServer) {
|
||||||
BLE_DEBUG_PRINTLN("onDisconnect()");
|
BLE_DEBUG_PRINTLN("onDisconnect()");
|
||||||
@@ -111,7 +149,11 @@ void SerialBLEInterface::onDisconnect(BLEServer* pServer) {
|
|||||||
|
|
||||||
// -------- BLECharacteristicCallbacks methods
|
// -------- BLECharacteristicCallbacks methods
|
||||||
|
|
||||||
|
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||||
|
void SerialBLEInterface::onWrite(BLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc) {
|
||||||
|
#else
|
||||||
void SerialBLEInterface::onWrite(BLECharacteristic* pCharacteristic, esp_ble_gatts_cb_param_t* param) {
|
void SerialBLEInterface::onWrite(BLECharacteristic* pCharacteristic, esp_ble_gatts_cb_param_t* param) {
|
||||||
|
#endif
|
||||||
uint8_t* rxValue = pCharacteristic->getData();
|
uint8_t* rxValue = pCharacteristic->getData();
|
||||||
int len = pCharacteristic->getLength();
|
int len = pCharacteristic->getLength();
|
||||||
|
|
||||||
|
|||||||
@@ -40,16 +40,30 @@ protected:
|
|||||||
void onPassKeyNotify(uint32_t pass_key) override;
|
void onPassKeyNotify(uint32_t pass_key) override;
|
||||||
bool onConfirmPIN(uint32_t pass_key) override;
|
bool onConfirmPIN(uint32_t pass_key) override;
|
||||||
bool onSecurityRequest() override;
|
bool onSecurityRequest() override;
|
||||||
|
#if defined(CONFIG_NIMBLE_ENABLED) // Arduino-ESP32 3.x on NimBLE (ESP32-S3 default)
|
||||||
|
void onAuthenticationComplete(ble_gap_conn_desc* desc, int status) override;
|
||||||
|
#else
|
||||||
void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) override;
|
void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
// BLEServerCallbacks methods
|
// BLEServerCallbacks methods
|
||||||
void onConnect(BLEServer* pServer) override;
|
void onConnect(BLEServer* pServer) override;
|
||||||
|
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||||
|
void onConnect(BLEServer* pServer, ble_gap_conn_desc* desc) override;
|
||||||
|
void onMtuChanged(BLEServer* pServer, ble_gap_conn_desc* desc, uint16_t mtu) override;
|
||||||
|
#else
|
||||||
void onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) override;
|
void onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) override;
|
||||||
void onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) override;
|
void onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) override;
|
||||||
|
#endif
|
||||||
void onDisconnect(BLEServer* pServer) override;
|
void onDisconnect(BLEServer* pServer) override;
|
||||||
|
|
||||||
// BLECharacteristicCallbacks methods
|
// BLECharacteristicCallbacks methods
|
||||||
|
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||||
|
void onWrite(BLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc) override;
|
||||||
|
#else
|
||||||
void onWrite(BLECharacteristic* pCharacteristic, esp_ble_gatts_cb_param_t* param) override;
|
void onWrite(BLECharacteristic* pCharacteristic, esp_ble_gatts_cb_param_t* param) override;
|
||||||
|
#endif
|
||||||
|
void authDone(bool ok);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SerialBLEInterface() {
|
SerialBLEInterface() {
|
||||||
|
|||||||
+70
-21
@@ -356,9 +356,13 @@ void genericBuzzer::setVolume(uint8_t level) {
|
|||||||
// too) are driven from the caller's thread, which owns the bus.
|
// too) are driven from the caller's thread, which owns the bus.
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
#include <driver/i2s.h>
|
|
||||||
#include <esp_heap_caps.h>
|
#include <esp_heap_caps.h>
|
||||||
#include "ES8311.h"
|
#include "ES8311.h"
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
#include <driver/i2s_std.h> // Arduino-ESP32 3.x (the legacy driver goes in IDF 6)
|
||||||
|
#else
|
||||||
|
#include <driver/i2s.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef AUDIO_AMP_SETTLE_MS
|
#ifndef AUDIO_AMP_SETTLE_MS
|
||||||
// Silence after amp power-up, so the first note isn't clipped. Seeed's
|
// Silence after amp power-up, so the first note isn't clipped. Seeed's
|
||||||
@@ -367,7 +371,6 @@ void genericBuzzer::setVolume(uint8_t level) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const int SAMPLE_RATE = 16000;
|
static const int SAMPLE_RATE = 16000;
|
||||||
static const i2s_port_t I2S_PORT = I2S_NUM_0;
|
|
||||||
static const uint32_t AMP_LINGER_MS = 3000; // amp stays on between close sounds (no settle wait each time)
|
static const uint32_t AMP_LINGER_MS = 3000; // amp stays on between close sounds (no settle wait each time)
|
||||||
static const uint32_t CLK_SETTLE_MS = 30; // codec clocked this long before the amp comes on
|
static const uint32_t CLK_SETTLE_MS = 30; // codec clocked this long before the amp comes on
|
||||||
// Note edges follow a raised cosine (a linear 3 ms ramp still ticked): the
|
// Note edges follow a raised cosine (a linear 3 ms ramp still ticked): the
|
||||||
@@ -395,22 +398,51 @@ static int16_t peakFor(uint8_t level) {
|
|||||||
return PEAK[level < 5 ? level : 4];
|
return PEAK[level < 5 ? level : 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool genericBuzzer::_i2sBegin() {
|
// ── The I2S channel: 16 kHz, 16-bit stereo, 6 DMA buffers of CHUNK frames,
|
||||||
// i2s_driver_install() crashes in IDF's cleanup when its DMA allocation
|
// MCLK = 256 fs; an underrun plays silence, not the last buffer again. ──────
|
||||||
// fails (PR #3381 saw a boot loop): don't try without clear headroom.
|
static const uint32_t WAIT_FOREVER = 0xFFFFFFFF;
|
||||||
if (heap_caps_get_free_size(MALLOC_CAP_DMA) < 32000) return false;
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
for (int i = 0; i < 256; i++) s_sine[i] = (int16_t)(32767.0f * sinf(i * 2.0f * (float)M_PI / 256.0f));
|
static i2s_chan_handle_t s_tx = nullptr;
|
||||||
for (int i = 0; i <= 64; i++) s_ease[i] = (int16_t)(32767.0f * 0.5f * (1.0f - cosf(i * (float)M_PI / 64.0f)));
|
static bool i2sInstall() {
|
||||||
|
i2s_chan_config_t cc = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
||||||
|
cc.dma_desc_num = 6; // 48 ms queued: short, since the task keeps it topped up
|
||||||
|
cc.dma_frame_num = CHUNK;
|
||||||
|
cc.auto_clear_after_cb = true;
|
||||||
|
if (i2s_new_channel(&cc, &s_tx, nullptr) != ESP_OK) return false;
|
||||||
|
i2s_std_config_t sc = {};
|
||||||
|
sc.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE);
|
||||||
|
sc.clk_cfg.mclk_multiple = I2S_MCLK_MULTIPLE_256;
|
||||||
|
sc.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO);
|
||||||
|
sc.gpio_cfg.mclk = (gpio_num_t)PIN_I2S_MCLK;
|
||||||
|
sc.gpio_cfg.bclk = (gpio_num_t)PIN_I2S_BCK;
|
||||||
|
sc.gpio_cfg.ws = (gpio_num_t)PIN_I2S_WS;
|
||||||
|
sc.gpio_cfg.dout = (gpio_num_t)PIN_I2S_DOUT;
|
||||||
|
sc.gpio_cfg.din = I2S_GPIO_UNUSED;
|
||||||
|
if (i2s_channel_init_std_mode(s_tx, &sc) != ESP_OK || i2s_channel_enable(s_tx) != ESP_OK) {
|
||||||
|
i2s_del_channel(s_tx); s_tx = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static void i2sUninstall() { i2s_channel_disable(s_tx); i2s_del_channel(s_tx); s_tx = nullptr; }
|
||||||
|
static void i2sWrite(const void* src, size_t n, uint32_t ms) {
|
||||||
|
size_t w;
|
||||||
|
i2s_channel_write(s_tx, src, n, &w, ms);
|
||||||
|
}
|
||||||
|
static void i2sStop() { i2s_channel_disable(s_tx); } // clocks off
|
||||||
|
static void i2sRestart() { i2s_channel_enable(s_tx); } // auto-clear left the buffers silent
|
||||||
|
#else
|
||||||
|
static const i2s_port_t I2S_PORT = I2S_NUM_0;
|
||||||
|
static bool i2sInstall() {
|
||||||
i2s_config_t cfg = {};
|
i2s_config_t cfg = {};
|
||||||
cfg.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
|
cfg.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
|
||||||
cfg.sample_rate = SAMPLE_RATE;
|
cfg.sample_rate = SAMPLE_RATE;
|
||||||
cfg.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
|
cfg.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
|
||||||
cfg.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
cfg.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
||||||
cfg.communication_format = I2S_COMM_FORMAT_STAND_I2S;
|
cfg.communication_format = I2S_COMM_FORMAT_STAND_I2S;
|
||||||
cfg.dma_buf_count = 6; // 48 ms queued: short, since the task keeps it topped up
|
cfg.dma_buf_count = 6;
|
||||||
cfg.dma_buf_len = CHUNK;
|
cfg.dma_buf_len = CHUNK;
|
||||||
cfg.tx_desc_auto_clear = true; // an underrun plays silence, not the last buffer again
|
cfg.tx_desc_auto_clear = true;
|
||||||
cfg.mclk_multiple = I2S_MCLK_MULTIPLE_256;
|
cfg.mclk_multiple = I2S_MCLK_MULTIPLE_256;
|
||||||
i2s_pin_config_t pins = {};
|
i2s_pin_config_t pins = {};
|
||||||
pins.mck_io_num = PIN_I2S_MCLK;
|
pins.mck_io_num = PIN_I2S_MCLK;
|
||||||
@@ -421,10 +453,29 @@ bool genericBuzzer::_i2sBegin() {
|
|||||||
if (i2s_driver_install(I2S_PORT, &cfg, 0, nullptr) != ESP_OK) return false;
|
if (i2s_driver_install(I2S_PORT, &cfg, 0, nullptr) != ESP_OK) return false;
|
||||||
if (i2s_set_pin(I2S_PORT, &pins) != ESP_OK) { i2s_driver_uninstall(I2S_PORT); return false; }
|
if (i2s_set_pin(I2S_PORT, &pins) != ESP_OK) { i2s_driver_uninstall(I2S_PORT); return false; }
|
||||||
i2s_zero_dma_buffer(I2S_PORT);
|
i2s_zero_dma_buffer(I2S_PORT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static void i2sUninstall() { i2s_driver_uninstall(I2S_PORT); }
|
||||||
|
static void i2sWrite(const void* src, size_t n, uint32_t ms) {
|
||||||
|
size_t w;
|
||||||
|
i2s_write(I2S_PORT, src, n, &w, ms == WAIT_FOREVER ? portMAX_DELAY : pdMS_TO_TICKS(ms));
|
||||||
|
}
|
||||||
|
static void i2sStop() { i2s_stop(I2S_PORT); }
|
||||||
|
static void i2sRestart() { i2s_zero_dma_buffer(I2S_PORT); i2s_start(I2S_PORT); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool genericBuzzer::_i2sBegin() {
|
||||||
|
// i2s_driver_install() crashes in IDF's cleanup when its DMA allocation
|
||||||
|
// fails (PR #3381 saw a boot loop): don't try without clear headroom.
|
||||||
|
if (heap_caps_get_free_size(MALLOC_CAP_DMA) < 32000) return false;
|
||||||
|
for (int i = 0; i < 256; i++) s_sine[i] = (int16_t)(32767.0f * sinf(i * 2.0f * (float)M_PI / 256.0f));
|
||||||
|
for (int i = 0; i <= 64; i++) s_ease[i] = (int16_t)(32767.0f * 0.5f * (1.0f - cosf(i * (float)M_PI / 64.0f)));
|
||||||
|
|
||||||
|
if (!i2sInstall()) return false;
|
||||||
|
|
||||||
// MCLK is running now, so the codec's clock tree comes up with it.
|
// MCLK is running now, so the codec's clock tree comes up with it.
|
||||||
if (!es8311::begin(Wire, BUZZER_CODEC_ES8311)) {
|
if (!es8311::begin(Wire, BUZZER_CODEC_ES8311)) {
|
||||||
i2s_driver_uninstall(I2S_PORT);
|
i2sUninstall();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_clk_on_ms = millis();
|
_clk_on_ms = millis();
|
||||||
@@ -445,7 +496,6 @@ void genericBuzzer::_taskLoop() {
|
|||||||
static const int16_t zeros[CHUNK * 2] = {0};
|
static const int16_t zeros[CHUNK * 2] = {0};
|
||||||
static char mel[MEL_MAX];
|
static char mel[MEL_MAX];
|
||||||
uint32_t done_req = 0;
|
uint32_t done_req = 0;
|
||||||
size_t w;
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// Between sounds, while the codec is clocked, keep the DMA queue full of
|
// Between sounds, while the codec is clocked, keep the DMA queue full of
|
||||||
// silence: a sound starting into a queue that had run dry could be played
|
// silence: a sound starting into a queue that had run dry could be played
|
||||||
@@ -455,13 +505,13 @@ void genericBuzzer::_taskLoop() {
|
|||||||
uint32_t idle_since = millis();
|
uint32_t idle_since = millis();
|
||||||
while (_req == done_req) {
|
while (_req == done_req) {
|
||||||
if (!_clk_running) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); idle_since = millis(); continue; }
|
if (!_clk_running) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); idle_since = millis(); continue; }
|
||||||
i2s_write(I2S_PORT, zeros, sizeof(zeros), &w, pdMS_TO_TICKS(50));
|
i2sWrite(zeros, sizeof(zeros), 50);
|
||||||
if (millis() - idle_since < AMP_LINGER_MS + 500) continue;
|
if (millis() - idle_since < AMP_LINGER_MS + 500) continue;
|
||||||
bool stop = false;
|
bool stop = false;
|
||||||
portENTER_CRITICAL(&s_mux);
|
portENTER_CRITICAL(&s_mux);
|
||||||
if (!_amp_on && !_amp_pending && _req == done_req) { _clk_running = false; stop = true; }
|
if (!_amp_on && !_amp_pending && _req == done_req) { _clk_running = false; stop = true; }
|
||||||
portEXIT_CRITICAL(&s_mux);
|
portEXIT_CRITICAL(&s_mux);
|
||||||
if (stop) i2s_stop(I2S_PORT); else idle_since = millis();
|
if (stop) i2sStop(); else idle_since = millis();
|
||||||
}
|
}
|
||||||
ulTaskNotifyTake(pdTRUE, 0); // its request is being taken now
|
ulTaskNotifyTake(pdTRUE, 0); // its request is being taken now
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@@ -475,8 +525,7 @@ void genericBuzzer::_taskLoop() {
|
|||||||
_note_idx = -1;
|
_note_idx = -1;
|
||||||
if (stop || !mel[0]) { _task_playing = false; break; }
|
if (stop || !mel[0]) { _task_playing = false; break; }
|
||||||
if (!_clk_running) {
|
if (!_clk_running) {
|
||||||
i2s_zero_dma_buffer(I2S_PORT);
|
i2sRestart();
|
||||||
i2s_start(I2S_PORT);
|
|
||||||
portENTER_CRITICAL(&s_mux);
|
portENTER_CRITICAL(&s_mux);
|
||||||
_clk_on_ms = millis();
|
_clk_on_ms = millis();
|
||||||
_clk_running = true; // loop() powers the amp once the codec settles
|
_clk_running = true; // loop() powers the amp once the codec settles
|
||||||
@@ -487,7 +536,7 @@ void genericBuzzer::_taskLoop() {
|
|||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
for (uint32_t n = (uint32_t)settle * SAMPLE_RATE / 1000; n > 0 && !cut; ) {
|
for (uint32_t n = (uint32_t)settle * SAMPLE_RATE / 1000; n > 0 && !cut; ) {
|
||||||
uint32_t k = n < CHUNK ? n : CHUNK;
|
uint32_t k = n < CHUNK ? n : CHUNK;
|
||||||
i2s_write(I2S_PORT, buf, k * 4, &w, portMAX_DELAY);
|
i2sWrite(buf, k * 4, WAIT_FOREVER);
|
||||||
n -= k;
|
n -= k;
|
||||||
cut = _req != req;
|
cut = _req != req;
|
||||||
}
|
}
|
||||||
@@ -517,7 +566,7 @@ void genericBuzzer::_taskLoop() {
|
|||||||
}
|
}
|
||||||
buf[j * 2] = buf[j * 2 + 1] = s;
|
buf[j * 2] = buf[j * 2 + 1] = s;
|
||||||
}
|
}
|
||||||
i2s_write(I2S_PORT, buf, k * 4, &w, portMAX_DELAY);
|
i2sWrite(buf, k * 4, WAIT_FOREVER);
|
||||||
cut = _req != req;
|
cut = _req != req;
|
||||||
}
|
}
|
||||||
if (cut && freq && g) { // fade out from where the note was cut
|
if (cut && freq && g) { // fade out from where the note was cut
|
||||||
@@ -528,14 +577,14 @@ void genericBuzzer::_taskLoop() {
|
|||||||
buf[m * 2] = buf[m * 2 + 1] = (int16_t)((int32_t)s_sine[phase >> 24] * gj / 32767);
|
buf[m * 2] = buf[m * 2 + 1] = (int16_t)((int32_t)s_sine[phase >> 24] * gj / 32767);
|
||||||
phase += step;
|
phase += step;
|
||||||
}
|
}
|
||||||
i2s_write(I2S_PORT, buf, k * 4, &w, portMAX_DELAY);
|
i2sWrite(buf, k * 4, WAIT_FOREVER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_note_idx = -1;
|
_note_idx = -1;
|
||||||
if (cut) continue; // a newer request: take it at once
|
if (cut) continue; // a newer request: take it at once
|
||||||
// Let the DMA queue play out before reporting the melody done.
|
// Let the DMA queue play out before reporting the melody done.
|
||||||
for (int i = 0; i < 6; i++) i2s_write(I2S_PORT, zeros, sizeof(zeros), &w, portMAX_DELAY);
|
for (int i = 0; i < 6; i++) i2sWrite(zeros, sizeof(zeros), WAIT_FOREVER);
|
||||||
if (_req == req) _task_playing = false;
|
if (_req == req) _task_playing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user