Skip to content

Event Reference

Generated reference โ€” the facts. The reasons live in the prose docs: Events for automation authors, and How a Run Ends for why the end-of-run events exist and which paths fire them. Regenerate with python scripts/gen_event_docs.py; CI fails if this file is not what the generator emits.

The integration fires 11 events on hass.bus from 26 call sites, carrying 92 payload key slots (54 distinct key names). Every event name below was resolved from the constant that names it, not read as a literal.

Listen with the event trigger platform; every payload is a plain dict.

Payload is derived, not declared: an event whose every key is an identifier (vacuum_entity_id, map_id, job_id) tells a consumer only THAT something happened and must be followed by a state-inspection service call; the rest carry the state in the event itself. Home Assistant's event bus has no supports_response โ€” this is the nearest fact a caller actually needs, and it is computed from the resolved payload.

Event Constant Fire sites Keys Payload
eufy_vacuum_external_run_pending EVENT_EXTERNAL_RUN_PENDING 1 5 carries state
eufy_vacuum_job_finished EVENT_JOB_FINISHED 6 11 carries state
eufy_vacuum_job_progress_tick EVENT_JOB_PROGRESS_TICK 1 2 identity only โ€” pull signal
eufy_vacuum_path_blocked EVENT_PATH_BLOCKED 2 18 carries state
eufy_vacuum_room_completed EVENT_ROOM_COMPLETED 1 7 carries state
eufy_vacuum_room_finished EVENT_ROOM_FINISHED 2 10 carries state
eufy_vacuum_room_skipped EVENT_ROOM_SKIPPED 1 6 carries state
eufy_vacuum_room_started EVENT_ROOM_STARTED 3 8 carries state
eufy_vacuum_run_incomplete EVENT_RUN_INCOMPLETE 5 5 carries state
eufy_vacuum_stall_captured EVENT_STALL_CAPTURED 1 6 carries state
eufy_vacuum_stall_detected EVENT_STALL_DETECTED 3 14 carries state

An EVENT_* constant that nothing fires is listed here rather than above โ€” the name exists, the event does not:

Constant Defined Value
EVENT_BOUNDARY_SAVED custom_components/eufy_vacuum/const.py::EVENT_BOUNDARY_SAVED eufy_vacuum_boundary_saved

eufy_vacuum_external_run_pending

Constant: EVENT_EXTERNAL_RUN_PENDING โ€” custom_components/eufy_vacuum/const.py::EVENT_EXTERNAL_RUN_PENDING, f-string f'{DOMAIN}_external_run_pending'

Fired from 1 call site(s) in 1 module(s): custom_components/eufy_vacuum/learning/external_run.py

Payload

Key Present Value expression
detection_ts every site detection_ts
map_id every site str(map_id)
record_path every site result['path']
segment_count every site result['segment_count']
vacuum_entity_id every site vacuum_entity_id

Fire sites

Location Payload built by Nearest guards
custom_components/eufy_vacuum/learning/external_run.py::ExternalRunManager._finalize_external_run dict literal at the call site result is not None

eufy_vacuum_job_finished

Constant: EVENT_JOB_FINISHED โ€” custom_components/eufy_vacuum/const.py::EVENT_JOB_FINISHED, f-string f'{DOMAIN}_job_finished'

Fired from 6 call site(s) in 5 module(s): custom_components/eufy_vacuum/learning/services.py, custom_components/eufy_vacuum/listeners/lifecycle.py, custom_components/eufy_vacuum/listeners/path_blockers.py, custom_components/eufy_vacuum/listeners/pause_timeout.py, custom_components/eufy_vacuum/services/job_control.py

Payload

Key Present Value expression
actual_cleaning_minutes 5 of 6 sites job_info.get('actual_cleaning_minutes')
duration_minutes 5 of 6 sites job_info.get('duration_minutes')
finalized_at every site result.get('completed_job', {}).get('finalized_at') ยท completed_job.get('finalized_at')
job_id every site result.get('job_id') ยท finalize_result.get('job_id') ยท result.get('job_id') or result.get('finalize_result', {}).get('job_id')
job_path every site result.get('job_path') ยท finalize_result.get('job_path') ยท job_path
map_id every site str(call.data['map_id']) ยท str(map_id)
reason_detail every site outcome.get('lifecycle_message') ยท outcome.get('lifecycle_message') or outcome.get('status')
room_count every site result.get('completed_job', {}).get('job', {}).get('room_count') ยท job_info.get('room_count')
status every site status ยท outcome.get('status', 'completed')
used_for_learning every site outcome.get('used_for_learning')
vacuum_entity_id every site call.data['vacuum_entity_id'] ยท vacuum_entity_id

