Tests for the Fronius integration (#57269)

* tests for a Symo inverter system

* update testing requirement

* add tests for energy meter data

* move response JSONs to fixture directory

* add storage system response

* review suggestion
pull/59187/head
Matthias Alphart 2021-11-05 19:27:17 +01:00 committed by GitHub
parent d384feb87f
commit d2ffecbca4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 716 additions and 0 deletions

View File

@ -891,6 +891,9 @@ pyfreedompro==1.1.0
# homeassistant.components.fritzbox
pyfritzhome==0.6.2
# homeassistant.components.fronius
pyfronius==0.7.0
# homeassistant.components.ifttt
pyfttt==0.3

View File

@ -0,0 +1,23 @@
"""Tests for the Fronius integration."""
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_RESOURCE
from homeassistant.setup import async_setup_component
from .const import DOMAIN, MOCK_HOST
async def setup_fronius_integration(hass, devices):
"""Create the Fronius integration."""
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
CONF_RESOURCE: MOCK_HOST,
CONF_MONITORED_CONDITIONS: devices,
}
},
)
await hass.async_block_till_done()

View File

@ -0,0 +1,4 @@
"""Constants for Fronius tests."""
DOMAIN = "fronius"
MOCK_HOST = "http://fronius"

View File

@ -0,0 +1,305 @@
"""Tests for the Fronius sensor platform."""
from homeassistant.components.fronius.sensor import (
CONF_SCOPE,
DEFAULT_SCAN_INTERVAL,
SCOPE_DEVICE,
TYPE_INVERTER,
TYPE_LOGGER_INFO,
TYPE_METER,
TYPE_POWER_FLOW,
)
from homeassistant.const import CONF_DEVICE, CONF_SENSOR_TYPE, STATE_UNKNOWN
from homeassistant.util import dt
from . import setup_fronius_integration
from .const import MOCK_HOST
from tests.common import async_fire_time_changed, load_fixture
from tests.test_util.aiohttp import AiohttpClientMocker
def mock_responses(aioclient_mock: AiohttpClientMocker, night: bool = False) -> None:
"""Mock responses for Fronius Symo inverter with meter."""
aioclient_mock.clear_requests()
_day_or_night = "night" if night else "day"
aioclient_mock.get(
f"{MOCK_HOST}/solar_api/GetAPIVersion.cgi",
text=load_fixture("fronius/symo/GetAPIVersion.json"),
)
aioclient_mock.get(
f"{MOCK_HOST}/solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&"
"DeviceId=1&DataCollection=CommonInverterData",
text=load_fixture(
f"fronius/symo/GetInverterRealtimeDate_Device_1_{_day_or_night}.json"
),
)
aioclient_mock.get(
f"{MOCK_HOST}/solar_api/v1/GetInverterInfo.cgi",
text=load_fixture("fronius/symo/GetInverterInfo.json"),
)
aioclient_mock.get(
f"{MOCK_HOST}/solar_api/v1/GetLoggerInfo.cgi",
text=load_fixture("fronius/symo/GetLoggerInfo.json"),
)
aioclient_mock.get(
f"{MOCK_HOST}/solar_api/v1/GetMeterRealtimeData.cgi?Scope=Device&DeviceId=0",
text=load_fixture("fronius/symo/GetMeterRealtimeData_Device_0.json"),
)
aioclient_mock.get(
f"{MOCK_HOST}/solar_api/v1/GetMeterRealtimeData.cgi?Scope=System",
text=load_fixture("fronius/symo/GetMeterRealtimeData_System.json"),
)
aioclient_mock.get(
f"{MOCK_HOST}/solar_api/v1/GetPowerFlowRealtimeData.fcgi",
text=load_fixture(
f"fronius/symo/GetPowerFlowRealtimeData_{_day_or_night}.json"
),
)
aioclient_mock.get(
f"{MOCK_HOST}/solar_api/v1/GetStorageRealtimeData.cgi?Scope=System",
text=load_fixture("fronius/symo/GetStorageRealtimeData_System.json"),
)
async def test_symo_inverter(hass, aioclient_mock):
"""Test Fronius Symo inverter entities."""
def assert_state(entity_id, expected_state):
state = hass.states.get(entity_id)
assert state.state == str(expected_state)
# Init at night
mock_responses(aioclient_mock, night=True)
config = {
CONF_SENSOR_TYPE: TYPE_INVERTER,
CONF_SCOPE: SCOPE_DEVICE,
CONF_DEVICE: 1,
}
await setup_fronius_integration(hass, [config])
assert len(hass.states.async_all()) == 10
# 5 ignored from DeviceStatus
assert_state("sensor.current_dc_fronius_inverter_1_http_fronius", 0)
assert_state("sensor.energy_day_fronius_inverter_1_http_fronius", 10828)
assert_state("sensor.energy_total_fronius_inverter_1_http_fronius", 44186900)
assert_state("sensor.energy_year_fronius_inverter_1_http_fronius", 25507686)
assert_state("sensor.voltage_dc_fronius_inverter_1_http_fronius", 16)
# Second test at daytime when inverter is producing
mock_responses(aioclient_mock, night=False)
async_fire_time_changed(hass, dt.utcnow() + DEFAULT_SCAN_INTERVAL)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 14
# 4 additional AC entities
assert_state("sensor.current_dc_fronius_inverter_1_http_fronius", 2.19)
assert_state("sensor.energy_day_fronius_inverter_1_http_fronius", 1113)
assert_state("sensor.energy_total_fronius_inverter_1_http_fronius", 44188000)
assert_state("sensor.energy_year_fronius_inverter_1_http_fronius", 25508798)
assert_state("sensor.voltage_dc_fronius_inverter_1_http_fronius", 518)
assert_state("sensor.current_ac_fronius_inverter_1_http_fronius", 5.19)
assert_state("sensor.frequency_ac_fronius_inverter_1_http_fronius", 49.94)
assert_state("sensor.power_ac_fronius_inverter_1_http_fronius", 1190)
assert_state("sensor.voltage_ac_fronius_inverter_1_http_fronius", 227.90)
async def test_symo_logger(hass, aioclient_mock):
"""Test Fronius Symo logger entities."""
def assert_state(entity_id, expected_state):
state = hass.states.get(entity_id)
assert state
assert state.state == str(expected_state)
mock_responses(aioclient_mock)
config = {
CONF_SENSOR_TYPE: TYPE_LOGGER_INFO,
}
await setup_fronius_integration(hass, [config])
assert len(hass.states.async_all()) == 12
# ignored constant entities:
# hardware_platform, hardware_version, product_type
# software_version, time_zone, time_zone_location
# time_stamp, unique_identifier, utc_offset
#
# states are rounded to 2 decimals
assert_state(
"sensor.cash_factor_fronius_logger_info_0_http_fronius",
0.08,
)
assert_state(
"sensor.co2_factor_fronius_logger_info_0_http_fronius",
0.53,
)
assert_state(
"sensor.delivery_factor_fronius_logger_info_0_http_fronius",
0.15,
)
async def test_symo_meter(hass, aioclient_mock):
"""Test Fronius Symo meter entities."""
def assert_state(entity_id, expected_state):
state = hass.states.get(entity_id)
assert state
assert state.state == str(expected_state)
mock_responses(aioclient_mock)
config = {
CONF_SENSOR_TYPE: TYPE_METER,
CONF_SCOPE: SCOPE_DEVICE,
CONF_DEVICE: 0,
}
await setup_fronius_integration(hass, [config])
assert len(hass.states.async_all()) == 39
# ignored entities:
# manufacturer, model, serial, enable, timestamp, visible, meter_location
#
# states are rounded to 2 decimals
assert_state("sensor.current_ac_phase_1_fronius_meter_0_http_fronius", 7.75)
assert_state("sensor.current_ac_phase_2_fronius_meter_0_http_fronius", 6.68)
assert_state("sensor.current_ac_phase_3_fronius_meter_0_http_fronius", 10.1)
assert_state(
"sensor.energy_reactive_ac_consumed_fronius_meter_0_http_fronius", 59960790
)
assert_state(
"sensor.energy_reactive_ac_produced_fronius_meter_0_http_fronius", 723160
)
assert_state("sensor.energy_real_ac_minus_fronius_meter_0_http_fronius", 35623065)
assert_state("sensor.energy_real_ac_plus_fronius_meter_0_http_fronius", 15303334)
assert_state("sensor.energy_real_consumed_fronius_meter_0_http_fronius", 15303334)
assert_state("sensor.energy_real_produced_fronius_meter_0_http_fronius", 35623065)
assert_state("sensor.frequency_phase_average_fronius_meter_0_http_fronius", 50)
assert_state("sensor.power_apparent_phase_1_fronius_meter_0_http_fronius", 1772.79)
assert_state("sensor.power_apparent_phase_2_fronius_meter_0_http_fronius", 1527.05)
assert_state("sensor.power_apparent_phase_3_fronius_meter_0_http_fronius", 2333.56)
assert_state("sensor.power_apparent_fronius_meter_0_http_fronius", 5592.57)
assert_state("sensor.power_factor_phase_1_fronius_meter_0_http_fronius", -0.99)
assert_state("sensor.power_factor_phase_2_fronius_meter_0_http_fronius", -0.99)
assert_state("sensor.power_factor_phase_3_fronius_meter_0_http_fronius", 0.99)
assert_state("sensor.power_factor_fronius_meter_0_http_fronius", 1)
assert_state("sensor.power_reactive_phase_1_fronius_meter_0_http_fronius", 51.48)
assert_state("sensor.power_reactive_phase_2_fronius_meter_0_http_fronius", 115.63)
assert_state("sensor.power_reactive_phase_3_fronius_meter_0_http_fronius", -164.24)
assert_state("sensor.power_reactive_fronius_meter_0_http_fronius", 2.87)
assert_state("sensor.power_real_phase_1_fronius_meter_0_http_fronius", 1765.55)
assert_state("sensor.power_real_phase_2_fronius_meter_0_http_fronius", 1515.8)
assert_state("sensor.power_real_phase_3_fronius_meter_0_http_fronius", 2311.22)
assert_state("sensor.power_real_fronius_meter_0_http_fronius", 5592.57)
assert_state("sensor.voltage_ac_phase_1_fronius_meter_0_http_fronius", 228.6)
assert_state("sensor.voltage_ac_phase_2_fronius_meter_0_http_fronius", 228.6)
assert_state("sensor.voltage_ac_phase_3_fronius_meter_0_http_fronius", 231)
assert_state(
"sensor.voltage_ac_phase_to_phase_12_fronius_meter_0_http_fronius", 395.9
)
assert_state(
"sensor.voltage_ac_phase_to_phase_23_fronius_meter_0_http_fronius", 398
)
assert_state(
"sensor.voltage_ac_phase_to_phase_31_fronius_meter_0_http_fronius", 398
)
async def test_symo_power_flow(hass, aioclient_mock):
"""Test Fronius Symo power flow entities."""
async_fire_time_changed(hass, dt.utcnow())
def assert_state(entity_id, expected_state):
state = hass.states.get(entity_id)
assert state.state == str(expected_state)
# First test at night
mock_responses(aioclient_mock, night=True)
config = {
CONF_SENSOR_TYPE: TYPE_POWER_FLOW,
}
await setup_fronius_integration(hass, [config])
assert len(hass.states.async_all()) == 12
# ignored: location, mode, timestamp
#
# states are rounded to 2 decimals
assert_state(
"sensor.energy_day_fronius_power_flow_0_http_fronius",
10828,
)
assert_state(
"sensor.energy_total_fronius_power_flow_0_http_fronius",
44186900,
)
assert_state(
"sensor.energy_year_fronius_power_flow_0_http_fronius",
25507686,
)
assert_state(
"sensor.power_battery_fronius_power_flow_0_http_fronius",
STATE_UNKNOWN,
)
assert_state(
"sensor.power_grid_fronius_power_flow_0_http_fronius",
975.31,
)
assert_state(
"sensor.power_load_fronius_power_flow_0_http_fronius",
-975.31,
)
assert_state(
"sensor.power_photovoltaics_fronius_power_flow_0_http_fronius",
STATE_UNKNOWN,
)
assert_state(
"sensor.relative_autonomy_fronius_power_flow_0_http_fronius",
0,
)
assert_state(
"sensor.relative_self_consumption_fronius_power_flow_0_http_fronius",
STATE_UNKNOWN,
)
# Second test at daytime when inverter is producing
mock_responses(aioclient_mock, night=False)
async_fire_time_changed(hass, dt.utcnow() + DEFAULT_SCAN_INTERVAL)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 12
assert_state(
"sensor.energy_day_fronius_power_flow_0_http_fronius",
1101.70,
)
assert_state(
"sensor.energy_total_fronius_power_flow_0_http_fronius",
44188000,
)
assert_state(
"sensor.energy_year_fronius_power_flow_0_http_fronius",
25508788,
)
assert_state(
"sensor.power_battery_fronius_power_flow_0_http_fronius",
STATE_UNKNOWN,
)
assert_state(
"sensor.power_grid_fronius_power_flow_0_http_fronius",
1703.74,
)
assert_state(
"sensor.power_load_fronius_power_flow_0_http_fronius",
-2814.74,
)
assert_state(
"sensor.power_photovoltaics_fronius_power_flow_0_http_fronius",
1111,
)
assert_state(
"sensor.relative_autonomy_fronius_power_flow_0_http_fronius",
39.47,
)
assert_state(
"sensor.relative_self_consumption_fronius_power_flow_0_http_fronius",
100,
)

