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;
16 import java.net.URISyntaxException;
17 import java.util.Objects;
18 import java.util.concurrent.*;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.eclipse.jetty.client.HttpClient;
23 import org.eclipse.jetty.client.api.Authentication;
24 import org.eclipse.jetty.client.api.AuthenticationStore;
25 import org.eclipse.jetty.client.util.DigestAuthentication;
26 import org.eclipse.jetty.client.util.StringContentProvider;
27 import org.openhab.binding.wlanthermo.internal.api.nano.WlanThermoNanoCommandHandler;
28 import org.openhab.binding.wlanthermo.internal.api.nano.data.Data;
29 import org.openhab.binding.wlanthermo.internal.api.nano.settings.Settings;
30 import org.openhab.core.common.ThreadPoolManager;
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 WlanThermoNanoHandler} is responsible for handling commands, which are
44 * sent to one of the channels.
46 * @author Christian Schlipp - Initial contribution
49 public class WlanThermoNanoHandler extends BaseThingHandler {
51 private final Logger logger = LoggerFactory.getLogger(WlanThermoNanoHandler.class);
53 private WlanThermoNanoConfiguration config = new WlanThermoNanoConfiguration();
54 private WlanThermoNanoCommandHandler wlanThermoNanoCommandHandler = new WlanThermoNanoCommandHandler();
55 private final HttpClient httpClient;
56 private @Nullable ScheduledFuture<?> pollingScheduler;
57 private final ScheduledExecutorService scheduler = ThreadPoolManager
58 .getScheduledPool(WlanThermoBindingConstants.WLANTHERMO_THREAD_POOL);
59 private final Gson gson = new Gson();
60 private Data data = new Data();
61 private Settings settings = new Settings();
63 public WlanThermoNanoHandler(Thing thing, HttpClient httpClient) {
65 this.httpClient = httpClient;
69 public void initialize() {
70 logger.debug("Start initializing WlanThermo Nano!");
71 config = getConfigAs(WlanThermoNanoConfiguration.class);
73 updateStatus(ThingStatus.UNKNOWN);
75 if (config.getUsername() != null && !config.getUsername().isEmpty() && config.getPassword() != null
76 && !config.getPassword().isEmpty()) {
77 AuthenticationStore authStore = httpClient.getAuthenticationStore();
78 authStore.addAuthentication(new DigestAuthentication(config.getUri(), Authentication.ANY_REALM,
79 config.getUsername(), config.getPassword()));
81 scheduler.schedule(this::checkConnection, config.getPollingInterval(), TimeUnit.SECONDS);
83 logger.debug("Finished initializing WlanThermo Nano!");
84 } catch (URISyntaxException e) {
85 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
86 "Failed to initialize WlanThermo Nano!");
87 logger.debug("Failed to initialize WlanThermo Nano!", e);
91 private void checkConnection() {
93 if (httpClient.GET(config.getUri()).getStatus() == 200) {
94 updateStatus(ThingStatus.ONLINE);
95 ScheduledFuture<?> oldScheduler = pollingScheduler;
96 if (oldScheduler != null) {
97 oldScheduler.cancel(false);
99 pollingScheduler = scheduler.scheduleWithFixedDelay(this::update, 0, config.getPollingInterval(),
102 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
103 "WlanThermo not found under given address.");
105 } catch (URISyntaxException | InterruptedException | ExecutionException | TimeoutException e) {
106 logger.debug("Failed to connect.", e);
107 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
108 "Could not connect to WlanThermo at " + config.getIpAddress());
109 ScheduledFuture<?> oldScheduler = pollingScheduler;
110 if (oldScheduler != null) {
111 oldScheduler.cancel(false);
113 pollingScheduler = scheduler.schedule(this::checkConnection, config.getPollingInterval(), TimeUnit.SECONDS);
118 public void handleCommand(ChannelUID channelUID, Command command) {
119 if (command instanceof RefreshType) {
120 State s = wlanThermoNanoCommandHandler.getState(channelUID, data, settings);
122 updateState(channelUID, s);
124 if (wlanThermoNanoCommandHandler.setState(channelUID, command, data)) {
125 logger.debug("Data updated, pushing changes");
128 logger.debug("Could not handle command of type {} for channel {}!",
129 command.getClass().toGenericString(), channelUID.getId());
134 private void update() {
136 // Update objects with data from device
137 String json = httpClient.GET(config.getUri("/data")).getContentAsString();
138 data = Objects.requireNonNull(gson.fromJson(json, Data.class));
139 logger.debug("Received at /data: {}", json);
140 json = httpClient.GET(config.getUri("/settings")).getContentAsString();
141 settings = Objects.requireNonNull(gson.fromJson(json, Settings.class));
142 logger.debug("Received at /settings: {}", json);
145 for (Channel channel : thing.getChannels()) {
146 State state = wlanThermoNanoCommandHandler.getState(channel.getUID(), data, settings);
148 updateState(channel.getUID(), state);
150 // if we could not obtain a state, try trigger instead
151 String trigger = wlanThermoNanoCommandHandler.getTrigger(channel.getUID(), data);
152 if (trigger != null) {
153 triggerChannel(channel.getUID(), trigger);
157 } catch (URISyntaxException | InterruptedException | ExecutionException | TimeoutException e) {
158 logger.debug("Update failed, checking connection", e);
159 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Update failed, reconnecting...");
160 ScheduledFuture<?> oldScheduler = pollingScheduler;
161 if (oldScheduler != null) {
162 oldScheduler.cancel(false);
164 for (Channel channel : thing.getChannels()) {
165 updateState(channel.getUID(), UnDefType.UNDEF);
171 private void push() {
172 data.getChannel().forEach(c -> {
174 String json = gson.toJson(c);
175 logger.debug("Pushing: {}", json);
176 URI uri = config.getUri("/setchannels");
177 int status = httpClient.POST(uri).content(new StringContentProvider(json), "application/json")
178 .timeout(5, TimeUnit.SECONDS).send().getStatus();
180 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR,
181 "No or wrong login credentials provided. Please configure username/password for write access to WlanThermo!");
182 } else if (status != 200) {
183 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Failed to update channel "
184 + c.getName() + " on device, Statuscode " + status + " on URI " + uri.toString());
186 updateStatus(ThingStatus.ONLINE);
188 } catch (InterruptedException | TimeoutException | ExecutionException | URISyntaxException e) {
189 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR,
190 "Failed to update channel " + c.getName() + " on device!");
191 logger.debug("Failed to update channel {} on device", c.getName(), e);
197 public void dispose() {
198 ScheduledFuture<?> oldScheduler = pollingScheduler;
199 if (oldScheduler != null) {
200 boolean stopped = oldScheduler.cancel(true);
201 logger.debug("Stopped polling: {}", stopped);
203 pollingScheduler = null;