Let the screen own its settings, answer its pings, and show them as cards (0.2.57)
A code check found that a setting changed on a screen could jump back. ESP Screen Manager kept its own copy and sent it with every full update: after a restart of the app or Home Assistant, every hour and after every save. A change it missed while it or Home Assistant restarted came back to the old value, a reported setting was lost when save() refused a layout with a removed entity, and every step of a held - or + on the screen resent the whole screen. Firmware 0.2.49 offers every setting as an entity over the existing preference records (no migration): Night mode, Night starts/ends, 24-hour clock, Back to page 1 and after how long, Back to page 1 on standby, Swipe between pages and, on the Guition, Rotation, next to the four numbers and Auto standby. The settings page and every entity write through settings_screen::set(), which clamps like the page and stores, applies and reports only a real change. screen_message answers a caller that asks with its status and layout revision (api.respond). The settings tile opens without Home Assistant. The app reads and changes the settings of such a screen through its entities and leaves them out of the layout message. Older firmware keeps them with the layout; a setting it reports is stored without the tile check and not sent back. Pings to screens that can answer learn at once whether a tile is missing and resend after 30 s instead of 120 s. Layout sensors come back after a Home Assistant restart, and weather tiles only ask for the forecasts the entity supports. The editor's settings form is now three cards with the rows of the screen's own page; a change applies at once, and an offline screen shows its values as unknown. Checked with 245 Python and 14 C++ tests, generate_packages --check, ESP32 builds of both profiles, host-build API tests on both boards, and a live run against Home Assistant 2026.9.1 with a temporary host device. Firmware 0.2.49. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -55,19 +55,27 @@ class SettingEvents(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(ha.messages, [], 'nothing changed: no resend of the whole screen')
|
||||
self.assertIs(m.layouts['text.screen'], stored, 'and no save')
|
||||
self.assertEqual(ha.setting_events, [], 'the event is handled all the same')
|
||||
# A real change is still saved and sent.
|
||||
# A real change is kept (app 0.2.57), and not sent back: the screen has it already, and an echo that
|
||||
# arrives after the screen stepped further would undo that step (a held + on its settings page).
|
||||
rev = m.sent['text.screen']['rev']
|
||||
ha.setting_events.append({'inbox': 'text.screen', 'key': 'standby_seconds', 'value': '600'})
|
||||
ha.changed.set()
|
||||
await self.run_loop_for(m, 0.6)
|
||||
self.assertEqual(m.layouts['text.screen']['settings']['standby_seconds'], 600)
|
||||
self.assertIn('layout', [msg['op'] for _, msg, _ in ha.messages])
|
||||
self.assertEqual(ha.messages, [], 'nothing goes back to the screen')
|
||||
self.assertEqual(m.sent['text.screen']['layout']['settings']['standby_seconds'], 600, 'what the screen holds follows')
|
||||
self.assertEqual(m.sent['text.screen']['rev'], rev, 'the ping keeps the revision the screen has')
|
||||
# So is a brightness that pulls the standby brightness down with it.
|
||||
ha.messages.clear()
|
||||
ha.setting_events.append({'inbox': 'text.screen', 'key': 'brightness', 'value': '10'})
|
||||
ha.changed.set()
|
||||
await self.run_loop_for(m, 0.6)
|
||||
settings = m.layouts['text.screen']['settings']
|
||||
self.assertEqual((settings['brightness'], settings['standby_brightness']), (10, 10))
|
||||
self.assertEqual(ha.messages, [])
|
||||
# The hourly repeat carries the kept values, so the screen and the stored layout agree.
|
||||
await m.sync_one('text.screen', m.layouts['text.screen'], force=True, screen=m.screen('text.screen'), dirty=set())
|
||||
layout = next(msg for _, msg, _ in ha.messages if msg['op'] == 'layout')
|
||||
self.assertEqual((layout['settings']['brightness'], layout['settings']['standby_seconds']), (10, 600))
|
||||
|
||||
|
||||
class Firmware(unittest.TestCase):
|
||||
@@ -75,12 +83,14 @@ class Firmware(unittest.TestCase):
|
||||
self.profiles = {name: (ROOT / name).read_text() for name in PROFILES}
|
||||
|
||||
def test_home_assistant_setting_the_same_value_writes_and_reports_nothing(self):
|
||||
# Firmware 0.2.49+: the setting entities change a value through settings_screen::set(), which stores,
|
||||
# applies and reports only a real change (tests/test_settings_screen.cpp checks that).
|
||||
screen = (ROOT / 'components/smart_display/settings_screen.h').read_text()
|
||||
self.assertRegex(screen, r'return SetResult::same;\n changed\(key\.c_str\(\), reported\);')
|
||||
for name, text in self.profiles.items():
|
||||
for key in ('brightness', 'standby_brightness', 'night_brightness', 'standby_seconds'):
|
||||
action = re.search(r'const auto before = s;\n(?:.*\n){1,6}?\s+if \(s == before\) return;\n'
|
||||
r'\s+if \(runtime_tiles::enabled\) runtime_tiles::settings_preference\.save\(&s\);\n'
|
||||
rf'\s+runtime_tiles::setting_event\("{key}",', text)
|
||||
self.assertTrue(action, f'{name}: {key} saves or reports a value it already has')
|
||||
self.assertIn(f"set_action:\n - lambda: 'settings_screen::set(\"{key}\", (int32_t) lround(x));'", text, f'{name}: {key}')
|
||||
self.assertNotIn('runtime_tiles::setting_event("', text, f'{name}: settings are reported through set() only')
|
||||
|
||||
def test_a_repeated_layout_does_not_redraw_the_page(self):
|
||||
block = RUNTIME[RUNTIME.index('if (op == "layout")'):RUNTIME.index('if (op == "ping")')]
|
||||
|
||||
Reference in New Issue
Block a user