View File

@ -0,0 +1,5 @@
{
"APIVersion": 1,
"BaseURL": "/solar_api/v1/",
"CompatibilityRange": "1.6-3"
}

View File

@ -0,0 +1,24 @@
{
"Body": {
"Data": {
"1": {
"CustomName": "Symo 20",
"DT": 121,
"ErrorCode": 0,
"PVPower": 23100,
"Show": 1,
"StatusCode": 7,
"UniqueID": "1234567"
}
}
},
"Head": {
"RequestArguments": {},
"Status": {
"Code": 0,
"Reason": "",
"UserMessage": ""
},
"Timestamp": "2021-10-07T13:41:00+02:00"
}
}

View File

@ -0,0 +1,64 @@
{
"Body": {
"Data": {
"DAY_ENERGY": {
"Unit": "Wh",
"Value": 1113
},
"DeviceStatus": {
"ErrorCode": 0,
"LEDColor": 2,
"LEDState": 0,
"MgmtTimerRemainingTime": -1,
"StateToReset": false,
"StatusCode": 7
},
"FAC": {
"Unit": "Hz",
"Value": 49.939999999999998
},
"IAC": {
"Unit": "A",
"Value": 5.1900000000000004
},
"IDC": {
"Unit": "A",
"Value": 2.1899999999999999
},
"PAC": {
"Unit": "W",
"Value": 1190
},
"TOTAL_ENERGY": {
"Unit": "Wh",
"Value": 44188000
},
"UAC": {
"Unit": "V",
"Value": 227.90000000000001
},
"UDC": {
"Unit": "V",
"Value": 518
},
"YEAR_ENERGY": {
"Unit": "Wh",
"Value": 25508798
}
}
},
"Head": {
"RequestArguments": {
"DataCollection": "CommonInverterData",
"DeviceClass": "Inverter",
"DeviceId": "1",
"Scope": "Device"
},
"Status": {
"Code": 0,
"Reason": "",
"UserMessage": ""
},
"Timestamp": "2021-10-07T10:01:17+02:00"
}
}

