-
-
Notifications
You must be signed in to change notification settings - Fork 37.4k
Add radio_frequency platform to Broadlink #169107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| """Radio Frequency platform for Broadlink.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
|
|
||
| from broadlink.exceptions import BroadlinkException | ||
| from rf_protocols import ModulationType, RadioFrequencyCommand | ||
|
|
||
| from homeassistant.components.radio_frequency import RadioFrequencyTransmitterEntity | ||
| from homeassistant.config_entries import ConfigEntry | ||
| from homeassistant.core import HomeAssistant | ||
| from homeassistant.exceptions import HomeAssistantError | ||
| from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback | ||
|
|
||
| from .const import DOMAIN | ||
| from .device import BroadlinkDevice | ||
| from .entity import BroadlinkEntity | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| PARALLEL_UPDATES = 0 | ||
|
|
||
| _TICK_US = 32.84 | ||
|
|
||
| _RF_433_TYPE_BYTE = 0xB2 | ||
| _RF_315_TYPE_BYTE = 0xB4 | ||
|
|
||
| _RF_433_RANGE = (433_050_000, 434_790_000) | ||
| _RF_315_RANGE = (314_950_000, 315_250_000) | ||
|
|
||
| SUPPORTED_FREQUENCY_RANGES: list[tuple[int, int]] = [_RF_433_RANGE, _RF_315_RANGE] | ||
|
|
||
|
|
||
| def _type_byte_for_frequency(frequency: int) -> int: | ||
| """Return the Broadlink RF type byte for a given carrier frequency.""" | ||
| if _RF_433_RANGE[0] <= frequency <= _RF_433_RANGE[1]: | ||
| return _RF_433_TYPE_BYTE | ||
| if _RF_315_RANGE[0] <= frequency <= _RF_315_RANGE[1]: | ||
| return _RF_315_TYPE_BYTE | ||
| raise HomeAssistantError( | ||
| translation_domain=DOMAIN, | ||
| translation_key="frequency_not_supported", | ||
| translation_placeholders={"frequency": str(frequency)}, | ||
| ) | ||
|
|
||
|
|
||
| def encode_rf_packet( | ||
| *, | ||
| type_byte: int, | ||
| repeat_count: int, | ||
| timings_us: list[int], | ||
| ) -> bytes: | ||
| """Encode raw OOK timings as a Broadlink RF pulse-length packet. | ||
|
|
||
| The layout is:: | ||
|
|
||
| byte 0 type byte (0xB2 for 433 MHz, 0xB4 for 315 MHz) | ||
| byte 1 repeat count (additional transmissions after the first) | ||
| bytes 2..3 payload length (little-endian), counted from byte 4 | ||
| bytes 4..N-1 pulses: 1 byte when ticks < 256, otherwise | ||
| 0x00 followed by a 2-byte big-endian tick count | ||
|
|
||
| Each pulse is expressed as multiples of 32.84 µs ticks, which is the | ||
| timing resolution of the Broadlink RF front-end. | ||
| """ | ||
| buf = bytearray([type_byte, repeat_count & 0xFF, 0, 0]) | ||
| for duration in timings_us: | ||
| ticks = round(abs(duration) / _TICK_US) | ||
| div, mod = divmod(ticks, 256) | ||
| if div: | ||
| buf.append(0x00) | ||
| buf.append(div) | ||
| buf.append(mod) | ||
| payload_len = len(buf) - 4 | ||
|
Comment on lines
+68
to
+75
|
||
| buf[2] = payload_len & 0xFF | ||
| buf[3] = (payload_len >> 8) & 0xFF | ||
| return bytes(buf) | ||
|
|
||
|
|
||
| async def async_setup_entry( | ||
| hass: HomeAssistant, | ||
| config_entry: ConfigEntry, | ||
| async_add_entities: AddConfigEntryEntitiesCallback, | ||
| ) -> None: | ||
| """Set up a Broadlink radio frequency transmitter.""" | ||
| # Uses legacy hass.data[DOMAIN] pattern | ||
| # pylint: disable-next=hass-use-runtime-data | ||
| device: BroadlinkDevice = hass.data[DOMAIN].devices[config_entry.entry_id] | ||
| async_add_entities([BroadlinkRadioFrequency(device)]) | ||
|
|
||
|
|
||
| class BroadlinkRadioFrequency(BroadlinkEntity, RadioFrequencyTransmitterEntity): | ||
| """Representation of a Broadlink RF transmitter.""" | ||
|
|
||
| _attr_has_entity_name = True | ||
| _attr_name = None | ||
|
|
||
| def __init__(self, device: BroadlinkDevice) -> None: | ||
| """Initialize the entity.""" | ||
| super().__init__(device) | ||
| self._attr_unique_id = device.unique_id | ||
|
|
||
| @property | ||
| def supported_frequency_ranges(self) -> list[tuple[int, int]]: | ||
| """Return the Broadlink-supported narrow RF bands.""" | ||
| return SUPPORTED_FREQUENCY_RANGES | ||
|
|
||
| async def async_send_command(self, command: RadioFrequencyCommand) -> None: | ||
| """Encode an OOK command and transmit it via the Broadlink device.""" | ||
| if command.modulation is not ModulationType.OOK: | ||
| raise HomeAssistantError( | ||
| translation_domain=DOMAIN, | ||
| translation_key="modulation_not_supported", | ||
| translation_placeholders={"modulation": str(command.modulation)}, | ||
| ) | ||
|
|
||
| type_byte = _type_byte_for_frequency(command.frequency) | ||
| packet = encode_rf_packet( | ||
| type_byte=type_byte, | ||
| repeat_count=command.repeat_count, | ||
| timings_us=command.get_raw_timings(), | ||
| ) | ||
| _LOGGER.debug( | ||
| "Transmitting RF packet: %d bytes on %d Hz (repeat=%d)", | ||
| len(packet), | ||
| command.frequency, | ||
| command.repeat_count, | ||
| ) | ||
|
|
||
| device = self._device | ||
| try: | ||
| await device.async_request(device.api.send_data, packet) | ||
| except (BroadlinkException, OSError) as err: | ||
| raise HomeAssistantError( | ||
| translation_domain=DOMAIN, | ||
| translation_key="transmit_failed", | ||
| translation_placeholders={"error": str(err)}, | ||
| ) from err | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -48,6 +48,17 @@ | |||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| }, | ||||||||||||||
| "exceptions": { | ||||||||||||||
| "frequency_not_supported": { | ||||||||||||||
| "message": "Broadlink devices cannot transmit on {frequency} Hz" | ||||||||||||||
| }, | ||||||||||||||
| "modulation_not_supported": { | ||||||||||||||
| "message": "Broadlink devices only support OOK modulation, got {modulation}" | ||||||||||||||
| }, | ||||||||||||||
|
Comment on lines
+52
to
+57
|
||||||||||||||
| "frequency_not_supported": { | |
| "message": "Broadlink devices cannot transmit on {frequency} Hz" | |
| }, | |
| "modulation_not_supported": { | |
| "message": "Broadlink devices only support OOK modulation, got {modulation}" | |
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add explicit bounds checks for repeat_count (0–255) and tick counts (<= 0xFFFF) instead of truncating/overflowing into invalid bytes, and raise a clear error when inputs exceed Broadlink’s wire format limits.