diff --git a/homeassistant/components/fronius/const.py b/homeassistant/components/fronius/const.py index c19d0b07cd..0b76daa18a 100644 --- a/homeassistant/components/fronius/const.py +++ b/homeassistant/components/fronius/const.py @@ -65,3 +65,31 @@ _INVERTER_STATUS_CODES: Final[dict[int, InverterStatusCodeOption]] = { def get_inverter_status_message(code: StateType) -> InverterStatusCodeOption: """Return a status message for a given status code.""" return _INVERTER_STATUS_CODES.get(code, InverterStatusCodeOption.INVALID) # type: ignore[arg-type] + + +class MeterLocationCodeOption(StrEnum): + """Meter location codes for Fronius meters.""" + + # these are keys for state translations - so snake_case is used + FEED_IN = "feed_in" + CONSUMPTION_PATH = "consumption_path" + GENERATOR = "external_generator" + SUBLOAD = "subload" + + +def get_meter_location_description(code: StateType) -> MeterLocationCodeOption | None: + """Return a location_description for a given location code.""" + try: + code = int(code) # type: ignore[arg-type] + except ValueError: + return None + match code: + case 0: + return MeterLocationCodeOption.FEED_IN + case 1: + return MeterLocationCodeOption.CONSUMPTION_PATH + case 3: + return MeterLocationCodeOption.GENERATOR + case _ as _code if 256 <= _code <= 511: + return MeterLocationCodeOption.SUBLOAD + return None diff --git a/homeassistant/components/fronius/sensor.py b/homeassistant/components/fronius/sensor.py index 57521515f1..6c1e508ff7 100644 --- a/homeassistant/components/fronius/sensor.py +++ b/homeassistant/components/fronius/sensor.py @@ -30,7 +30,13 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, InverterStatusCodeOption, get_inverter_status_message +from .const import ( + DOMAIN, + InverterStatusCodeOption, + MeterLocationCodeOption, + get_inverter_status_message, + get_meter_location_description, +) if TYPE_CHECKING: from . import FroniusSolarNet @@ -293,6 +299,15 @@ METER_ENTITY_DESCRIPTIONS: list[FroniusSensorEntityDescription] = [ FroniusSensorEntityDescription( key="meter_location", entity_category=EntityCategory.DIAGNOSTIC, + value_fn=int, # type: ignore[arg-type] + ), + FroniusSensorEntityDescription( + key="meter_location_description", + response_key="meter_location", + entity_category=EntityCategory.DIAGNOSTIC, + device_class=SensorDeviceClass.ENUM, + options=[opt.value for opt in MeterLocationCodeOption], + value_fn=get_meter_location_description, ), FroniusSensorEntityDescription( key="power_apparent_phase_1", diff --git a/homeassistant/components/fronius/strings.json b/homeassistant/components/fronius/strings.json index 08672274ac..b862a45c12 100644 --- a/homeassistant/components/fronius/strings.json +++ b/homeassistant/components/fronius/strings.json @@ -129,6 +129,15 @@ "meter_location": { "name": "Meter location" }, + "meter_location_description": { + "name": "Meter location description", + "state": { + "feed_in": "Grid interconnection point", + "consumption_path": "Consumption path", + "external_generator": "External generator", + "subload": "Subload" + } + }, "power_apparent_phase_1": { "name": "Apparent power phase 1" }, diff --git a/tests/components/fronius/test_sensor.py b/tests/components/fronius/test_sensor.py index 59817ea744..0340fb4b5c 100644 --- a/tests/components/fronius/test_sensor.py +++ b/tests/components/fronius/test_sensor.py @@ -29,11 +29,11 @@ async def test_symo_inverter( mock_responses(aioclient_mock, night=True) config_entry = await setup_fronius_integration(hass) - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 20 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 21 await enable_all_entities( hass, config_entry.entry_id, FroniusInverterUpdateCoordinator.default_interval ) - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 53 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 54 assert_state("sensor.symo_20_dc_current", 0) assert_state("sensor.symo_20_energy_day", 10828) assert_state("sensor.symo_20_total_energy", 44186900) @@ -47,11 +47,11 @@ async def test_symo_inverter( hass, dt_util.utcnow() + FroniusInverterUpdateCoordinator.default_interval ) await hass.async_block_till_done() - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 57 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 58 await enable_all_entities( hass, config_entry.entry_id, FroniusInverterUpdateCoordinator.default_interval ) - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 59 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 60 # 4 additional AC entities assert_state("sensor.symo_20_dc_current", 2.19) assert_state("sensor.symo_20_energy_day", 1113) @@ -88,7 +88,7 @@ async def test_symo_logger( mock_responses(aioclient_mock) await setup_fronius_integration(hass) - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 24 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 25 # states are rounded to 4 decimals assert_state("sensor.solarnet_grid_export_tariff", 0.078) assert_state("sensor.solarnet_co2_factor", 0.53) @@ -108,11 +108,11 @@ async def test_symo_meter( mock_responses(aioclient_mock) config_entry = await setup_fronius_integration(hass) - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 24 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 25 await enable_all_entities( hass, config_entry.entry_id, FroniusMeterUpdateCoordinator.default_interval ) - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 59 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 60 # states are rounded to 4 decimals assert_state("sensor.smart_meter_63a_current_phase_1", 7.755) assert_state("sensor.smart_meter_63a_current_phase_2", 6.68) @@ -146,6 +146,8 @@ async def test_symo_meter( assert_state("sensor.smart_meter_63a_voltage_phase_1_2", 395.9) assert_state("sensor.smart_meter_63a_voltage_phase_2_3", 398) assert_state("sensor.smart_meter_63a_voltage_phase_3_1", 398) + assert_state("sensor.smart_meter_63a_meter_location", 0) + assert_state("sensor.smart_meter_63a_meter_location_description", "feed_in") async def test_symo_power_flow( @@ -162,11 +164,11 @@ async def test_symo_power_flow( mock_responses(aioclient_mock, night=True) config_entry = await setup_fronius_integration(hass) - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 20 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 21 await enable_all_entities( hass, config_entry.entry_id, FroniusInverterUpdateCoordinator.default_interval ) - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 53 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 54 # states are rounded to 4 decimals assert_state("sensor.solarnet_energy_day", 10828) assert_state("sensor.solarnet_total_energy", 44186900) @@ -182,7 +184,7 @@ async def test_symo_power_flow( ) await hass.async_block_till_done() # 54 because power_flow `rel_SelfConsumption` and `P_PV` is not `null` anymore - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 55 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 56 assert_state("sensor.solarnet_energy_day", 1101.7001) assert_state("sensor.solarnet_total_energy", 44188000) assert_state("sensor.solarnet_energy_year", 25508788) @@ -198,7 +200,7 @@ async def test_symo_power_flow( hass, dt_util.utcnow() + FroniusPowerFlowUpdateCoordinator.default_interval ) await hass.async_block_till_done() - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 55 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 56 assert_state("sensor.solarnet_energy_day", 10828) assert_state("sensor.solarnet_total_energy", 44186900) assert_state("sensor.solarnet_energy_year", 25507686) @@ -220,11 +222,11 @@ async def test_gen24(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) - mock_responses(aioclient_mock, fixture_set="gen24") config_entry = await setup_fronius_integration(hass, is_logger=False) - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 22 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 23 await enable_all_entities( hass, config_entry.entry_id, FroniusMeterUpdateCoordinator.default_interval ) - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 53 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 54 # inverter 1 assert_state("sensor.inverter_name_ac_current", 0.1589) assert_state("sensor.inverter_name_dc_current_2", 0.0754) @@ -244,7 +246,8 @@ async def test_gen24(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) - assert_state("sensor.smart_meter_ts_65a_3_real_energy_consumed", 2013105.0) assert_state("sensor.smart_meter_ts_65a_3_real_power", 653.1) assert_state("sensor.smart_meter_ts_65a_3_frequency_phase_average", 49.9) - assert_state("sensor.smart_meter_ts_65a_3_meter_location", 0.0) + assert_state("sensor.smart_meter_ts_65a_3_meter_location", 0) + assert_state("sensor.smart_meter_ts_65a_3_meter_location_description", "feed_in") assert_state("sensor.smart_meter_ts_65a_3_power_factor", 0.828) assert_state("sensor.smart_meter_ts_65a_3_reactive_energy_consumed", 88221.0) assert_state("sensor.smart_meter_ts_65a_3_real_energy_minus", 3863340.0) @@ -298,11 +301,11 @@ async def test_gen24_storage( hass, is_logger=False, unique_id="12345678" ) - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 34 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 35 await enable_all_entities( hass, config_entry.entry_id, FroniusMeterUpdateCoordinator.default_interval ) - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 65 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 66 # inverter 1 assert_state("sensor.gen24_storage_dc_current", 0.3952) assert_state("sensor.gen24_storage_dc_voltage_2", 318.8103) @@ -323,7 +326,8 @@ async def test_gen24_storage( assert_state("sensor.smart_meter_ts_65a_3_power_factor", 0.698) assert_state("sensor.smart_meter_ts_65a_3_real_energy_consumed", 1247204.0) assert_state("sensor.smart_meter_ts_65a_3_frequency_phase_average", 49.9) - assert_state("sensor.smart_meter_ts_65a_3_meter_location", 0.0) + assert_state("sensor.smart_meter_ts_65a_3_meter_location", 0) + assert_state("sensor.smart_meter_ts_65a_3_meter_location_description", "feed_in") assert_state("sensor.smart_meter_ts_65a_3_reactive_power", -501.5) assert_state("sensor.smart_meter_ts_65a_3_reactive_energy_produced", 3266105.0) assert_state("sensor.smart_meter_ts_65a_3_real_power_phase_3", 19.6) @@ -421,11 +425,11 @@ async def test_primo_s0( mock_responses(aioclient_mock, fixture_set="primo_s0", inverter_ids=[1, 2]) config_entry = await setup_fronius_integration(hass, is_logger=True) - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 29 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 30 await enable_all_entities( hass, config_entry.entry_id, FroniusMeterUpdateCoordinator.default_interval ) - assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 42 + assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 43 # logger assert_state("sensor.solarnet_grid_export_tariff", 1) assert_state("sensor.solarnet_co2_factor", 0.53) @@ -462,6 +466,9 @@ async def test_primo_s0( assert_state("sensor.primo_3_0_1_led_state", 0) # meter assert_state("sensor.s0_meter_at_inverter_1_meter_location", 1) + assert_state( + "sensor.s0_meter_at_inverter_1_meter_location_description", "consumption_path" + ) assert_state("sensor.s0_meter_at_inverter_1_real_power", -2216.7487) # power_flow assert_state("sensor.solarnet_power_load", -2218.9349)