View File

@ -0,0 +1,48 @@
{
"Body": {
"Data": {
"DAY_ENERGY": {
"Unit": "Wh",
"Value": 10828
},
"DeviceStatus": {
"ErrorCode": 307,
"LEDColor": 1,
"LEDState": 0,
"MgmtTimerRemainingTime": 17,
"StateToReset": false,
"StatusCode": 3
},
"IDC": {
"Unit": "A",
"Value": 0
},
"TOTAL_ENERGY": {
"Unit": "Wh",
"Value": 44186900
},
"UDC": {
"Unit": "V",
"Value": 16
},
"YEAR_ENERGY": {
"Unit": "Wh",
"Value": 25507686
}
}
},
"Head": {
"RequestArguments": {
"DataCollection": "CommonInverterData",
"DeviceClass": "Inverter",
"DeviceId": "1",
"Scope": "Device"
},
"Status": {
"Code": 0,
"Reason": "",
"UserMessage": ""
},
"Timestamp": "2021-10-06T21:16:59+02:00"
}
}

View File

@ -0,0 +1,29 @@
{
"Body": {
"LoggerInfo": {
"CO2Factor": 0.52999997138977051,
"CO2Unit": "kg",
"CashCurrency": "EUR",
"CashFactor": 0.078000001609325409,
"DefaultLanguage": "en",
"DeliveryFactor": 0.15000000596046448,
"HWVersion": "2.4E",
"PlatformID": "wilma",
"ProductID": "fronius-datamanager-card",
"SWVersion": "3.18.7-1",
"TimezoneLocation": "Vienna",
"TimezoneName": "CEST",
"UTCOffset": 7200,
"UniqueID": "123.4567890"
}
},
"Head": {
"RequestArguments": {},
"Status": {
"Code": 0,
"Reason": "",
"UserMessage": ""
},
"Timestamp": "2021-10-06T23:56:32+02:00"
}
}

