Public Access
Tests against lost bytes in dense frames
- test_nano: every hex digit of B data is one drawDots() call, never single drawPixel() calls (the slow path that broke dense images in 2.19.2) - tools/display_smoke_test.py: all yellow, a dense image and a checkerboard in live mode on the real display; fails on any frame check problem (wrong image, no answer, Nano restart, ERR reply) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7a28e34380
commit
abd63d070f
3 files changed
+126
-1
No files matched your search
@@ -0,0 +1,105 @@
|
||||
"""Checks on the real display that the Nano gets every frame right, also very dense ones.
|
||||
Run it after changes to the Nano firmware, the driver (lib/BROSE9323) or the frames.
|
||||
|
||||
python tools/display_smoke_test.py [ESP32 address, default 10.1.1.198]
|
||||
|
||||
It shows a few test images in live mode (all yellow, a dense image, a checkerboard), waits
|
||||
for the frame check of each (the Nano's checksum of what it received) and then goes back
|
||||
to normal. It fails when the frame check found a wrong image, a missing answer, a Nano
|
||||
restart or an ERR reply (e.g. "Line too long": lost bytes). This is the speed test the PC
|
||||
tests can't do: with 2.19.2 the Nano drew dense images slower than they arrived, which
|
||||
broke all yellow. The dots flip for about 20 s. Only the Python standard library is used.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
ESP = 'http://' + (sys.argv[1] if len(sys.argv) > 1 else '10.1.1.198')
|
||||
W, H, ROW_BYTES = 84, 19, 11
|
||||
CHECK_TIMEOUT = 20 # s: the ESP asks every 3 s at most, the answer takes up to 2 s
|
||||
COUNTERS = ('mismatches', 'no_reply', 'errors', 'restarts', 'repairs')
|
||||
|
||||
|
||||
def request(path, body=None):
|
||||
data = None if body is None else json.dumps(body).encode()
|
||||
req = urllib.request.Request(ESP + path, data=data, headers={'Content-Type': 'application/json'})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return json.loads(r.read())
|
||||
except urllib.error.HTTPError as e:
|
||||
sys.exit('%s: %s' % (path, e.read().decode(errors='replace')))
|
||||
|
||||
|
||||
def image(dot):
|
||||
"""Bitmap hex (logic/bitmap_format.h: rows of 11 bytes, MSB = left dot) of dot(x, y)."""
|
||||
out = []
|
||||
for y in range(H):
|
||||
for i in range(ROW_BYTES):
|
||||
v = 0
|
||||
for k in range(8):
|
||||
x = i * 8 + k
|
||||
if x < W and dot(x, y):
|
||||
v |= 0x80 >> k
|
||||
out.append('%02x' % v)
|
||||
return ''.join(out)
|
||||
|
||||
|
||||
TESTS = [
|
||||
('all yellow', image(lambda x, y: True)),
|
||||
# About 90 % of the dots, scattered: like the panda bitmap that failed
|
||||
('dense image', image(lambda x, y: (x * 7 + y * 13) % 11 != 0)),
|
||||
('checkerboard', image(lambda x, y: (x + y) % 2 == 0)),
|
||||
]
|
||||
|
||||
|
||||
def link():
|
||||
return request('/api/status')['nano_link']
|
||||
|
||||
|
||||
def answered(counts):
|
||||
return counts['checks'] + counts['mismatches'] + counts['no_reply']
|
||||
|
||||
|
||||
def main():
|
||||
status = request('/api/status')
|
||||
if not status['power_enabled']:
|
||||
sys.exit('The Power switch is off (Home Assistant): live mode is not possible')
|
||||
effect_s = request('/api/settings')['drive']['effect_time'] / 1000
|
||||
start = link()
|
||||
print('Firmware %s, frame check before: %s' % (status['version'], start))
|
||||
|
||||
failed = False
|
||||
try:
|
||||
for name, data in TESTS:
|
||||
before = link()
|
||||
request('/api/live', {'data': data})
|
||||
deadline = time.time() + CHECK_TIMEOUT
|
||||
now = before
|
||||
while answered(now) == answered(before) and time.time() < deadline:
|
||||
time.sleep(0.5)
|
||||
now = link()
|
||||
if answered(now) == answered(before):
|
||||
print('%-13s no frame check within %d s' % (name, CHECK_TIMEOUT))
|
||||
failed = True
|
||||
continue
|
||||
bad = {c: now[c] - before[c] for c in COUNTERS if now[c] != before[c]}
|
||||
print('%-13s %s' % (name, 'ok' if not bad else 'FAILED %s %s' % (bad, now['last_error'])))
|
||||
failed |= bool(bad)
|
||||
time.sleep(effect_s + 1) # let it finish flipping, so each image can be seen
|
||||
finally:
|
||||
request('/api/live/stop', {})
|
||||
|
||||
end = link()
|
||||
bad = {c: end[c] - start[c] for c in COUNTERS if end[c] != start[c]}
|
||||
if bad:
|
||||
print('Frame check after: %s, last error: %s' % (bad, end['last_error']))
|
||||
failed = True
|
||||
print('FAILED' if failed else 'All frames arrived right')
|
||||
sys.exit(1 if failed else 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user