Where a key lists more than one expression, the fire sites build it differently. That is how the value is OBTAINED, not a claim that the resulting values differ โ€” types are not inferred.

Fire sites

Location Payload built by Nearest guards
custom_components/eufy_vacuum/learning/services.py::handle_finalize_learning_job dict literal at the call site โ€”
custom_components/eufy_vacuum/listeners/lifecycle.py::_process builder job_finished_event_data() finalize_result_succeeded(finalize_result)
custom_components/eufy_vacuum/listeners/path_blockers.py::_process builder job_finished_event_data() path_block_action == 'cancel_and_event' โŸถ bool((action_result or {}).get('cancelled'))
custom_components/eufy_vacuum/listeners/pause_timeout.py::_reap_one_slot builder job_finished_event_data() isinstance(timeout_report, dict) โŸถ bool(result.get('cancelled'))
custom_components/eufy_vacuum/listeners/pause_timeout.py::_reap_one_slot builder job_finished_event_data() isinstance(stranded_report, dict) โŸถ bool(result.get('finalized'))
custom_components/eufy_vacuum/services/job_control.py::_handle_cancel_active_job builder job_finished_event_payload() payload.get('cancelled')

eufy_vacuum_job_progress_tick

Constant: EVENT_JOB_PROGRESS_TICK โ€” custom_components/eufy_vacuum/const.py::EVENT_JOB_PROGRESS_TICK, f-string f'{DOMAIN}_job_progress_tick'

Fired from 1 call site(s) in 1 module(s): custom_components/eufy_vacuum/listeners/job_progress.py

Payload

Key Present Value expression
map_id every site map_id_str
vacuum_entity_id every site vacuum_entity_id

Fire sites

Location Payload built by Nearest guards
custom_components/eufy_vacuum/listeners/job_progress.py::_handle_job_progress_tick dict literal at the call site โ€”

eufy_vacuum_path_blocked

Constant: EVENT_PATH_BLOCKED โ€” custom_components/eufy_vacuum/const.py::EVENT_PATH_BLOCKED, f-string f'{DOMAIN}_path_blocked'

Fired from 2 call site(s) in 1 module(s): custom_components/eufy_vacuum/listeners/path_blockers.py

Payload

Key Present Value expression
action_result conditional action_result
action_taken every site action_taken ยท 'cancel_suppressed_recheck'
affected_remaining_room_ids every site [str(item['room_id']) for item in affected_remaining_rooms]
affected_remaining_room_names every site [str(item.get('name') or f"Room {item['room_id']}") for item in affected_remaining_rooms]
affected_rooms every site affected_remaining_rooms
directly_blocked_room_ids every site [str(room_id) for room_id in directly_blocked_remaining_room_ids]
event_scope every site 'active_job_path_blocked'
indeterminate_rules every site indeterminate_rules
indirectly_blocked_room_ids every site [str(room_id) for room_id in indirectly_blocked_remaining_room_ids]
job_id every site active_job.get('job_id')
map_id every site str(map_id)
path_block_action every site path_block_action
reason_codes every site sorted({str(item.get('reason') or '').strip() for item in affected_remaining_rooms if str(item.get('reason') or '').strip()})
remaining_room_ids every site [str(room_id) for room_id in remaining_room_ids]
requires_attention every site True
trigger_entity_id every site trigger_entity_id
trigger_entity_state every site trigger_entity_state
vacuum_entity_id every site vacuum_entity_id

Where a key lists more than one expression, the fire sites build it differently. That is how the value is OBTAINED, not a claim that the resulting values differ โ€” types are not inferred.

Fire sites

