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.openhab.binding.fronius.internal.FroniusBaseDeviceConfiguration;
20 import org.openhab.binding.fronius.internal.FroniusBindingConstants;
21 import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
22 import org.openhab.binding.fronius.internal.FroniusCommunicationException;
23 import org.openhab.binding.fronius.internal.api.InverterRealtimeBodyData;
24 import org.openhab.binding.fronius.internal.api.InverterRealtimeResponse;
25 import org.openhab.binding.fronius.internal.api.PowerFlowRealtimeInverter;
26 import org.openhab.binding.fronius.internal.api.PowerFlowRealtimeResponse;
27 import org.openhab.binding.fronius.internal.api.PowerFlowRealtimeSite;
28 import org.openhab.binding.fronius.internal.api.ValueUnit;
29 import org.openhab.core.library.types.DecimalType;
30 import org.openhab.core.library.types.QuantityType;
31 import org.openhab.core.library.unit.Units;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.types.State;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
38 * The {@link FroniusSymoInverterHandler} is responsible for updating the data, which are
39 * sent to one of the channels.
41 * @author Thomas Rokohl - Initial contribution
42 * @author Peter Schraffl - Added device status and error status channels
43 * @author Thomas Kordelle - Added inverter power, battery state of charge and PV solar yield
44 * @author Jimmy Tanagra - Add powerflow autonomy, self consumption channels
46 public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
48 private final Logger logger = LoggerFactory.getLogger(FroniusSymoInverterHandler.class);
49 private InverterRealtimeResponse inverterRealtimeResponse;
50 private PowerFlowRealtimeResponse powerFlowResponse;
51 private FroniusBaseDeviceConfiguration config;
53 public FroniusSymoInverterHandler(Thing thing) {
58 protected String getDescription() {
59 return "Fronius Symo Inverter";
63 protected void handleRefresh(FroniusBridgeConfiguration bridgeConfiguration) throws FroniusCommunicationException {
64 updateData(bridgeConfiguration, config);
69 public void initialize() {
70 config = getConfigAs(FroniusBaseDeviceConfiguration.class);
75 * Update the channel from the last data retrieved
77 * @param channelId the id identifying the channel to be updated
78 * @return the last retrieved data
81 protected State getValue(String channelId) {
82 final String[] fields = channelId.split("#");
83 if (fields.length < 1) {
86 final String fieldName = fields[0];
88 if (inverterRealtimeResponse != null) {
89 InverterRealtimeBodyData inverterData = inverterRealtimeResponse.getBody().getData();
91 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_PAC:
92 return getQuantityOrZero(inverterData.getPac(), Units.WATT);
93 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_FAC:
94 return getQuantityOrZero(inverterData.getFac(), Units.HERTZ);
95 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_IAC:
96 return getQuantityOrZero(inverterData.getIac(), Units.AMPERE);
97 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_IDC:
98 return getQuantityOrZero(inverterData.getIdc(), Units.AMPERE);
99 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_IDC2:
100 return getQuantityOrZero(inverterData.getIdc2(), Units.AMPERE);
101 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_IDC3:
102 return getQuantityOrZero(inverterData.getIdc3(), Units.AMPERE);
103 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_UAC:
104 return getQuantityOrZero(inverterData.getUac(), Units.VOLT);
105 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_UDC:
106 return getQuantityOrZero(inverterData.getUdc(), Units.VOLT);
107 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_UDC2:
108 return getQuantityOrZero(inverterData.getUdc2(), Units.VOLT);
109 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_UDC3:
110 return getQuantityOrZero(inverterData.getUdc3(), Units.VOLT);
111 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_PDC:
112 return calculatePower(inverterData.getUdc(), inverterData.getIdc());
113 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_PDC2:
114 return calculatePower(inverterData.getUdc2(), inverterData.getIdc2());
115 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_PDC3:
116 return calculatePower(inverterData.getUdc3(), inverterData.getIdc3());
117 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_DAY_ENERGY:
118 // Convert the unit to kWh for backwards compatibility with non-quantity type
119 return getQuantityOrZero(inverterData.getDayEnergy(), Units.KILOWATT_HOUR).toUnit("kWh");
120 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_TOTAL:
121 // Convert the unit to MWh for backwards compatibility with non-quantity type
122 return getQuantityOrZero(inverterData.getTotalEnergy(), Units.MEGAWATT_HOUR).toUnit("MWh");
123 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_YEAR:
124 // Convert the unit to MWh for backwards compatibility with non-quantity type
125 return getQuantityOrZero(inverterData.getYearEnergy(), Units.MEGAWATT_HOUR).toUnit("MWh");
126 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_DEVICE_STATUS_ERROR_CODE:
127 return new DecimalType(inverterData.getDeviceStatus().getErrorCode());
128 case FroniusBindingConstants.INVERTER_DATA_CHANNEL_DEVICE_STATUS_STATUS_CODE:
129 return new DecimalType(inverterData.getDeviceStatus().getStatusCode());
135 if (powerFlowResponse != null) {
136 PowerFlowRealtimeSite site = powerFlowResponse.getBody().getData().getSite();
138 case FroniusBindingConstants.POWER_FLOW_P_GRID:
139 return new QuantityType<>(site.getPgrid(), Units.WATT);
140 case FroniusBindingConstants.POWER_FLOW_P_LOAD:
141 return new QuantityType<>(site.getPload(), Units.WATT);
142 case FroniusBindingConstants.POWER_FLOW_P_AKKU:
143 return new QuantityType<>(site.getPakku(), Units.WATT);
144 case FroniusBindingConstants.POWER_FLOW_P_PV:
145 return new QuantityType<>(site.getPpv(), Units.WATT);
146 case FroniusBindingConstants.POWER_FLOW_AUTONOMY:
147 return new QuantityType<>(site.getRelAutonomy(), Units.PERCENT);
148 case FroniusBindingConstants.POWER_FLOW_SELF_CONSUMPTION:
149 return new QuantityType<>(site.getRelSelfConsumption(), Units.PERCENT);
150 case FroniusBindingConstants.POWER_FLOW_INVERTER_POWER:
151 return new QuantityType<>(getInverter(config.deviceId).getP(), Units.WATT);
152 case FroniusBindingConstants.POWER_FLOW_INVERTER_SOC:
153 return new QuantityType<>(getInverter(config.deviceId).getSoc(), Units.PERCENT);
155 // Kept for backwards compatibility
156 case FroniusBindingConstants.POWER_FLOW_INVERTER_1_POWER:
157 return new QuantityType<>(getInverter(1).getP(), Units.WATT);
158 case FroniusBindingConstants.POWER_FLOW_INVERTER_1_SOC:
159 return new QuantityType<>(getInverter(1).getSoc(), Units.PERCENT);
169 * get flow data for a specific inverter.
171 * @param number The inverter object of the given index
172 * @return a PowerFlowRealtimeInverter object.
174 private PowerFlowRealtimeInverter getInverter(final int number) {
175 return powerFlowResponse.getBody().getData().getInverters().get(Integer.toString(number));
179 * Return the value as QuantityType with the unit extracted from ValueUnit
180 * or a zero QuantityType with the given unit argument when value is null
182 * @param value The ValueUnit data
183 * @param unit The default unit to use when value is null
184 * @return a QuantityType from the given value
186 private QuantityType<?> getQuantityOrZero(ValueUnit value, Unit unit) {
187 return Optional.ofNullable(value).map(val -> val.asQuantityType().toUnit(unit))
188 .orElse(new QuantityType<>(0, unit));
194 private void updateData(FroniusBridgeConfiguration bridgeConfiguration, FroniusBaseDeviceConfiguration config)
195 throws FroniusCommunicationException {
196 inverterRealtimeResponse = getRealtimeData(bridgeConfiguration.hostname, config.deviceId);
197 powerFlowResponse = getPowerFlowRealtime(bridgeConfiguration.hostname);
201 * Make the PowerFlowRealtimeDataRequest
203 * @param ip address of the device
204 * @return {PowerFlowRealtimeResponse} the object representation of the json response
206 private PowerFlowRealtimeResponse getPowerFlowRealtime(String ip) throws FroniusCommunicationException {
207 String location = FroniusBindingConstants.getPowerFlowDataUrl(ip);
208 return collectDataFromUrl(PowerFlowRealtimeResponse.class, location);
212 * Make the InverterRealtimeDataRequest
214 * @param ip address of the device
215 * @param deviceId of the device
216 * @return {InverterRealtimeResponse} the object representation of the json response
218 private InverterRealtimeResponse getRealtimeData(String ip, int deviceId) throws FroniusCommunicationException {
219 String location = FroniusBindingConstants.getInverterDataUrl(ip, deviceId);
220 return collectDataFromUrl(InverterRealtimeResponse.class, location);
224 * Calculate the power value from the given voltage and current channels
226 * @param voltage the voltage ValueUnit
227 * @param current the current ValueUnit
228 * @return {QuantityType<>} the power value calculated by multiplying voltage and current
230 private QuantityType<?> calculatePower(ValueUnit voltage, ValueUnit current) {
231 QuantityType<?> qtyVoltage = getQuantityOrZero(voltage, Units.VOLT);
232 QuantityType<?> qtyCurrent = getQuantityOrZero(current, Units.AMPERE);
233 return qtyVoltage.multiply(qtyCurrent).toUnit(Units.WATT);