]> git.basschouten.com Git - openhab-addons.git/blob
60f59e74eb0663fb4d9e6d81b91a838fcd7e98d0
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.solax.internal.handlers;
14
15 import java.time.ZonedDateTime;
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.Set;
19
20 import javax.measure.Quantity;
21 import javax.measure.Unit;
22
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;
46
47 import com.google.gson.JsonParseException;
48
49 /**
50  * The {@link SolaxLocalAccessHandler} is responsible for handling commands, which are
51  * sent to one of the channels.
52  *
53  * @author Konstantin Polihronov - Initial contribution
54  */
55 @NonNullByDefault
56 public class SolaxLocalAccessHandler extends AbstractSolaxHandler {
57
58     private final Logger logger = LoggerFactory.getLogger(SolaxLocalAccessHandler.class);
59
60     private boolean alreadyRemovedUnsupportedChannels;
61
62     private final Set<String> unsupportedExistingChannels = new HashSet<>();
63
64     public SolaxLocalAccessHandler(Thing thing, TranslationProvider i18nProvider, TimeZoneProvider timeZoneProvider) {
65         super(thing, i18nProvider, timeZoneProvider);
66     }
67
68     @Override
69     protected SolaxConnector createConnector(SolaxConfiguration config) {
70         return new LocalHttpConnector(config.password, config.hostname);
71     }
72
73     @Override
74     protected void updateFromData(String rawJsonData) {
75         try {
76             LocalConnectRawDataBean rawDataBean = parseJson(rawJsonData);
77             InverterType inverterType = calculateInverterType(rawDataBean);
78             RawDataParser parser = inverterType.getParser();
79             if (parser != null) {
80                 if (!alreadyRemovedUnsupportedChannels) {
81                     removeUnsupportedChannels(inverterType.getSupportedChannels());
82                     alreadyRemovedUnsupportedChannels = true;
83                 }
84
85                 LocalInverterData genericInverterData = parser.getData(rawDataBean);
86                 updateChannels(parser, genericInverterData);
87                 updateProperties(genericInverterData);
88             } else {
89                 cancelSchedule();
90                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
91                         "@text/offline.configuration-error.parser-not-implemented [\"" + inverterType.name() + "\"]");
92             }
93         } catch (JsonParseException e) {
94             logger.debug("Unable to deserialize from JSON.", e);
95             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
96         }
97     }
98
99     private LocalConnectRawDataBean parseJson(String rawJsonData) {
100         LocalConnectRawDataBean fromJson = LocalConnectRawDataBean.fromJson(rawJsonData);
101         logger.debug("Received a new inverter JSON object. Data = {}", fromJson.toString());
102         return fromJson;
103     }
104
105     private InverterType calculateInverterType(LocalConnectRawDataBean rawDataBean) {
106         int type = rawDataBean.getType();
107         return InverterType.fromIndex(type);
108     }
109
110     private void updateProperties(LocalInverterData genericInverterData) {
111         updateProperty(Thing.PROPERTY_SERIAL_NUMBER, genericInverterData.getWifiSerial());
112         updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, genericInverterData.getInverterType().name());
113     }
114
115     private void updateChannels(RawDataParser parser, LocalInverterData inverterData) {
116         updateState(SolaxBindingConstants.CHANNEL_RAW_DATA, new StringType(inverterData.getRawData()));
117
118         Set<String> supportedChannels = parser.getSupportedChannels();
119         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_POWER, inverterData.getPV1Power(), Units.WATT,
120                 supportedChannels);
121         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_CURRENT, inverterData.getPV1Current(), Units.AMPERE,
122                 supportedChannels);
123         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_VOLTAGE, inverterData.getPV1Voltage(), Units.VOLT,
124                 supportedChannels);
125
126         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_POWER, inverterData.getPV2Power(), Units.WATT,
127                 supportedChannels);
128         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_CURRENT, inverterData.getPV2Current(), Units.AMPERE,
129                 supportedChannels);
130         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_VOLTAGE, inverterData.getPV2Voltage(), Units.VOLT,
131                 supportedChannels);
132
133         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_POWER, inverterData.getPVTotalPower(), Units.WATT,
134                 supportedChannels);
135         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_CURRENT, inverterData.getPVTotalCurrent(),
136                 Units.AMPERE, supportedChannels);
137
138         updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_POWER, inverterData.getBatteryPower(), Units.WATT,
139                 supportedChannels);
140         updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_CURRENT, inverterData.getBatteryCurrent(), Units.AMPERE,
141                 supportedChannels);
142         updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_VOLTAGE, inverterData.getBatteryVoltage(), Units.VOLT,
143                 supportedChannels);
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,
149                 supportedChannels);
150         updateChannel(SolaxBindingConstants.CHANNEL_POWER_USAGE, inverterData.getPowerUsage(), Units.WATT,
151                 supportedChannels);
152         updateState(SolaxBindingConstants.CHANNEL_INVERTER_WORKMODE,
153                 new StringType(inverterData.getInverterWorkMode()));
154
155         // Totals
156         updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_ENERGY, inverterData.getTotalEnergy(), Units.KILOWATT_HOUR,
157                 supportedChannels);
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);
168
169         // Today's
170         updateChannel(SolaxBindingConstants.CHANNEL_TODAY_ENERGY, inverterData.getTodayEnergy(), Units.KILOWATT_HOUR,
171                 supportedChannels);
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);
180
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);
190
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);
200
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);
207
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);
214
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);
221
222         // Binding provided data
223         updateState(SolaxBindingConstants.CHANNEL_TIMESTAMP, new DateTimeType(ZonedDateTime.now()));
224     }
225
226     private void removeUnsupportedChannels(Set<String> supportedChannels) {
227         if (supportedChannels.isEmpty()) {
228             return;
229         }
230         List<Channel> channels = getThing().getChannels();
231         List<Channel> channelsToRemove = channels.stream()
232                 .filter(channel -> !supportedChannels.contains(channel.getUID().getId())).toList();
233
234         if (!channelsToRemove.isEmpty()) {
235             if (logger.isDebugEnabled()) {
236                 logRemovedChannels(channelsToRemove);
237             }
238             updateThing(editThing().withoutChannels(channelsToRemove).build());
239         }
240     }
241
242     private void logRemovedChannels(List<Channel> channelsToRemove) {
243         List<String> channelsToRemoveForLog = channelsToRemove.stream().map(channel -> channel.getUID().getId())
244                 .toList();
245         logger.debug("Detected unsupported channels for the current inverter. Channels to be removed: {}",
246                 channelsToRemoveForLog);
247     }
248
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);
257                 logger.warn(
258                         "Channel {} is marked as supported, but its value is out of the defined range. Value = {}. This is unexpected behaviour. Please file a bug.",
259                         channelID, value);
260             }
261         }
262     }
263 }