Location Payload built by Nearest guards
custom_components/eufy_vacuum/listeners/path_blockers.py::_process local report: builder get_runtime_path_block_report(); mutated in place path_block_action == 'cancel_and_event' โŸถ not _still_matches
custom_components/eufy_vacuum/listeners/path_blockers.py::_process local report: builder get_runtime_path_block_report(); mutated in place โ€”

eufy_vacuum_room_completed

Constant: EVENT_ROOM_COMPLETED โ€” custom_components/eufy_vacuum/const.py::EVENT_ROOM_COMPLETED, f-string f'{DOMAIN}_room_completed'

Fired from 1 call site(s) in 1 module(s): custom_components/eufy_vacuum/mapping/tracker.py

Payload

Key Present Value expression
confidence every site confidence
duration_seconds every site round(duration_seconds, 1)
entered_at every site entered_at_str
map_id every site str(map_id)
room_id every site str(room_id)
room_name every site room_name
vacuum_entity_id every site vacuum_entity_id

Fire sites

Location Payload built by Nearest guards
custom_components/eufy_vacuum/mapping/tracker.py::MappingTracker._fire_room_completed local event_data: dict literal โ€”

eufy_vacuum_room_finished

Constant: EVENT_ROOM_FINISHED โ€” custom_components/eufy_vacuum/const.py::EVENT_ROOM_FINISHED, f-string f'{DOMAIN}_room_finished'

Fired from 2 call site(s) in 1 module(s): custom_components/eufy_vacuum/jobs/active_job.py

Payload

Key Present Value expression
actual_duration_minutes every site round(elapsed_minutes, 2)
completed_at every site completed_at
completed_room_ids every site updated_active_job.get('completed_room_ids', []) ยท job.get('completed_room_ids', [])
confidence 1 of 2 sites round(confidence_score, 4) if confidence_score > 0 else None
job_id every site updated_active_job.get('job_id') ยท job.get('job_id')
map_id every site str(map_id)
room_id every site str(current_room_id) ยท str(complete_room_id)
room_name every site room_name ยท finished_name
source every site source ยท 'native_signal'
vacuum_entity_id every site vacuum_entity_id

Where a key lists more than one expression, the fire sites build it differently. That is how the value is OBTAINED, not a claim that the resulting values differ โ€” types are not inferred.

Fire sites

Location Payload built by Nearest guards
custom_components/eufy_vacuum/jobs/active_job.py::ActiveJobTracker._apply_room_rollover dict literal at the call site โ€”
custom_components/eufy_vacuum/jobs/active_job.py::ActiveJobTracker._set_native_current_room dict literal at the call site complete_room_id is not None and complete_room_id >= 0

eufy_vacuum_room_skipped

Constant: EVENT_ROOM_SKIPPED โ€” custom_components/eufy_vacuum/const.py::EVENT_ROOM_SKIPPED, f-string f'{DOMAIN}_room_skipped'

Fired from 1 call site(s) in 1 module(s): custom_components/eufy_vacuum/jobs/active_job.py

Payload

Key Present Value expression
completed_room_ids every site list(completed_room_ids)
job_id every site active_job.get('job_id')
map_id every site str(map_id)
room_id every site _rid
room_name every site self._room_name_from_active_job(active_job, _rid) or f'Room {_rid}'
vacuum_entity_id every site vacuum_entity_id

Fire sites

Location Payload built by Nearest guards
custom_components/eufy_vacuum/jobs/active_job.py::ActiveJobTracker.detect_run_anomalies dict literal at the call site emit and skipped_room_ids โŸถ _new_skips

eufy_vacuum_room_started

Constant: EVENT_ROOM_STARTED โ€” custom_components/eufy_vacuum/const.py::EVENT_ROOM_STARTED, f-string f'{DOMAIN}_room_started'

Fired from 3 call site(s) in 2 module(s): custom_components/eufy_vacuum/core/manager.py, custom_components/eufy_vacuum/jobs/active_job.py

Payload

