]> git.basschouten.com Git - openhab-addons.git/blob
3cebdc84db436ffe731ae0233b361b0635548cb0
[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.groheondus.internal.handler;
14
15 import static org.openhab.binding.groheondus.internal.GroheOndusBindingConstants.CHANNEL_CONFIG_TIMEFRAME;
16 import static org.openhab.binding.groheondus.internal.GroheOndusBindingConstants.CHANNEL_NAME;
17 import static org.openhab.binding.groheondus.internal.GroheOndusBindingConstants.CHANNEL_PRESSURE;
18 import static org.openhab.binding.groheondus.internal.GroheOndusBindingConstants.CHANNEL_TEMPERATURE_GUARD;
19 import static org.openhab.binding.groheondus.internal.GroheOndusBindingConstants.CHANNEL_VALVE_OPEN;
20 import static org.openhab.binding.groheondus.internal.GroheOndusBindingConstants.CHANNEL_WATERCONSUMPTION;
21 import static org.openhab.binding.groheondus.internal.GroheOndusBindingConstants.CHANNEL_WATERCONSUMPTION_SINCE_MIDNIGHT;
22
23 import java.io.IOException;
24 import java.math.BigDecimal;
25 import java.time.Instant;
26 import java.time.ZoneId;
27 import java.time.ZonedDateTime;
28 import java.time.temporal.ChronoUnit;
29 import java.util.Collections;
30 import java.util.Comparator;
31 import java.util.List;
32 import java.util.Optional;
33
34 import javax.measure.quantity.Volume;
35
36 import org.eclipse.jdt.annotation.NonNullByDefault;
37 import org.eclipse.jdt.annotation.Nullable;
38 import org.openhab.core.library.types.OnOffType;
39 import org.openhab.core.library.types.QuantityType;
40 import org.openhab.core.library.types.StringType;
41 import org.openhab.core.library.unit.SIUnits;
42 import org.openhab.core.library.unit.Units;
43 import org.openhab.core.thing.Channel;
44 import org.openhab.core.thing.ChannelUID;
45 import org.openhab.core.thing.Thing;
46 import org.openhab.core.thing.ThingStatus;
47 import org.openhab.core.thing.ThingStatusDetail;
48 import org.openhab.core.types.Command;
49 import org.openhab.core.types.RefreshType;
50 import org.openhab.core.types.State;
51 import org.openhab.core.types.UnDefType;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 import io.github.floriansw.ondus.api.OndusService;
56 import io.github.floriansw.ondus.api.model.BaseApplianceCommand;
57 import io.github.floriansw.ondus.api.model.BaseApplianceData;
58 import io.github.floriansw.ondus.api.model.guard.Appliance;
59 import io.github.floriansw.ondus.api.model.guard.ApplianceCommand;
60 import io.github.floriansw.ondus.api.model.guard.ApplianceData;
61 import io.github.floriansw.ondus.api.model.guard.ApplianceData.Data;
62 import io.github.floriansw.ondus.api.model.guard.ApplianceData.Measurement;
63
64 /**
65  * @author Florian Schmidt and Arne Wohlert - Initial contribution
66  */
67 @NonNullByDefault
68 public class GroheOndusSenseGuardHandler<T, M> extends GroheOndusBaseHandler<Appliance, Data> {
69     private static final int MIN_API_TIMEFRAME_DAYS = 1;
70     private static final int MAX_API_TIMEFRAME_DAYS = 90;
71     private static final int DEFAULT_TIMEFRAME_DAYS = 1;
72
73     private final Logger logger = LoggerFactory.getLogger(GroheOndusSenseGuardHandler.class);
74
75     public GroheOndusSenseGuardHandler(Thing thing, int thingCounter) {
76         super(thing, Appliance.TYPE, thingCounter);
77     }
78
79     @Override
80     protected int getPollingInterval(Appliance appliance) {
81         if (config.pollingInterval > 0) {
82             return config.pollingInterval;
83         }
84         return appliance.getConfig().getMeasurementTransmissionIntervall();
85     }
86
87     @Override
88     protected void updateChannel(ChannelUID channelUID, Appliance appliance, Data dataPoint) {
89         String channelId = channelUID.getIdWithoutGroup();
90         State newState = UnDefType.UNDEF;
91         Measurement lastMeasurement = getLastMeasurement(dataPoint);
92         switch (channelId) {
93             case CHANNEL_NAME:
94                 newState = new StringType(appliance.getName());
95                 break;
96             case CHANNEL_PRESSURE:
97                 newState = new QuantityType<>(lastMeasurement.getPressure(), Units.BAR);
98                 break;
99             case CHANNEL_TEMPERATURE_GUARD:
100                 newState = new QuantityType<>(lastMeasurement.getTemperatureGuard(), SIUnits.CELSIUS);
101                 break;
102             case CHANNEL_VALVE_OPEN:
103                 OnOffType valveOpenType = getValveOpenType(appliance);
104                 if (valveOpenType != null) {
105                     newState = valveOpenType;
106                 }
107                 break;
108             case CHANNEL_WATERCONSUMPTION:
109                 newState = sumWaterConsumption(dataPoint);
110                 break;
111             case CHANNEL_WATERCONSUMPTION_SINCE_MIDNIGHT:
112                 newState = sumWaterConsumptionSinceMidnight(dataPoint);
113                 break;
114             default:
115                 throw new IllegalArgumentException("Channel " + channelUID + " not supported.");
116         }
117         updateState(channelUID, newState);
118     }
119
120     private QuantityType<Volume> sumWaterConsumptionSinceMidnight(Data dataPoint) {
121         ZonedDateTime earliestWithdrawal = ZonedDateTime.now(ZoneId.systemDefault()).truncatedTo(ChronoUnit.DAYS);
122         ZonedDateTime latestWithdrawal = earliestWithdrawal.plus(1, ChronoUnit.DAYS);
123
124         Double waterConsumption = dataPoint.getWithdrawals().stream()
125                 .filter(e -> earliestWithdrawal.isBefore(e.starttime.toInstant().atZone(ZoneId.systemDefault()))
126                         && latestWithdrawal.isAfter(e.starttime.toInstant().atZone(ZoneId.systemDefault())))
127                 .mapToDouble(withdrawal -> withdrawal.getWaterconsumption()).sum();
128         return new QuantityType<>(waterConsumption, Units.LITRE);
129     }
130
131     private QuantityType<Volume> sumWaterConsumption(Data dataPoint) {
132         Double waterConsumption = dataPoint.getWithdrawals().stream()
133                 .mapToDouble(withdrawal -> withdrawal.getWaterconsumption()).sum();
134         return new QuantityType<Volume>(waterConsumption, Units.LITRE);
135     }
136
137     private Measurement getLastMeasurement(Data dataPoint) {
138         List<Measurement> measurementList = dataPoint.getMeasurement();
139         return measurementList.isEmpty() ? new Measurement() : measurementList.get(measurementList.size() - 1);
140     }
141
142     @Nullable
143     private OnOffType getValveOpenType(Appliance appliance) {
144         OndusService service = getOndusService();
145         if (service == null) {
146             return null;
147         }
148         Optional<BaseApplianceCommand> commandOptional;
149         try {
150             commandOptional = service.applianceCommand(appliance);
151         } catch (IOException e) {
152             logger.debug("Could not get appliance command", e);
153             return null;
154         }
155         if (commandOptional.isEmpty()) {
156             return null;
157         }
158         if (commandOptional.get().getType() != Appliance.TYPE) {
159             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.notsenseguard");
160             return null;
161         }
162         return OnOffType.from(((ApplianceCommand) commandOptional.get()).getCommand().getValveOpen());
163     }
164
165     @Override
166     protected Data getLastDataPoint(Appliance appliance) {
167         if (getOndusService() == null) {
168             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "@text/error.noservice");
169             return new Data();
170         }
171
172         ApplianceData applianceData = getApplianceData(appliance);
173         if (applianceData == null) {
174             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.empty.response");
175             return new Data();
176         }
177         Data data = applianceData.getData();
178         Collections.sort(data.measurement, Comparator.comparing(e -> ZonedDateTime.parse(e.timestamp)));
179         Collections.sort(data.withdrawals, Comparator.comparing(e -> e.starttime));
180         return data;
181     }
182
183     private @Nullable ApplianceData getApplianceData(Appliance appliance) {
184         Instant from = fromTime();
185         // Truncated to date only inside api package
186         Instant to = Instant.now().plus(1, ChronoUnit.DAYS);
187
188         OndusService service = getOndusService();
189         if (service == null) {
190             return null;
191         }
192         try {
193             logger.debug("Fetching data for {} from {} to {}", thing.getUID(), from, to);
194             BaseApplianceData applianceData = service.applianceData(appliance, from, to).orElse(null);
195             if (applianceData != null) {
196                 if (applianceData.getType() != Appliance.TYPE) {
197                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
198                             "@text/error.notsenseguard");
199                     return null;
200                 }
201                 return (ApplianceData) applianceData;
202             } else {
203                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
204                         "@text/error.failedtoloaddata");
205             }
206         } catch (IOException e) {
207             logger.debug("Could not load appliance data for {}", thing.getUID(), e);
208         }
209         return null;
210     }
211
212     private Instant fromTime() {
213         Instant from = Instant.now().minus(DEFAULT_TIMEFRAME_DAYS, ChronoUnit.DAYS);
214         Channel waterconsumptionChannel = this.thing.getChannel(CHANNEL_WATERCONSUMPTION);
215         if (waterconsumptionChannel == null) {
216             return from;
217         }
218
219         Object timeframeConfig = waterconsumptionChannel.getConfiguration().get(CHANNEL_CONFIG_TIMEFRAME);
220         if (!(timeframeConfig instanceof BigDecimal)) {
221             return from;
222         }
223
224         int timeframe = ((BigDecimal) timeframeConfig).intValue();
225         if (timeframe < MIN_API_TIMEFRAME_DAYS && timeframe > MAX_API_TIMEFRAME_DAYS) {
226             logger.info(
227                     "timeframe configuration of waterconsumption channel needs to be a number between 1 to 90, got {}",
228                     timeframe);
229             return from;
230         }
231
232         return Instant.now().minus(timeframe, ChronoUnit.DAYS);
233     }
234
235     @Override
236     public void handleCommand(ChannelUID channelUID, Command command) {
237         if (command instanceof RefreshType) {
238             updateChannels();
239             return;
240         }
241
242         if (!CHANNEL_VALVE_OPEN.equals(channelUID.getIdWithoutGroup())) {
243             return;
244         }
245         if (!(command instanceof OnOffType)) {
246             logger.debug("Invalid command received for channel. Expected OnOffType, received {}.",
247                     command.getClass().getName());
248             return;
249         }
250         OnOffType openClosedCommand = (OnOffType) command;
251         boolean openState = openClosedCommand == OnOffType.ON;
252
253         OndusService service = getOndusService();
254         if (service == null) {
255             return;
256         }
257         Appliance appliance = getAppliance(service);
258         if (appliance == null) {
259             return;
260         }
261         try {
262             service.setValveOpen(appliance, openState);
263             updateChannels();
264         } catch (IOException e) {
265             logger.debug("Could not update valve open state", e);
266         }
267     }
268 }