Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,26 @@ Works with any remote integration that stores learnt commands using HA's `helper

## Configuration

### Initial setup

1. [![Add integration](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=remote_buttons)
2. Select the remote entities you want to watch — only remotes that support `learn_command` are shown.
3. Button entities are created automatically for all learnt commands on the selected remotes.

#### Setup parameters

| Parameter | Description |
|---|---|
| **Remote entities** | One or more remote entities to watch. Only remotes on supported platforms (Broadlink, Tuya Local) that advertise the `learn_command` feature are listed. |

### Options

You can update the selection at any time via **Settings > Devices & Services > Remote Buttons > Configure**.

| Parameter | Description |
|---|---|
| **Remote entities** | Add or remove remotes from the watch list. Removing a remote deletes all its button and number entities. Adding a remote triggers an immediate scan for learnt commands. |

## Removal

1. Go to **Settings > Devices & Services > Remote Buttons**.
Expand Down
2 changes: 2 additions & 0 deletions custom_components/remote_buttons/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

from .const import DOMAIN

PARALLEL_UPDATES = 0

ATTR_COMMAND = "command"
ATTR_DELAY_SECS = "delay_secs"
ATTR_DEVICE = "device"
Expand Down
2 changes: 2 additions & 0 deletions custom_components/remote_buttons/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

from .const import DEFAULT_IR_DELAY, DEFAULT_IR_REPEATS, DOMAIN

PARALLEL_UPDATES = 0


async def async_setup_entry(
hass: HomeAssistant, entry, async_add_entities: AddEntitiesCallback
Expand Down
36 changes: 36 additions & 0 deletions tests/test_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock, patch

from homeassistant.components.number import NumberMode
from homeassistant.const import EntityCategory, UnitOfTime

Expand Down Expand Up @@ -78,3 +80,37 @@ def test_number_attributes() -> None:
assert repeats.native_step == 1.0
assert repeats.native_unit_of_measurement is None
assert repeats.translation_key == "ir_repeat"


async def test_number_set_native_value() -> None:
"""Test that setting a value updates the attribute and writes state."""
delay, _ = create_ir_number_pair(
remote_entity_id="remote.living_room",
remote_device_id="abc123",
remote_domain="broadlink",
subdevice="TV",
)
delay.async_write_ha_state = MagicMock()

await delay.async_set_native_value(2.5)
assert delay.native_value == 2.5
delay.async_write_ha_state.assert_called_once()


async def test_number_restores_last_state() -> None:
"""Test that the number restores its value from the last known state."""
delay, _ = create_ir_number_pair(
remote_entity_id="remote.living_room",
remote_device_id="abc123",
remote_domain="broadlink",
subdevice="TV",
)

last_state = MagicMock()
last_state.state = "3.0"
delay.async_get_last_state = AsyncMock(return_value=last_state)

with patch.object(type(delay).__mro__[2], "async_added_to_hass", new_callable=AsyncMock):
await delay.async_added_to_hass()

assert delay.native_value == 3.0
26 changes: 26 additions & 0 deletions tests/test_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,29 @@ async def test_tuya_local_reader_returns_commands(hass: HomeAssistant) -> None:

assert "Fan" in result
assert result["Fan"]["speed1"] == "code1"


async def test_tuya_local_reader_returns_empty_on_missing(hass: HomeAssistant) -> None:
"""Test TuyaLocalStorageReader returns empty dict when no storage exists."""
with patch(
"custom_components.remote_buttons.storage.Store.async_load",
new_callable=AsyncMock,
return_value=None,
):
reader = TuyaLocalStorageReader()
result = await reader.async_read_commands(hass, "missing_uid")

assert result == {}


async def test_tuya_local_reader_returns_empty_on_error(hass: HomeAssistant) -> None:
"""Test TuyaLocalStorageReader returns empty dict on storage error."""
with patch(
"custom_components.remote_buttons.storage.Store.async_load",
new_callable=AsyncMock,
side_effect=OSError("disk error"),
):
reader = TuyaLocalStorageReader()
result = await reader.async_read_commands(hass, "broken_uid")

assert result == {}