View File

@ -0,0 +1,60 @@
{
"Body": {
"Data": {
"Current_AC_Phase_1": 7.7549999999999999,
"Current_AC_Phase_2": 6.6799999999999997,
"Current_AC_Phase_3": 10.102,
"Details": {
"Manufacturer": "Fronius",
"Model": "Smart Meter 63A",
"Serial": "12345678"
},
"Enable": 1,
"EnergyReactive_VArAC_Sum_Consumed": 59960790,
"EnergyReactive_VArAC_Sum_Produced": 723160,
"EnergyReal_WAC_Minus_Absolute": 35623065,
"EnergyReal_WAC_Plus_Absolute": 15303334,
"EnergyReal_WAC_Sum_Consumed": 15303334,
"EnergyReal_WAC_Sum_Produced": 35623065,
"Frequency_Phase_Average": 50,
"Meter_Location_Current": 0,
"PowerApparent_S_Phase_1": 1772.7929999999999,
"PowerApparent_S_Phase_2": 1527.048,
"PowerApparent_S_Phase_3": 2333.5619999999999,
"PowerApparent_S_Sum": 5592.5699999999997,
"PowerFactor_Phase_1": -0.98999999999999999,
"PowerFactor_Phase_2": -0.98999999999999999,
"PowerFactor_Phase_3": 0.98999999999999999,
"PowerFactor_Sum": 1,
"PowerReactive_Q_Phase_1": 51.479999999999997,
"PowerReactive_Q_Phase_2": 115.63,
"PowerReactive_Q_Phase_3": -164.24000000000001,
"PowerReactive_Q_Sum": 2.8700000000000001,
"PowerReal_P_Phase_1": 1765.55,
"PowerReal_P_Phase_2": 1515.8,
"PowerReal_P_Phase_3": 2311.2199999999998,
"PowerReal_P_Sum": 5592.5699999999997,
"TimeStamp": 1633977078,
"Visible": 1,
"Voltage_AC_PhaseToPhase_12": 395.89999999999998,
"Voltage_AC_PhaseToPhase_23": 398,
"Voltage_AC_PhaseToPhase_31": 398,
"Voltage_AC_Phase_1": 228.59999999999999,
"Voltage_AC_Phase_2": 228.59999999999999,
"Voltage_AC_Phase_3": 231
}
},
"Head": {
"RequestArguments": {
"DeviceClass": "Meter",
"DeviceId": "0",
"Scope": "Device"
},
"Status": {
"Code": 0,
"Reason": "",
"UserMessage": ""
},
"Timestamp": "2021-10-11T20:31:18+02:00"
}
}

