]> git.basschouten.com Git - openhab-addons.git/blob
5d4b342ae6b3a00740dd025b90cb6149a33d5efd
[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.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
104                 OnOffType valveOpenType = getValveOpenType(appliance);
105                 if (valveOpenType != null) {
106                     newState = valveOpenType;
107                 }
108                 break;
109             case CHANNEL_WATERCONSUMPTION:
110                 newState = sumWaterConsumption(dataPoint);
111                 break;
112             case CHANNEL_WATERCONSUMPTION_SINCE_MIDNIGHT:
113                 newState = sumWaterConsumptionSinceMidnight(dataPoint);
114                 break;
115             default:
116                 throw new IllegalArgumentException("Channel " + channelUID + " not supported.");
117         }
118         updateState(channelUID, newState);
119     }
120
121     private QuantityType<Volume> sumWaterConsumptionSinceMidnight(Data dataPoint) {
122         ZonedDateTime earliestWithdrawal = ZonedDateTime.now(ZoneId.systemDefault()).truncatedTo(ChronoUnit.DAYS);
123         ZonedDateTime latestWithdrawal = earliestWithdrawal.plus(1, ChronoUnit.DAYS);
124
125         Double waterConsumption = dataPoint.getWithdrawals().stream()
126                 .filter(e -> earliestWithdrawal.isBefore(e.starttime.toInstant().atZone(ZoneId.systemDefault()))
127                         && latestWithdrawal.isAfter(e.starttime.toInstant().atZone(ZoneId.systemDefault())))
128                 .mapToDouble(withdrawal -> withdrawal.getWaterconsumption()).sum();
129         return new QuantityType<>(waterConsumption, Units.LITRE);
130     }
131
132     private QuantityType<Volume> sumWaterConsumption(Data dataPoint) {
133         Double waterConsumption = dataPoint.getWithdrawals().stream()
134                 .mapToDouble(withdrawal -> withdrawal.getWaterconsumption()).sum();
135         return new QuantityType<Volume>(waterConsumption, Units.LITRE);
136     }
137
138     private Measurement getLastMeasurement(Data dataPoint) {
139         List<Measurement> measurementList = dataPoint.getMeasurement();
140         return measurementList.isEmpty() ? new Measurement() : measurementList.get(measurementList.size() - 1);
141     }
142
143     @Nullable
144     private OnOffType getValveOpenType(Appliance appliance) {
145         OndusService service = getOndusService();
146         if (service == null) {
147             return null;
148         }
149         Optional<BaseApplianceCommand> commandOptional;
150         try {
151             commandOptional = service.applianceCommand(appliance);
152         } catch (IOException e) {
153             logger.debug("Could not get appliance command", e);
154             return null;
155         }
156         if (commandOptional.isEmpty()) {
157             return null;
158         }
159         if (commandOptional.get().getType() != Appliance.TYPE) {
160             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.notsenseguard");
161             return null;
162         }
163         return ((ApplianceCommand) commandOptional.get()).getCommand().getValveOpen() ? OnOffType.ON : OnOffType.OFF;
164     }
165
166     @Override
167     protected Data getLastDataPoint(Appliance appliance) {
168         if (getOndusService() == null) {
169             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "@text/error.noservice");
170             return new Data();
171         }
172
173         ApplianceData applianceData = getApplianceData(appliance);
174         if (applianceData == null) {
175             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.empty.response");
176             return new Data();
177         }
178         Data data = applianceData.getData();
179         Collections.sort(data.measurement, Comparator.comparing(e -> ZonedDateTime.parse(e.timestamp)));
180         Collections.sort(data.withdrawals, Comparator.comparing(e -> e.starttime));
181         return data;
182     }
183
184     private @Nullable ApplianceData getApplianceData(Appliance appliance) {
185         Instant from = fromTime();
186         // Truncated to date only inside api package
187         Instant to = Instant.now().plus(1, ChronoUnit.DAYS);
188
189         OndusService service = getOndusService();
190         if (service == null) {
191             return null;
192         }
193         try {
194             logger.debug("Fetching data for {} from {} to {}", thing.getUID(), from, to);
195             BaseApplianceData applianceData = service.applianceData(appliance, from, to).orElse(null);
196             if (applianceData != null) {
197                 if (applianceData.getType() != Appliance.TYPE) {
198                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
199                             "@text/error.notsenseguard");
200                     return null;
201                 }
202                 return (ApplianceData) applianceData;
203             } else {
204                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
205                         "@text/error.failedtoloaddata");
206             }
207         } catch (IOException e) {
208             logger.debug("Could not load appliance data for {}", thing.getUID(), e);
209         }
210         return null;
211     }
212
213     private Instant fromTime() {
214         Instant from = Instant.now().minus(DEFAULT_TIMEFRAME_DAYS, ChronoUnit.DAYS);
215         Channel waterconsumptionChannel = this.thing.getChannel(CHANNEL_WATERCONSUMPTION);
216         if (waterconsumptionChannel == null) {
217             return from;
218         }
219
220         Object timeframeConfig = waterconsumptionChannel.getConfiguration().get(CHANNEL_CONFIG_TIMEFRAME);
221         if (!(timeframeConfig instanceof BigDecimal)) {
222             return from;
223         }
224
225         int timeframe = ((BigDecimal) timeframeConfig).intValue();
226         if (timeframe < MIN_API_TIMEFRAME_DAYS && timeframe > MAX_API_TIMEFRAME_DAYS) {
227             logger.info(
228                     "timeframe configuration of waterconsumption channel needs to be a number between 1 to 90, got {}",
229                     timeframe);
230             return from;
231         }
232
233         return Instant.now().minus(timeframe, ChronoUnit.DAYS);
234     }
235
236     @Override
237     public void handleCommand(ChannelUID channelUID, Command command) {
238         if (command instanceof RefreshType) {
239             updateChannels();
240             return;
241         }
242
243         if (!CHANNEL_VALVE_OPEN.equals(channelUID.getIdWithoutGroup())) {
244             return;
245         }
246         if (!(command instanceof OnOffType)) {
247             logger.debug("Invalid command received for channel. Expected OnOffType, received {}.",
248                     command.getClass().getName());
249             return;
250         }
251         OnOffType openClosedCommand = (OnOffType) command;
252         boolean openState = openClosedCommand == OnOffType.ON;
253
254         OndusService service = getOndusService();
255         if (service == null) {
256             return;
257         }
258         Appliance appliance = getAppliance(service);
259         if (appliance == null) {
260             return;
261         }
262         try {
263             service.setValveOpen(appliance, openState);
264             updateChannels();
265         } catch (IOException e) {
266             logger.debug("Could not update valve open state", e);
267         }
268     }
269 }