Public Access
Fix shifted pictures: non-blocking Nano drawing, frame check, refresh settings
The Nano's periodic full redraw blocked for ~0.8 s. A frame that arrived meanwhile overflowed its 64-byte serial buffer; lost bytes in a bitmap shift the rest of the image (picture moved up), and diff frames never repaired it. - Nano: all drawing (frames, effects, the dot refresh) runs from update() in ~1 ms slices; the refresh pauses while a frame is received or shown - New commands: C (reply with the image checksum) and I (refresh minutes, EEPROM) - ESP32: NanoMonitor asks for the checksum after frames and sends the whole image again on a mismatch, missing answer or Nano restart; counts ERR replies. Periodic whole-image resync. - Settings: "Dot refresh" and "Image resync" intervals (settings v5, v4 records are migrated) - System tab: loop timing, free memory, frame check counts - test_driver: the real driver against a simulated panel Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
58a7cb03c2
commit
090c2bf8ae
45 files changed
+1265
-157
No files matched your search
@@ -12,6 +12,8 @@
|
||||
#include <vector>
|
||||
|
||||
#include "config.h"
|
||||
#include "logic/frame_check.h"
|
||||
#include "logic/loop_stats.h"
|
||||
#include "logic/bitmap_format.h"
|
||||
#include "logic/builtin_fonts.h"
|
||||
#include "logic/builtin_pages.h"
|
||||
@@ -1900,6 +1902,133 @@ void test_ha_text_map()
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
}
|
||||
|
||||
void test_loop_stats()
|
||||
{
|
||||
LoopStats stats;
|
||||
TEST_ASSERT_EQUAL_UINT32(0, stats.loopsPerSecond());
|
||||
// 995 passes of 10 ms (the micros() counter wraps on the way), one of them 50 ms
|
||||
uint32_t t = 0xFFFFFFFFUL - 5000000UL;
|
||||
stats.tick(t);
|
||||
for (int i = 0; i < 995; i++)
|
||||
{
|
||||
t += i == 500 ? 50000 : 10000;
|
||||
stats.tick(t);
|
||||
}
|
||||
TEST_ASSERT_EQUAL_UINT32(0, stats.loopsPerSecond()); // window not complete yet
|
||||
t += 10000;
|
||||
stats.tick(t);
|
||||
TEST_ASSERT_EQUAL_UINT32(99, stats.loopsPerSecond());
|
||||
TEST_ASSERT_EQUAL_UINT32(10040, stats.averageUs());
|
||||
TEST_ASSERT_EQUAL_UINT32(50000, stats.maxUs());
|
||||
TEST_ASSERT_EQUAL_UINT32(50000, stats.maxEverUs());
|
||||
|
||||
// Next window without the slow pass: the window max drops, the boot max stays
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
t += 10000;
|
||||
stats.tick(t);
|
||||
}
|
||||
TEST_ASSERT_EQUAL_UINT32(100, stats.loopsPerSecond());
|
||||
TEST_ASSERT_EQUAL_UINT32(10000, stats.maxUs());
|
||||
TEST_ASSERT_EQUAL_UINT32(50000, stats.maxEverUs());
|
||||
}
|
||||
|
||||
void test_frame_check_ok()
|
||||
{
|
||||
FrameCheck c;
|
||||
c.reset(1000);
|
||||
TEST_ASSERT_TRUE(c.due(5, 1000)); // first check right after switching on
|
||||
c.sent(0x1234, 5, 1000);
|
||||
TEST_ASSERT_FALSE(c.due(6, 5000)); // waiting for the answer
|
||||
c.line("C 1234\r", 1010);
|
||||
TEST_ASSERT_EQUAL_UINT32(1, c.stats().checks);
|
||||
TEST_ASSERT_FALSE(c.resyncNow(1020, 0));
|
||||
|
||||
// The next one after a new frame, at most every FRAME_CHECK_INTERVAL
|
||||
TEST_ASSERT_FALSE(c.due(5, 10000)); // no new frame
|
||||
TEST_ASSERT_FALSE(c.due(6, 1000 + FRAME_CHECK_INTERVAL - 1));
|
||||
TEST_ASSERT_TRUE(c.due(6, 1000 + FRAME_CHECK_INTERVAL));
|
||||
}
|
||||
|
||||
void test_frame_check_mismatch_repairs()
|
||||
{
|
||||
FrameCheck c;
|
||||
c.reset(0);
|
||||
c.sent(0x1234, 1, 20000);
|
||||
c.line("C 1235", 20010);
|
||||
TEST_ASSERT_EQUAL_UINT32(1, c.stats().mismatches);
|
||||
TEST_ASSERT_FALSE(c.due(2, 30000)); // the repair comes first
|
||||
TEST_ASSERT_TRUE(c.resyncNow(20010, 0));
|
||||
TEST_ASSERT_EQUAL_UINT32(1, c.stats().repairs);
|
||||
TEST_ASSERT_FALSE(c.resyncNow(20020, 0)); // once
|
||||
TEST_ASSERT_TRUE(c.due(2, 30000)); // then the repaired frame is checked
|
||||
}
|
||||
|
||||
void test_frame_check_repairs_at_most_every_gap()
|
||||
{
|
||||
FrameCheck c;
|
||||
c.reset(0);
|
||||
c.sent(1, 1, 100);
|
||||
c.line("C 0002", 110);
|
||||
TEST_ASSERT_FALSE(c.resyncNow(FRAME_REPAIR_MIN_GAP - 1, 0)); // switched on just now
|
||||
TEST_ASSERT_TRUE(c.resyncNow(FRAME_REPAIR_MIN_GAP, 0));
|
||||
}
|
||||
|
||||
void test_frame_check_no_reply()
|
||||
{
|
||||
FrameCheck c;
|
||||
c.reset(0);
|
||||
// Never answered (old Nano firmware, TX not wired): counted, but no repairs
|
||||
c.sent(1, 1, 100);
|
||||
TEST_ASSERT_FALSE(c.resyncNow(100 + FRAME_CHECK_TIMEOUT - 1, 0));
|
||||
TEST_ASSERT_FALSE(c.resyncNow(100 + FRAME_CHECK_TIMEOUT + FRAME_REPAIR_MIN_GAP, 0));
|
||||
TEST_ASSERT_EQUAL_UINT32(1, c.stats().no_reply);
|
||||
TEST_ASSERT_EQUAL_UINT32(0, c.stats().repairs);
|
||||
|
||||
// It answered before: a lost answer means lost bytes, repair
|
||||
uint32_t t = 100000;
|
||||
c.sent(1, 2, t);
|
||||
c.line("C 0001", t + 5);
|
||||
t += FRAME_CHECK_INTERVAL;
|
||||
c.sent(1, 3, t);
|
||||
TEST_ASSERT_TRUE(c.resyncNow(t + FRAME_CHECK_TIMEOUT, 0));
|
||||
TEST_ASSERT_EQUAL_UINT32(2, c.stats().no_reply);
|
||||
TEST_ASSERT_EQUAL_UINT32(1, c.stats().repairs);
|
||||
// A late answer to the timed out check is ignored
|
||||
c.line("C 0009", t + FRAME_CHECK_TIMEOUT + 10);
|
||||
TEST_ASSERT_EQUAL_UINT32(0, c.stats().mismatches);
|
||||
}
|
||||
|
||||
void test_frame_check_errors_and_restarts()
|
||||
{
|
||||
FrameCheck c;
|
||||
c.reset(0);
|
||||
c.line("ERR: Line too long", 10);
|
||||
c.line("ERR: Invalid bitmap width (not dividable by 4) and a lot more text than fits", 20);
|
||||
TEST_ASSERT_EQUAL_UINT32(2, c.stats().errors);
|
||||
TEST_ASSERT_EQUAL_UINT32(sizeof(c.stats().last_error) - 1, strlen(c.stats().last_error));
|
||||
TEST_ASSERT_EQUAL(0, strncmp(c.stats().last_error, "ERR: Invalid bitmap", 19));
|
||||
TEST_ASSERT_FALSE(c.resyncNow(FRAME_REPAIR_MIN_GAP, 0)); // errors alone: the check decides
|
||||
|
||||
c.line("READY", 20000);
|
||||
TEST_ASSERT_EQUAL_UINT32(1, c.stats().restarts);
|
||||
TEST_ASSERT_TRUE(c.resyncNow(20000, 0));
|
||||
c.line("garbage", 20010);
|
||||
c.line("C 12", 20010);
|
||||
TEST_ASSERT_EQUAL_UINT32(0, c.stats().checks + c.stats().mismatches);
|
||||
}
|
||||
|
||||
void test_frame_check_periodic_resync()
|
||||
{
|
||||
FrameCheck c;
|
||||
c.reset(1000);
|
||||
TEST_ASSERT_FALSE(c.resyncNow(1000 + 600000 - 1, 600000));
|
||||
TEST_ASSERT_TRUE(c.resyncNow(1000 + 600000, 600000));
|
||||
TEST_ASSERT_FALSE(c.resyncNow(1000 + 600001, 600000));
|
||||
TEST_ASSERT_FALSE(c.resyncNow(0xFFFFFFF0u, 0)); // 0: never
|
||||
TEST_ASSERT_EQUAL_UINT32(0, c.stats().repairs); // not counted as a repair
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
@@ -2022,6 +2151,13 @@ int main()
|
||||
RUN_TEST(test_ha_duration);
|
||||
RUN_TEST(test_ha_timestamps);
|
||||
RUN_TEST(test_ha_text_map);
|
||||
RUN_TEST(test_loop_stats);
|
||||
RUN_TEST(test_frame_check_ok);
|
||||
RUN_TEST(test_frame_check_mismatch_repairs);
|
||||
RUN_TEST(test_frame_check_repairs_at_most_every_gap);
|
||||
RUN_TEST(test_frame_check_no_reply);
|
||||
RUN_TEST(test_frame_check_errors_and_restarts);
|
||||
RUN_TEST(test_frame_check_periodic_resync);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in new issue
Block a user