]> git.basschouten.com Git - openhab-addons.git/blob
78902720e14e974029fa4e119821ca57e1942c28
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.senechome.internal;
14
15 import static org.openhab.core.types.RefreshType.REFRESH;
16
17 import java.io.IOException;
18 import java.math.BigDecimal;
19 import java.math.RoundingMode;
20 import java.time.Duration;
21 import java.util.Date;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
25 import java.util.concurrent.TimeoutException;
26
27 import javax.measure.quantity.Dimensionless;
28 import javax.measure.quantity.ElectricCurrent;
29 import javax.measure.quantity.ElectricPotential;
30 import javax.measure.quantity.Energy;
31 import javax.measure.quantity.Frequency;
32 import javax.measure.quantity.Power;
33
34 import org.eclipse.jdt.annotation.NonNullByDefault;
35 import org.eclipse.jdt.annotation.Nullable;
36 import org.eclipse.jetty.client.HttpClient;
37 import org.openhab.binding.senechome.internal.json.SenecHomeResponse;
38 import org.openhab.core.cache.ExpiringCache;
39 import org.openhab.core.library.types.DecimalType;
40 import org.openhab.core.library.types.OnOffType;
41 import org.openhab.core.library.types.QuantityType;
42 import org.openhab.core.library.types.StringType;
43 import org.openhab.core.library.unit.SmartHomeUnits;
44 import org.openhab.core.thing.Channel;
45 import org.openhab.core.thing.ChannelUID;
46 import org.openhab.core.thing.Thing;
47 import org.openhab.core.thing.ThingStatus;
48 import org.openhab.core.thing.ThingStatusDetail;
49 import org.openhab.core.thing.binding.BaseThingHandler;
50 import org.openhab.core.types.Command;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 /**
55  * The {@link SenecHomeHandler} is responsible for handling commands, which are
56  * sent to one of the channels.
57  *
58  * @author Steven Schwarznau - Initial contribution
59  */
60 @NonNullByDefault
61 public class SenecHomeHandler extends BaseThingHandler {
62
63     private final Logger logger = LoggerFactory.getLogger(SenecHomeHandler.class);
64     private final static String VALUE_TYPE_INT = "u3";
65     private final static String VALUE_TYPE_UNSIGNED_INT = "u8";
66     private final static String VALUE_TYPE_FLOAT = "fl";
67
68     private @Nullable ScheduledFuture<?> refreshJob;
69     private @Nullable PowerLimitationStatusDTO limitationStatus = null;
70     private final @Nullable SenecHomeApi senecHomeApi;
71     private SenecHomeConfigurationDTO config = new SenecHomeConfigurationDTO();
72     private final ExpiringCache<Boolean> refreshCache = new ExpiringCache<>(Duration.ofSeconds(5), this::refreshState);
73
74     public SenecHomeHandler(Thing thing, HttpClient httpClient) {
75         super(thing);
76         this.senecHomeApi = new SenecHomeApi(httpClient);
77     }
78
79     @Override
80     public void handleRemoval() {
81         stopJobIfRunning();
82         super.handleRemoval();
83     }
84
85     @Override
86     public void handleCommand(ChannelUID channelUID, Command command) {
87         if (command == REFRESH) {
88             logger.debug("Refreshing {}", channelUID);
89             refresh();
90         } else {
91             logger.trace("The SenecHome-Binding is a read-only binding and can not handle commands");
92         }
93     }
94
95     @Override
96     public void dispose() {
97         stopJobIfRunning();
98         super.dispose();
99     }
100
101     protected void stopJobIfRunning() {
102         final ScheduledFuture<?> refreshJob = this.refreshJob;
103         if (refreshJob != null && !refreshJob.isCancelled()) {
104             refreshJob.cancel(true);
105             this.refreshJob = null;
106         }
107     }
108
109     @Override
110     public void initialize() {
111         config = getConfigAs(SenecHomeConfigurationDTO.class);
112         senecHomeApi.setHostname(config.hostname);
113         refreshJob = scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refreshInterval, TimeUnit.SECONDS);
114         limitationStatus = null;
115     }
116
117     private void refresh() {
118         refreshCache.getValue();
119     }
120
121     public @Nullable Boolean refreshState() {
122         try {
123             SenecHomeResponse response = senecHomeApi.getStatistics();
124             logger.trace("received {}", response);
125
126             BigDecimal pvLimitation = new BigDecimal(100).subtract(getSenecValue(response.limitation.powerLimitation))
127                     .setScale(0, RoundingMode.HALF_UP);
128
129             updateState(SenecHomeBindingConstants.CHANNEL_SENEC_POWER_LIMITATION,
130                     new QuantityType<Dimensionless>(pvLimitation, SmartHomeUnits.PERCENT));
131
132             Channel channelLimitationState = getThing()
133                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_POWER_LIMITATION_STATE);
134             if (channelLimitationState != null) {
135                 updatePowerLimitationStatus(channelLimitationState,
136                         (100 - pvLimitation.intValue()) <= config.limitationTresholdValue, config.limitationDuration);
137             }
138
139             Channel channelConsumption = getThing()
140                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_POWER_CONSUMPTION);
141             if (channelConsumption != null) {
142                 updateState(channelConsumption.getUID(),
143                         new QuantityType<Power>(
144                                 getSenecValue(response.energy.homePowerConsumption).setScale(2, RoundingMode.HALF_UP),
145                                 SmartHomeUnits.WATT));
146             }
147
148             Channel channelEnergyProduction = getThing()
149                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_ENERGY_PRODUCTION);
150             if (channelEnergyProduction != null) {
151                 updateState(channelEnergyProduction.getUID(), new QuantityType<Power>(
152                         getSenecValue(response.energy.inverterPowerGeneration).setScale(0, RoundingMode.HALF_UP),
153                         SmartHomeUnits.WATT));
154             }
155
156             Channel channelBatteryPower = getThing().getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_BATTERY_POWER);
157             if (channelBatteryPower != null) {
158                 updateState(channelBatteryPower.getUID(),
159                         new QuantityType<Power>(
160                                 getSenecValue(response.energy.batteryPower).setScale(2, RoundingMode.HALF_UP),
161                                 SmartHomeUnits.WATT));
162             }
163
164             Channel channelBatteryFuelCharge = getThing()
165                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_BATTERY_FUEL_CHARGE);
166             if (channelBatteryFuelCharge != null) {
167                 updateState(channelBatteryFuelCharge.getUID(),
168                         new QuantityType<Dimensionless>(
169                                 getSenecValue(response.energy.batteryFuelCharge).setScale(0, RoundingMode.HALF_UP),
170                                 SmartHomeUnits.PERCENT));
171             }
172
173             Channel channelGridCurrentPhase1 = getThing()
174                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_CURRENT_PH1);
175             updateState(channelGridCurrentPhase1.getUID(), new QuantityType<ElectricCurrent>(
176                     getSenecValue(response.grid.currentGridCurrentPerPhase[0]).setScale(2, RoundingMode.HALF_UP),
177                     SmartHomeUnits.AMPERE));
178
179             Channel channelGridCurrentPhase2 = getThing()
180                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_CURRENT_PH2);
181             updateState(channelGridCurrentPhase2.getUID(), new QuantityType<ElectricCurrent>(
182                     getSenecValue(response.grid.currentGridCurrentPerPhase[1]).setScale(2, RoundingMode.HALF_UP),
183                     SmartHomeUnits.AMPERE));
184
185             Channel channelGridCurrentPhase3 = getThing()
186                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_CURRENT_PH3);
187             updateState(channelGridCurrentPhase3.getUID(), new QuantityType<ElectricCurrent>(
188                     getSenecValue(response.grid.currentGridCurrentPerPhase[2]).setScale(2, RoundingMode.HALF_UP),
189                     SmartHomeUnits.AMPERE));
190
191             Channel channelGridPowerPhase1 = getThing()
192                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER_PH1);
193             updateState(channelGridPowerPhase1.getUID(),
194                     new QuantityType<Power>(
195                             getSenecValue(response.grid.currentGridPowerPerPhase[0]).setScale(2, RoundingMode.HALF_UP),
196                             SmartHomeUnits.WATT));
197
198             Channel channelGridPowerPhase2 = getThing()
199                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER_PH2);
200             updateState(channelGridPowerPhase2.getUID(),
201                     new QuantityType<Power>(
202                             getSenecValue(response.grid.currentGridPowerPerPhase[1]).setScale(2, RoundingMode.HALF_UP),
203                             SmartHomeUnits.WATT));
204
205             Channel channelGridPowerPhase3 = getThing()
206                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER_PH3);
207             updateState(channelGridPowerPhase3.getUID(),
208                     new QuantityType<Power>(
209                             getSenecValue(response.grid.currentGridPowerPerPhase[2]).setScale(2, RoundingMode.HALF_UP),
210                             SmartHomeUnits.WATT));
211
212             Channel channelGridVoltagePhase1 = getThing()
213                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_VOLTAGE_PH1);
214             updateState(channelGridVoltagePhase1.getUID(), new QuantityType<ElectricPotential>(
215                     getSenecValue(response.grid.currentGridVoltagePerPhase[0]).setScale(2, RoundingMode.HALF_UP),
216                     SmartHomeUnits.VOLT));
217
218             Channel channelGridVoltagePhase2 = getThing()
219                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_VOLTAGE_PH2);
220             updateState(channelGridVoltagePhase2.getUID(), new QuantityType<ElectricPotential>(
221                     getSenecValue(response.grid.currentGridVoltagePerPhase[1]).setScale(2, RoundingMode.HALF_UP),
222                     SmartHomeUnits.VOLT));
223
224             Channel channelGridVoltagePhase3 = getThing()
225                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_VOLTAGE_PH3);
226             updateState(channelGridVoltagePhase3.getUID(), new QuantityType<ElectricPotential>(
227                     getSenecValue(response.grid.currentGridVoltagePerPhase[2]).setScale(2, RoundingMode.HALF_UP),
228                     SmartHomeUnits.VOLT));
229
230             Channel channelGridFrequency = getThing()
231                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_FREQUENCY);
232             updateState(channelGridFrequency.getUID(),
233                     new QuantityType<Frequency>(
234                             getSenecValue(response.grid.currentGridFrequency).setScale(2, RoundingMode.HALF_UP),
235                             SmartHomeUnits.HERTZ));
236
237             Channel channelBatteryStateValue = getThing()
238                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_BATTERY_STATE_VALUE);
239             updateState(channelBatteryStateValue.getUID(),
240                     new DecimalType(getSenecValue(response.energy.batteryState).intValue()));
241
242             Channel channelLiveBatCharge = getThing()
243                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_LIVE_BAT_CHARGE);
244             updateState(channelLiveBatCharge.getUID(),
245                     new QuantityType<Energy>(
246                             getSenecValue(response.statistics.liveBatCharge).setScale(2, RoundingMode.HALF_UP),
247                             SmartHomeUnits.WATT_HOUR));
248
249             Channel channelLiveBatDischarge = getThing()
250                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_LIVE_BAT_DISCHARGE);
251             updateState(channelLiveBatDischarge.getUID(),
252                     new QuantityType<Energy>(
253                             getSenecValue(response.statistics.liveBatDischarge).setScale(2, RoundingMode.HALF_UP),
254                             SmartHomeUnits.WATT_HOUR));
255
256             Channel channelLiveGridImport = getThing()
257                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_LIVE_GRID_IMPORT);
258             updateState(channelLiveGridImport.getUID(),
259                     new QuantityType<Energy>(
260                             getSenecValue(response.statistics.liveGridImport).setScale(2, RoundingMode.HALF_UP),
261                             SmartHomeUnits.WATT_HOUR));
262
263             Channel channelLiveGridExport = getThing()
264                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_LIVE_GRID_EXPORT);
265             updateState(channelLiveGridExport.getUID(),
266                     new QuantityType<Energy>(
267                             getSenecValue(response.statistics.liveGridExport).setScale(2, RoundingMode.HALF_UP),
268                             SmartHomeUnits.WATT_HOUR));
269
270             Channel channelBatteryVoltage = getThing()
271                     .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_BATTERY_VOLTAGE);
272             updateState(channelBatteryVoltage.getUID(),
273                     new QuantityType<ElectricPotential>(
274                             getSenecValue(response.energy.batteryVoltage).setScale(2, RoundingMode.HALF_UP),
275                             SmartHomeUnits.VOLT));
276
277             Channel channelBatteryState = getThing().getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_BATTERY_STATE);
278             if (channelBatteryState != null) {
279                 updateBatteryState(channelBatteryState, getSenecValue(response.energy.batteryState).intValue());
280             }
281
282             updateGridPowerValues(getSenecValue(response.grid.currentGridValue));
283
284             updateStatus(ThingStatus.ONLINE);
285         } catch (IOException | InterruptedException | TimeoutException | ExecutionException e) {
286             logger.warn("Error refreshing source '{}'", getThing().getUID(), e);
287             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
288                     "Could not connect to Senec web interface:" + e.getMessage());
289         }
290
291         return Boolean.TRUE;
292     }
293
294     protected BigDecimal getSenecValue(String value) {
295         String[] type = value.split("_");
296
297         if (VALUE_TYPE_INT.equalsIgnoreCase(type[0])) {
298             return new BigDecimal(Integer.valueOf(type[1], 16));
299         } else if (VALUE_TYPE_UNSIGNED_INT.equalsIgnoreCase(type[0])) {
300             return new BigDecimal(Integer.valueOf(type[1], 16));
301         } else if (VALUE_TYPE_FLOAT.equalsIgnoreCase(type[0])) {
302             return parseFloatValue(type[1]);
303         } else {
304             logger.warn("Unknown value type [{}]", type[0]);
305         }
306
307         return BigDecimal.ZERO;
308     }
309
310     /**
311      * Parse the hex coded float value of Senec device and return as BigDecimal
312      *
313      * @param value String with hex float
314      * @return BigDecimal with float value
315      */
316     private static BigDecimal parseFloatValue(String value) {
317         // sample: value = 43E26188
318
319         float f = Float.intBitsToFloat(Integer.parseUnsignedInt(value, 16));
320         return new BigDecimal(f);
321     }
322
323     protected void updatePowerLimitationStatus(Channel channel, boolean status, int duration) {
324         if (this.limitationStatus != null) {
325             if (this.limitationStatus.state == status) {
326                 long stateSince = new Date().getTime() - this.limitationStatus.time;
327
328                 if (((int) (stateSince / 1000)) < duration) {
329                     // skip updating state (possible flapping state)
330                     return;
331                 } else {
332                     logger.debug("{} longer than required duration {}", status, duration);
333                 }
334             } else {
335                 this.limitationStatus.state = status;
336                 this.limitationStatus.time = new Date().getTime();
337
338                 // skip updating state (state changed, possible flapping state)
339                 return;
340             }
341         } else {
342             this.limitationStatus = new PowerLimitationStatusDTO();
343             this.limitationStatus.state = status;
344         }
345
346         logger.debug("Updating power limitation state {}", status);
347         updateState(channel.getUID(), status ? OnOffType.ON : OnOffType.OFF);
348     }
349
350     protected void updateBatteryState(Channel channel, int code) {
351         updateState(channel.getUID(), new StringType(SenecBatteryStatus.descriptionFromCode(code)));
352     }
353
354     protected void updateGridPowerValues(BigDecimal gridTotalValue) {
355         BigDecimal gridTotal = gridTotalValue.setScale(2, RoundingMode.HALF_UP);
356
357         Channel channelGridPower = getThing().getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER);
358         if (channelGridPower != null) {
359             updateState(channelGridPower.getUID(), new QuantityType<Power>(gridTotal, SmartHomeUnits.WATT));
360         }
361
362         Channel channelGridPowerSupply = getThing()
363                 .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER_SUPPLY);
364         if (channelGridPowerSupply != null) {
365             BigDecimal gridSupply = gridTotal.compareTo(BigDecimal.ZERO) < 0 ? gridTotal.abs() : BigDecimal.ZERO;
366             updateState(channelGridPowerSupply.getUID(), new QuantityType<Power>(gridSupply, SmartHomeUnits.WATT));
367         }
368
369         Channel channelGridPowerDraw = getThing().getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER_DRAW);
370         if (channelGridPowerDraw != null) {
371             BigDecimal gridDraw = gridTotal.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : gridTotal.abs();
372             updateState(channelGridPowerDraw.getUID(), new QuantityType<Power>(gridDraw, SmartHomeUnits.WATT));
373         }
374     }
375 }