diff --git a/homeassistant/components/elgato/button.py b/homeassistant/components/elgato/button.py index b79a521a427..9f68074bb28 100644 --- a/homeassistant/components/elgato/button.py +++ b/homeassistant/components/elgato/button.py @@ -7,7 +7,11 @@ from typing import Any from elgato import Elgato, ElgatoError -from homeassistant.components.button import ButtonEntity, ButtonEntityDescription +from homeassistant.components.button import ( + ButtonDeviceClass, + ButtonEntity, + ButtonEntityDescription, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant @@ -41,6 +45,13 @@ BUTTONS = [ entity_category=EntityCategory.CONFIG, press_fn=lambda client: client.identify(), ), + ElgatoButtonEntityDescription( + key="restart", + name="Restart", + device_class=ButtonDeviceClass.RESTART, + entity_category=EntityCategory.CONFIG, + press_fn=lambda client: client.restart(), + ), ] diff --git a/homeassistant/components/elgato/sensor.py b/homeassistant/components/elgato/sensor.py index cee25e15fe4..2692cf10850 100644 --- a/homeassistant/components/elgato/sensor.py +++ b/homeassistant/components/elgato/sensor.py @@ -11,7 +11,13 @@ from homeassistant.components.sensor import ( SensorStateClass, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PERCENTAGE, EntityCategory +from homeassistant.const import ( + PERCENTAGE, + EntityCategory, + UnitOfElectricCurrent, + UnitOfElectricPotential, + UnitOfPower, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -48,6 +54,57 @@ SENSORS = [ has_fn=lambda x: x.battery is not None, value_fn=lambda x: x.battery.level if x.battery else None, ), + ElgatoSensorEntityDescription( + key="voltage", + name="Battery voltage", + entity_registry_enabled_default=False, + device_class=SensorDeviceClass.VOLTAGE, + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT, + state_class=SensorStateClass.MEASUREMENT, + suggested_unit_of_measurement=UnitOfElectricPotential.VOLT, + suggested_display_precision=2, + has_fn=lambda x: x.battery is not None, + value_fn=lambda x: x.battery.voltage if x.battery else None, + ), + ElgatoSensorEntityDescription( + key="input_charge_current", + name="Charging current", + entity_registry_enabled_default=False, + device_class=SensorDeviceClass.CURRENT, + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=UnitOfElectricCurrent.MILLIAMPERE, + state_class=SensorStateClass.MEASUREMENT, + suggested_unit_of_measurement=UnitOfElectricCurrent.AMPERE, + suggested_display_precision=2, + has_fn=lambda x: x.battery is not None, + value_fn=lambda x: x.battery.input_charge_current if x.battery else None, + ), + ElgatoSensorEntityDescription( + key="charge_power", + name="Charging power", + entity_registry_enabled_default=False, + device_class=SensorDeviceClass.POWER, + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=UnitOfPower.WATT, + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=0, + has_fn=lambda x: x.battery is not None, + value_fn=lambda x: x.battery.charge_power if x.battery else None, + ), + ElgatoSensorEntityDescription( + key="input_charge_voltage", + name="Charging voltage", + entity_registry_enabled_default=False, + device_class=SensorDeviceClass.VOLTAGE, + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT, + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=2, + suggested_unit_of_measurement=UnitOfElectricPotential.VOLT, + has_fn=lambda x: x.battery is not None, + value_fn=lambda x: x.battery.input_charge_voltage if x.battery else None, + ), ] diff --git a/homeassistant/components/elgato/switch.py b/homeassistant/components/elgato/switch.py index a7bcde022e7..00159099718 100644 --- a/homeassistant/components/elgato/switch.py +++ b/homeassistant/components/elgato/switch.py @@ -46,6 +46,17 @@ SWITCHES = [ is_on_fn=lambda x: x.settings.battery.bypass if x.settings.battery else None, set_fn=lambda client, on: client.battery_bypass(on=on), ), + ElgatoSwitchEntityDescription( + key="energy_saving", + name="Energy saving", + icon="mdi:leaf", + entity_category=EntityCategory.CONFIG, + has_fn=lambda x: x.battery is not None, + is_on_fn=lambda x: ( + x.settings.battery.energy_saving.enabled if x.settings.battery else None + ), + set_fn=lambda client, on: client.energy_saving(on=on), + ), ] diff --git a/tests/components/elgato/test_button.py b/tests/components/elgato/test_button.py index a73319bec70..6c85fbf31e9 100644 --- a/tests/components/elgato/test_button.py +++ b/tests/components/elgato/test_button.py @@ -11,14 +11,20 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er +pytestmark = [ + pytest.mark.parametrize("device_fixtures", ["key-light-mini"]), + pytest.mark.usefixtures("device_fixtures", "init_integration"), + pytest.mark.freeze_time("2021-11-13 11:48:00"), +] -@pytest.mark.freeze_time("2021-11-13 11:48:00") -@pytest.mark.usefixtures("init_integration") -async def test_button_identify(hass: HomeAssistant, mock_elgato: MagicMock) -> None: + +async def test_button_identify( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + mock_elgato: MagicMock, +) -> None: """Test the Elgato identify button.""" - device_registry = dr.async_get(hass) - entity_registry = er.async_get(hass) - state = hass.states.get("button.frenck_identify") assert state assert state.attributes.get(ATTR_ICON) == "mdi:help" @@ -26,7 +32,7 @@ async def test_button_identify(hass: HomeAssistant, mock_elgato: MagicMock) -> N entry = entity_registry.async_get("button.frenck_identify") assert entry - assert entry.unique_id == "CN11A1A00001_identify" + assert entry.unique_id == "GW24L1A02987_identify" assert entry.entity_category == EntityCategory.CONFIG assert entry.device_id @@ -37,12 +43,12 @@ async def test_button_identify(hass: HomeAssistant, mock_elgato: MagicMock) -> N (dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff") } assert device_entry.entry_type is None - assert device_entry.identifiers == {(DOMAIN, "CN11A1A00001")} + assert device_entry.identifiers == {(DOMAIN, "GW24L1A02987")} assert device_entry.manufacturer == "Elgato" - assert device_entry.model == "Elgato Key Light" + assert device_entry.model == "Elgato Key Light Mini" assert device_entry.name == "Frenck" - assert device_entry.sw_version == "1.0.3 (192)" - assert device_entry.hw_version == "53" + assert device_entry.sw_version == "1.0.4 (229)" + assert device_entry.hw_version == "202" await hass.services.async_call( BUTTON_DOMAIN, @@ -59,11 +65,40 @@ async def test_button_identify(hass: HomeAssistant, mock_elgato: MagicMock) -> N assert state.state == "2021-11-13T11:48:00+00:00" -@pytest.mark.usefixtures("init_integration") -async def test_button_identify_error( - hass: HomeAssistant, mock_elgato: MagicMock +async def test_button_restart( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + mock_elgato: MagicMock, ) -> None: - """Test an error occurs with the Elgato identify button.""" + """Test the Elgato restart button.""" + state = hass.states.get("button.frenck_restart") + assert state + assert state.state == STATE_UNKNOWN + assert not state.attributes.get(ATTR_ICON) + + entry = entity_registry.async_get("button.frenck_restart") + assert entry + assert entry.unique_id == "GW24L1A02987_restart" + assert entry.entity_category == EntityCategory.CONFIG + + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: "button.frenck_restart"}, + blocking=True, + ) + + assert len(mock_elgato.restart.mock_calls) == 1 + mock_elgato.restart.assert_called_with() + + state = hass.states.get("button.frenck_restart") + assert state + assert state.state == "2021-11-13T11:48:00+00:00" + + +async def test_button_error(hass: HomeAssistant, mock_elgato: MagicMock) -> None: + """Test an error occurs with the Elgato buttons.""" mock_elgato.identify.side_effect = ElgatoError with pytest.raises( diff --git a/tests/components/elgato/test_sensor.py b/tests/components/elgato/test_sensor.py index 7cc55f58656..1d50177c11a 100644 --- a/tests/components/elgato/test_sensor.py +++ b/tests/components/elgato/test_sensor.py @@ -15,23 +15,28 @@ from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, PERCENTAGE, EntityCategory, + UnitOfElectricCurrent, + UnitOfElectricPotential, + UnitOfPower, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er +pytestmark = [ + pytest.mark.parametrize("device_fixtures", ["key-light-mini"]), + pytest.mark.usefixtures("device_fixtures", "init_integration", "mock_elgato"), +] -@pytest.mark.parametrize("device_fixtures", ["key-light-mini"]) -@pytest.mark.usefixtures( - "device_fixtures", - "entity_registry_enabled_by_default", - "init_integration", - "mock_elgato", -) -async def test_battery_sensor(hass: HomeAssistant) -> None: - """Test the Elgato battery sensor.""" - device_registry = dr.async_get(hass) - entity_registry = er.async_get(hass) +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_sensors( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, +) -> None: + """Test the Elgato sensors.""" + + # Battery sensor state = hass.states.get("sensor.frenck_battery") assert state assert state.state == "78.57" @@ -47,6 +52,92 @@ async def test_battery_sensor(hass: HomeAssistant) -> None: assert entry.entity_category == EntityCategory.DIAGNOSTIC assert entry.options == {"sensor": {"suggested_display_precision": 0}} + # Battery voltage sensor + state = hass.states.get("sensor.frenck_battery_voltage") + assert state + assert state.state == "3.86" + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.VOLTAGE + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frenck Battery voltage" + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfElectricPotential.VOLT + ) + assert not state.attributes.get(ATTR_ICON) + + entry = entity_registry.async_get("sensor.frenck_battery_voltage") + assert entry + assert entry.unique_id == "GW24L1A02987_voltage" + assert entry.entity_category == EntityCategory.DIAGNOSTIC + assert entry.options == { + "sensor": {"suggested_display_precision": 2}, + "sensor.private": { + "suggested_unit_of_measurement": UnitOfElectricPotential.VOLT + }, + } + + # Charging current sensor + state = hass.states.get("sensor.frenck_charging_current") + assert state + assert state.state == "3.008" + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.CURRENT + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frenck Charging current" + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfElectricCurrent.AMPERE + ) + assert not state.attributes.get(ATTR_ICON) + + entry = entity_registry.async_get("sensor.frenck_charging_current") + assert entry + assert entry.unique_id == "GW24L1A02987_input_charge_current" + assert entry.entity_category == EntityCategory.DIAGNOSTIC + assert entry.options == { + "sensor": {"suggested_display_precision": 2}, + "sensor.private": { + "suggested_unit_of_measurement": UnitOfElectricCurrent.AMPERE + }, + } + + # Charging power sensor + state = hass.states.get("sensor.frenck_charging_power") + assert state + assert state.state == "12.66" + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.POWER + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frenck Charging power" + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfPower.WATT + assert not state.attributes.get(ATTR_ICON) + + entry = entity_registry.async_get("sensor.frenck_charging_power") + assert entry + assert entry.unique_id == "GW24L1A02987_charge_power" + assert entry.entity_category == EntityCategory.DIAGNOSTIC + assert entry.options == {"sensor": {"suggested_display_precision": 0}} + + # Charging voltage sensor + state = hass.states.get("sensor.frenck_charging_voltage") + assert state + assert state.state == "4.208" + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.VOLTAGE + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frenck Charging voltage" + assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfElectricPotential.VOLT + ) + assert not state.attributes.get(ATTR_ICON) + + entry = entity_registry.async_get("sensor.frenck_charging_voltage") + assert entry + assert entry.unique_id == "GW24L1A02987_input_charge_voltage" + assert entry.entity_category == EntityCategory.DIAGNOSTIC + assert entry.options == { + "sensor": {"suggested_display_precision": 2}, + "sensor.private": { + "suggested_unit_of_measurement": UnitOfElectricPotential.VOLT + }, + } + + # Check if the entity is well registered in the device registry assert entry.device_id device_entry = device_registry.async_get(entry.device_id) assert device_entry @@ -61,3 +152,25 @@ async def test_battery_sensor(hass: HomeAssistant) -> None: assert device_entry.name == "Frenck" assert device_entry.sw_version == "1.0.4 (229)" assert device_entry.hw_version == "202" + + +@pytest.mark.parametrize( + "entity_id", + [ + "sensor.frenck_battery_voltage", + "sensor.frenck_charging_current", + "sensor.frenck_charging_power", + "sensor.frenck_charging_voltage", + ], +) +async def test_disabled_by_default_sensors( + hass: HomeAssistant, entity_registry: er.EntityRegistry, entity_id: str +) -> None: + """Test the disabled by default Elgato sensors.""" + state = hass.states.get(entity_id) + assert state is None + + entry = entity_registry.async_get(entity_id) + assert entry + assert entry.disabled + assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION diff --git a/tests/components/elgato/test_switch.py b/tests/components/elgato/test_switch.py index 98241c417c4..dab891065cf 100644 --- a/tests/components/elgato/test_switch.py +++ b/tests/components/elgato/test_switch.py @@ -22,18 +22,19 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er +pytestmark = [ + pytest.mark.parametrize("device_fixtures", ["key-light-mini"]), + pytest.mark.usefixtures("device_fixtures", "init_integration"), +] -@pytest.mark.parametrize("device_fixtures", ["key-light-mini"]) -@pytest.mark.usefixtures( - "device_fixtures", - "entity_registry_enabled_by_default", - "init_integration", -) -async def test_battery_bypass(hass: HomeAssistant, mock_elgato: MagicMock) -> None: + +async def test_battery_bypass( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + mock_elgato: MagicMock, +) -> None: """Test the Elgato battery bypass switch.""" - device_registry = dr.async_get(hass) - entity_registry = er.async_get(hass) - state = hass.states.get("switch.frenck_studio_mode") assert state assert state.state == STATE_OFF @@ -108,3 +109,42 @@ async def test_battery_bypass(hass: HomeAssistant, mock_elgato: MagicMock) -> No await hass.async_block_till_done() assert len(mock_elgato.battery_bypass.mock_calls) == 4 + + +async def test_battery_energy_saving( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_elgato: MagicMock, +) -> None: + """Test the Elgato energy saving switch.""" + state = hass.states.get("switch.frenck_energy_saving") + assert state + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frenck Energy saving" + assert state.attributes.get(ATTR_ICON) == "mdi:leaf" + assert not state.attributes.get(ATTR_DEVICE_CLASS) + + entry = entity_registry.async_get("switch.frenck_energy_saving") + assert entry + assert entry.unique_id == "GW24L1A02987_energy_saving" + assert entry.entity_category == EntityCategory.CONFIG + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.frenck_energy_saving"}, + blocking=True, + ) + + assert len(mock_elgato.energy_saving.mock_calls) == 1 + mock_elgato.energy_saving.assert_called_once_with(on=True) + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.frenck_energy_saving"}, + blocking=True, + ) + + assert len(mock_elgato.energy_saving.mock_calls) == 2 + mock_elgato.energy_saving.assert_called_with(on=False)