def async_track_point_in_utc_time(hass, action, point_in_time): """Add a listener that fires once after a specific point in UTC time.""" # Ensure point_in_time is UTC point_in_time = event.dt_util.as_utc(point_in_time) # Since this is called once, we accept a HassJob so we can avoid # having to figure out how to call the action every time its called. job = action if isinstance(action, ha.HassJob) else ha.HassJob(action) @ha.callback def point_in_time_listener(event): """Listen for matching time_changed events.""" now = event.data[ATTR_NOW] if now < point_in_time or hasattr(point_in_time_listener, "run"): return # Set variable so that we will never run twice. # Because the event bus might have to wait till a thread comes # available to execute this listener it might occur that the # listener gets lined up twice to be executed. This will make # sure the second time it does nothing. setattr(point_in_time_listener, "run", True) async_unsub() hass.async_run_hass_job(job, now) async_unsub = hass.bus.async_listen(EVENT_TIME_CHANGED, point_in_time_listener) return async_unsub
def async_listen_platform( hass: core.HomeAssistant, component: str, callback: Callable[[str, Optional[Dict[str, Any]]], Any], ) -> None: """Register a platform loader listener. This method must be run in the event loop. """ service = EVENT_LOAD_PLATFORM.format(component) job = core.HassJob(callback) async def discovery_platform_listener(event: core.Event) -> None: """Listen for platform discovery events.""" if event.data.get(ATTR_SERVICE) != service: return platform = event.data.get(ATTR_PLATFORM) if not platform: return task = hass.async_run_hass_job(job, platform, event.data.get(ATTR_DISCOVERED)) if task: await task hass.bus.async_listen(EVENT_PLATFORM_DISCOVERED, discovery_platform_listener)
def async_listen_platform( hass: core.HomeAssistant, component: str, callback: Callable[[str, dict[str, Any] | None], Any], ) -> None: """Register a platform loader listener. This method must be run in the event loop. """ service = EVENT_LOAD_PLATFORM.format(component) job = core.HassJob(callback) async def discovery_platform_listener(discovered: DiscoveryDict) -> None: """Listen for platform discovery events.""" platform = discovered["platform"] if not platform: return task = hass.async_run_hass_job(job, platform, discovered.get("discovered")) if task: await task async_dispatcher_connect( hass, SIGNAL_PLATFORM_DISCOVERED.format(service), discovery_platform_listener )
def async_listen( hass: core.HomeAssistant, service: Union[str, Collection[str]], callback: CALLBACK_TYPE, ) -> None: """Set up listener for discovery of specific service. Service can be a string or a list/tuple. """ if isinstance(service, str): service = (service, ) else: service = tuple(service) job = core.HassJob(callback) async def discovery_event_listener(event: core.Event) -> None: """Listen for discovery events.""" if ATTR_SERVICE in event.data and event.data[ATTR_SERVICE] in service: task = hass.async_run_hass_job(job, event.data[ATTR_SERVICE], event.data.get(ATTR_DISCOVERED)) if task: await task hass.bus.async_listen(EVENT_PLATFORM_DISCOVERED, discovery_event_listener)
def async_track_utc_time_change( hass, action, hour=None, minute=None, second=None, local=False ): """Add a listener that will fire if time matches a pattern.""" job = ha.HassJob(action) # We do not have to wrap the function with time pattern matching logic # if no pattern given if all(val is None for val in (hour, minute, second)): @ha.callback def time_change_listener(ev) -> None: """Fire every time event that comes in.""" hass.async_run_hass_job(job, ev.data[ATTR_NOW]) return hass.bus.async_listen(EVENT_TIME_CHANGED, time_change_listener) matching_seconds = event.dt_util.parse_time_expression(second, 0, 59) matching_minutes = event.dt_util.parse_time_expression(minute, 0, 59) matching_hours = event.dt_util.parse_time_expression(hour, 0, 23) next_time = None def calculate_next(now) -> None: """Calculate and set the next time the trigger should fire.""" nonlocal next_time localized_now = event.dt_util.as_local(now) if local else now next_time = event.dt_util.find_next_time_expression_time( localized_now, matching_seconds, matching_minutes, matching_hours ) # Make sure rolling back the clock doesn't prevent the timer from # triggering. last_now = None @ha.callback def pattern_time_change_listener(ev) -> None: """Listen for matching time_changed events.""" nonlocal next_time, last_now now = ev.data[ATTR_NOW] if last_now is None or now < last_now: # Time rolled back or next time not yet calculated calculate_next(now) last_now = now if next_time <= now: hass.async_run_hass_job( job, event.dt_util.as_local(now) if local else now ) calculate_next(now + datetime.timedelta(seconds=1)) # We can't use async_track_point_in_utc_time here because it would # break in the case that the system time abruptly jumps backwards. # Our custom last_now logic takes care of resolving that scenario. return hass.bus.async_listen(EVENT_TIME_CHANGED, pattern_time_change_listener)
def test_async_add_hass_job_schedule_callback(): """Test that we schedule coroutines and add jobs to the job pool.""" hass = MagicMock() job = MagicMock() ha.HomeAssistant.async_add_hass_job(hass, ha.HassJob(ha.callback(job))) assert len(hass.loop.call_soon.mock_calls) == 1 assert len(hass.loop.create_task.mock_calls) == 0 assert len(hass.add_job.mock_calls) == 0
def test_async_add_hass_job_schedule_partial_callback(): """Test that we schedule partial coros and add jobs to the job pool.""" hass = MagicMock() job = MagicMock() partial = functools.partial(ha.callback(job)) ha.HomeAssistant.async_add_hass_job(hass, ha.HassJob(partial)) assert len(hass.loop.call_soon.mock_calls) == 1 assert len(hass.loop.create_task.mock_calls) == 0 assert len(hass.add_job.mock_calls) == 0
def test_async_add_hass_job_schedule_coroutinefunction(loop): """Test that we schedule coroutines and add jobs to the job pool.""" hass = MagicMock(loop=MagicMock(wraps=loop)) async def job(): pass ha.HomeAssistant.async_add_hass_job(hass, ha.HassJob(job)) assert len(hass.loop.call_soon.mock_calls) == 0 assert len(hass.loop.create_task.mock_calls) == 1 assert len(hass.add_job.mock_calls) == 0
def test_async_run_hass_job_delegates_non_async(): """Test that the callback annotation is respected.""" hass = MagicMock() calls = [] def job(): calls.append(1) ha.HomeAssistant.async_run_hass_job(hass, ha.HassJob(job)) assert len(calls) == 0 assert len(hass.async_add_hass_job.mock_calls) == 1
def test_async_add_job_add_hass_threaded_job_to_pool(): """Test that we schedule coroutines and add jobs to the job pool.""" hass = MagicMock() def job(): pass ha.HomeAssistant.async_add_hass_job(hass, ha.HassJob(job)) assert len(hass.loop.call_soon.mock_calls) == 0 assert len(hass.loop.create_task.mock_calls) == 0 assert len(hass.loop.run_in_executor.mock_calls) == 1
async def test_hassjob_forbid_coroutine(): """Test hassjob forbids coroutines.""" async def bla(): pass coro = bla() with pytest.raises(ValueError): ha.HassJob(coro) # To avoid warning about unawaited coro await coro
def async_listen( hass: core.HomeAssistant, service: str, callback: CALLBACK_TYPE, ) -> None: """Set up listener for discovery of specific service. Service can be a string or a list/tuple. """ job = core.HassJob(callback) async def discovery_event_listener(discovered: DiscoveryDict) -> None: """Listen for discovery events.""" task = hass.async_run_hass_job(job, discovered["service"], discovered["discovered"]) if task: await task async_dispatcher_connect(hass, SIGNAL_PLATFORM_DISCOVERED.format(service), discovery_event_listener)