Key Present Value expression
completed_room_ids every site active_job.get('completed_room_ids', []) ยท updated_active_job.get('completed_room_ids', []) ยท job.get('completed_room_ids', [])
job_id every site job_id ยท updated_active_job.get('job_id') ยท job.get('job_id')
map_id every site str(map_id)
room_id every site str(active_job.get('current_room_id')) ยท str(next_room_id) ยท str(new_room_id)
room_name every site self._room_name_from_active_job(active_job, _safe_int(active_job.get('current_room_id'), -1)) ยท self._room_name_from_active_job(updated_active_job, next_room_id) ยท self._room_name_from_active_job(job, new_room_id)
source every site 'job_start' ยท source ยท 'native_signal'
started_at every site started_at ยท updated_active_job.get('current_room_started_at') ยท completed_at
vacuum_entity_id every site vacuum_entity_id

Where a key lists more than one expression, the fire sites build it differently. That is how the value is OBTAINED, not a claim that the resulting values differ โ€” types are not inferred.

Fire sites

Location Payload built by Nearest guards
custom_components/eufy_vacuum/core/manager.py::EufyVacuumManager.start_selected_rooms dict literal at the call site active_job.get('current_room_id') not in (None, '')
custom_components/eufy_vacuum/jobs/active_job.py::ActiveJobTracker._apply_room_rollover dict literal at the call site next_room_id >= 0
custom_components/eufy_vacuum/jobs/active_job.py::ActiveJobTracker._set_native_current_room dict literal at the call site โ€”

eufy_vacuum_run_incomplete

Constant: EVENT_RUN_INCOMPLETE โ€” custom_components/eufy_vacuum/const.py::EVENT_RUN_INCOMPLETE, f-string f'{DOMAIN}_run_incomplete'

Fired from 5 call site(s) in 4 module(s): custom_components/eufy_vacuum/learning/services.py, custom_components/eufy_vacuum/listeners/path_blockers.py, custom_components/eufy_vacuum/listeners/pause_timeout.py, custom_components/eufy_vacuum/services/job_control.py

Payload

Key Present Value expression
job_id every site incomplete_log.get('job_id')
missed_room_ids every site list(incomplete_log.get('missed_room_ids', []))
missed_rooms every site list(incomplete_log.get('missed_rooms', []))
outcome_status every site incomplete_log.get('outcome_status')
vacuum_entity_id every site call.data['vacuum_entity_id'] ยท vacuum_entity_id

Where a key lists more than one expression, the fire sites build it differently. That is how the value is OBTAINED, not a claim that the resulting values differ โ€” types are not inferred.

Fire sites

Location Payload built by Nearest guards
custom_components/eufy_vacuum/learning/services.py::handle_finalize_learning_job dict literal at the call site isinstance(incomplete_log, dict) and incomplete_log.get('missed_room_ids')
custom_components/eufy_vacuum/listeners/path_blockers.py::_process local run_incomplete: builder run_incomplete_event_data() path_block_action == 'cancel_and_event' โŸถ bool((action_result or {}).get('cancelled')) โŸถ run_incomplete is not None
custom_components/eufy_vacuum/listeners/pause_timeout.py::_reap_one_slot local run_incomplete: builder run_incomplete_event_data() isinstance(timeout_report, dict) โŸถ bool(result.get('cancelled')) โŸถ run_incomplete is not None
custom_components/eufy_vacuum/listeners/pause_timeout.py::_reap_one_slot local run_incomplete: builder run_incomplete_event_data() isinstance(stranded_report, dict) โŸถ bool(result.get('finalized')) โŸถ run_incomplete is not None
custom_components/eufy_vacuum/services/job_control.py::_handle_cancel_active_job local run_incomplete: builder run_incomplete_event_payload() payload.get('cancelled') โŸถ run_incomplete is not None

eufy_vacuum_stall_captured

Constant: EVENT_STALL_CAPTURED โ€” custom_components/eufy_vacuum/listeners/stall_capture.py::EVENT_STALL_CAPTURED, f-string f'{DOMAIN}_stall_captured'

Fired from 1 call site(s) in 1 module(s): custom_components/eufy_vacuum/listeners/stall_capture.py

Payload

Key Present Value expression
image_path every site path
map_id every site str(map_id)
message every site message
room_id every site room_id
room_name every site room_name
vacuum_entity_id every site vacuum_entity_id

Fire sites

Location Payload built by Nearest guards
custom_components/eufy_vacuum/listeners/stall_capture.py::_capture dict literal at the call site โ€”

eufy_vacuum_stall_detected

