Show a spinner while a screen starts and while a camera loads, bring a camera's first picture sooner, and round the alert's camera picture (0.2.87)
Starting screen: until the first layout, render() keeps the status text
("Connecting to Home Assistant", "Waiting for ESP Screens") and a spinner
as one block in the middle of the page; the first layout deletes both.
The room label stays empty meanwhile, and ROOM_NAME defaults to "".
Camera: the full screen shows a spinner instead of "Loading image". The
screen asks when the camera opens and loads a link as soon as it comes
(camera_load, never under a finger) instead of waiting for the 250 ms
tick twice, and online_image reads 16 KB per loop instead of 4 KB. On the
bench Guition with the EZVIZ camera: cold 5.8 -> 4.55 s, warm 3.4 -> 2.06 s.
Alert picture: the image takes the frame's radius (18, the alert card's);
LVGL's software renderer clips it row by row, without a layer.
One spinner_create() makes the busy sheet's, the starting screen's and the
camera's spinner; theme role CAMERA_TRACK colours the camera's ring. The
README gets a row of camera pictures; docs/CAMERA.md, RELEASING.md and
TEST_RESULTS_0287.md describe the measurements.
Checked with tools/check.sh (473 Python tests, 19/19 C++, editor) and
--firmware: CYD 1,678,816 B (91.5 %, +1,312 B), Guition 2,055,280 B
(25.3 %). Host renders of both boards in both looks; four OTA builds on
the bench Guition Wallbox, which runs this firmware.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -96,6 +96,21 @@ class Rules(unittest.TestCase):
|
||||
config = (ROOT / 'screen_manager/config.yaml').read_text()
|
||||
self.assertIn(f'{camera_feed.PORT}/tcp: {camera_feed.PORT}', config)
|
||||
|
||||
def test_the_first_picture_waits_for_no_tick(self):
|
||||
# Firmware 0.2.73+: the camera asks when it opens and loads the link when it comes, not on the next 250 ms tick;
|
||||
# the spinner turns until the first picture or a note is there.
|
||||
opened = TILES.split('inline void camera_open(const std::string &entity, const std::string &name) {', 1)[1].split('\n}\n', 1)[0]
|
||||
self.assertIn('camera_spinner = spinner_create(camera_root,', opened)
|
||||
self.assertIn('if (awake() && fresh() && camera.should_ask(now)) {', opened)
|
||||
answer = TILES.split('inline void camera_answer(const std::string &view, const std::string &entity, const std::string &url) {', 1)[1].split('\n}\n', 1)[0]
|
||||
self.assertIn('if (awake() && camera.should_load(now)) camera_load(now);', answer)
|
||||
# Never under a finger, from the answer or from the tick.
|
||||
load = TILES.split('inline void camera_load(uint32_t now) {', 1)[1].split('\n}\n', 1)[0]
|
||||
self.assertIn('lv_indev_get_state(input) == LV_INDEV_STATE_PRESSED) return;', load)
|
||||
note = TILES.split('inline void camera_note_text(const char *text) {', 1)[1].split('\n}\n', 1)[0]
|
||||
self.assertIn('lv_obj_delete(camera_spinner);', note)
|
||||
self.assertNotIn('Loading image', TILES)
|
||||
|
||||
|
||||
@unittest.skipUnless(HAS_PIL, 'Pillow comes with ESPHome in the add-on image')
|
||||
class Encoding(unittest.TestCase):
|
||||
|
||||
@@ -221,9 +221,24 @@ class ParityTests(unittest.TestCase):
|
||||
self.assertIn('runtime_tiles::time_label = id(lbl_time);', text, name)
|
||||
refresh = text.split(' - id: ui_refresh', 1)[1].split('return;', 1)[0]
|
||||
self.assertNotIn('lbl_time', refresh, name)
|
||||
self.assertIn('render_header();', TILES.split('inline void render(lv_obj_t *room) {', 1)[1].split('\n', 5)[4])
|
||||
self.assertIn('render_header();', TILES.split('inline void render(lv_obj_t *room) {', 1)[1].split('\n}', 1)[0])
|
||||
self.assertLessEqual(HEADER_MIN_FIRMWARE, tuple(int(p) for p in FIRMWARE_VERSION.split('.')))
|
||||
|
||||
def test_a_starting_screen_says_what_it_waits_for_in_the_middle(self):
|
||||
# Firmware 0.2.73+: until the first layout the text and a spinner stand in the middle and the name stays empty;
|
||||
# the first layout deletes them, so no spinner turns behind the tiles.
|
||||
render = TILES.split('inline void render(lv_obj_t *room) {', 1)[1].split('\n}', 1)[0]
|
||||
self.assertIn('if (!model.configured) boot_status(lv_obj_get_parent(room), !ha_connected() ? '
|
||||
'"Connecting to Home Assistant" : "Waiting for ESP Screens");', render)
|
||||
self.assertIn('else if (boot_panel) { lv_obj_delete(boot_panel);', render)
|
||||
self.assertIn('label(room, !model.configured ? "" :', render)
|
||||
boot = TILES.split('inline void boot_status(', 1)[1].split('\n}', 1)[0]
|
||||
self.assertIn('boot_spinner = spinner_create(boot_panel,', boot)
|
||||
# Under the tiles, the cards and an alert, in the name's place.
|
||||
self.assertIn('lv_obj_move_to_index(boot_panel, lv_obj_get_index(room_label));', boot)
|
||||
for name in PROFILES:
|
||||
self.assertNotIn('Choose tiles in HA', profiles.text(name), name)
|
||||
|
||||
def test_second_hand_runs_only_while_the_screen_is_awake(self):
|
||||
for name in PROFILES:
|
||||
text = profiles.text(name)
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ class Paints(unittest.TestCase):
|
||||
tiles = (COMPONENT / 'runtime_tiles.h').read_text()
|
||||
for needle in ('lv_obj_add_style(w.slider,theme::style(theme::Paint::knob),LV_PART_KNOB);',
|
||||
'lv_obj_add_style(w.busy,theme::style(theme::Paint::veil),0);',
|
||||
'lv_obj_add_style(w.spinner,theme::style(theme::Paint::spinner),LV_PART_MAIN);',
|
||||
'lv_obj_add_style(spinner, theme::style(theme::Paint::spinner), LV_PART_MAIN);',
|
||||
'lv_obj_add_style(part, theme::style(theme::Paint::slate), 0);',
|
||||
'inline void restyle() {'):
|
||||
self.assertIn(needle, tiles)
|
||||
|
||||
Reference in New Issue
Block a user