2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.wlanthermo.internal;
15 import static org.openhab.binding.wlanthermo.internal.WlanThermoUtil.requireNonNull;
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;
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;
40 import com.google.gson.Gson;
43 * The {@link WlanThermoHandler} is responsible for handling commands, which are
44 * sent to one of the channels.
46 * @author Christian Schlipp - Initial contribution
49 public abstract class WlanThermoHandler extends BaseThingHandler {
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;
58 public WlanThermoHandler(Thing thing, HttpClient httpClient, boolean extendedConfig) {
60 this.httpClient = httpClient;
61 this.extendedConfig = extendedConfig;
65 public void initialize() {
66 updateStatus(ThingStatus.UNKNOWN);
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()));
77 config = getConfigAs(WlanThermoConfiguration.class);
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());
88 public void dispose() {
89 ScheduledFuture<?> oldScheduler = pollingScheduler;
90 if (oldScheduler != null) {
91 boolean stopped = oldScheduler.cancel(true);
92 logger.debug("Stopped polling: {}", stopped);
94 pollingScheduler = null;
97 protected void checkConnectionAndUpdate() {
98 if (this.thing.getStatus() != ThingStatus.ONLINE) {
100 if (httpClient.GET(config.getUri()).getStatus() == 200) {
101 updateStatus(ThingStatus.ONLINE);
102 // rerun immediately to update state
103 checkConnectionAndUpdate();
105 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
106 "WlanThermo not found under given address.");
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());
119 protected boolean doPost(String endpoint, String json) throws InterruptedException {
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();
125 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
126 "No or wrong login credentials provided. Please configure username/password for write access to WlanThermo!");
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
135 updateStatus(ThingStatus.ONLINE);
138 } catch (TimeoutException | ExecutionException | URISyntaxException e) {
139 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
140 "Failed to update channel on device: " + e.getMessage());
145 protected <T> T doGet(String endpoint, Class<T> object) throws InterruptedException, WlanThermoException {
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);
156 throw new WlanThermoException(e);
161 public void handleCommand(ChannelUID channelUID, Command command) {
162 if (command instanceof RefreshType) {
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());
171 if (setState(channelUID, command) && thing.getStatus() == ThingStatus.ONLINE) {
172 logger.debug("Data updated, pushing changes");
173 scheduler.execute(this::push);
175 logger.debug("Could not handle command of type {} for channel {}!",
176 command.getClass().toGenericString(), channelUID.getId());
181 protected abstract void push();
183 protected abstract void pull();
185 protected abstract State getState(ChannelUID channelUID)
186 throws WlanThermoInputException, WlanThermoUnknownChannelException;
188 protected abstract boolean setState(ChannelUID channelUID, Command command);