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.solax.internal.handlers;
15 import java.time.ZonedDateTime;
16 import java.util.HashSet;
17 import java.util.List;
20 import javax.measure.Quantity;
21 import javax.measure.Unit;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.openhab.binding.solax.internal.SolaxBindingConstants;
25 import org.openhab.binding.solax.internal.SolaxConfiguration;
26 import org.openhab.binding.solax.internal.connectivity.LocalHttpConnector;
27 import org.openhab.binding.solax.internal.connectivity.SolaxConnector;
28 import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
29 import org.openhab.binding.solax.internal.model.InverterType;
30 import org.openhab.binding.solax.internal.model.local.LocalInverterData;
31 import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser;
32 import org.openhab.core.i18n.TimeZoneProvider;
33 import org.openhab.core.i18n.TranslationProvider;
34 import org.openhab.core.library.types.DateTimeType;
35 import org.openhab.core.library.types.QuantityType;
36 import org.openhab.core.library.types.StringType;
37 import org.openhab.core.library.unit.SIUnits;
38 import org.openhab.core.library.unit.Units;
39 import org.openhab.core.thing.Channel;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.types.UnDefType;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
47 import com.google.gson.JsonParseException;
50 * The {@link SolaxLocalAccessHandler} is responsible for handling commands, which are
51 * sent to one of the channels.
53 * @author Konstantin Polihronov - Initial contribution
56 public class SolaxLocalAccessHandler extends AbstractSolaxHandler {
58 private final Logger logger = LoggerFactory.getLogger(SolaxLocalAccessHandler.class);
60 private boolean alreadyRemovedUnsupportedChannels;
62 private final Set<String> unsupportedExistingChannels = new HashSet<>();
64 public SolaxLocalAccessHandler(Thing thing, TranslationProvider i18nProvider, TimeZoneProvider timeZoneProvider) {
65 super(thing, i18nProvider, timeZoneProvider);
69 protected SolaxConnector createConnector(SolaxConfiguration config) {
70 return new LocalHttpConnector(config.password, config.hostname);
74 protected void updateFromData(String rawJsonData) {
76 LocalConnectRawDataBean rawDataBean = parseJson(rawJsonData);
77 InverterType inverterType = calculateInverterType(rawDataBean);
78 RawDataParser parser = inverterType.getParser();
80 if (!alreadyRemovedUnsupportedChannels) {
81 removeUnsupportedChannels(inverterType.getSupportedChannels());
82 alreadyRemovedUnsupportedChannels = true;
85 LocalInverterData genericInverterData = parser.getData(rawDataBean);
86 updateChannels(parser, genericInverterData);
87 updateProperties(genericInverterData);
90 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
91 "@text/offline.configuration-error.parser-not-implemented [\"" + inverterType.name() + "\"]");
93 } catch (JsonParseException e) {
94 logger.debug("Unable to deserialize from JSON.", e);
95 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
99 private LocalConnectRawDataBean parseJson(String rawJsonData) {
100 LocalConnectRawDataBean fromJson = LocalConnectRawDataBean.fromJson(rawJsonData);
101 logger.debug("Received a new inverter JSON object. Data = {}", fromJson.toString());
105 private InverterType calculateInverterType(LocalConnectRawDataBean rawDataBean) {
106 int type = rawDataBean.getType();
107 return InverterType.fromIndex(type);
110 private void updateProperties(LocalInverterData genericInverterData) {
111 updateProperty(Thing.PROPERTY_SERIAL_NUMBER, genericInverterData.getWifiSerial());
112 updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, genericInverterData.getInverterType().name());
115 private void updateChannels(RawDataParser parser, LocalInverterData inverterData) {
116 updateState(SolaxBindingConstants.CHANNEL_RAW_DATA, new StringType(inverterData.getRawData()));
118 Set<String> supportedChannels = parser.getSupportedChannels();
119 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_POWER, inverterData.getPV1Power(), Units.WATT,
121 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_CURRENT, inverterData.getPV1Current(), Units.AMPERE,
123 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_VOLTAGE, inverterData.getPV1Voltage(), Units.VOLT,
126 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_POWER, inverterData.getPV2Power(), Units.WATT,
128 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_CURRENT, inverterData.getPV2Current(), Units.AMPERE,
130 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_VOLTAGE, inverterData.getPV2Voltage(), Units.VOLT,
133 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_POWER, inverterData.getPVTotalPower(), Units.WATT,
135 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_CURRENT, inverterData.getPVTotalCurrent(),
136 Units.AMPERE, supportedChannels);
138 updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_POWER, inverterData.getBatteryPower(), Units.WATT,
140 updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_CURRENT, inverterData.getBatteryCurrent(), Units.AMPERE,
142 updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_VOLTAGE, inverterData.getBatteryVoltage(), Units.VOLT,
144 updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_TEMPERATURE, inverterData.getBatteryTemperature(),
145 SIUnits.CELSIUS, supportedChannels);
146 updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_STATE_OF_CHARGE, inverterData.getBatteryLevel(),
147 Units.PERCENT, supportedChannels);
148 updateChannel(SolaxBindingConstants.CHANNEL_FEED_IN_POWER, inverterData.getFeedInPower(), Units.WATT,
150 updateChannel(SolaxBindingConstants.CHANNEL_POWER_USAGE, inverterData.getPowerUsage(), Units.WATT,
152 updateState(SolaxBindingConstants.CHANNEL_INVERTER_WORKMODE,
153 new StringType(inverterData.getInverterWorkMode()));
156 updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_ENERGY, inverterData.getTotalEnergy(), Units.KILOWATT_HOUR,
158 updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_DISCHARGE_ENERGY,
159 inverterData.getTotalBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
160 updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY,
161 inverterData.getTotalBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
162 updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_PV_ENERGY, inverterData.getTotalPVEnergy(),
163 Units.KILOWATT_HOUR, supportedChannels);
164 updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_FEED_IN_ENERGY, inverterData.getTotalFeedInEnergy(),
165 Units.KILOWATT_HOUR, supportedChannels);
166 updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_CONSUMPTION, inverterData.getTotalConsumption(),
167 Units.KILOWATT_HOUR, supportedChannels);
170 updateChannel(SolaxBindingConstants.CHANNEL_TODAY_ENERGY, inverterData.getTodayEnergy(), Units.KILOWATT_HOUR,
172 updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY,
173 inverterData.getTodayBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
174 updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_CHARGE_ENERGY,
175 inverterData.getTodayBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
176 updateChannel(SolaxBindingConstants.CHANNEL_TODAY_FEED_IN_ENERGY, inverterData.getTodayFeedInEnergy(),
177 Units.KILOWATT_HOUR, supportedChannels);
178 updateChannel(SolaxBindingConstants.CHANNEL_TODAY_CONSUMPTION, inverterData.getTodayConsumption(),
179 Units.KILOWATT_HOUR, supportedChannels);
181 // Single phase specific channels
182 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER, inverterData.getInverterOutputPower(),
183 Units.WATT, supportedChannels);
184 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT, inverterData.getInverterCurrent(),
185 Units.AMPERE, supportedChannels);
186 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE, inverterData.getInverterVoltage(),
187 Units.VOLT, supportedChannels);
188 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY, inverterData.getInverterFrequency(),
189 Units.HERTZ, supportedChannels);
191 // Three phase specific channels
192 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, inverterData.getOutputPowerPhase1(),
193 Units.WATT, supportedChannels);
194 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE2, inverterData.getOutputPowerPhase2(),
195 Units.WATT, supportedChannels);
196 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, inverterData.getOutputPowerPhase3(),
197 Units.WATT, supportedChannels);
198 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, inverterData.getTotalOutputPower(),
199 Units.WATT, supportedChannels);
201 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, inverterData.getCurrentPhase1(),
202 Units.AMPERE, supportedChannels);
203 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, inverterData.getCurrentPhase2(),
204 Units.AMPERE, supportedChannels);
205 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, inverterData.getCurrentPhase3(),
206 Units.AMPERE, supportedChannels);
208 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, inverterData.getVoltagePhase1(),
209 Units.VOLT, supportedChannels);
210 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, inverterData.getVoltagePhase2(),
211 Units.VOLT, supportedChannels);
212 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, inverterData.getVoltagePhase3(),
213 Units.VOLT, supportedChannels);
215 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, inverterData.getFrequencyPhase1(),
216 Units.HERTZ, supportedChannels);
217 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2, inverterData.getFrequencyPhase2(),
218 Units.HERTZ, supportedChannels);
219 updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, inverterData.getFrequencyPhase3(),
220 Units.HERTZ, supportedChannels);
222 // Binding provided data
223 updateState(SolaxBindingConstants.CHANNEL_TIMESTAMP, new DateTimeType(ZonedDateTime.now()));
226 private void removeUnsupportedChannels(Set<String> supportedChannels) {
227 if (supportedChannels.isEmpty()) {
230 List<Channel> channels = getThing().getChannels();
231 List<Channel> channelsToRemove = channels.stream()
232 .filter(channel -> !supportedChannels.contains(channel.getUID().getId())).toList();
234 if (!channelsToRemove.isEmpty()) {
235 if (logger.isDebugEnabled()) {
236 logRemovedChannels(channelsToRemove);
238 updateThing(editThing().withoutChannels(channelsToRemove).build());
242 private void logRemovedChannels(List<Channel> channelsToRemove) {
243 List<String> channelsToRemoveForLog = channelsToRemove.stream().map(channel -> channel.getUID().getId())
245 logger.debug("Detected unsupported channels for the current inverter. Channels to be removed: {}",
246 channelsToRemoveForLog);
249 private <T extends Quantity<T>> void updateChannel(String channelID, double value, Unit<T> unit,
250 Set<String> supportedChannels) {
251 if (supportedChannels.contains(channelID)) {
252 if (value > Short.MIN_VALUE) {
253 updateState(channelID, new QuantityType<>(value, unit));
254 } else if (!unsupportedExistingChannels.contains(channelID)) {
255 updateState(channelID, UnDefType.UNDEF);
256 unsupportedExistingChannels.add(channelID);
258 "Channel {} is marked as supported, but its value is out of the defined range. Value = {}. This is unexpected behaviour. Please file a bug.",