refactor(ui-lvgl): Diagnostics > Noise is one measurement for any board

The Noise tab held the L2's interference hunt: parts of the board off one
by one, a slow cycle for a second radio, a spike hunt and three sweeps
(~90 s, L2 only). It is now one Measure: the noise floor on the mesh
frequency (median and 10th percentile of 5 s) and a +-1.1 MHz sweep round
it, ~12 s, with a short legend for reading it. Works wherever the variant
defines the radio.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-28 21:41:16 +02:00
co-authored by Claude Opus 5.5
parent e47738ce01
commit 2e9654fd1c
3 changed files with 47 additions and 271 deletions
+5 -3
View File
@@ -43,9 +43,12 @@ found the interference source (noise floor with the board's parts off one by
one, the 850-930 MHz and mesh-channel sweeps, the spike hunt, the 15 s states one, the 850-930 MHz and mesh-channel sweeps, the spike hunt, the 15 s states
for a second radio). They are L2-specific and block the loop for ~90 s. for a second radio). They are L2-specific and block the loop for ~90 s.
- [ ] Decide: remove them, or keep one universal tool (for example a noise - [x] Decide: remove them, or keep one universal tool (for example a noise
floor sweep round the mesh frequency that works on any board) and drop floor sweep round the mesh frequency that works on any board) and drop
the L2 part toggling and spike hunt. the L2 part toggling and spike hunt. Done: one universal Measure (the
floor on the mesh frequency + a +-1.1 MHz sweep, ~12 s, a legend under
it); the L2-only tests are gone. Left for stage F: the L2 board's
setGrovePower / setSdPower, now unused.
## D. Quiet hours in the core ## D. Quiet hours in the core
@@ -104,6 +107,5 @@ it; implement the tiers, docs and website after the merge.
## Open decisions ## Open decisions
- C: remove the noise tests or keep a universal version.
- D: alarm during quiet hours; messages waking the screen. - D: alarm during quiet hours; messages waking the screen.
- I: tier spec before stage F, or after the merge. - I: tier spec before stage F, or after the merge.
+42 -266
View File
@@ -1,12 +1,12 @@
#pragma once #pragma once
// Settings > Diagnostics -- ui-new's Tools > Diagnostics. Tabs Live / System / // Settings > Diagnostics -- ui-new's Tools > Diagnostics. Tabs Live / System /
// Font; the rows come from ui-core/Diagnostics.h. Live refreshes every second // Font / Noise; the rows come from ui-core/Diagnostics.h. Live refreshes every second
// and its header button resets the counters (after a confirm). // and its header button resets the counters (after a confirm).
// //
// Single-TU fragment: included by ui-lvgl/UITask.cpp after DeviceScreen.h. // Single-TU fragment: included by ui-lvgl/UITask.cpp after DeviceScreen.h.
#if defined(SEEED_WIO_TRACKER_L2) #ifndef SIM_PLATFORM
extern RADIO_CLASS radio; // variants/wio-tracker-l2/target.cpp (the noise sweep retunes it) extern RADIO_CLASS radio; // the variant's target.cpp (the noise sweep retunes it)
#endif #endif
namespace diagview { namespace diagview {
@@ -120,55 +120,16 @@ static lv_obj_t* pairLine(lv_obj_t* card, const char* name, lv_obj_t** in, lv_ob
return r; return r;
} }
// Noise tab: the LoRa noise floor measured with the board's parts turned off // Noise tab: how much the radio hears with nothing on air, on any board --
// one at a time -- which of them is the one raising it (the L2 read ~25 dB // the floor on the mesh's frequency now (the median of 5 s of readings, and
// above the L1). Blocking, ~90 s: the main loop doesn't run meanwhile, so the // the 10th percentile, which a packet can't lift), then a sweep of +-1.1 MHz
// screen isn't redrawn and only what runs by itself is measured. // round it. A floor well above ~-115 dBm, flat across the sweep, is broadband
enum : uint8_t { N_BL = 1, N_TOUCH = 2, N_GNSS = 4, N_GROVE = 8, N_CPU = 16, N_LCD = 32, N_SD = 64, N_SLEEP = 128 }; // noise (a power supply, a charger); a spike is some clock's harmonic landing
struct NoiseStep { const char* name; uint8_t off; }; // there. Blocking, ~12 s. (The L2's noise was found this way: its I2S clock,
static const NoiseStep NOISE_STEPS[] = { // now stopped between sounds.)
{ "Everything on", 0 }, { "Backlight off", N_BL }, { "Touch asleep", N_TOUCH },
{ "Panel asleep", N_LCD }, { "GPS off", N_GNSS }, { "Grove port off", N_GROVE },
{ "CPU at 80 MHz", N_CPU },
{ "All of these off", N_BL | N_TOUCH | N_GNSS | N_GROVE | N_CPU | N_LCD },
};
// Then two sweeps: 850-930 MHz 1 MHz apart, and the mesh's frequency +-1.1
// MHz 25 kHz apart. Flat is broadband noise (a power supply, the front end);
// a spike is some clock's harmonic landing there.
struct Sweep { float f0, step; int n; int16_t v[96]; }; struct Sweep { float f0, step; int n; int16_t v[96]; };
static Sweep s_wide = { 850.0f, 1.0f, 81, {} };
static Sweep s_near = { 0, 0.025f, 91, {} }; static Sweep s_near = { 0, 0.025f, 91, {} };
// Then +-125 kHz around the loudest of those, 5 kHz apart with the receiver static int16_t s_floor_med = 0, s_floor_lo = 0;
// narrowed to 7.8 kHz: where exactly the spike is, and how wide.
static Sweep s_zoom = { 0, 0.005f, 51, {} };
static const int NOISE_N = sizeof(NOISE_STEPS) / sizeof(NOISE_STEPS[0]);
// For a second radio next to this one (its noise floor read by eye): each
// state held 15 s. The L2 radiates the noise (an L1 beside it read -96 dBm
// instead of -108 with the L2 off), and is quiet in its bootloader.
static const NoiseStep DETECT_STEPS[] = {
{ "Everything on", 0 }, { "Backlight off", N_BL }, { "Panel asleep", N_LCD },
{ "Touch asleep", N_TOUCH }, { "GPS off", N_GNSS }, { "SD card off", N_SD },
{ "Grove port off", N_GROVE }, { "CPU at 80 MHz", N_CPU }, { "CPU light sleep", N_SLEEP },
{ "All but sleep off", N_BL | N_LCD | N_TOUCH | N_GNSS | N_SD | N_GROVE | N_CPU },
{ "Everything on again", 0 },
};
static const int DETECT_N = sizeof(DETECT_STEPS) / sizeof(DETECT_STEPS[0]);
static const uint32_t DETECT_HOLD_MS = 15000;
static void onDetectRun(lv_event_t* e) { (void)e; s_ui->diagNoiseDetect(); }
// Spike hunt: one clean line (869.633 MHz, 10 kHz wide) sits in the mesh's
// channel, from the ESP32 while awake. One frequency doesn't name the clock;
// the spacing of its neighbours does (a clock's harmonics are a comb). So
// 864-876 MHz 5 kHz apart through a 7.8 kHz receiver, and the lines standing
// out of the floor listed.
static const float HUNT_F0 = 864.0f, HUNT_STEP = 0.005f;
static const int HUNT_N = 2401;
static const int HUNT_MAX = 16;
struct Spike { float f; int16_t dbm; };
static Spike s_spikes[HUNT_MAX];
static int s_spike_n = -1; // -1: not run
static int16_t s_hunt_floor = 0;
static void onSpikeHunt(lv_event_t* e) { (void)e; s_ui->diagSpikeHunt(); }
static int16_t s_noise_med[NOISE_N], s_noise_lo[NOISE_N];
static bool s_noise_have = false; static bool s_noise_have = false;
static bool s_noise_usb = false; static bool s_noise_usb = false;
static lv_obj_t* s_noise_status = nullptr; static lv_obj_t* s_noise_status = nullptr;
@@ -203,37 +164,13 @@ static void plotSweep(lv_obj_t* list, const char* title, const Sweep& sw, float
} }
lv_obj_t* card = infoCard(list); lv_obj_t* card = infoCard(list);
char v[40]; char v[40];
const int prec = sw.step < 0.01f ? 4 : (sw.step < 1 ? 3 : 0); snprintf(v, sizeof(v), "%.3f MHz: %d dBm", sw.f0 + sw.step * lo, sw.v[lo]);
snprintf(v, sizeof(v), "%.*f MHz: %d dBm", prec, sw.f0 + sw.step * lo, sw.v[lo]);
infoRow(card, "Quietest", v); infoRow(card, "Quietest", v);
snprintf(v, sizeof(v), "%.*f MHz: %d dBm", prec, sw.f0 + sw.step * hi, sw.v[hi]); snprintf(v, sizeof(v), "%.3f MHz: %d dBm", sw.f0 + sw.step * hi, sw.v[hi]);
infoRow(card, "Loudest", v); infoRow(card, "Loudest", v);
if (mesh_i >= 0 && mesh_i < sw.n) {
snprintf(v, sizeof(v), "%.*f MHz: %d dBm", prec, sw.f0 + sw.step * mesh_i, sw.v[mesh_i]);
infoRow(card, "Mesh frequency", v);
}
}
#if defined(SEEED_WIO_TRACKER_L2)
// Only what changes from `prev` is touched: GPS power back on means its
// reset, touch wake a pulse on its line.
static void noiseApply(uint8_t off, uint8_t prev, bool gnss_was_on, uint32_t cpu_mhz, uint8_t bright) {
uint8_t ch = off ^ prev;
if (ch & N_LCD) { // the controller's sleep: its oscillator and charge pumps stop
if (off & N_LCD) display.lgfxDevice()->sleep();
else { display.lgfxDevice()->wakeup(); delay(120); display.setBrightness(bright); }
}
if (ch & N_BL) { if (off & N_BL) display.lgfxDevice()->setBrightness(0); else display.setBrightness(bright); }
if (ch & N_TOUCH) { if (off & N_TOUCH) board.touchSleep(); else board.touchWake(); }
if ((ch & N_GNSS) && gnss_was_on) board.setGnssPower(!(off & N_GNSS));
if (ch & N_GROVE) board.setGrovePower(!(off & N_GROVE));
if (ch & N_CPU) setCpuFrequencyMhz((off & N_CPU) ? 80 : cpu_mhz);
if (ch & N_SD) { // unmounted and unpowered; back on, mounted again
if (off & N_SD) { SD_MMC.end(); board.setSdPower(false); }
else { board.setSdPower(true); delay(50); SD_MMC.setPins(2, 3, 1); SD_MMC.begin("/sdcard", true); }
}
} }
#ifndef SIM_PLATFORM
// 250 instantaneous RSSI readings over 5 s: the median, and the 10th // 250 instantaneous RSSI readings over 5 s: the median, and the 10th
// percentile (a packet on air only lifts the top). // percentile (a packet on air only lifts the top).
static void noiseSample(int16_t& med, int16_t& lo) { static void noiseSample(int16_t& med, int16_t& lo) {
@@ -245,12 +182,11 @@ static void noiseSample(int16_t& med, int16_t& lo) {
lo = (int16_t)lroundf(v[N / 10]); lo = (int16_t)lroundf(v[N / 10]);
} }
// The radio retuned step by step (50 readings each, the median), then put // The radio retuned step by step (the median of 20 readings each), then put
// back on the mesh's settings and receiving again. // back on the mesh's settings and receiving again.
static void noiseSweep(Sweep& sw, const NodePrefs* p, float bw_khz = 0) { static void noiseSweep(Sweep& sw, const NodePrefs* p) {
static const int K = 50; static const int K = 20;
float v[K]; float v[K];
if (bw_khz > 0) { radio.standby(); radio.setBandwidth(bw_khz); }
for (int i = 0; i < sw.n; i++) { for (int i = 0; i < sw.n; i++) {
radio.standby(); radio.standby();
radio.setFrequency(sw.f0 + sw.step * i); radio.setFrequency(sw.f0 + sw.step * i);
@@ -270,128 +206,20 @@ static void noiseSweep(Sweep& sw, const NodePrefs* p, float bw_khz = 0) {
void UITask::diagNoiseRun() { void UITask::diagNoiseRun() {
using namespace diagview; using namespace diagview;
#if defined(SEEED_WIO_TRACKER_L2) #ifndef SIM_PLATFORM
bool gnss_on = board.gnssPowered(); if (!_prefs) return;
uint32_t cpu = getCpuFrequencyMhz(); auto status = [&](const char* t) {
uint8_t bright = _prefs ? _prefs->display_brightness : 3;
s_noise_usb = board.isExternalPowered();
for (int i = 0; i < NOISE_N; i++) {
if (s_noise_status) {
char t[48];
snprintf(t, sizeof(t), "Measuring %d of %d: %s", i + 1, NOISE_N, NOISE_STEPS[i].name);
lv_label_set_text(s_noise_status, t);
lv_refr_now(NULL);
}
uint8_t off = NOISE_STEPS[i].off;
noiseApply(off, 0, gnss_on, cpu, bright);
delay(1500); // rails, clocks and the receiver's AGC settle
noiseSample(s_noise_med[i], s_noise_lo[i]);
noiseApply(0, off, gnss_on, cpu, bright);
}
if (s_noise_status) {
lv_label_set_text(s_noise_status, "Sweeping 850-930 MHz");
lv_refr_now(NULL);
}
noiseSweep(s_wide, _prefs);
if (_prefs) {
if (s_noise_status) {
lv_label_set_text(s_noise_status, "Sweeping around the mesh frequency");
lv_refr_now(NULL);
}
s_near.f0 = _prefs->freq - s_near.step * (s_near.n / 2);
noiseSweep(s_near, _prefs);
int hi = 0;
for (int i = 1; i < s_near.n; i++) if (s_near.v[i] > s_near.v[hi]) hi = i;
if (s_noise_status) {
lv_label_set_text(s_noise_status, "Zooming in on the loudest");
lv_refr_now(NULL);
}
s_zoom.f0 = s_near.f0 + s_near.step * hi - s_zoom.step * (s_zoom.n / 2);
noiseSweep(s_zoom, _prefs, 7.8f);
}
s_noise_have = true;
buildDiag();
#else
showToast("Only on the device");
#endif
}
void UITask::diagNoiseDetect() {
using namespace diagview;
#if defined(SEEED_WIO_TRACKER_L2)
auto show = [&](int i, int n, const char* name) { // shown before the state (dark ones hide it)
if (!s_noise_status) return; if (!s_noise_status) return;
char t[48];
snprintf(t, sizeof(t), "%d/%d %s", i + 1, n, name);
lv_label_set_text(s_noise_status, t); lv_label_set_text(s_noise_status, t);
lv_refr_now(NULL); lv_refr_now(NULL);
}; };
bool gnss_on = board.gnssPowered(); s_noise_usb = board.isExternalPowered();
uint32_t cpu = getCpuFrequencyMhz(); status("Listening on the mesh frequency...");
uint8_t bright = _prefs ? _prefs->display_brightness : 3; noiseSample(s_floor_med, s_floor_lo);
for (int i = 0; i < DETECT_N; i++) { status("Sweeping round it...");
uint8_t off = DETECT_STEPS[i].off; s_near.f0 = _prefs->freq - s_near.step * (s_near.n / 2);
show(i, DETECT_N, DETECT_STEPS[i].name); noiseSweep(s_near, _prefs);
if (off & N_SLEEP) { s_noise_have = true;
esp_sleep_enable_timer_wakeup((uint64_t)DETECT_HOLD_MS * 1000);
if (esp_light_sleep_start() != ESP_OK) delay(DETECT_HOLD_MS); // refused (a radio busy): hold awake
continue;
}
noiseApply(off, 0, gnss_on, cpu, bright);
delay(DETECT_HOLD_MS);
noiseApply(0, off, gnss_on, cpu, bright);
}
buildDiag();
showToast("Cycle done");
#else
showToast("Only on the device");
#endif
}
void UITask::diagSpikeHunt() {
using namespace diagview;
#if defined(SEEED_WIO_TRACKER_L2)
int16_t* v = psramBuf<int16_t>(HUNT_N);
if (!v) { showToast("Out of memory"); return; }
if (s_noise_status) {
lv_label_set_text(s_noise_status, "Hunting spikes 864-876 MHz");
lv_refr_now(NULL);
}
radio.standby();
radio.setBandwidth(7.8f);
float r[7];
for (int i = 0; i < HUNT_N; i++) {
radio.standby();
radio.setFrequency(HUNT_F0 + HUNT_STEP * i);
radio.startReceive();
delay(12); // 4 ms read the register's floor (-127.5): no reading yet
for (int k = 0; k < 7; k++) { r[k] = radio.getRSSI(false); delay(3); }
std::sort(r, r + 7);
v[i] = (int16_t)lroundf(r[3]);
}
radio.standby();
if (_prefs) radio_driver.setParams(_prefs->freq, _prefs->bw, _prefs->sf, _prefs->cr);
radio.startReceive();
// The floor: the median. A line: 8 dB over it, the top within +-3 steps.
int16_t* tmp = psramBuf<int16_t>(HUNT_N);
if (tmp) { memcpy(tmp, v, HUNT_N * sizeof(int16_t)); std::sort(tmp, tmp + HUNT_N); s_hunt_floor = tmp[HUNT_N / 2]; free(tmp); }
s_spike_n = 0;
for (int i = 3; i < HUNT_N - 3; i++) {
if (v[i] < s_hunt_floor + 8) continue;
bool top = true;
for (int d = -3; d <= 3 && top; d++) if (d && (v[i + d] > v[i] || (d < 0 && v[i + d] == v[i]))) top = false;
if (!top) continue;
Spike sp = { HUNT_F0 + HUNT_STEP * i, v[i] };
if (s_spike_n < HUNT_MAX) s_spikes[s_spike_n++] = sp;
else { // full: replace the weakest if this one is stronger
int w = 0;
for (int k = 1; k < HUNT_MAX; k++) if (s_spikes[k].dbm < s_spikes[w].dbm) w = k;
if (sp.dbm > s_spikes[w].dbm) s_spikes[w] = sp;
}
}
std::sort(s_spikes, s_spikes + s_spike_n, [](const Spike& a, const Spike& b) { return a.f < b.f; });
free(v);
buildDiag(); buildDiag();
#else #else
showToast("Only on the device"); showToast("Only on the device");
@@ -456,9 +284,8 @@ void UITask::buildDiag() {
s_noise_status = nullptr; s_noise_status = nullptr;
if (s_tab == TAB_NOISE) { if (s_tab == TAB_NOISE) {
lv_obj_t* t = label(s_list, "LoRa noise floor with parts of the board off, one at a time (about 90 s; " lv_obj_t* t = label(s_list, "What the radio hears with nothing on air, on the mesh frequency and 1.1 MHz "
"the screen goes dark for a moment). Lower is better; ~-115 dBm is a quiet receiver.", "either side. Takes about 12 seconds.", THEME_FONT_SMALL, theme::TEXT_MUTED);
THEME_FONT_SMALL, theme::TEXT_MUTED);
lv_label_set_long_mode(t, LV_LABEL_LONG_WRAP); lv_label_set_long_mode(t, LV_LABEL_LONG_WRAP);
lv_obj_set_width(t, LV_PCT(100)); lv_obj_set_width(t, LV_PCT(100));
lv_obj_t* b = lv_button_create(s_list); lv_obj_t* b = lv_button_create(s_list);
@@ -467,75 +294,24 @@ void UITask::buildDiag() {
lv_obj_set_style_radius(b, theme::RADIUS, 0); lv_obj_set_style_radius(b, theme::RADIUS, 0);
lv_obj_set_style_bg_color(b, lv_color_hex(theme::ACCENT_DIM), 0); lv_obj_set_style_bg_color(b, lv_color_hex(theme::ACCENT_DIM), 0);
lv_obj_add_event_cb(b, onNoiseRun, LV_EVENT_CLICKED, NULL); lv_obj_add_event_cb(b, onNoiseRun, LV_EVENT_CLICKED, NULL);
s_noise_status = label(b, LV_SYMBOL_PLAY " Run test", THEME_FONT_BODY, theme::TEXT); s_noise_status = label(b, s_noise_have ? LV_SYMBOL_REFRESH " Measure again" : LV_SYMBOL_PLAY " Measure", THEME_FONT_BODY, theme::TEXT);
lv_obj_center(s_noise_status); lv_obj_center(s_noise_status);
lv_obj_t* t2 = label(s_list, "Second radio: an L1 beside this one shows the noise this device sends out. "
"Each state is held 15 s (under 3 min), its name shown first; read the L1's noise floor.",
THEME_FONT_SMALL, theme::TEXT_MUTED);
lv_label_set_long_mode(t2, LV_LABEL_LONG_WRAP);
lv_obj_set_width(t2, LV_PCT(100));
lv_obj_t* b2 = lv_button_create(s_list);
lv_obj_set_size(b2, LV_PCT(100), 40);
lv_obj_set_style_shadow_width(b2, 0, 0);
lv_obj_set_style_radius(b2, theme::RADIUS, 0);
lv_obj_set_style_bg_color(b2, lv_color_hex(theme::SURFACE), 0);
lv_obj_add_event_cb(b2, onDetectRun, LV_EVENT_CLICKED, NULL);
lv_obj_t* l2 = label(b2, LV_SYMBOL_LOOP " Slow cycle for a second radio", THEME_FONT_BODY, theme::TEXT);
lv_obj_center(l2);
lv_obj_t* b3 = lv_button_create(s_list);
lv_obj_set_size(b3, LV_PCT(100), 40);
lv_obj_set_style_shadow_width(b3, 0, 0);
lv_obj_set_style_radius(b3, theme::RADIUS, 0);
lv_obj_set_style_bg_color(b3, lv_color_hex(theme::SURFACE), 0);
lv_obj_add_event_cb(b3, onSpikeHunt, LV_EVENT_CLICKED, NULL);
lv_obj_t* l3 = label(b3, LV_SYMBOL_EYE_OPEN " Spike hunt 864-876 MHz (~2.5 min)", THEME_FONT_BODY, theme::TEXT);
lv_obj_center(l3);
if (s_spike_n >= 0) {
char t[48];
snprintf(t, sizeof(t), "SPIKES (floor %d dBm)", s_hunt_floor);
sectionTitle(s_list, t);
lv_obj_t* card = infoCard(s_list);
if (s_hunt_floor <= -127) infoRow(card, "Invalid", "readings at the register's floor");
else if (!s_spike_n) infoRow(card, "None", "nothing 8 dB over the floor");
for (int i = 0; i < s_spike_n; i++) {
char k[24], v[32];
snprintf(k, sizeof(k), "%.3f MHz", s_spikes[i].f);
if (i) snprintf(v, sizeof(v), "%d dBm +%.0f kHz", s_spikes[i].dbm, (s_spikes[i].f - s_spikes[i - 1].f) * 1000);
else snprintf(v, sizeof(v), "%d dBm", s_spikes[i].dbm);
infoRow(card, k, v);
}
}
if (s_noise_have) { if (s_noise_have) {
float mesh_f = _prefs ? _prefs->freq : 0;
lv_obj_t* card = infoCard(s_list); lv_obj_t* card = infoCard(s_list);
char v[40]; char v[40];
for (int i = 0; i < NOISE_N; i++) { snprintf(v, sizeof(v), "%d dBm (low %d)", s_floor_med, s_floor_lo);
int d = s_noise_med[i] - s_noise_med[0]; infoRow(card, "Noise floor", v, s_floor_med <= -110 ? theme::OK : theme::TEXT);
if (i == 0) snprintf(v, sizeof(v), "%d dBm (low %d)", s_noise_med[i], s_noise_lo[i]); snprintf(v, sizeof(v), "%.3f MHz", mesh_f);
else snprintf(v, sizeof(v), "%d dBm (%+d)", s_noise_med[i], d); infoRow(card, "Mesh frequency", v);
infoRow(card, NOISE_STEPS[i].name, v, i && d <= -3 ? theme::OK : theme::TEXT); infoRow(card, "Powered from", s_noise_usb ? "USB (a charger can add noise)" : "battery");
} plotSweep(s_list, "+- 1.1 MHz", s_near, mesh_f);
infoRow(card, "Powered from", s_noise_usb ? "USB" : "battery");
float mesh_f = _prefs ? _prefs->freq : 0;
plotSweep(s_list, "850 - 930 MHz", s_wide, mesh_f);
if (s_near.f0 > 0) {
char t[40];
snprintf(t, sizeof(t), "%.3f MHz +- 1.1", mesh_f);
plotSweep(s_list, t, s_near, mesh_f);
}
if (s_zoom.f0 > 0) {
char t[48];
float c = s_zoom.f0 + s_zoom.step * (s_zoom.n / 2);
snprintf(t, sizeof(t), "%.3f MHz +- 125 kHz (7.8 kHz wide)", c);
plotSweep(s_list, t, s_zoom, mesh_f);
int hi = 0, wide = 0;
for (int i = 1; i < s_zoom.n; i++) if (s_zoom.v[i] > s_zoom.v[hi]) hi = i;
for (int i = 0; i < s_zoom.n; i++) if (s_zoom.v[i] >= s_zoom.v[hi] - 6) wide++;
lv_obj_t* card = infoCard(s_list);
char v2[40];
snprintf(v2, sizeof(v2), "%d kHz (within 6 dB of the top)", wide * 5);
infoRow(card, "Spike width", v2);
}
} }
sectionTitle(s_list, "READING IT"); // lower is better
lv_obj_t* key = infoCard(s_list);
infoRow(key, "About -115 dBm", "quiet receiver");
infoRow(key, "Raised, flat", "charger / power supply");
infoRow(key, "One spike", "a clock nearby");
return; return;
} }
lv_obj_t* card = infoCard(s_list); lv_obj_t* card = infoCard(s_list);
@@ -278,8 +278,6 @@ public:
void diagResetPopup(); void diagResetPopup();
void diagReset(); void diagReset();
void diagNoiseRun(); void diagNoiseRun();
void diagNoiseDetect();
void diagSpikeHunt();
void showCompass(); void showCompass();
void favTap(int slot); void favTap(int slot);
void favHold(int slot); void favHold(int slot);