diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index e0196fbea5e..2737270ac76 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -82,7 +82,7 @@ SENSORS: Final = { key="device|deviceTemp", name="Device temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, - value=lambda value: round(value, 1), + suggested_display_precision=1, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, @@ -92,7 +92,6 @@ SENSORS: Final = { key="emeter|current", name="Current", native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, - value=lambda value: value, device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, ), @@ -100,7 +99,7 @@ SENSORS: Final = { key="light|power", name="Power", native_unit_of_measurement=UnitOfPower.WATT, - value=lambda value: round(value, 1), + suggested_display_precision=1, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, @@ -109,7 +108,7 @@ SENSORS: Final = { key="device|power", name="Power", native_unit_of_measurement=UnitOfPower.WATT, - value=lambda value: round(value, 1), + suggested_display_precision=1, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, ), @@ -117,7 +116,7 @@ SENSORS: Final = { key="emeter|power", name="Power", native_unit_of_measurement=UnitOfPower.WATT, - value=lambda value: round(value, 1), + suggested_display_precision=1, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, ), @@ -125,7 +124,7 @@ SENSORS: Final = { key="device|voltage", name="Voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, - value=lambda value: round(value, 1), + suggested_display_precision=1, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, @@ -134,15 +133,14 @@ SENSORS: Final = { key="emeter|voltage", name="Voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, - value=lambda value: round(value, 1), + suggested_display_precision=1, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, ), ("emeter", "powerFactor"): BlockSensorDescription( key="emeter|powerFactor", name="Power factor", - native_unit_of_measurement=PERCENTAGE, - value=lambda value: round(value * 100, 1), + suggested_display_precision=2, device_class=SensorDeviceClass.POWER_FACTOR, state_class=SensorStateClass.MEASUREMENT, ), @@ -150,7 +148,7 @@ SENSORS: Final = { key="relay|power", name="Power", native_unit_of_measurement=UnitOfPower.WATT, - value=lambda value: round(value, 1), + suggested_display_precision=1, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, ), @@ -158,23 +156,26 @@ SENSORS: Final = { key="roller|rollerPower", name="Power", native_unit_of_measurement=UnitOfPower.WATT, - value=lambda value: round(value, 1), + suggested_display_precision=1, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, ), ("device", "energy"): BlockSensorDescription( key="device|energy", name="Energy", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda value: round(value / 60 / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda value: value / 60, + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), ("emeter", "energy"): BlockSensorDescription( key="emeter|energy", name="Energy", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda value: round(value / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, available=lambda block: cast(int, block.energy) != -1, @@ -182,8 +183,9 @@ SENSORS: Final = { ("emeter", "energyReturned"): BlockSensorDescription( key="emeter|energyReturned", name="Energy returned", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda value: round(value / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, available=lambda block: cast(int, block.energyReturned) != -1, @@ -191,8 +193,10 @@ SENSORS: Final = { ("light", "energy"): BlockSensorDescription( key="light|energy", name="Energy", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda value: round(value / 60 / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda value: value / 60, + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, entity_registry_enabled_default=False, @@ -200,16 +204,20 @@ SENSORS: Final = { ("relay", "energy"): BlockSensorDescription( key="relay|energy", name="Energy", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda value: round(value / 60 / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda value: value / 60, + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), ("roller", "rollerEnergy"): BlockSensorDescription( key="roller|rollerEnergy", name="Energy", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda value: round(value / 60 / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda value: value / 60, + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), @@ -224,7 +232,7 @@ SENSORS: Final = { key="sensor|temp", name="Temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, - value=lambda value: round(value, 1), + suggested_display_precision=1, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, @@ -233,7 +241,7 @@ SENSORS: Final = { key="sensor|extTemp", name="Temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, - value=lambda value: round(value, 1), + suggested_display_precision=1, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, available=lambda block: cast(int, block.extTemp) != 999 @@ -243,7 +251,7 @@ SENSORS: Final = { key="sensor|humidity", name="Humidity", native_unit_of_measurement=PERCENTAGE, - value=lambda value: round(value, 1), + suggested_display_precision=1, device_class=SensorDeviceClass.HUMIDITY, state_class=SensorStateClass.MEASUREMENT, available=lambda block: cast(int, block.humidity) != 999 @@ -269,7 +277,8 @@ SENSORS: Final = { name="Lamp life", native_unit_of_measurement=PERCENTAGE, icon="mdi:progress-wrench", - value=lambda value: round(100 - (value / 3600 / SHAIR_MAX_WORK_HOURS), 1), + value=lambda value: 100 - (value / 3600 / SHAIR_MAX_WORK_HOURS), + suggested_display_precision=1, extra_state_attributes=lambda block: { "Operational hours": round(cast(int, block.totalWorkTime) / 3600, 1) }, @@ -279,7 +288,7 @@ SENSORS: Final = { key="adc|adc", name="ADC", native_unit_of_measurement=UnitOfElectricPotential.VOLT, - value=lambda value: round(value, 2), + suggested_display_precision=2, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, ), @@ -397,7 +406,8 @@ RPC_SENSORS: Final = { sub_key="voltage", name="Voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, - value=lambda status, _: None if status is None else round(float(status), 1), + value=lambda status, _: None if status is None else float(status), + suggested_display_precision=1, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, @@ -460,8 +470,10 @@ RPC_SENSORS: Final = { key="switch", sub_key="aenergy", name="Energy", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda status, _: round(status["total"] / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda status, _: status["total"], + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), @@ -469,8 +481,10 @@ RPC_SENSORS: Final = { key="emdata", sub_key="total_act", name="Total active energy", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda status, _: round(float(status) / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda status, _: float(status), + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), @@ -478,8 +492,10 @@ RPC_SENSORS: Final = { key="emdata", sub_key="a_total_act_energy", name="Phase A total active energy", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda status, _: round(float(status) / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda status, _: float(status), + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, entity_registry_enabled_default=False, @@ -488,8 +504,10 @@ RPC_SENSORS: Final = { key="emdata", sub_key="b_total_act_energy", name="Phase B total active energy", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda status, _: round(float(status) / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda status, _: float(status), + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, entity_registry_enabled_default=False, @@ -498,8 +516,10 @@ RPC_SENSORS: Final = { key="emdata", sub_key="c_total_act_energy", name="Phase C total active energy", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda status, _: round(float(status) / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda status, _: float(status), + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, entity_registry_enabled_default=False, @@ -508,8 +528,10 @@ RPC_SENSORS: Final = { key="emdata", sub_key="total_act_ret", name="Total active returned energy", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda status, _: round(float(status) / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda status, _: float(status), + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), @@ -517,8 +539,10 @@ RPC_SENSORS: Final = { key="emdata", sub_key="a_total_act_ret_energy", name="Phase A total active returned energy", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda status, _: round(float(status) / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda status, _: float(status), + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, entity_registry_enabled_default=False, @@ -527,8 +551,10 @@ RPC_SENSORS: Final = { key="emdata", sub_key="b_total_act_ret_energy", name="Phase B total active returned energy", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda status, _: round(float(status) / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda status, _: float(status), + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, entity_registry_enabled_default=False, @@ -537,8 +563,10 @@ RPC_SENSORS: Final = { key="emdata", sub_key="c_total_act_ret_energy", name="Phase C total active returned energy", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - value=lambda status, _: round(float(status) / 1000, 2), + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda status, _: float(status), + suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, entity_registry_enabled_default=False, @@ -548,7 +576,8 @@ RPC_SENSORS: Final = { sub_key="temperature", name="Device temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, - value=lambda status, _: round(status["tC"], 1), + value=lambda status, _: status["tC"], + suggested_display_precision=1, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, @@ -560,7 +589,7 @@ RPC_SENSORS: Final = { sub_key="tC", name="Temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, - value=lambda status, _: round(status, 1), + suggested_display_precision=1, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, ), @@ -590,7 +619,7 @@ RPC_SENSORS: Final = { sub_key="rh", name="Humidity", native_unit_of_measurement=PERCENTAGE, - value=lambda status, _: round(status, 1), + suggested_display_precision=1, device_class=SensorDeviceClass.HUMIDITY, state_class=SensorStateClass.MEASUREMENT, ), @@ -609,7 +638,8 @@ RPC_SENSORS: Final = { sub_key="voltage", name="Voltmeter", native_unit_of_measurement=UnitOfElectricPotential.VOLT, - value=lambda status, _: round(float(status), 2), + value=lambda status, _: float(status), + suggested_display_precision=2, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, available=lambda status: status is not None, diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index c8cb1b4d087..8fa5b0eaeac 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -70,11 +70,13 @@ MOCK_BLOCKS = [ "inputEventCnt": 2, "overpower": 0, "power": 53.4, + "energy": 1234567.89, }, channel="0", type="relay", overpower=0, power=53.4, + energy=1234567.89, description="relay_0", set_state=AsyncMock(side_effect=lambda turn: {"ison": turn == "on"}), ), @@ -120,6 +122,15 @@ MOCK_BLOCKS = [ description="device_0", type="device", ), + Mock( + sensor_ids={"powerFactor": 0.98}, + channel="0", + powerFactor=0.98, + targetTemp=4, + temp=22.1, + description="emeter_0", + type="emeter", + ), ] MOCK_CONFIG = { @@ -185,7 +196,7 @@ MOCK_STATUS_RPC = { "stable": {"version": "some_beta_version"}, } }, - "voltmeter": {"voltage": 4.3}, + "voltmeter": {"voltage": 4.321}, "wifi": {"rssi": -63}, } diff --git a/tests/components/shelly/test_climate.py b/tests/components/shelly/test_climate.py index 7daf640cc76..6c0ac74296a 100644 --- a/tests/components/shelly/test_climate.py +++ b/tests/components/shelly/test_climate.py @@ -29,6 +29,7 @@ from tests.common import mock_restore_cache, mock_restore_cache_with_extra_data SENSOR_BLOCK_ID = 3 DEVICE_BLOCK_ID = 4 +EMETER_BLOCK_ID = 5 ENTITY_ID = f"{CLIMATE_DOMAIN}.test_name" @@ -43,6 +44,7 @@ async def test_climate_hvac_mode( {"battery": 98, "valvePos": 50, "targetTemp": 21.0}, ) monkeypatch.setattr(mock_block_device.blocks[DEVICE_BLOCK_ID], "valveError", 0) + monkeypatch.delattr(mock_block_device.blocks[EMETER_BLOCK_ID], "targetTemp") await init_integration(hass, 1, sleep_period=1000, model="SHTRV-01") # Make device online @@ -195,6 +197,7 @@ async def test_block_restored_climate( """Test block restored climate.""" monkeypatch.delattr(mock_block_device.blocks[DEVICE_BLOCK_ID], "targetTemp") monkeypatch.setattr(mock_block_device.blocks[DEVICE_BLOCK_ID], "valveError", 0) + monkeypatch.delattr(mock_block_device.blocks[EMETER_BLOCK_ID], "targetTemp") entry = await init_integration(hass, 1, sleep_period=1000, skip_setup=True) register_device(device_reg, entry) entity_id = register_entity( @@ -257,6 +260,7 @@ async def test_block_restored_climate_us_customery( hass.config.units = US_CUSTOMARY_SYSTEM monkeypatch.delattr(mock_block_device.blocks[DEVICE_BLOCK_ID], "targetTemp") monkeypatch.setattr(mock_block_device.blocks[DEVICE_BLOCK_ID], "valveError", 0) + monkeypatch.delattr(mock_block_device.blocks[EMETER_BLOCK_ID], "targetTemp") entry = await init_integration(hass, 1, sleep_period=1000, skip_setup=True) register_device(device_reg, entry) entity_id = register_entity( diff --git a/tests/components/shelly/test_sensor.py b/tests/components/shelly/test_sensor.py index 822f007525f..d5da1e96bce 100644 --- a/tests/components/shelly/test_sensor.py +++ b/tests/components/shelly/test_sensor.py @@ -1,6 +1,13 @@ """Tests for Shelly sensor platform.""" from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN -from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN +from homeassistant.components.shelly.const import DOMAIN +from homeassistant.const import ( + ATTR_UNIT_OF_MEASUREMENT, + PERCENTAGE, + STATE_UNAVAILABLE, + STATE_UNKNOWN, + UnitOfEnergy, +) from homeassistant.core import HomeAssistant, State from homeassistant.helpers.entity_registry import async_get @@ -35,6 +42,52 @@ async def test_block_sensor( assert hass.states.get(entity_id).state == "60.1" +async def test_energy_sensor(hass: HomeAssistant, mock_block_device) -> None: + """Test energy sensor.""" + entity_id = f"{SENSOR_DOMAIN}.test_name_channel_1_energy" + await init_integration(hass, 1) + + state = hass.states.get(entity_id) + # 1234567.89 Wmin / 60 / 1000 = 20.5761315 kWh + assert state.state == "20.5761315" + # suggested unit is KWh + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfEnergy.KILO_WATT_HOUR + + +async def test_power_factory_unit_migration( + hass: HomeAssistant, mock_block_device +) -> None: + """Test migration unit of the power factory sensor.""" + registry = async_get(hass) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "123456789ABC-emeter_0-powerFactor", + suggested_object_id="test_name_power_factor", + unit_of_measurement="%", + ) + + entity_id = f"{SENSOR_DOMAIN}.test_name_power_factor" + await init_integration(hass, 1) + + state = hass.states.get(entity_id) + # Value of 0.98 is converted to 98.0% + assert state.state == "98.0" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE + + +async def test_power_factory_without_unit_migration( + hass: HomeAssistant, mock_block_device +) -> None: + """Test unit and value of the power factory sensor without unit migration.""" + entity_id = f"{SENSOR_DOMAIN}.test_name_power_factor" + await init_integration(hass, 1) + + state = hass.states.get(entity_id) + assert state.state == "0.98" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None + + async def test_block_rest_sensor( hass: HomeAssistant, mock_block_device, monkeypatch ) -> None: @@ -228,7 +281,7 @@ async def test_rpc_sensor_error( entity_id = f"{SENSOR_DOMAIN}.test_name_voltmeter" await init_integration(hass, 2) - assert hass.states.get(entity_id).state == "4.3" + assert hass.states.get(entity_id).state == "4.321" mutate_rpc_device_status(monkeypatch, mock_rpc_device, "voltmeter", "voltage", None) mock_rpc_device.mock_update()