"""Tests for Broadlink remotes.""" from base64 import b64decode from unittest.mock import call from broadlink.exceptions import BroadlinkException from freezegun.api import FrozenDateTimeFactory import pytest from homeassistant.components.broadlink.const import DOMAIN from homeassistant.components.broadlink.updater import BroadlinkRMUpdateManager from homeassistant.components.remote import ( DOMAIN as REMOTE_DOMAIN, SERVICE_SEND_COMMAND, SERVICE_TURN_OFF, SERVICE_TURN_ON, ) from homeassistant.const import ( ATTR_FRIENDLY_NAME, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from . import get_device from tests.common import async_fire_time_changed REMOTE_DEVICES = ["Entrance", "Living Room", "Office", "Garage"] IR_PACKET = ( "JgBGAJKVETkRORA6ERQRFBEUERQRFBE5ETkQOhAVEBUQFREUEBUQ" "OhEUERQRORE5EBURFBA6EBUQOhE5EBUQFRA6EDoRFBEADQUAAA==" ) async def test_remote_setup_works( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, ) -> None: """Test a successful setup with all remotes.""" for device in map(get_device, REMOTE_DEVICES): mock_setup = await device.setup_entry(hass) device_entry = device_registry.async_get_device_by_identifier( (DOMAIN, mock_setup.entry.unique_id), mock_setup.entry.entry_id ) entries = er.async_entries_for_device(entity_registry, device_entry.id) remotes = [entry for entry in entries if entry.domain == Platform.REMOTE] assert len(remotes) == 1 remote = remotes[0] assert ( hass.states.get(remote.entity_id).attributes[ATTR_FRIENDLY_NAME] == device.name ) assert hass.states.get(remote.entity_id).state == STATE_ON assert mock_setup.api.auth.call_count == 1 async def test_remote_send_command( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, ) -> None: """Test sending a command with all remotes.""" for device in map(get_device, REMOTE_DEVICES): mock_setup = await device.setup_entry(hass) device_entry = device_registry.async_get_device_by_identifier( (DOMAIN, mock_setup.entry.unique_id), mock_setup.entry.entry_id ) entries = er.async_entries_for_device(entity_registry, device_entry.id) remotes = [entry for entry in entries if entry.domain == Platform.REMOTE] assert len(remotes) == 1 remote = remotes[0] await hass.services.async_call( REMOTE_DOMAIN, SERVICE_SEND_COMMAND, {"entity_id": remote.entity_id, "command": "b64:" + IR_PACKET}, blocking=True, ) assert mock_setup.api.send_data.call_count == 1 assert mock_setup.api.send_data.call_args == call(b64decode(IR_PACKET)) assert mock_setup.api.auth.call_count == 1 @pytest.mark.parametrize( ("error", "ticks_to_unavailable"), [ # OSError flips availability on the first failure (fast path). (OSError("connection refused"), 1), # A generic BroadlinkException keeps the entity available across the # first three failed cycles and only flips once SCAN_INTERVAL * 3 has # elapsed since the last successful update. (BroadlinkException("update failed"), 4), ], ) async def test_remote_availability( hass: HomeAssistant, freezer: FrozenDateTimeFactory, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, error: Exception, ticks_to_unavailable: int, ) -> None: """Test the remote becomes unavailable on disconnect and recovers on reconnect.""" device = get_device("Garage") mock_setup = await device.setup_entry(hass) device_entry = device_registry.async_get_device_by_identifier( (DOMAIN, mock_setup.entry.unique_id), mock_setup.entry.entry_id ) entries = er.async_entries_for_device(entity_registry, device_entry.id) remote = next(entry for entry in entries if entry.domain == Platform.REMOTE) assert hass.states.get(remote.entity_id).state == STATE_ON mock_setup.api.check_sensors.side_effect = error for _ in range(ticks_to_unavailable - 1): freezer.tick(BroadlinkRMUpdateManager.SCAN_INTERVAL) async_fire_time_changed(hass) await hass.async_block_till_done() assert hass.states.get(remote.entity_id).state == STATE_ON freezer.tick(BroadlinkRMUpdateManager.SCAN_INTERVAL) async_fire_time_changed(hass) await hass.async_block_till_done() assert hass.states.get(remote.entity_id).state == STATE_UNAVAILABLE mock_setup.api.check_sensors.side_effect = None freezer.tick(BroadlinkRMUpdateManager.SCAN_INTERVAL) async_fire_time_changed(hass) await hass.async_block_till_done() assert hass.states.get(remote.entity_id).state == STATE_ON async def test_remote_turn_off_turn_on( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, ) -> None: """Test we do not send commands if the remotes are off.""" for device in map(get_device, REMOTE_DEVICES): mock_setup = await device.setup_entry(hass) device_entry = device_registry.async_get_device_by_identifier( (DOMAIN, mock_setup.entry.unique_id), mock_setup.entry.entry_id ) entries = er.async_entries_for_device(entity_registry, device_entry.id) remotes = [entry for entry in entries if entry.domain == Platform.REMOTE] assert len(remotes) == 1 remote = remotes[0] await hass.services.async_call( REMOTE_DOMAIN, SERVICE_TURN_OFF, {"entity_id": remote.entity_id}, blocking=True, ) assert hass.states.get(remote.entity_id).state == STATE_OFF await hass.services.async_call( REMOTE_DOMAIN, SERVICE_SEND_COMMAND, {"entity_id": remote.entity_id, "command": "b64:" + IR_PACKET}, blocking=True, ) assert mock_setup.api.send_data.call_count == 0 await hass.services.async_call( REMOTE_DOMAIN, SERVICE_TURN_ON, {"entity_id": remote.entity_id}, blocking=True, ) assert hass.states.get(remote.entity_id).state == STATE_ON await hass.services.async_call( REMOTE_DOMAIN, SERVICE_SEND_COMMAND, {"entity_id": remote.entity_id, "command": "b64:" + IR_PACKET}, blocking=True, ) assert mock_setup.api.send_data.call_count == 1 assert mock_setup.api.send_data.call_args == call(b64decode(IR_PACKET)) assert mock_setup.api.auth.call_count == 1