1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-18 14:29:57 +01:00
Files
Paulus Schoutsen 62ac3f9834 Update rf-protocols to 3.0.0 (#170301)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 12:46:26 +02:00

136 lines
5.0 KiB
Python

"""Tests for the Broadlink radio_frequency platform."""
from datetime import timedelta
from unittest.mock import MagicMock, call
from broadlink.exceptions import BroadlinkException
import pytest
from rf_protocols.commands.ook import OOKCommand
from homeassistant.components import radio_frequency
from homeassistant.components.broadlink.const import DOMAIN
from homeassistant.components.broadlink.radio_frequency import (
_RF_315_TYPE_BYTE,
_RF_433_TYPE_BYTE,
_TICK_US,
encode_rf_packet,
)
from homeassistant.const import STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.util import dt as dt_util
from . import get_device
from tests.common import async_fire_time_changed
_FREQ_433 = 433_920_000
_FREQ_315 = 315_000_000
async def _setup_rf_device(hass: HomeAssistant) -> tuple[MagicMock, str]:
"""Set up the RMPRO test device, return its api mock and RF entity_id."""
device = get_device("Office")
mock_setup = await device.setup_entry(hass)
entity_registry = er.async_get(hass)
device_registry = dr.async_get(hass)
device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, mock_setup.entry.unique_id)}
)
entries = er.async_entries_for_device(entity_registry, device_entry.id)
rf_entity = next(e for e in entries if e.domain == Platform.RADIO_FREQUENCY)
return mock_setup.api, rf_entity.entity_id
@pytest.mark.parametrize(
("device_name", "has_rf"),
[
("Office", True), # RMPRO
("Garage", True), # RM4PRO
("Entrance", False), # RMMINI
],
)
async def test_radio_frequency_setup(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
device_registry: dr.DeviceRegistry,
device_name: str,
has_rf: bool,
) -> None:
"""RF entity is created only for RF-capable devices."""
device = get_device(device_name)
mock_setup = await device.setup_entry(hass)
device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, mock_setup.entry.unique_id)}
)
entries = er.async_entries_for_device(entity_registry, device_entry.id)
rf_entities = [e for e in entries if e.domain == Platform.RADIO_FREQUENCY]
assert len(rf_entities) == (1 if has_rf else 0)
def test_encode_rf_packet() -> None:
"""Pulses are encoded inline below 256 ticks, escape-prefixed above."""
timings = [round(12 * _TICK_US), round(300 * _TICK_US), round(12 * _TICK_US)]
packet = encode_rf_packet(
type_byte=_RF_433_TYPE_BYTE, repeat_count=3, timings_us=timings
)
# type byte, repeat count, payload length (le16), 12, escape (00 01 2c), 12
assert packet == bytes([0xB2, 0x03, 0x05, 0x00, 0x0C, 0x00, 0x01, 0x2C, 0x0C])
async def test_send_command(hass: HomeAssistant) -> None:
"""An OOK command transmits the encoded packet once."""
api, entity_id = await _setup_rf_device(hass)
timings = [400, -800, 400, -800]
command = OOKCommand(frequency=_FREQ_433, timings=timings)
await radio_frequency.async_send_command(hass, entity_id, command)
expected = encode_rf_packet(
type_byte=_RF_433_TYPE_BYTE, repeat_count=0, timings_us=timings
)
assert api.send_data.call_args == call(expected)
async def test_send_command_315_band(hass: HomeAssistant) -> None:
"""A 315 MHz command uses the 0xB4 type byte."""
api, entity_id = await _setup_rf_device(hass)
command = OOKCommand(frequency=_FREQ_315, timings=[400, -800])
await radio_frequency.async_send_command(hass, entity_id, command)
assert api.send_data.call_args.args[0][0] == _RF_315_TYPE_BYTE
async def test_send_command_rejects_out_of_band(hass: HomeAssistant) -> None:
"""An out-of-band frequency is rejected before send."""
api, entity_id = await _setup_rf_device(hass)
command = OOKCommand(frequency=868_000_000, timings=[400, -800])
with pytest.raises(HomeAssistantError):
await radio_frequency.async_send_command(hass, entity_id, command)
api.send_data.assert_not_called()
async def test_send_command_transmit_failure(hass: HomeAssistant) -> None:
"""A broadlink exception surfaces as HomeAssistantError."""
api, entity_id = await _setup_rf_device(hass)
api.send_data.side_effect = BroadlinkException("nope")
command = OOKCommand(frequency=_FREQ_433, timings=[400, -800])
with pytest.raises(HomeAssistantError):
await radio_frequency.async_send_command(hass, entity_id, command)
async def test_entity_availability(hass: HomeAssistant) -> None:
"""Entity becomes unavailable when the device stops responding."""
api, entity_id = await _setup_rf_device(hass)
assert hass.states.get(entity_id).state != STATE_UNAVAILABLE
api.check_sensors.side_effect = OSError("disconnected")
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=2))
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE