@@ -1395,3 +1395,198 @@ async def test_vehicle_detection_timer_cleanup_on_remove(
13951395 # Entity should be gone and no event should have fired
13961396 state = hass .states .get (entity_id )
13971397 assert state is None
1398+
1399+
1400+ async def test_vehicle_detection_refire_on_lpr_data (
1401+ hass : HomeAssistant ,
1402+ ufp : MockUFPFixture ,
1403+ doorbell : Camera ,
1404+ unadopted_camera : Camera ,
1405+ fixed_now : datetime ,
1406+ ) -> None :
1407+ """Test that event refires when LPR data arrives after initial detection."""
1408+
1409+ await init_entry (hass , ufp , [doorbell , unadopted_camera ])
1410+ assert_entity_counts (hass , Platform .EVENT , 4 , 4 )
1411+ events : list [HAEvent ] = []
1412+
1413+ @callback
1414+ def _capture_event (event : HAEvent ) -> None :
1415+ events .append (event )
1416+
1417+ _ , entity_id = await ids_from_device_description (
1418+ hass , Platform .EVENT , doorbell , EVENT_DESCRIPTIONS [3 ]
1419+ )
1420+
1421+ unsub = async_track_state_change_event (hass , entity_id , _capture_event )
1422+
1423+ # Create event with vehicle thumbnail but NO LPR data
1424+ event = Event (
1425+ model = ModelType .EVENT ,
1426+ id = "test_refire_lpr_id" ,
1427+ type = EventType .SMART_DETECT ,
1428+ start = fixed_now - timedelta (seconds = 1 ),
1429+ end = None ,
1430+ score = 100 ,
1431+ smart_detect_types = [],
1432+ smart_detect_event_ids = [],
1433+ camera_id = doorbell .id ,
1434+ api = ufp .api ,
1435+ metadata = {
1436+ "detected_thumbnails" : [
1437+ {
1438+ "type" : "vehicle" ,
1439+ "confidence" : 85 ,
1440+ "clock_best_wall" : fixed_now ,
1441+ "cropped_id" : "test_thumb_id" ,
1442+ }
1443+ ]
1444+ },
1445+ )
1446+
1447+ new_camera = doorbell .model_copy ()
1448+ new_camera .last_smart_detect_event_id = "test_refire_lpr_id"
1449+ ufp .api .bootstrap .cameras = {new_camera .id : new_camera }
1450+ ufp .api .bootstrap .events = {event .id : event }
1451+
1452+ mock_msg = Mock ()
1453+ mock_msg .changed_data = {}
1454+ mock_msg .new_obj = event
1455+ ufp .ws_msg (mock_msg )
1456+
1457+ # Wait for the timer to expire - first event should fire without LPR
1458+ await asyncio .sleep (TEST_VEHICLE_EVENT_DELAY * 2 )
1459+ await hass .async_block_till_done ()
1460+
1461+ # Should have received first event without LPR
1462+ assert len (events ) == 1
1463+ state = events [0 ].data ["new_state" ]
1464+ assert state
1465+ assert state .attributes [ATTR_EVENT_ID ] == "test_refire_lpr_id"
1466+ assert state .attributes ["confidence" ] == 85
1467+ assert "license_plate" not in state .attributes
1468+
1469+ # Now LPR data arrives for the same event
1470+ event .metadata = {
1471+ "detected_thumbnails" : [
1472+ {
1473+ "type" : "vehicle" ,
1474+ "confidence" : 85 ,
1475+ "clock_best_wall" : fixed_now ,
1476+ "cropped_id" : "test_thumb_id" ,
1477+ },
1478+ {
1479+ "type" : "vehicle" ,
1480+ "confidence" : 95 ,
1481+ "clock_best_wall" : fixed_now + timedelta (seconds = 1 ),
1482+ "cropped_id" : "test_thumb_id_lpr" ,
1483+ "group" : {
1484+ "id" : "lpr_group" ,
1485+ "matched_name" : "ABC123" ,
1486+ "confidence" : 95 ,
1487+ },
1488+ },
1489+ ]
1490+ }
1491+
1492+ ufp .api .bootstrap .events = {event .id : event }
1493+ mock_msg .new_obj = event
1494+ ufp .ws_msg (mock_msg )
1495+
1496+ # Wait for the new timer to expire
1497+ await asyncio .sleep (TEST_VEHICLE_EVENT_DELAY * 2 )
1498+ await hass .async_block_till_done ()
1499+
1500+ # Should have received second event WITH LPR data
1501+ assert len (events ) == 2
1502+ state = events [1 ].data ["new_state" ]
1503+ assert state
1504+ assert state .attributes [ATTR_EVENT_ID ] == "test_refire_lpr_id"
1505+ assert state .attributes ["confidence" ] == 95
1506+ assert state .attributes ["license_plate" ] == "ABC123"
1507+
1508+ unsub ()
1509+
1510+
1511+ async def test_vehicle_detection_no_refire_same_data (
1512+ hass : HomeAssistant ,
1513+ ufp : MockUFPFixture ,
1514+ doorbell : Camera ,
1515+ unadopted_camera : Camera ,
1516+ fixed_now : datetime ,
1517+ ) -> None :
1518+ """Test that event does NOT refire when same data arrives again."""
1519+
1520+ await init_entry (hass , ufp , [doorbell , unadopted_camera ])
1521+ assert_entity_counts (hass , Platform .EVENT , 4 , 4 )
1522+ events : list [HAEvent ] = []
1523+
1524+ @callback
1525+ def _capture_event (event : HAEvent ) -> None :
1526+ events .append (event )
1527+
1528+ _ , entity_id = await ids_from_device_description (
1529+ hass , Platform .EVENT , doorbell , EVENT_DESCRIPTIONS [3 ]
1530+ )
1531+
1532+ unsub = async_track_state_change_event (hass , entity_id , _capture_event )
1533+
1534+ # Create event with vehicle thumbnail
1535+ event = Event (
1536+ model = ModelType .EVENT ,
1537+ id = "test_no_refire_id" ,
1538+ type = EventType .SMART_DETECT ,
1539+ start = fixed_now - timedelta (seconds = 1 ),
1540+ end = None ,
1541+ score = 100 ,
1542+ smart_detect_types = [],
1543+ smart_detect_event_ids = [],
1544+ camera_id = doorbell .id ,
1545+ api = ufp .api ,
1546+ metadata = {
1547+ "detected_thumbnails" : [
1548+ {
1549+ "type" : "vehicle" ,
1550+ "confidence" : 90 ,
1551+ "clock_best_wall" : fixed_now ,
1552+ "cropped_id" : "test_thumb_id" ,
1553+ "group" : {
1554+ "id" : "lpr_group" ,
1555+ "matched_name" : "XYZ789" ,
1556+ "confidence" : 90 ,
1557+ },
1558+ }
1559+ ]
1560+ },
1561+ )
1562+
1563+ new_camera = doorbell .model_copy ()
1564+ new_camera .last_smart_detect_event_id = "test_no_refire_id"
1565+ ufp .api .bootstrap .cameras = {new_camera .id : new_camera }
1566+ ufp .api .bootstrap .events = {event .id : event }
1567+
1568+ mock_msg = Mock ()
1569+ mock_msg .changed_data = {}
1570+ mock_msg .new_obj = event
1571+ ufp .ws_msg (mock_msg )
1572+
1573+ # Wait for the timer to expire
1574+ await asyncio .sleep (TEST_VEHICLE_EVENT_DELAY * 2 )
1575+ await hass .async_block_till_done ()
1576+
1577+ # Should have received one event
1578+ assert len (events ) == 1
1579+ state = events [0 ].data ["new_state" ]
1580+ assert state
1581+ assert state .attributes [ATTR_EVENT_ID ] == "test_no_refire_id"
1582+ assert state .attributes ["license_plate" ] == "XYZ789"
1583+
1584+ # Send the same event again with identical data
1585+ ufp .ws_msg (mock_msg )
1586+ await asyncio .sleep (TEST_VEHICLE_EVENT_DELAY * 2 )
1587+ await hass .async_block_till_done ()
1588+
1589+ # Should NOT have received another event (same data)
1590+ assert len (events ) == 1
1591+
1592+ unsub ()
0 commit comments