Constant: EVENT_STALL_DETECTED โ€” custom_components/eufy_vacuum/const.py::EVENT_STALL_DETECTED, f-string f'{DOMAIN}_stall_detected'

Fired from 3 call site(s) in 3 module(s): custom_components/eufy_vacuum/core/manager.py, custom_components/eufy_vacuum/jobs/active_job.py, custom_components/eufy_vacuum/services/stall_capture.py

Payload

Key Present Value expression
elapsed_minutes 2 of 3 sites stall_elapsed_minutes ยท None
error_code 1 of 3 sites (latch or {}).get('current_code')
error_message 1 of 3 sites (latch or {}).get('current_message')
expected_minutes 2 of 3 sites stall_expected_minutes ยท None
injected 1 of 3 sites True
map_id every site str(map_id)
min_progress_m2 1 of 3 sites limits['min_progress_m2']
progress_m2 1 of 3 sites round(float(result['progress_m2']), 2)
room_id every site room_id ยท current_room_id
room_name every site room_name ยท _stall_room_name ยท room.get('name') or f'Room {room_id}'
stall_ratio 2 of 3 sites stall_ratio ยท None
trigger 2 of 3 sites trigger ยท 'timing'
vacuum_entity_id every site vacuum_entity_id
window_minutes 1 of 3 sites limits['window_minutes']

Where a key lists more than one expression, the fire sites build it differently. That is how the value is OBTAINED, not a claim that the resulting values differ โ€” types are not inferred.

Caller-supplied key groups

These keys are not written at the fire site. The firing helper extends its payload with a dict passed in by its caller, so the set depends on which caller fired it.

Discriminator Keys Passed at
trigger='area' window_minutes, progress_m2, min_progress_m2 custom_components/eufy_vacuum/core/manager.py::EufyVacuumManager.apply_stuck_watch_tick
trigger='error' error_code, error_message custom_components/eufy_vacuum/core/manager.py::EufyVacuumManager.apply_stuck_watch_tick

Fire sites

Location Payload built by Nearest guards
custom_components/eufy_vacuum/core/manager.py::EufyVacuumManager._fire_stuck_event local payload: dict literal; extended by the caller via detail= โ€”
custom_components/eufy_vacuum/jobs/active_job.py::ActiveJobTracker.detect_run_anomalies dict literal at the call site _stall_threshold > 0 and current_room_elapsed_minutes >= _stall_threshold * _STALL_RATIO โŸถ emit โŸถ current_room_id not in _notified
custom_components/eufy_vacuum/services/stall_capture.py::_dev_inject_stall local payload: dict literal โ€”

Line numbers here are current by construction. They are regenerated from source and CI fails when this file disagrees with the generator, which is the only reason a reference is allowed to cite them at all โ€” prose in this repo cites symbols precisely because prose has no such mechanism.

What this reference cannot see

Declared rather than omitted. An analysis that cannot see a construction site reports absence with total confidence: this repo's theme-token trace once called 135 live tokens dead for exactly that reason, and deleting them would have broken theming everywhere. So every limit of the static pass is listed here, grouped, with its count.

Blind spot n What it means for a reader
builder-self-delegate 2 get_runtime_path_block_report() delegate: get_runtime_path_block_report delegates to another definition of the same name โ€” not followed a second time; the keys come from the sibling definition
firing-conditions-not-derived 1 the if guards printed per site are the enclosing tests only โ€” dedup ledgers, adapter capability gates and cross-tick state machines that decide whether a site is reached are not modelled
flow-insensitive-mutation 1 in-place payload mutations are attributed by LINE ORDER, not control flow โ€” a mutation in a branch not taken is still counted for any fire site textually below it
no-type-inference 1 payload value TYPES are not inferred; the reference prints the value EXPRESSION for 92 key slots instead

builder-self-delegate โ€” 2 site(s):

  • custom_components/eufy_vacuum/core/manager.py โ€” get_runtime_path_block_report() delegate: get_runtime_path_block_report delegates to another definition of the same name โ€” not followed a second time; the keys come from the sibling definition
  • custom_components/eufy_vacuum/core/manager.py โ€” get_runtime_path_block_report() delegate: get_runtime_path_block_report delegates to another definition of the same name โ€” not followed a second time; the keys come from the sibling definition