mirror of
https://github.com/Electric-Special/ha-core.git
synced 2026-03-21 06:05:26 +01:00
Add switch domain to VegeHub integration (#148436)
Co-authored-by: GhoweVege <85890024+GhoweVege@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,6 @@ from homeassistant.const import Platform
|
||||
|
||||
DOMAIN = "vegehub"
|
||||
NAME = "VegeHub"
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
PLATFORMS = [Platform.SENSOR, Platform.SWITCH]
|
||||
MANUFACTURER = "vegetronix"
|
||||
MODEL = "VegeHub"
|
||||
|
||||
@@ -39,6 +39,11 @@
|
||||
"battery_volts": {
|
||||
"name": "Battery voltage"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"name": "Actuator {index}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
80
homeassistant/components/vegehub/switch.py
Normal file
80
homeassistant/components/vegehub/switch.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""Switch configuration for VegeHub integration."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
SwitchDeviceClass,
|
||||
SwitchEntity,
|
||||
SwitchEntityDescription,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import VegeHubConfigEntry, VegeHubCoordinator
|
||||
from .entity import VegeHubEntity
|
||||
|
||||
SWITCH_TYPES: dict[str, SwitchEntityDescription] = {
|
||||
"switch": SwitchEntityDescription(
|
||||
key="switch",
|
||||
translation_key="switch",
|
||||
device_class=SwitchDeviceClass.SWITCH,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: VegeHubConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up VegeHub switches from a config entry."""
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
VegeHubSwitch(
|
||||
index=i,
|
||||
duration=600, # Default duration of 10 minutes
|
||||
coordinator=coordinator,
|
||||
description=SWITCH_TYPES["switch"],
|
||||
)
|
||||
for i in range(coordinator.vegehub.num_actuators)
|
||||
)
|
||||
|
||||
|
||||
class VegeHubSwitch(VegeHubEntity, SwitchEntity):
|
||||
"""Class for VegeHub Switches."""
|
||||
|
||||
_attr_device_class = SwitchDeviceClass.SWITCH
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
index: int,
|
||||
duration: int,
|
||||
coordinator: VegeHubCoordinator,
|
||||
description: SwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the switch."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
# Set unique ID for pulling data from the coordinator
|
||||
self.data_key = f"actuator_{index}"
|
||||
self._attr_unique_id = f"{self._mac_address}_{self.data_key}"
|
||||
self._attr_translation_placeholders = {"index": str(index + 1)}
|
||||
self._attr_available = False
|
||||
self.index = index
|
||||
self.duration = duration
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if the switch is on."""
|
||||
if self.coordinator.data is None or self._attr_unique_id is None:
|
||||
return False
|
||||
return self.coordinator.data.get(self.data_key, 0) > 0
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch on."""
|
||||
await self.coordinator.vegehub.set_actuator(1, self.index, self.duration)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch off."""
|
||||
await self.coordinator.vegehub.set_actuator(0, self.index, self.duration)
|
||||
@@ -28,7 +28,7 @@ HUB_DATA = {
|
||||
"first_boot": False,
|
||||
"page_updated": False,
|
||||
"error_message": 0,
|
||||
"num_channels": 2,
|
||||
"num_channels": 4,
|
||||
"num_actuators": 2,
|
||||
"version": "3.4.5",
|
||||
"agenda": 1,
|
||||
@@ -57,7 +57,7 @@ def mock_vegehub() -> Generator[Any, Any, Any]:
|
||||
mock_instance.unique_id = TEST_UNIQUE_ID
|
||||
mock_instance.url = f"http://{TEST_IP}"
|
||||
mock_instance.info = load_fixture("vegehub/info_hub.json")
|
||||
mock_instance.num_sensors = 2
|
||||
mock_instance.num_sensors = 4
|
||||
mock_instance.num_actuators = 2
|
||||
mock_instance.sw_version = "3.4.5"
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1.330000043',
|
||||
'state': '9.314800262',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities[sensor.vegehub_input_1-entry]
|
||||
@@ -158,3 +158,109 @@
|
||||
'state': '1.45599997',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities[sensor.vegehub_input_3-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.vegehub_input_3',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Input 3',
|
||||
'platform': 'vegehub',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'analog_sensor',
|
||||
'unique_id': 'A1B2C3D4E5F6_analog_2',
|
||||
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities[sensor.vegehub_input_3-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'voltage',
|
||||
'friendly_name': 'VegeHub Input 3',
|
||||
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.vegehub_input_3',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1.330000043',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities[sensor.vegehub_input_4-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.vegehub_input_4',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Input 4',
|
||||
'platform': 'vegehub',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'analog_sensor',
|
||||
'unique_id': 'A1B2C3D4E5F6_analog_3',
|
||||
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities[sensor.vegehub_input_4-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'voltage',
|
||||
'friendly_name': 'VegeHub Input 4',
|
||||
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.vegehub_input_4',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.075999998',
|
||||
})
|
||||
# ---
|
||||
|
||||
99
tests/components/vegehub/snapshots/test_switch.ambr
Normal file
99
tests/components/vegehub/snapshots/test_switch.ambr
Normal file
@@ -0,0 +1,99 @@
|
||||
# serializer version: 1
|
||||
# name: test_switch_entities[switch.vegehub_actuator_1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': None,
|
||||
'entity_id': 'switch.vegehub_actuator_1',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SwitchDeviceClass.SWITCH: 'switch'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Actuator 1',
|
||||
'platform': 'vegehub',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'switch',
|
||||
'unique_id': 'A1B2C3D4E5F6_actuator_0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_entities[switch.vegehub_actuator_1-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'switch',
|
||||
'friendly_name': 'VegeHub Actuator 1',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.vegehub_actuator_1',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_entities[switch.vegehub_actuator_2-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': None,
|
||||
'entity_id': 'switch.vegehub_actuator_2',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SwitchDeviceClass.SWITCH: 'switch'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Actuator 2',
|
||||
'platform': 'vegehub',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'switch',
|
||||
'unique_id': 'A1B2C3D4E5F6_actuator_1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_entities[switch.vegehub_actuator_2-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'switch',
|
||||
'friendly_name': 'VegeHub Actuator 2',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.vegehub_actuator_2',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
107
tests/components/vegehub/test_switch.py
Normal file
107
tests/components/vegehub/test_switch.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""Unit tests for the VegeHub integration's switch.py."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import init_integration
|
||||
from .conftest import TEST_SIMPLE_MAC, TEST_WEBHOOK_ID
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
UPDATE_DATA = {
|
||||
"api_key": "",
|
||||
"mac": TEST_SIMPLE_MAC,
|
||||
"error_code": 0,
|
||||
"sensors": [
|
||||
{"slot": 1, "samples": [{"v": 1.5, "t": "2025-01-15T16:51:23Z"}]},
|
||||
{"slot": 2, "samples": [{"v": 1.45599997, "t": "2025-01-15T16:51:23Z"}]},
|
||||
{"slot": 3, "samples": [{"v": 1.330000043, "t": "2025-01-15T16:51:23Z"}]},
|
||||
{"slot": 4, "samples": [{"v": 0.075999998, "t": "2025-01-15T16:51:23Z"}]},
|
||||
{"slot": 5, "samples": [{"v": 9.314800262, "t": "2025-01-15T16:51:23Z"}]},
|
||||
{"slot": 6, "samples": [{"v": 1, "t": "2025-01-15T16:51:23Z"}]},
|
||||
{"slot": 7, "samples": [{"v": 0, "t": "2025-01-15T16:51:23Z"}]},
|
||||
],
|
||||
"send_time": 1736959883,
|
||||
"wifi_str": -27,
|
||||
}
|
||||
|
||||
|
||||
async def test_switch_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
hass_client_no_auth: ClientSessionGenerator,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mocked_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test all entities."""
|
||||
|
||||
with patch("homeassistant.components.vegehub.PLATFORMS", [Platform.SWITCH]):
|
||||
await init_integration(hass, mocked_config_entry)
|
||||
|
||||
assert TEST_WEBHOOK_ID in hass.data["webhook"], "Webhook was not registered"
|
||||
|
||||
# Verify the webhook handler
|
||||
webhook_info = hass.data["webhook"][TEST_WEBHOOK_ID]
|
||||
assert webhook_info["handler"], "Webhook handler is not set"
|
||||
|
||||
client = await hass_client_no_auth()
|
||||
resp = await client.post(f"/api/webhook/{TEST_WEBHOOK_ID}", json=UPDATE_DATA)
|
||||
|
||||
# Send the same update again so that the coordinator modifies existing data
|
||||
# instead of creating new data.
|
||||
resp = await client.post(f"/api/webhook/{TEST_WEBHOOK_ID}", json=UPDATE_DATA)
|
||||
|
||||
# Wait for remaining tasks to complete.
|
||||
await hass.async_block_till_done()
|
||||
assert resp.status == 200, f"Unexpected status code: {resp.status}"
|
||||
await snapshot_platform(
|
||||
hass, entity_registry, snapshot, mocked_config_entry.entry_id
|
||||
)
|
||||
|
||||
|
||||
async def test_switch_turn_on_off(
|
||||
hass: HomeAssistant,
|
||||
hass_client_no_auth: ClientSessionGenerator,
|
||||
mocked_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test switch turn_on and turn_off methods."""
|
||||
with patch("homeassistant.components.vegehub.PLATFORMS", [Platform.SWITCH]):
|
||||
await init_integration(hass, mocked_config_entry)
|
||||
|
||||
# Send webhook data to initialize switches
|
||||
client = await hass_client_no_auth()
|
||||
resp = await client.post(f"/api/webhook/{TEST_WEBHOOK_ID}", json=UPDATE_DATA)
|
||||
await hass.async_block_till_done()
|
||||
assert resp.status == 200
|
||||
|
||||
# Get switch entity IDs
|
||||
switch_entity_ids = hass.states.async_entity_ids("switch")
|
||||
assert len(switch_entity_ids) > 0, "No switch entities found"
|
||||
|
||||
# Test turn_on method
|
||||
with patch(
|
||||
"homeassistant.components.vegehub.VegeHub.set_actuator"
|
||||
) as mock_set_actuator:
|
||||
await hass.services.async_call(
|
||||
"switch", "turn_on", {"entity_id": switch_entity_ids[0]}, blocking=True
|
||||
)
|
||||
mock_set_actuator.assert_called_once_with(
|
||||
1, 0, 600
|
||||
) # on, index 0, duration 600
|
||||
|
||||
# Test turn_off method
|
||||
with patch(
|
||||
"homeassistant.components.vegehub.VegeHub.set_actuator"
|
||||
) as mock_set_actuator:
|
||||
await hass.services.async_call(
|
||||
"switch", "turn_off", {"entity_id": switch_entity_ids[0]}, blocking=True
|
||||
)
|
||||
mock_set_actuator.assert_called_once_with(
|
||||
0, 0, 600
|
||||
) # off, index 0, duration 600
|
||||
Reference in New Issue
Block a user