]> git.basschouten.com Git - openhab-addons.git/blob
ce01f317aa571968518f3912badab8fc7fd4f79d
[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.opengarage.internal;
14
15 import java.io.IOException;
16 import java.time.Duration;
17 import java.time.Instant;
18 import java.util.concurrent.CompletableFuture;
19 import java.util.concurrent.Future;
20 import java.util.concurrent.TimeUnit;
21 import java.util.function.Function;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.openhab.binding.opengarage.internal.api.ControllerVariables;
25 import org.openhab.binding.opengarage.internal.api.Enums.OpenGarageCommand;
26 import org.openhab.core.library.types.DecimalType;
27 import org.openhab.core.library.types.OnOffType;
28 import org.openhab.core.library.types.OpenClosedType;
29 import org.openhab.core.library.types.QuantityType;
30 import org.openhab.core.library.types.StopMoveType;
31 import org.openhab.core.library.types.StringType;
32 import org.openhab.core.library.types.UpDownType;
33 import org.openhab.core.library.unit.MetricPrefix;
34 import org.openhab.core.library.unit.SIUnits;
35 import org.openhab.core.thing.Channel;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.types.Command;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * The {@link OpenGarageHandler} is responsible for handling commands, which are
47  * sent to one of the channels.
48  *
49  * @author Paul Smedley - Initial contribution
50  * @author Dan Cunningham - Minor improvements to vehicle state and invert option
51  */
52 @NonNullByDefault
53 public class OpenGarageHandler extends BaseThingHandler {
54
55     private final Logger logger = LoggerFactory.getLogger(OpenGarageHandler.class);
56
57     private @NonNullByDefault({}) OpenGarageWebTargets webTargets;
58
59     // reference to periodically scheduled poll task
60     private Future<?> pollScheduledFuture = CompletableFuture.completedFuture(null);
61
62     // reference to one-shot poll task which gets scheduled after a garage state change command
63     private Future<?> pollScheduledFutureTransition = CompletableFuture.completedFuture(null);
64     private Instant lastTransition;
65     private String lastTransitionText;
66
67     private OpenGarageConfiguration config = new OpenGarageConfiguration();
68
69     public OpenGarageHandler(Thing thing) {
70         super(thing);
71         this.lastTransition = Instant.MIN;
72         this.lastTransitionText = "";
73     }
74
75     @Override
76     public synchronized void handleCommand(ChannelUID channelUID, Command command) {
77         try {
78             logger.debug("Received command {} for thing '{}' on channel {}", command, thing.getUID().getAsString(),
79                     channelUID.getId());
80             Function<Boolean, Boolean> maybeInvert = getInverter(channelUID.getId());
81             switch (channelUID.getId()) {
82                 case OpenGarageBindingConstants.CHANNEL_OG_STATUS:
83                 case OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH:
84                 case OpenGarageBindingConstants.CHANNEL_OG_STATUS_ROLLERSHUTTER:
85                     if (command.equals(StopMoveType.STOP) || command.equals(StopMoveType.MOVE)) {
86                         changeStatus(OpenGarageCommand.CLICK);
87                     } else {
88                         boolean doorOpen = command.equals(OnOffType.ON) || command.equals(UpDownType.UP);
89                         changeStatus(maybeInvert.apply(doorOpen) ? OpenGarageCommand.OPEN : OpenGarageCommand.CLOSE);
90                         this.lastTransition = Instant.now();
91                         this.lastTransitionText = doorOpen ? this.config.doorOpeningState
92                                 : this.config.doorClosingState;
93
94                         this.poll(); // invoke poll directly to communicate the door transition state
95                         this.pollScheduledFutureTransition.cancel(false);
96                         this.pollScheduledFutureTransition = this.scheduler.schedule(this::poll,
97                                 this.config.doorTransitionTimeSeconds, TimeUnit.SECONDS);
98                     }
99                     break;
100                 default:
101             }
102         } catch (OpenGarageCommunicationException ex) {
103             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage());
104         }
105     }
106
107     @Override
108     public void initialize() {
109         this.config = getConfigAs(OpenGarageConfiguration.class);
110         logger.debug("config.hostname = {}, refresh = {}, port = {}", config.hostname, config.refresh, config.port);
111         if (config.hostname.isEmpty()) {
112             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hostname/IP address must be set");
113         } else {
114             updateStatus(ThingStatus.UNKNOWN);
115             int requestTimeout = Math.max(OpenGarageWebTargets.DEFAULT_TIMEOUT_MS, config.refresh * 1000);
116             webTargets = new OpenGarageWebTargets(config.hostname, config.port, config.password, requestTimeout);
117             this.pollScheduledFuture = this.scheduler.scheduleWithFixedDelay(this::poll, 1, config.refresh,
118                     TimeUnit.SECONDS);
119         }
120     }
121
122     @Override
123     public void dispose() {
124         this.pollScheduledFuture.cancel(true);
125         this.pollScheduledFutureTransition.cancel(true);
126         super.dispose();
127     }
128
129     /**
130      * Update the state of the controller.
131      *
132      *
133      */
134     private synchronized void poll() {
135         try {
136             logger.debug("Polling for state");
137             ControllerVariables controllerVariables = webTargets.getControllerVariables();
138             long lastTransitionAgoSecs = Duration.between(lastTransition, Instant.now()).getSeconds();
139             boolean inTransition = lastTransitionAgoSecs < this.config.doorTransitionTimeSeconds;
140             if (controllerVariables != null) {
141                 updateStatus(ThingStatus.ONLINE);
142                 updateState(OpenGarageBindingConstants.CHANNEL_OG_DISTANCE,
143                         new QuantityType<>(controllerVariables.dist, MetricPrefix.CENTI(SIUnits.METRE)));
144                 Function<Boolean, Boolean> maybeInvert = getInverter(
145                         OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH);
146
147                 if ((controllerVariables.door != 0) && (controllerVariables.door != 1)) {
148                     logger.debug("Received unknown door value: {}", controllerVariables.door);
149                 } else {
150                     boolean doorOpen = controllerVariables.door == 1;
151                     OnOffType onOff = OnOffType.from(maybeInvert.apply(doorOpen));
152                     UpDownType upDown = doorOpen ? UpDownType.UP : UpDownType.DOWN;
153                     OpenClosedType contact = doorOpen ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
154
155                     String transitionText;
156                     if (inTransition) {
157                         transitionText = this.lastTransitionText;
158                     } else {
159                         transitionText = doorOpen ? this.config.doorOpenState : this.config.doorClosedState;
160                     }
161                     if (!inTransition) {
162                         updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS, onOff); // deprecated channel
163                         updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH, onOff);
164                     }
165                     updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_ROLLERSHUTTER, upDown);
166                     updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_CONTACT, contact);
167                     updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_TEXT, new StringType(transitionText));
168                 }
169
170                 switch (controllerVariables.vehicle) {
171                     case 0:
172                         updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
173                                 new StringType("No vehicle detected"));
174                         break;
175                     case 1:
176                         updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE, new StringType("Vehicle detected"));
177                         break;
178                     case 2:
179                         updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
180                                 new StringType("Vehicle status unknown"));
181                         break;
182                     case 3:
183                         updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
184                                 new StringType("Vehicle status not available"));
185                         break;
186
187                     default:
188                         logger.debug("Received unknown vehicle value: {}", controllerVariables.vehicle);
189                 }
190                 updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE_STATUS,
191                         new DecimalType(controllerVariables.vehicle));
192             }
193         } catch (IOException e) {
194             logger.debug("Could not connect to OpenGarage controller", e);
195             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
196                     "Could not connect to OpenGarage controller");
197         } catch (RuntimeException e) {
198             logger.debug("Unexpected error connecting to OpenGarage controller", e);
199             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
200         }
201     }
202
203     private void changeStatus(OpenGarageCommand status) throws OpenGarageCommunicationException {
204         webTargets.setControllerVariables(status);
205     }
206
207     private Function<Boolean, Boolean> getInverter(String channelUID) {
208         Channel channel = getThing().getChannel(channelUID);
209         boolean invert = channel != null && channel.getConfiguration().as(OpenGarageChannelConfiguration.class).invert;
210         if (invert) {
211             return onOff -> !onOff;
212         } else {
213             return Function.identity();
214         }
215     }
216 }