2 * Copyright (c) 2010-2023 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.Channel;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingStatusDetail;
36 import org.openhab.core.thing.binding.BaseThingHandler;
37 import org.openhab.core.types.Command;
38 import org.openhab.core.types.RefreshType;
39 import org.openhab.core.types.State;
40 import org.openhab.core.types.UnDefType;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
44 import com.google.gson.Gson;
47 * The {@link WlanThermoHandler} is responsible for handling commands, which are
48 * sent to one of the channels.
50 * @author Christian Schlipp - Initial contribution
53 public abstract class WlanThermoHandler extends BaseThingHandler {
55 private final boolean extendedConfig;
56 protected WlanThermoConfiguration config = new WlanThermoConfiguration();
57 protected final HttpClient httpClient;
58 protected final Logger logger = LoggerFactory.getLogger(WlanThermoHandler.class);
59 protected final Gson gson = new Gson();
60 protected @Nullable ScheduledFuture<?> pollingScheduler;
62 public WlanThermoHandler(Thing thing, HttpClient httpClient, boolean extendedConfig) {
64 this.httpClient = httpClient;
65 this.extendedConfig = extendedConfig;
69 public void initialize() {
70 updateStatus(ThingStatus.UNKNOWN);
73 config = getConfigAs(WlanThermoExtendedConfiguration.class);
74 WlanThermoExtendedConfiguration extendedConfig = (WlanThermoExtendedConfiguration) config;
75 if (extendedConfig.getUsername().isEmpty() && !extendedConfig.getPassword().isEmpty()) {
76 AuthenticationStore authStore = httpClient.getAuthenticationStore();
77 authStore.addAuthentication(new DigestAuthentication(config.getUri(), Authentication.ANY_REALM,
78 extendedConfig.getUsername(), extendedConfig.getPassword()));
81 config = getConfigAs(WlanThermoConfiguration.class);
83 pollingScheduler = scheduler.scheduleWithFixedDelay(this::checkConnectionAndUpdate, 0,
84 config.getPollingInterval(), TimeUnit.SECONDS);
85 } catch (URISyntaxException e) {
86 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
87 "Failed to initialize WlanThermo: " + e.getMessage());
92 public void dispose() {
93 ScheduledFuture<?> oldScheduler = pollingScheduler;
94 if (oldScheduler != null) {
95 boolean stopped = oldScheduler.cancel(true);
96 logger.debug("Stopped polling: {}", stopped);
98 pollingScheduler = null;
101 protected void checkConnectionAndUpdate() {
102 if (this.thing.getStatus() != ThingStatus.ONLINE) {
104 if (httpClient.GET(config.getUri()).getStatus() == 200) {
105 updateStatus(ThingStatus.ONLINE);
106 // rerun immediately to update state
107 checkConnectionAndUpdate();
109 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
110 "WlanThermo not found under given address.");
112 } catch (URISyntaxException | ExecutionException | TimeoutException e) {
113 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
114 "Could not connect to WlanThermo at " + config.getIpAddress() + ": " + e.getMessage());
115 } catch (InterruptedException e) {
116 logger.debug("Connection check interrupted. {}", e.getMessage());
123 protected boolean doPost(String endpoint, String json) throws InterruptedException {
125 URI uri = config.getUri(endpoint);
126 int status = httpClient.POST(uri).content(new StringContentProvider(json), "application/json")
127 .timeout(5, TimeUnit.SECONDS).send().getStatus();
129 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
130 "No or wrong login credentials provided. Please configure username/password for write access to WlanThermo!");
132 } else if (status != 200) {
133 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
134 "Failed to update channel on device, Statuscode " + status + " on URI " + uri.toString());
135 logger.debug("Payload sent: {}", json);
136 // Still continue to try next channel
139 updateStatus(ThingStatus.ONLINE);
142 } catch (TimeoutException | ExecutionException | URISyntaxException e) {
143 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
144 "Failed to update channel on device: " + e.getMessage());
149 protected <T> T doGet(String endpoint, Class<T> object) throws InterruptedException, WlanThermoException {
151 String json = httpClient.GET(config.getUri(endpoint)).getContentAsString();
152 logger.debug("Received at {}: {}", endpoint, json);
153 return requireNonNull(gson.fromJson(json, object));
154 } catch (URISyntaxException | ExecutionException | TimeoutException | WlanThermoException e) {
155 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
156 "Update failed: " + e.getMessage());
157 for (Channel channel : thing.getChannels()) {
158 updateState(channel.getUID(), UnDefType.UNDEF);
160 throw new WlanThermoException(e);
165 public void handleCommand(ChannelUID channelUID, Command command) {
166 if (command instanceof RefreshType) {
168 State s = getState(channelUID);
169 updateState(channelUID, s);
170 } catch (WlanThermoException e) {
171 logger.debug("Could not handle command of type {} for channel {}!",
172 command.getClass().toGenericString(), channelUID.getId());
175 if (setState(channelUID, command) && thing.getStatus() == ThingStatus.ONLINE) {
176 logger.debug("Data updated, pushing changes");
177 scheduler.execute(this::push);
179 logger.debug("Could not handle command of type {} for channel {}!",
180 command.getClass().toGenericString(), channelUID.getId());
185 protected abstract void push();
187 protected abstract void pull();
189 protected abstract State getState(ChannelUID channelUID)
190 throws WlanThermoInputException, WlanThermoUnknownChannelException;
192 protected abstract boolean setState(ChannelUID channelUID, Command command);