View File

@ -0,0 +1,61 @@
{
"Body": {
"Data": {
"0": {
"Current_AC_Phase_1": 7.7549999999999999,
"Current_AC_Phase_2": 6.6799999999999997,
"Current_AC_Phase_3": 10.102,
"Details": {
"Manufacturer": "Fronius",
"Model": "Smart Meter 63A",
"Serial": "12345678"
},
"Enable": 1,
"EnergyReactive_VArAC_Sum_Consumed": 59960790,
"EnergyReactive_VArAC_Sum_Produced": 723160,
"EnergyReal_WAC_Minus_Absolute": 35623065,
"EnergyReal_WAC_Plus_Absolute": 15303334,
"EnergyReal_WAC_Sum_Consumed": 15303334,
"EnergyReal_WAC_Sum_Produced": 35623065,
"Frequency_Phase_Average": 50,
"Meter_Location_Current": 0,
"PowerApparent_S_Phase_1": 1772.7929999999999,
"PowerApparent_S_Phase_2": 1527.048,
"PowerApparent_S_Phase_3": 2333.5619999999999,
"PowerApparent_S_Sum": 5592.5699999999997,
"PowerFactor_Phase_1": -0.98999999999999999,
"PowerFactor_Phase_2": -0.98999999999999999,
"PowerFactor_Phase_3": 0.98999999999999999,
"PowerFactor_Sum": 1,
"PowerReactive_Q_Phase_1": 51.479999999999997,
"PowerReactive_Q_Phase_2": 115.63,
"PowerReactive_Q_Phase_3": -164.24000000000001,
"PowerReactive_Q_Sum": 2.8700000000000001,
"PowerReal_P_Phase_1": 1765.55,
"PowerReal_P_Phase_2": 1515.8,
"PowerReal_P_Phase_3": 2311.2199999999998,
"PowerReal_P_Sum": 5592.5699999999997,
"TimeStamp": 1633977078,
"Visible": 1,
"Voltage_AC_PhaseToPhase_12": 395.89999999999998,
"Voltage_AC_PhaseToPhase_23": 398,
"Voltage_AC_PhaseToPhase_31": 398,
"Voltage_AC_Phase_1": 228.59999999999999,
"Voltage_AC_Phase_2": 228.59999999999999,
"Voltage_AC_Phase_3": 231
}
}
},
"Head": {
"RequestArguments": {
"DeviceClass": "Meter",
"Scope": "System"
},
"Status": {
"Code": 0,
"Reason": "",
"UserMessage": ""
},
"Timestamp": "2021-10-11T20:31:18+02:00"
}
}

