]> git.basschouten.com Git - openhab-addons.git/blob
d53e7aeb40ae2fbff4872c3f9f179e66afc20363
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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;
14
15 import java.io.IOException;
16 import java.time.ZonedDateTime;
17 import java.util.HashSet;
18 import java.util.List;
19 import java.util.Set;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22
23 import javax.measure.Quantity;
24 import javax.measure.Unit;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.solax.internal.connectivity.LocalHttpConnector;
29 import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
30 import org.openhab.binding.solax.internal.model.InverterData;
31 import org.openhab.binding.solax.internal.model.InverterType;
32 import org.openhab.binding.solax.internal.model.parsers.RawDataParser;
33 import org.openhab.core.library.types.DateTimeType;
34 import org.openhab.core.library.types.QuantityType;
35 import org.openhab.core.library.types.StringType;
36 import org.openhab.core.library.unit.SIUnits;
37 import org.openhab.core.library.unit.Units;
38 import org.openhab.core.thing.Channel;
39 import org.openhab.core.thing.ChannelUID;
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.thing.binding.BaseThingHandler;
44 import org.openhab.core.types.Command;
45 import org.openhab.core.types.UnDefType;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import com.google.gson.JsonParseException;
50
51 /**
52  * The {@link SolaxLocalAccessHandler} is responsible for handling commands, which are
53  * sent to one of the channels.
54  *
55  * @author Konstantin Polihronov - Initial contribution
56  */
57 @NonNullByDefault
58 public class SolaxLocalAccessHandler extends BaseThingHandler {
59
60     private final Logger logger = LoggerFactory.getLogger(SolaxLocalAccessHandler.class);
61
62     private static final int INITIAL_SCHEDULE_DELAY_SECONDS = 5;
63
64     private @NonNullByDefault({}) LocalHttpConnector localHttpConnector;
65
66     private @Nullable ScheduledFuture<?> schedule;
67
68     private boolean alreadyRemovedUnsupportedChannels;
69
70     private final Set<String> unsupportedExistingChannels = new HashSet<String>();
71
72     public SolaxLocalAccessHandler(Thing thing) {
73         super(thing);
74     }
75
76     @Override
77     public void initialize() {
78         updateStatus(ThingStatus.UNKNOWN);
79
80         SolaxConfiguration config = getConfigAs(SolaxConfiguration.class);
81         localHttpConnector = new LocalHttpConnector(config.password, config.hostname);
82         int refreshInterval = config.refreshInterval;
83         TimeUnit timeUnit = TimeUnit.SECONDS;
84
85         logger.debug("Scheduling regular interval retrieval every {} {}", refreshInterval, timeUnit);
86         schedule = scheduler.scheduleWithFixedDelay(this::retrieveData, INITIAL_SCHEDULE_DELAY_SECONDS, refreshInterval,
87                 timeUnit);
88     }
89
90     private void retrieveData() {
91         try {
92             String rawJsonData = localHttpConnector.retrieveData();
93             logger.debug("Raw data retrieved = {}", rawJsonData);
94
95             if (rawJsonData != null && !rawJsonData.isEmpty()) {
96                 updateFromData(rawJsonData);
97             } else {
98                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
99                         SolaxBindingConstants.I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED);
100             }
101         } catch (IOException e) {
102             logger.debug("Exception received while attempting to retrieve data via HTTP", e);
103             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
104         }
105     }
106
107     private void updateFromData(String rawJsonData) {
108         try {
109             LocalConnectRawDataBean rawDataBean = parseJson(rawJsonData);
110             InverterType inverterType = calculateInverterType(rawDataBean);
111             RawDataParser parser = inverterType.getParser();
112             if (parser != null) {
113                 if (!alreadyRemovedUnsupportedChannels) {
114                     removeUnsupportedChannels(inverterType.getSupportedChannels());
115                     alreadyRemovedUnsupportedChannels = true;
116                 }
117
118                 InverterData genericInverterData = parser.getData(rawDataBean);
119                 updateChannels(parser, genericInverterData);
120                 updateProperties(genericInverterData);
121
122                 if (getThing().getStatus() != ThingStatus.ONLINE) {
123                     updateStatus(ThingStatus.ONLINE);
124                 }
125             } else {
126                 cancelSchedule();
127                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
128                         "@text/offline.configuration-error.parser-not-implemented [\"" + inverterType.name() + "\"]");
129             }
130         } catch (JsonParseException e) {
131             logger.debug("Unable to deserialize from JSON.", e);
132             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
133         }
134     }
135
136     private LocalConnectRawDataBean parseJson(String rawJsonData) {
137         LocalConnectRawDataBean inverterParsedData = LocalConnectRawDataBean.fromJson(rawJsonData);
138         logger.debug("Received a new inverter JSON object. Data = {}", inverterParsedData.toString());
139         return inverterParsedData;
140     }
141
142     private InverterType calculateInverterType(LocalConnectRawDataBean rawDataBean) {
143         int type = rawDataBean.getType();
144         return InverterType.fromIndex(type);
145     }
146
147     private void updateProperties(InverterData genericInverterData) {
148         updateProperty(Thing.PROPERTY_SERIAL_NUMBER, genericInverterData.getWifiSerial());
149         updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, genericInverterData.getInverterType().name());
150     }
151
152     private void updateChannels(RawDataParser parser, InverterData inverterData) {
153         updateState(SolaxBindingConstants.CHANNEL_RAW_DATA, new StringType(inverterData.getRawData()));
154
155         Set<String> supportedChannels = parser.getSupportedChannels();
156         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_POWER, inverterData.getPV1Power(), Units.WATT,
157                 supportedChannels);
158         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_CURRENT, inverterData.getPV1Current(), Units.AMPERE,
159                 supportedChannels);
160         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_VOLTAGE, inverterData.getPV1Voltage(), Units.VOLT,
161                 supportedChannels);
162
163         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_POWER, inverterData.getPV2Power(), Units.WATT,
164                 supportedChannels);
165         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_CURRENT, inverterData.getPV2Current(), Units.AMPERE,
166                 supportedChannels);
167         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_VOLTAGE, inverterData.getPV2Voltage(), Units.VOLT,
168                 supportedChannels);
169
170         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_POWER, inverterData.getPVTotalPower(), Units.WATT,
171                 supportedChannels);
172         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_CURRENT, inverterData.getPVTotalCurrent(),
173                 Units.AMPERE, supportedChannels);
174
175         updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_POWER, inverterData.getBatteryPower(), Units.WATT,
176                 supportedChannels);
177         updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_CURRENT, inverterData.getBatteryCurrent(), Units.AMPERE,
178                 supportedChannels);
179         updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_VOLTAGE, inverterData.getBatteryVoltage(), Units.VOLT,
180                 supportedChannels);
181         updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_TEMPERATURE, inverterData.getBatteryTemperature(),
182                 SIUnits.CELSIUS, supportedChannels);
183         updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_STATE_OF_CHARGE, inverterData.getBatteryLevel(),
184                 Units.PERCENT, supportedChannels);
185         updateChannel(SolaxBindingConstants.CHANNEL_FEED_IN_POWER, inverterData.getFeedInPower(), Units.WATT,
186                 supportedChannels);
187         updateChannel(SolaxBindingConstants.CHANNEL_POWER_USAGE, inverterData.getPowerUsage(), Units.WATT,
188                 supportedChannels);
189
190         // Totals
191         updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_ENERGY, inverterData.getTotalEnergy(), Units.KILOWATT_HOUR,
192                 supportedChannels);
193         updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_DISCHARGE_ENERGY,
194                 inverterData.getTotalBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
195         updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY,
196                 inverterData.getTotalBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
197         updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_PV_ENERGY, inverterData.getTotalPVEnergy(),
198                 Units.KILOWATT_HOUR, supportedChannels);
199         updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_FEED_IN_ENERGY, inverterData.getTotalFeedInEnergy(),
200                 Units.KILOWATT_HOUR, supportedChannels);
201         updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_CONSUMPTION, inverterData.getTotalConsumption(),
202                 Units.KILOWATT_HOUR, supportedChannels);
203
204         // Today's
205         updateChannel(SolaxBindingConstants.CHANNEL_TODAY_ENERGY, inverterData.getTodayEnergy(), Units.KILOWATT_HOUR,
206                 supportedChannels);
207         updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY,
208                 inverterData.getTodayBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
209         updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_CHARGE_ENERGY,
210                 inverterData.getTodayBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
211         updateChannel(SolaxBindingConstants.CHANNEL_TODAY_FEED_IN_ENERGY, inverterData.getTodayFeedInEnergy(),
212                 Units.KILOWATT_HOUR, supportedChannels);
213         updateChannel(SolaxBindingConstants.CHANNEL_TODAY_CONSUMPTION, inverterData.getTodayConsumption(),
214                 Units.KILOWATT_HOUR, supportedChannels);
215
216         // Single phase specific channels
217         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER, inverterData.getInverterOutputPower(),
218                 Units.WATT, supportedChannels);
219         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT, inverterData.getInverterCurrent(),
220                 Units.AMPERE, supportedChannels);
221         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE, inverterData.getInverterVoltage(),
222                 Units.VOLT, supportedChannels);
223         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY, inverterData.getInverterFrequency(),
224                 Units.HERTZ, supportedChannels);
225
226         // Three phase specific channels
227         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, inverterData.getOutputPowerPhase1(),
228                 Units.WATT, supportedChannels);
229         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE2, inverterData.getOutputPowerPhase2(),
230                 Units.WATT, supportedChannels);
231         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, inverterData.getOutputPowerPhase3(),
232                 Units.WATT, supportedChannels);
233         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, inverterData.getTotalOutputPower(),
234                 Units.WATT, supportedChannels);
235
236         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, inverterData.getCurrentPhase1(),
237                 Units.AMPERE, supportedChannels);
238         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, inverterData.getCurrentPhase2(),
239                 Units.AMPERE, supportedChannels);
240         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, inverterData.getCurrentPhase3(),
241                 Units.AMPERE, supportedChannels);
242
243         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, inverterData.getVoltagePhase1(),
244                 Units.VOLT, supportedChannels);
245         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, inverterData.getVoltagePhase2(),
246                 Units.VOLT, supportedChannels);
247         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, inverterData.getVoltagePhase3(),
248                 Units.VOLT, supportedChannels);
249
250         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, inverterData.getFrequencyPhase1(),
251                 Units.HERTZ, supportedChannels);
252         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2, inverterData.getFrequencyPhase2(),
253                 Units.HERTZ, supportedChannels);
254         updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, inverterData.getFrequencyPhase3(),
255                 Units.HERTZ, supportedChannels);
256
257         // Binding provided data
258         updateState(SolaxBindingConstants.CHANNEL_TIMESTAMP, new DateTimeType(ZonedDateTime.now()));
259     }
260
261     private void removeUnsupportedChannels(Set<String> supportedChannels) {
262         if (supportedChannels.isEmpty()) {
263             return;
264         }
265         List<Channel> channels = getThing().getChannels();
266         List<Channel> channelsToRemove = channels.stream()
267                 .filter(channel -> !supportedChannels.contains(channel.getUID().getId())).toList();
268
269         if (!channelsToRemove.isEmpty()) {
270             if (logger.isDebugEnabled()) {
271                 logRemovedChannels(channelsToRemove);
272             }
273             updateThing(editThing().withoutChannels(channelsToRemove).build());
274         }
275     }
276
277     private void logRemovedChannels(List<Channel> channelsToRemove) {
278         List<String> channelsToRemoveForLog = channelsToRemove.stream().map(channel -> channel.getUID().getId())
279                 .toList();
280         logger.debug("Detected unsupported channels for the current inverter. Channels to be removed: {}",
281                 channelsToRemoveForLog);
282     }
283
284     @Override
285     public void handleCommand(ChannelUID channelUID, Command command) {
286         // Nothing to do here as of now. Maybe implement a REFRESH command in the future.
287     }
288
289     @Override
290     public void dispose() {
291         super.dispose();
292         cancelSchedule();
293     }
294
295     private void cancelSchedule() {
296         ScheduledFuture<?> schedule = this.schedule;
297         if (schedule != null) {
298             schedule.cancel(true);
299             this.schedule = null;
300         }
301     }
302
303     private <T extends Quantity<T>> void updateChannel(String channelID, double value, Unit<T> unit,
304             Set<String> supportedChannels) {
305         if (supportedChannels.contains(channelID)) {
306             if (value > Short.MIN_VALUE) {
307                 updateState(channelID, new QuantityType<>(value, unit));
308             } else if (!unsupportedExistingChannels.contains(channelID)) {
309                 updateState(channelID, UnDefType.UNDEF);
310                 unsupportedExistingChannels.add(channelID);
311                 logger.warn(
312                         "Channel {} is marked as supported, but its value is out of the defined range. Value = {}. This is unexpected behaviour. Please file a bug.",
313                         channelID, value);
314             }
315         }
316     }
317 }