mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-27 11:52:23 +00:00
Keep sensor UI polling and refresh working across uptime rollover
This commit is contained in:
1 parent
01d9b7c376
commit
a24f9d5ef8
6 files changed
+381
-10
No files matched your search
@@ -783,6 +783,7 @@ jobs:
|
||||
python3 -B test/test_rtttl_rollover.py -v
|
||||
python3 -B test/test_rtttl_rollover_recipes.py -v
|
||||
python3 -B test/test_companion_ui_deadlines.py -v
|
||||
python3 -B test/test_sensor_ui_polling.py -v
|
||||
python3 -B test/test_home_sensor_refresh.py -v
|
||||
python3 -B test/test_scrolling_status_bar_rollover.py -v
|
||||
python3 -B test/test_gps_voice_buttons.py -v
|
||||
|
||||
@@ -100,6 +100,11 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
|
||||
// v1.2.3 (1 Jan 2025)
|
||||
snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date);
|
||||
free(version);
|
||||
|
||||
// Make the first read/frame immediately due, including begin() after wrap.
|
||||
const uint32_t now = millis();
|
||||
_last_read = now - 200u;
|
||||
_last_refresh = now - 1000u;
|
||||
}
|
||||
|
||||
void UITask::resetRadioProfileDisplayPage() {
|
||||
@@ -208,7 +213,7 @@ void UITask::loop() {
|
||||
const bool morse_timing = mesh::morseTimingActive(activeSensorMesh().getProfileRadio());
|
||||
if (_display->servicePower(board.isExternalPowered() || board.isUsbHostConnected())) {
|
||||
resetRadioProfileDisplayPage();
|
||||
_next_refresh = 0;
|
||||
_last_refresh = millis() - 1000u;
|
||||
}
|
||||
#if defined(PIN_USER_BTN) && defined(DISPLAY_CLASS) \
|
||||
&& defined(MOMENTARY_BUTTON_WAKE_FROM_SLEEP) \
|
||||
@@ -218,12 +223,12 @@ void UITask::loop() {
|
||||
if (!_display->isOn()) {
|
||||
_display->wake(mesh::ui::DisplayWake::Button);
|
||||
resetRadioProfileDisplayPage();
|
||||
_next_refresh = 0;
|
||||
_last_refresh = millis() - 1000u;
|
||||
}
|
||||
_display->wake(mesh::ui::DisplayWake::Button);
|
||||
}
|
||||
#elif defined(PIN_USER_BTN)
|
||||
if (millis() >= _next_read) {
|
||||
if (static_cast<uint32_t>(millis() - _last_read) >= 200u) {
|
||||
int btnState = digitalRead(PIN_USER_BTN);
|
||||
if (btnState != _prevBtnState) {
|
||||
if (btnState == USER_BTN_PRESSED) { // pressed?
|
||||
@@ -237,17 +242,17 @@ void UITask::loop() {
|
||||
}
|
||||
_prevBtnState = btnState;
|
||||
}
|
||||
_next_read = millis() + 200; // 5 reads per second
|
||||
_last_read = millis(); // 5 reads per second, timed from completed I/O
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!morse_timing && _display->isOn()) {
|
||||
if (millis() >= _next_refresh) {
|
||||
if (static_cast<uint32_t>(millis() - _last_refresh) >= 1000u) {
|
||||
_display->startFrame();
|
||||
renderCurrScreen();
|
||||
_display->endFrame();
|
||||
|
||||
_next_refresh = millis() + 1000; // refresh every second
|
||||
_last_refresh = millis(); // refresh every second, after the frame ends
|
||||
}
|
||||
#if MOMENTARY_BUTTON_WAKE_HOLD_MS > 0 && defined(PIN_USER_BTN)
|
||||
if (user_btn.isWakeHoldActive()) _display->wake(mesh::ui::DisplayWake::Button);
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
class UITask {
|
||||
DisplayDriver* _display;
|
||||
unsigned long _next_read, _next_refresh;
|
||||
// Completion timestamps keep periodic work due across millis() rollover.
|
||||
uint32_t _last_read, _last_refresh;
|
||||
int _prevBtnState;
|
||||
NodePrefs* _node_prefs;
|
||||
char _version_info[32];
|
||||
@@ -16,7 +17,7 @@ class UITask {
|
||||
bool showingSecondaryRadioProfilePage();
|
||||
void renderCurrScreen();
|
||||
public:
|
||||
UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; }
|
||||
UITask(DisplayDriver& display) : _display(&display) { _last_read = _last_refresh = 0; }
|
||||
void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version);
|
||||
|
||||
void loop();
|
||||
|
||||
Vendored
+247
@@ -0,0 +1,247 @@
|
||||
// Production header, begin and loop; only hardware/render boundaries are fake.
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
static uint32_t now_ms = 0, read_delay = 0, render_delay = 0, end_delay = 0;
|
||||
static unsigned reads = 0, renders = 0;
|
||||
static constexpr int HIGH = 1, LOW = 0, USER_BTN_PRESSED = LOW;
|
||||
static constexpr int BUTTON_EVENT_NONE = 0;
|
||||
static int button_level = HIGH;
|
||||
static uint32_t millis() { return now_ms; }
|
||||
[[maybe_unused]] static int digitalRead(int pin) {
|
||||
if (pin != 7) std::abort();
|
||||
++reads;
|
||||
now_ms += read_delay;
|
||||
return button_level;
|
||||
}
|
||||
struct Radio { bool timing = false; } radio;
|
||||
namespace mesh {
|
||||
static bool morseTimingActive(const Radio* r) { return r && r->timing; }
|
||||
namespace ui { enum class DisplayWake { Button }; }
|
||||
}
|
||||
struct Board {
|
||||
bool external = false, usb = false;
|
||||
bool isExternalPowered() const { return external; }
|
||||
bool isUsbHostConnected() const { return usb; }
|
||||
} board;
|
||||
struct SensorMesh {
|
||||
Radio* getProfileRadio() { return &radio; }
|
||||
bool isDualRadioActive() const { return false; }
|
||||
} sensor_mesh;
|
||||
static SensorMesh& activeSensorMesh() { return sensor_mesh; }
|
||||
struct DisplayDriver {
|
||||
bool on = true, changed = false;
|
||||
unsigned frames = 0, wakes = 0;
|
||||
bool servicePower(bool) { bool result = changed; changed = false; return result; }
|
||||
bool isOn() const { return on; }
|
||||
void wake(mesh::ui::DisplayWake) { on = true; ++wakes; }
|
||||
void startFrame() { ++frames; }
|
||||
void endFrame() { now_ms += end_delay; }
|
||||
};
|
||||
struct MomentaryButton {
|
||||
int event = BUTTON_EVENT_NONE;
|
||||
bool held = false;
|
||||
void begin() { event = BUTTON_EVENT_NONE; }
|
||||
int check() { int result = event; event = BUTTON_EVENT_NONE; return result; }
|
||||
bool isWakeHoldActive() const { return held; }
|
||||
} user_btn;
|
||||
struct NodePrefs {};
|
||||
#include "UITask.h"
|
||||
|
||||
@BEGIN@
|
||||
@RESET@
|
||||
@LOOP@
|
||||
void UITask::renderCurrScreen() { ++renders; now_ms += render_delay; }
|
||||
|
||||
static const char* scenario;
|
||||
static unsigned checks = 0;
|
||||
#define CHECK(expr) do { ++checks; if (!(expr)) { \
|
||||
std::fprintf(stderr, "FAIL %s: %s (line %d)\n", scenario, #expr, __LINE__); \
|
||||
return 1; } } while (0)
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc != 2) return 2;
|
||||
scenario = argv[1];
|
||||
DisplayDriver display;
|
||||
NodePrefs prefs;
|
||||
UITask ui(display);
|
||||
now_ms = 10000;
|
||||
if (!std::strcmp(scenario, "zero")) now_ms = 0;
|
||||
else if (!std::strcmp(scenario, "upper-half")) now_ms = 0x80001000u;
|
||||
else if (!std::strcmp(scenario, "max")) now_ms = UINT32_MAX;
|
||||
else if (!std::strcmp(scenario, "wrapped-future")) now_ms = UINT32_MAX - 99u;
|
||||
else if (!std::strcmp(scenario, "zero-boundary")) now_ms = UINT32_MAX - 199u;
|
||||
else if (!std::strcmp(scenario, "overdue-wrap") || !std::strcmp(scenario, "morse-wrap"))
|
||||
now_ms = UINT32_MAX - 1999u;
|
||||
else if (!std::strcmp(scenario, "forced-refresh")) now_ms = 0x80001000u;
|
||||
else if (!std::strcmp(scenario, "forced-zero")
|
||||
|| !std::strcmp(scenario, "momentary-zero-wake")
|
||||
|| !std::strcmp(scenario, "momentary-zero-morse")) now_ms = UINT32_MAX - 99u;
|
||||
ui.begin(&prefs, "16 Oct 2026", "v1.2.3-test");
|
||||
if (!std::strcmp(scenario, "completion-anchor")) {
|
||||
read_delay = 10;
|
||||
render_delay = 20;
|
||||
end_delay = 30;
|
||||
}
|
||||
ui.loop();
|
||||
CHECK(display.frames == 1 && renders == 1);
|
||||
CHECK(reads == unsigned(TEST_RAW_BUTTON));
|
||||
|
||||
if (!std::strcmp(scenario, "zero") || !std::strcmp(scenario, "upper-half")
|
||||
|| !std::strcmp(scenario, "max") || !std::strcmp(scenario, "wrapped-future")) {
|
||||
++now_ms;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 1 && reads == unsigned(TEST_RAW_BUTTON));
|
||||
} else if (!std::strcmp(scenario, "cadence")) {
|
||||
now_ms += 199;
|
||||
ui.loop();
|
||||
CHECK(reads == unsigned(TEST_RAW_BUTTON) && display.frames == 1);
|
||||
++now_ms;
|
||||
ui.loop();
|
||||
CHECK(reads == 2u * TEST_RAW_BUTTON && display.frames == 1);
|
||||
now_ms += 799;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 1);
|
||||
++now_ms;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2);
|
||||
} else if (!std::strcmp(scenario, "zero-boundary")) {
|
||||
now_ms = UINT32_MAX;
|
||||
ui.loop();
|
||||
CHECK(reads == unsigned(TEST_RAW_BUTTON) && display.frames == 1);
|
||||
now_ms = 0;
|
||||
ui.loop();
|
||||
CHECK(reads == 2u * TEST_RAW_BUTTON && display.frames == 1);
|
||||
now_ms = 799;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 1);
|
||||
++now_ms;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2);
|
||||
} else if (!std::strcmp(scenario, "overdue-wrap")) {
|
||||
now_ms = 100;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2 && reads == 2u * TEST_RAW_BUTTON);
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2 && reads == 2u * TEST_RAW_BUTTON);
|
||||
} else if (!std::strcmp(scenario, "morse-wrap")) {
|
||||
radio.timing = true;
|
||||
now_ms += 1500;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 1 && reads == 2u * TEST_RAW_BUTTON);
|
||||
now_ms += 1000;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 1 && reads == 3u * TEST_RAW_BUTTON);
|
||||
radio.timing = false;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2 && reads == 3u * TEST_RAW_BUTTON);
|
||||
} else if (!std::strcmp(scenario, "forced-refresh")) {
|
||||
++now_ms;
|
||||
display.changed = true;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2);
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2);
|
||||
now_ms = UINT32_MAX;
|
||||
display.changed = true;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 3);
|
||||
now_ms = 0;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 3);
|
||||
} else if (!std::strcmp(scenario, "forced-zero")) {
|
||||
now_ms = 0;
|
||||
display.changed = true;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2);
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2);
|
||||
} else if (!std::strcmp(scenario, "forced-morse")) {
|
||||
++now_ms;
|
||||
display.changed = true;
|
||||
radio.timing = true;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 1);
|
||||
++now_ms;
|
||||
radio.timing = false;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2);
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2);
|
||||
} else if (!std::strcmp(scenario, "momentary-zero-wake")
|
||||
|| !std::strcmp(scenario, "momentary-zero-morse")) {
|
||||
const bool during_morse = !std::strcmp(scenario, "momentary-zero-morse");
|
||||
display.on = false;
|
||||
radio.timing = during_morse;
|
||||
now_ms = 0;
|
||||
user_btn.event = 1;
|
||||
ui.loop();
|
||||
#if TEST_MOMENTARY_BUTTON
|
||||
CHECK(display.on && display.wakes == 2);
|
||||
CHECK(display.frames == (during_morse ? 1u : 2u));
|
||||
now_ms = 1;
|
||||
radio.timing = false;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2);
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2);
|
||||
#else
|
||||
CHECK(!display.on && display.frames == 1 && display.wakes == 0);
|
||||
#endif
|
||||
} else if (!std::strcmp(scenario, "long-display-off")) {
|
||||
display.on = false;
|
||||
for (unsigned day = 1; day <= 26; ++day) {
|
||||
now_ms = 10000u + day * 86400000u;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 1);
|
||||
}
|
||||
display.on = true; // No force event: elapsed work must remain due.
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2);
|
||||
} else if (!std::strcmp(scenario, "completion-anchor")) {
|
||||
const uint32_t poll_finished = 10000u + (TEST_RAW_BUTTON ? 10u : 0u);
|
||||
const uint32_t frame_finished = poll_finished + 50u;
|
||||
read_delay = render_delay = end_delay = 0;
|
||||
now_ms = poll_finished + 199u;
|
||||
ui.loop();
|
||||
CHECK(reads == unsigned(TEST_RAW_BUTTON) && display.frames == 1);
|
||||
++now_ms;
|
||||
ui.loop();
|
||||
CHECK(reads == 2u * TEST_RAW_BUTTON && display.frames == 1);
|
||||
now_ms = frame_finished + 999u;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 1);
|
||||
++now_ms;
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2);
|
||||
} else if (!std::strcmp(scenario, "rebegin")) {
|
||||
++now_ms;
|
||||
ui.begin(&prefs, "16 Oct 2026", "v1.2.4-test");
|
||||
ui.loop();
|
||||
CHECK(display.frames == 2 && reads == 2u * TEST_RAW_BUTTON);
|
||||
} else if (!std::strcmp(scenario, "button-wake")) {
|
||||
display.on = false;
|
||||
now_ms += 200;
|
||||
button_level = LOW;
|
||||
user_btn.event = 1;
|
||||
ui.loop();
|
||||
#if TEST_RAW_BUTTON
|
||||
CHECK(display.on && display.wakes == 2 && reads == 2);
|
||||
// Existing raw-button policy keeps the one-second render throttle.
|
||||
CHECK(display.frames == 1);
|
||||
#elif TEST_MOMENTARY_BUTTON
|
||||
CHECK(display.on && display.wakes == 2 && display.frames == 2);
|
||||
user_btn.held = true;
|
||||
ui.loop();
|
||||
CHECK(display.wakes == 3 && display.frames == 2);
|
||||
#else
|
||||
CHECK(!display.on && display.wakes == 0 && display.frames == 1);
|
||||
#endif
|
||||
} else {
|
||||
std::fprintf(stderr, "unknown scenario: %s\n", scenario);
|
||||
return 2;
|
||||
}
|
||||
std::printf("PASS %s: %u checks\n", scenario, checks);
|
||||
}
|
||||
@@ -67,6 +67,7 @@ struct UI {
|
||||
struct Prefs { const char* node_name="test"; } prefs;
|
||||
Prefs* _node_prefs=&prefs;
|
||||
uint32_t _next_refresh=0,_alert_expiry=0,ui_started_at=0,next_batt_chck=0;
|
||||
uint32_t _last_refresh=0;
|
||||
uint16_t _cached_batt_mv=4000,voltage=3350;
|
||||
bool _need_refresh=true;
|
||||
char _alert[4]={},_origin[4]={},_msg[4]={};
|
||||
@@ -224,22 +225,29 @@ int main() {
|
||||
self.assertIn("_display->startFrame()", redraw)
|
||||
code = FIXTURE.replace("@REDRAW@", redraw).replace("@CUTOFF@", "") + r'''
|
||||
int main() {
|
||||
constexpr bool sensor_timestamps=@SENSOR_TIMESTAMPS@;
|
||||
for(bool external : {false,true}) {
|
||||
board.external=external;
|
||||
for(unsigned mode=0;mode<4;++mode) {
|
||||
UI ui; adc_reads=frames=0;
|
||||
const uint32_t last_refresh_before=ui._last_refresh;
|
||||
radio_driver.tx=mode==1; radio_driver.rx=mode==2; radio_driver.cw=mode==3;
|
||||
ui.redraw();
|
||||
if(mode==1 || mode==2) {
|
||||
assert(frames==0 && adc_reads==0 && ui._next_refresh==0);
|
||||
if(sensor_timestamps)
|
||||
assert(frames==0 && adc_reads==0 && ui._last_refresh==last_refresh_before);
|
||||
else assert(frames==0 && adc_reads==0 && ui._next_refresh==0);
|
||||
radio_driver.tx=radio_driver.rx=false;
|
||||
ui.redraw(); assert(frames==1 && adc_reads==1);
|
||||
} else assert(frames==1 && adc_reads==1);
|
||||
assert(ui._next_refresh>now_ms);
|
||||
if(sensor_timestamps) assert(ui._last_refresh==now_ms);
|
||||
else assert(ui._next_refresh>now_ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
code = code.replace("@SENSOR_TIMESTAMPS@",
|
||||
"true" if path == "examples/simple_sensor/UITask.cpp" else "false")
|
||||
compile_run(self, code, flags=("-Wno-unused-variable",), headers={"Dispatcher.h": RADIO})
|
||||
|
||||
def test_actual_full_and_tiny_ui_cutoff_reads_fresh_voltage_even_during_morse(self):
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
"""Exercise the actual sensor UI header, begin and full loop with a 32-bit clock.
|
||||
|
||||
Display, board, radio and button I/O are host adapters. Rendering, physical
|
||||
sleep and hardware timing are not qualified by this scheduling regression.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from test_replay_reset_integration import extract_braced
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
UI = ROOT / 'examples/simple_sensor'
|
||||
FIXTURE = ROOT / 'test/fixtures/sensor_ui_polling.cpp'
|
||||
|
||||
|
||||
class SensorUiPollingTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
compiler = os.environ.get('CXX') or shutil.which('g++') or shutil.which('clang++')
|
||||
if not compiler:
|
||||
raise unittest.SkipTest('a host C++17 compiler is required')
|
||||
cls.directory = tempfile.TemporaryDirectory(prefix='sensor-ui-polling-')
|
||||
cls.addClassCleanup(cls.directory.cleanup)
|
||||
work = Path(cls.directory.name)
|
||||
(work / 'helpers/ui').mkdir(parents=True)
|
||||
for stub in ('helpers/ui/DisplayDriver.h', 'helpers/CommonCLI.h'):
|
||||
(work / stub).write_text('#pragma once\n', encoding='ascii')
|
||||
(work / 'UITask.h').write_bytes((UI / 'UITask.h').read_bytes())
|
||||
source = (UI / 'UITask.cpp').read_text(encoding='ascii')
|
||||
generated = FIXTURE.read_text(encoding='ascii')
|
||||
for marker, signature in (
|
||||
('BEGIN', 'void UITask::begin('),
|
||||
('RESET', 'void UITask::resetRadioProfileDisplayPage('),
|
||||
('LOOP', 'void UITask::loop()')):
|
||||
generated = generated.replace('@' + marker + '@', extract_braced(source, signature))
|
||||
cpp = work / 'test.cpp'
|
||||
cpp.write_text(generated, encoding='ascii')
|
||||
sanitize = [] if os.name == 'nt' else [
|
||||
'-fsanitize=address,undefined', '-fno-sanitize-recover=all', '-fno-pie', '-no-pie']
|
||||
cls.binaries = {}
|
||||
for variant in ('none', 'raw', 'momentary'):
|
||||
macros = ['-DTEST_RAW_BUTTON=' + str(int(variant == 'raw')),
|
||||
'-DTEST_MOMENTARY_BUTTON=' + str(int(variant == 'momentary'))]
|
||||
if variant != 'none':
|
||||
macros += ['-DPIN_USER_BTN=7', '-DDISPLAY_CLASS=DisplayDriver']
|
||||
if variant == 'momentary':
|
||||
macros += ['-DMOMENTARY_BUTTON_WAKE_FROM_SLEEP=1',
|
||||
'-DMOMENTARY_BUTTON_WAKE_HOLD_MS=1000']
|
||||
binary = work / (variant + ('.exe' if os.name == 'nt' else ''))
|
||||
built = subprocess.run([
|
||||
compiler, '-std=c++17', '-O1', '-g', '-Wall', '-Wextra', '-Werror',
|
||||
*sanitize, *macros, '-I' + str(work), str(cpp), '-o', str(binary)],
|
||||
capture_output=True, text=True, timeout=60)
|
||||
if built.returncode:
|
||||
raise AssertionError(built.stdout + built.stderr)
|
||||
cls.binaries[variant] = binary
|
||||
|
||||
def check_case(self, case):
|
||||
for variant, binary in self.binaries.items():
|
||||
with self.subTest(variant=variant, case=case):
|
||||
checked = subprocess.run([str(binary), case], capture_output=True,
|
||||
text=True, timeout=10)
|
||||
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
|
||||
self.assertIn('PASS ' + case, checked.stdout)
|
||||
|
||||
def test_initial_work_at_zero_upper_half_and_max(self):
|
||||
for case in ('zero', 'upper-half', 'max'):
|
||||
self.check_case(case)
|
||||
|
||||
def test_normal_cadence_and_wrapped_future(self):
|
||||
for case in ('cadence', 'wrapped-future', 'zero-boundary'):
|
||||
self.check_case(case)
|
||||
|
||||
def test_overdue_polling_and_morse_deferral_across_wrap(self):
|
||||
for case in ('overdue-wrap', 'morse-wrap'):
|
||||
self.check_case(case)
|
||||
|
||||
def test_force_and_long_idle_refresh(self):
|
||||
for case in ('forced-refresh', 'long-display-off'):
|
||||
self.check_case(case)
|
||||
|
||||
def test_force_at_zero_and_force_during_morse(self):
|
||||
for case in ('forced-zero', 'forced-morse', 'momentary-zero-wake',
|
||||
'momentary-zero-morse'):
|
||||
self.check_case(case)
|
||||
|
||||
def test_work_is_anchored_after_io_and_begin_rearms(self):
|
||||
for case in ('completion-anchor', 'rebegin'):
|
||||
self.check_case(case)
|
||||
|
||||
def test_button_wake_paths(self):
|
||||
self.check_case('button-wake')
|
||||
|
||||
|
||||
class SensorUiPollingWiringTest(unittest.TestCase):
|
||||
def test_live_role_and_ci_run_the_regression(self):
|
||||
main = (UI / 'main.cpp').read_text(encoding='ascii')
|
||||
self.assertIn('ui_task.begin(', extract_braced(main, 'void setup()'))
|
||||
self.assertIn('ui_task.loop();', extract_braced(main, '\nvoid loop()'))
|
||||
workflow = (ROOT / '.github/workflows/run-unit-tests.yml').read_text(encoding='ascii')
|
||||
self.assertIn('python3 -B test/test_sensor_ui_polling.py -v', workflow)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user