2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.fronius.internal.handler;
15 import java.util.Optional;
17 import javax.measure.Unit;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.openhab.binding.fronius.internal.FroniusBaseDeviceConfiguration;
21 import org.openhab.binding.fronius.internal.FroniusBindingConstants;
22 import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
23 import org.openhab.binding.fronius.internal.api.FroniusCommunicationException;
24 import org.openhab.binding.fronius.internal.api.dto.ValueUnit;
25 import org.openhab.binding.fronius.internal.api.dto.inverter.InverterDeviceStatus;
26 import org.openhab.binding.fronius.internal.api.dto.inverter.InverterRealtimeBody;
27 import org.openhab.binding.fronius.internal.api.dto.inverter.InverterRealtimeBodyData;
28 import org.openhab.binding.fronius.internal.api.dto.inverter.InverterRealtimeResponse;
29 import org.openhab.binding.fronius.internal.api.dto.powerflow.PowerFlowRealtimeBody;
30 import org.openhab.binding.fronius.internal.api.dto.powerflow.PowerFlowRealtimeBodyData;
31 import org.openhab.binding.fronius.internal.api.dto.powerflow.PowerFlowRealtimeInverter;
32 import org.openhab.binding.fronius.internal.api.dto.powerflow.PowerFlowRealtimeResponse;
33 import org.openhab.binding.fronius.internal.api.dto.powerflow.PowerFlowRealtimeSite;
34 import org.openhab.core.library.types.DecimalType;
35 import org.openhab.core.library.types.QuantityType;
36 import org.openhab.core.library.unit.Units;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.types.State;
41 * The {@link FroniusSymoInverterHandler} is responsible for updating the data, which are
42 * sent to one of the channels.
44 * @author Thomas Rokohl - Initial contribution
45 * @author Peter Schraffl - Added device status and error status channels
46 * @author Thomas Kordelle - Added inverter power, battery state of charge and PV solar yield
47 * @author Jimmy Tanagra - Add powerflow autonomy, self consumption channels
49 public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
51 private @Nullable InverterRealtimeResponse inverterRealtimeResponse;
52 private @Nullable PowerFlowRealtimeResponse powerFlowResponse;
53 private FroniusBaseDeviceConfiguration config;
55 public FroniusSymoInverterHandler(Thing thing) {
60 protected String getDescription() {
61 return "Fronius Symo Inverter";
65 protected void handleRefresh(FroniusBridgeConfiguration bridgeConfiguration) throws FroniusCommunicationException {
66 updateData(bridgeConfiguration, config);
71 public void initialize() {
72 config = getConfigAs(FroniusBaseDeviceConfiguration.class);
77 * Update the channel from the last data retrieved
79 * @param channelId the id identifying the channel to be updated
80 * @return the last retrieved data
83 protected State getValue(String channelId) {
84 final String[] fields = channelId.split("#");
85 if (fields.length < 1) {
88 final String fieldName = fields[0];
90 InverterRealtimeBodyData inverterData = getInverterData();
91 if (inverterData == null) {
96 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_PAC:
97 return getQuantityOrZero(inverterData.getPac(), Units.WATT);
98 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_FAC:
99 return getQuantityOrZero(inverterData.getFac(), Units.HERTZ);
100 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_IAC:
101 return getQuantityOrZero(inverterData.getIac(), Units.AMPERE);
102 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_IDC:
103 return getQuantityOrZero(inverterData.getIdc(), Units.AMPERE);
104 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_IDC2:
105 return getQuantityOrZero(inverterData.getIdc2(), Units.AMPERE);
106 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_IDC3:
107 return getQuantityOrZero(inverterData.getIdc3(), Units.AMPERE);
108 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_UAC:
109 return getQuantityOrZero(inverterData.getUac(), Units.VOLT);
110 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_UDC:
111 return getQuantityOrZero(inverterData.getUdc(), Units.VOLT);
112 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_UDC2:
113 return getQuantityOrZero(inverterData.getUdc2(), Units.VOLT);
114 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_UDC3:
115 return getQuantityOrZero(inverterData.getUdc3(), Units.VOLT);
116 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_PDC:
117 return calculatePower(inverterData.getUdc(), inverterData.getIdc());
118 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_PDC2:
119 return calculatePower(inverterData.getUdc2(), inverterData.getIdc2());
120 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_PDC3:
121 return calculatePower(inverterData.getUdc3(), inverterData.getIdc3());
122 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_DAY_ENERGY:
123 // Convert the unit to kWh for backwards compatibility with non-quantity type
124 return getQuantityOrZero(inverterData.getDayEnergy(), Units.KILOWATT_HOUR).toUnit("kWh");
125 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_TOTAL:
126 // Convert the unit to MWh for backwards compatibility with non-quantity type
127 return getQuantityOrZero(inverterData.getTotalEnergy(), Units.MEGAWATT_HOUR).toUnit("MWh");
128 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_YEAR:
129 // Convert the unit to MWh for backwards compatibility with non-quantity type
130 return getQuantityOrZero(inverterData.getYearEnergy(), Units.MEGAWATT_HOUR).toUnit("MWh");
131 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_DEVICE_STATUS_ERROR_CODE:
132 InverterDeviceStatus deviceStatus = inverterData.getDeviceStatus();
133 if (deviceStatus == null) {
136 return new DecimalType(deviceStatus.getErrorCode());
137 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_DEVICE_STATUS_STATUS_CODE:
138 deviceStatus = inverterData.getDeviceStatus();
139 if (deviceStatus == null) {
142 return new DecimalType(deviceStatus.getStatusCode());
147 PowerFlowRealtimeBodyData powerFlowData = getPowerFlowRealtimeData();
148 if (powerFlowData == null) {
151 PowerFlowRealtimeSite site = powerFlowData.getSite();
156 return switch (fieldName) {
157 case FroniusBindingConstants.POWER_FLOW_P_GRID -> new QuantityType<>(site.getPgrid(), Units.WATT);
158 case FroniusBindingConstants.POWER_FLOW_P_LOAD -> new QuantityType<>(site.getPload(), Units.WATT);
159 case FroniusBindingConstants.POWER_FLOW_P_AKKU -> new QuantityType<>(site.getPakku(), Units.WATT);
160 case FroniusBindingConstants.POWER_FLOW_P_PV -> new QuantityType<>(site.getPpv(), Units.WATT);
161 case FroniusBindingConstants.POWER_FLOW_AUTONOMY ->
162 new QuantityType<>(site.getRelAutonomy(), Units.PERCENT);
163 case FroniusBindingConstants.POWER_FLOW_SELF_CONSUMPTION ->
164 new QuantityType<>(site.getRelSelfConsumption(), Units.PERCENT);
165 case FroniusBindingConstants.POWER_FLOW_INVERTER_POWER -> {
166 PowerFlowRealtimeInverter inverter = getInverter(config.deviceId);
167 if (inverter == null) {
170 yield new QuantityType<>(inverter.getP(), Units.WATT);
172 case FroniusBindingConstants.POWER_FLOW_INVERTER_SOC -> {
173 PowerFlowRealtimeInverter inverter = getInverter(config.deviceId);
174 if (inverter == null) {
177 yield new QuantityType<>(inverter.getSoc(), Units.PERCENT);
179 // Kept for backwards compatibility
180 case FroniusBindingConstants.POWER_FLOW_INVERTER_1_POWER -> {
181 PowerFlowRealtimeInverter inverter = getInverter(1);
182 if (inverter == null) {
185 yield new QuantityType<>(inverter.getP(), Units.WATT);
187 case FroniusBindingConstants.POWER_FLOW_INVERTER_1_SOC -> {
188 PowerFlowRealtimeInverter inverter = getInverter(1);
189 if (inverter == null) {
192 yield new QuantityType<>(inverter.getSoc(), Units.PERCENT);
199 private @Nullable InverterRealtimeBodyData getInverterData() {
200 InverterRealtimeResponse localInverterRealtimeResponse = inverterRealtimeResponse;
201 if (localInverterRealtimeResponse == null) {
204 InverterRealtimeBody inverterBody = localInverterRealtimeResponse.getBody();
205 return (inverterBody != null) ? inverterBody.getData() : null;
208 private @Nullable PowerFlowRealtimeBodyData getPowerFlowRealtimeData() {
209 PowerFlowRealtimeResponse localPowerFlowResponse = powerFlowResponse;
210 if (localPowerFlowResponse == null) {
213 PowerFlowRealtimeBody powerFlowBody = localPowerFlowResponse.getBody();
214 return (powerFlowBody != null) ? powerFlowBody.getData() : null;
218 * get flow data for a specific inverter.
220 * @param number The inverter object of the given index
221 * @return a PowerFlowRealtimeInverter object.
223 private @Nullable PowerFlowRealtimeInverter getInverter(final int number) {
224 PowerFlowRealtimeBodyData powerFlowData = getPowerFlowRealtimeData();
225 if (powerFlowData == null) {
228 return powerFlowData.getInverters().get(Integer.toString(number));
232 * Return the value as QuantityType with the unit extracted from ValueUnit
233 * or a zero QuantityType with the given unit argument when value is null
235 * @param value The ValueUnit data
236 * @param unit The default unit to use when value is null
237 * @return a QuantityType from the given value
239 private QuantityType<?> getQuantityOrZero(ValueUnit value, Unit unit) {
240 return Optional.ofNullable(value).map(val -> val.asQuantityType().toUnit(unit))
241 .orElse(new QuantityType<>(0, unit));
247 private void updateData(FroniusBridgeConfiguration bridgeConfiguration, FroniusBaseDeviceConfiguration config)
248 throws FroniusCommunicationException {
249 inverterRealtimeResponse = getRealtimeData(bridgeConfiguration.hostname, config.deviceId);
250 powerFlowResponse = getPowerFlowRealtime(bridgeConfiguration.hostname);
254 * Make the PowerFlowRealtimeDataRequest
256 * @param ip address of the device
257 * @return {PowerFlowRealtimeResponse} the object representation of the json response
259 private PowerFlowRealtimeResponse getPowerFlowRealtime(String ip) throws FroniusCommunicationException {
260 String location = FroniusBindingConstants.getPowerFlowDataUrl(ip);
261 return collectDataFromUrl(PowerFlowRealtimeResponse.class, location);
265 * Make the InverterRealtimeDataRequest
267 * @param ip address of the device
268 * @param deviceId of the device
269 * @return {InverterRealtimeResponse} the object representation of the json response
271 private InverterRealtimeResponse getRealtimeData(String ip, int deviceId) throws FroniusCommunicationException {
272 String location = FroniusBindingConstants.getInverterDataUrl(ip, deviceId);
273 return collectDataFromUrl(InverterRealtimeResponse.class, location);
277 * Calculate the power value from the given voltage and current channels
279 * @param voltage the voltage ValueUnit
280 * @param current the current ValueUnit
281 * @return {QuantityType<>} the power value calculated by multiplying voltage and current
283 private QuantityType<?> calculatePower(ValueUnit voltage, ValueUnit current) {
284 QuantityType<?> qtyVoltage = getQuantityOrZero(voltage, Units.VOLT);
285 QuantityType<?> qtyCurrent = getQuantityOrZero(current, Units.AMPERE);
286 return qtyVoltage.multiply(qtyCurrent).toUnit(Units.WATT);