View File

@ -0,0 +1,38 @@
{
"Body": {
"Data": {
"Inverters": {
"1": {
"DT": 121,
"E_Day": 1101.7000732421875,
"E_Total": 44188000,
"E_Year": 25508788,
"P": 1111
}
},
"Site": {
"E_Day": 1101.7000732421875,
"E_Total": 44188000,
"E_Year": 25508788,
"Meter_Location": "grid",
"Mode": "meter",
"P_Akku": null,
"P_Grid": 1703.74,
"P_Load": -2814.7399999999998,
"P_PV": 1111,
"rel_Autonomy": 39.4707859340472,
"rel_SelfConsumption": 100
},
"Version": "12"
}
},
"Head": {
"RequestArguments": {},
"Status": {
"Code": 0,
"Reason": "",
"UserMessage": ""
},
"Timestamp": "2021-10-07T10:00:43+02:00"
}
}

View File

@ -0,0 +1,38 @@
{
"Body": {
"Data": {
"Inverters": {
"1": {
"DT": 121,
"E_Day": 10828,
"E_Total": 44186900,
"E_Year": 25507686,
"P": 0
}
},
"Site": {
"E_Day": 10828,
"E_Total": 44186900,
"E_Year": 25507686,
"Meter_Location": "grid",
"Mode": "meter",
"P_Akku": null,
"P_Grid": 975.30999999999995,
"P_Load": -975.30999999999995,
"P_PV": null,
"rel_Autonomy": 0,
"rel_SelfConsumption": null
},
"Version": "12"
}
},
"Head": {
"RequestArguments": {},
"Status": {
"Code": 0,
"Reason": "",
"UserMessage": ""
},
"Timestamp": "2021-10-06T23:49:54+02:00"
}
}

View File

@ -0,0 +1,14 @@
{
"Body": {
"Data": {}
},
"Head": {
"RequestArguments": {},
"Status": {
"Code": 255,
"Reason": "GetStorageRealtimeData request is not supported by this device.",
"UserMessage": ""
},
"Timestamp": "2021-10-22T06:50:22+02:00"
}
}