@@ -163,6 +163,131 @@ def test_init_state_moves_to_connected(self):
163163 assert acc .lifecycle == HomieLifecycle .CONNECTED
164164
165165
166+ # ---------------------------------------------------------------------------
167+ # Panel reboot: $description clears stale property values
168+ # ---------------------------------------------------------------------------
169+
170+
171+ class TestDescriptionClearsProperties :
172+ """Verify that a panel reboot (disconnected -> $description) clears stale data.
173+
174+ The clear only happens when _received_description is False, which requires
175+ a $state=disconnected (or lost) to have reset the lifecycle first. A
176+ re-delivered retained $description on a pure network reconnect does NOT
177+ clear, because _received_description is still True from the previous session.
178+ """
179+
180+ def _simulate_reboot (self , acc : HomiePropertyAccumulator ) -> None :
181+ """Simulate the panel reboot lifecycle transition."""
182+ acc .handle_message (f"{ PREFIX } /$state" , "disconnected" )
183+ acc .handle_message (f"{ PREFIX } /$description" , SIMPLE_DESC )
184+ acc .handle_message (f"{ PREFIX } /$state" , "ready" )
185+
186+ def test_description_clears_property_values_on_reboot (self ):
187+ """A panel reboot must clear property values from the previous lifecycle."""
188+ acc = HomiePropertyAccumulator (SERIAL )
189+ _make_ready (acc )
190+ acc .handle_message (f"{ PREFIX } /circuit-1/exported-energy" , "1000" )
191+ assert acc .get_prop ("circuit-1" , "exported-energy" ) == "1000"
192+
193+ # Panel reboots — $state=disconnected resets lifecycle, then $description clears
194+ self ._simulate_reboot (acc )
195+ assert acc .get_prop ("circuit-1" , "exported-energy" ) == ""
196+
197+ def test_description_clears_timestamps_on_reboot (self ):
198+ """Timestamps must also be cleared so stale timing data doesn't persist."""
199+ acc = HomiePropertyAccumulator (SERIAL )
200+ _make_ready (acc )
201+ acc .handle_message (f"{ PREFIX } /circuit-1/exported-energy" , "1000" )
202+ assert acc .get_timestamp ("circuit-1" , "exported-energy" ) > 0
203+
204+ self ._simulate_reboot (acc )
205+ assert acc .get_timestamp ("circuit-1" , "exported-energy" ) == 0
206+
207+ def test_description_clears_target_values_on_reboot (self ):
208+ """Target values must also be cleared on panel reboot."""
209+ acc = HomiePropertyAccumulator (SERIAL )
210+ _make_ready (acc )
211+ acc .handle_message (f"{ PREFIX } /circuit-1/relay/$target" , "OPEN" )
212+ assert acc .get_target ("circuit-1" , "relay" ) == "OPEN"
213+
214+ self ._simulate_reboot (acc )
215+ assert acc .get_target ("circuit-1" , "relay" ) is None
216+
217+ def test_reboot_increments_generation (self ):
218+ """Each panel reboot must advance the generation counter."""
219+ acc = HomiePropertyAccumulator (SERIAL )
220+ assert acc .generation == 0
221+
222+ # First boot
223+ acc .handle_message (f"{ PREFIX } /$description" , SIMPLE_DESC )
224+ assert acc .generation == 1
225+
226+ # Reboot
227+ acc .handle_message (f"{ PREFIX } /$state" , "disconnected" )
228+ acc .handle_message (f"{ PREFIX } /$description" , SIMPLE_DESC )
229+ assert acc .generation == 2
230+
231+ def test_retained_redescription_does_not_clear (self ):
232+ """A re-delivered retained $description without a disconnect must NOT clear."""
233+ acc = HomiePropertyAccumulator (SERIAL )
234+ _make_ready (acc )
235+ acc .handle_message (f"{ PREFIX } /circuit-1/exported-energy" , "1000" )
236+
237+ # Simulate network reconnect — $description re-delivered without $state=disconnected
238+ acc .handle_message (f"{ PREFIX } /$description" , SIMPLE_DESC )
239+
240+ # Property values should be preserved
241+ assert acc .get_prop ("circuit-1" , "exported-energy" ) == "1000"
242+ assert acc .generation == 1 # still 1 from initial boot, no increment on re-delivery
243+
244+ def test_fresh_properties_available_after_reboot (self ):
245+ """Post-reboot properties should be stored normally after clear."""
246+ acc = HomiePropertyAccumulator (SERIAL )
247+ _make_ready (acc )
248+ acc .handle_message (f"{ PREFIX } /circuit-1/exported-energy" , "1000" )
249+
250+ self ._simulate_reboot (acc )
251+
252+ # Fresh post-reboot value
253+ acc .handle_message (f"{ PREFIX } /circuit-1/exported-energy" , "50" )
254+ assert acc .get_prop ("circuit-1" , "exported-energy" ) == "50"
255+
256+ def test_fresh_target_values_available_after_reboot (self ):
257+ """Post-reboot target values should be stored normally after clear."""
258+ acc = HomiePropertyAccumulator (SERIAL )
259+ _make_ready (acc )
260+ acc .handle_message (f"{ PREFIX } /circuit-1/relay/$target" , "OPEN" )
261+ assert acc .get_target ("circuit-1" , "relay" ) == "OPEN"
262+
263+ self ._simulate_reboot (acc )
264+
265+ # Fresh post-reboot target value
266+ acc .handle_message (f"{ PREFIX } /circuit-1/relay/$target" , "CLOSED" )
267+ assert acc .get_target ("circuit-1" , "relay" ) == "CLOSED"
268+
269+ def test_fast_reboot_without_lwt_still_clears (self ):
270+ """Panel reboots so fast that $state=disconnected (LWT) is skipped.
271+
272+ The panel goes directly from ready -> init -> description -> ready.
273+ $state=init must reset _received_description so the subsequent
274+ $description triggers the property clear.
275+ """
276+ acc = HomiePropertyAccumulator (SERIAL )
277+ _make_ready (acc )
278+ acc .handle_message (f"{ PREFIX } /circuit-1/exported-energy" , "1000" )
279+ gen_before = acc .generation
280+
281+ # Fast reboot: no $state=disconnected, straight to init
282+ acc .handle_message (f"{ PREFIX } /$state" , "init" )
283+ acc .handle_message (f"{ PREFIX } /$description" , SIMPLE_DESC )
284+ acc .handle_message (f"{ PREFIX } /$state" , "ready" )
285+
286+ # Property values should be cleared
287+ assert acc .get_prop ("circuit-1" , "exported-energy" ) == ""
288+ assert acc .generation == gen_before + 1
289+
290+
166291# ---------------------------------------------------------------------------
167292# Lifecycle: invalid JSON description
168293# ---------------------------------------------------------------------------
0 commit comments