]> git.basschouten.com Git - openhab-addons.git/blob
3a8bacc5696e664dd1cd7d0d51798b1052d75d9c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.wlanthermo.internal;
14
15 import static org.openhab.binding.wlanthermo.internal.WlanThermoUtil.requireNonNull;
16
17 import java.net.URI;
18 import java.net.URISyntaxException;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.TimeoutException;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.eclipse.jetty.client.api.Authentication;
28 import org.eclipse.jetty.client.api.AuthenticationStore;
29 import org.eclipse.jetty.client.util.DigestAuthentication;
30 import org.eclipse.jetty.client.util.StringContentProvider;
31 import org.openhab.core.thing.*;
32 import org.openhab.core.thing.binding.BaseThingHandler;
33 import org.openhab.core.types.Command;
34 import org.openhab.core.types.RefreshType;
35 import org.openhab.core.types.State;
36 import org.openhab.core.types.UnDefType;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import com.google.gson.Gson;
41
42 /**
43  * The {@link WlanThermoHandler} is responsible for handling commands, which are
44  * sent to one of the channels.
45  *
46  * @author Christian Schlipp - Initial contribution
47  */
48 @NonNullByDefault
49 public abstract class WlanThermoHandler extends BaseThingHandler {
50
51     private final boolean extendedConfig;
52     protected WlanThermoConfiguration config = new WlanThermoConfiguration();
53     protected final HttpClient httpClient;
54     protected final Logger logger = LoggerFactory.getLogger(WlanThermoHandler.class);
55     protected final Gson gson = new Gson();
56     protected @Nullable ScheduledFuture<?> pollingScheduler;
57
58     public WlanThermoHandler(Thing thing, HttpClient httpClient, boolean extendedConfig) {
59         super(thing);
60         this.httpClient = httpClient;
61         this.extendedConfig = extendedConfig;
62     }
63
64     @Override
65     public void initialize() {
66         updateStatus(ThingStatus.UNKNOWN);
67         try {
68             if (extendedConfig) {
69                 config = getConfigAs(WlanThermoExtendedConfiguration.class);
70                 WlanThermoExtendedConfiguration extendedConfig = (WlanThermoExtendedConfiguration) config;
71                 if (extendedConfig.getUsername().isEmpty() && !extendedConfig.getPassword().isEmpty()) {
72                     AuthenticationStore authStore = httpClient.getAuthenticationStore();
73                     authStore.addAuthentication(new DigestAuthentication(config.getUri(), Authentication.ANY_REALM,
74                             extendedConfig.getUsername(), extendedConfig.getPassword()));
75                 }
76             } else {
77                 config = getConfigAs(WlanThermoConfiguration.class);
78             }
79             pollingScheduler = scheduler.scheduleWithFixedDelay(this::checkConnectionAndUpdate, 0,
80                     config.getPollingInterval(), TimeUnit.SECONDS);
81         } catch (URISyntaxException e) {
82             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
83                     "Failed to initialize WlanThermo: " + e.getMessage());
84         }
85     }
86
87     @Override
88     public void dispose() {
89         ScheduledFuture<?> oldScheduler = pollingScheduler;
90         if (oldScheduler != null) {
91             boolean stopped = oldScheduler.cancel(true);
92             logger.debug("Stopped polling: {}", stopped);
93         }
94         pollingScheduler = null;
95     }
96
97     protected void checkConnectionAndUpdate() {
98         if (this.thing.getStatus() != ThingStatus.ONLINE) {
99             try {
100                 if (httpClient.GET(config.getUri()).getStatus() == 200) {
101                     updateStatus(ThingStatus.ONLINE);
102                     // rerun immediately to update state
103                     checkConnectionAndUpdate();
104                 } else {
105                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
106                             "WlanThermo not found under given address.");
107                 }
108             } catch (URISyntaxException | ExecutionException | TimeoutException e) {
109                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
110                         "Could not connect to WlanThermo at " + config.getIpAddress() + ": " + e.getMessage());
111             } catch (InterruptedException e) {
112                 logger.debug("Connection check interrupted. {}", e.getMessage());
113             }
114         } else {
115             pull();
116         }
117     }
118
119     protected boolean doPost(String endpoint, String json) throws InterruptedException {
120         try {
121             URI uri = config.getUri(endpoint);
122             int status = httpClient.POST(uri).content(new StringContentProvider(json), "application/json")
123                     .timeout(5, TimeUnit.SECONDS).send().getStatus();
124             if (status == 401) {
125                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
126                         "No or wrong login credentials provided. Please configure username/password for write access to WlanThermo!");
127                 return false;
128             } else if (status != 200) {
129                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
130                         "Failed to update channel on device, Statuscode " + status + " on URI " + uri.toString());
131                 logger.debug("Payload sent: {}", json);
132                 // Still continue to try next channel
133                 return true;
134             } else {
135                 updateStatus(ThingStatus.ONLINE);
136                 return true;
137             }
138         } catch (TimeoutException | ExecutionException | URISyntaxException e) {
139             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
140                     "Failed to update channel on device: " + e.getMessage());
141             return false;
142         }
143     }
144
145     protected <T> T doGet(String endpoint, Class<T> object) throws InterruptedException, WlanThermoException {
146         try {
147             String json = httpClient.GET(config.getUri(endpoint)).getContentAsString();
148             logger.debug("Received at {}: {}", endpoint, json);
149             return requireNonNull(gson.fromJson(json, object));
150         } catch (URISyntaxException | ExecutionException | TimeoutException | WlanThermoException e) {
151             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
152                     "Update failed: " + e.getMessage());
153             for (Channel channel : thing.getChannels()) {
154                 updateState(channel.getUID(), UnDefType.UNDEF);
155             }
156             throw new WlanThermoException(e);
157         }
158     }
159
160     @Override
161     public void handleCommand(ChannelUID channelUID, Command command) {
162         if (command instanceof RefreshType) {
163             try {
164                 State s = getState(channelUID);
165                 updateState(channelUID, s);
166             } catch (WlanThermoException e) {
167                 logger.debug("Could not handle command of type {} for channel {}!",
168                         command.getClass().toGenericString(), channelUID.getId());
169             }
170         } else {
171             if (setState(channelUID, command) && thing.getStatus() == ThingStatus.ONLINE) {
172                 logger.debug("Data updated, pushing changes");
173                 scheduler.execute(this::push);
174             } else {
175                 logger.debug("Could not handle command of type {} for channel {}!",
176                         command.getClass().toGenericString(), channelUID.getId());
177             }
178         }
179     }
180
181     protected abstract void push();
182
183     protected abstract void pull();
184
185     protected abstract State getState(ChannelUID channelUID)
186             throws WlanThermoInputException, WlanThermoUnknownChannelException;
187
188     protected abstract boolean setState(ChannelUID channelUID, Command command);
189 }