2 * Copyright (c) 2010-2020 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.concurrent.*;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.eclipse.jetty.client.HttpClient;
22 import org.eclipse.jetty.client.api.Authentication;
23 import org.eclipse.jetty.client.api.AuthenticationStore;
24 import org.eclipse.jetty.client.util.DigestAuthentication;
25 import org.eclipse.jetty.client.util.StringContentProvider;
26 import org.openhab.binding.wlanthermo.internal.api.nano.WlanThermoNanoCommandHandler;
27 import org.openhab.binding.wlanthermo.internal.api.nano.data.Data;
28 import org.openhab.binding.wlanthermo.internal.api.nano.settings.Settings;
29 import org.openhab.core.common.ThreadPoolManager;
30 import org.openhab.core.thing.*;
31 import org.openhab.core.thing.binding.BaseThingHandler;
32 import org.openhab.core.types.Command;
33 import org.openhab.core.types.RefreshType;
34 import org.openhab.core.types.State;
35 import org.openhab.core.types.UnDefType;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
39 import com.google.gson.Gson;
42 * The {@link WlanThermoNanoHandler} is responsible for handling commands, which are
43 * sent to one of the channels.
45 * @author Christian Schlipp - Initial contribution
48 public class WlanThermoNanoHandler extends BaseThingHandler {
50 private final Logger logger = LoggerFactory.getLogger(WlanThermoNanoHandler.class);
52 private WlanThermoNanoConfiguration config = new WlanThermoNanoConfiguration();
53 private WlanThermoNanoCommandHandler wlanThermoNanoCommandHandler = new WlanThermoNanoCommandHandler();
54 private final HttpClient httpClient;
55 private @Nullable ScheduledFuture<?> pollingScheduler;
56 private final ScheduledExecutorService scheduler = ThreadPoolManager
57 .getScheduledPool(WlanThermoBindingConstants.WLANTHERMO_THREAD_POOL);
58 private final Gson gson = new Gson();
59 private Data data = new Data();
60 private Settings settings = new Settings();
62 public WlanThermoNanoHandler(Thing thing, HttpClient httpClient) {
64 this.httpClient = httpClient;
68 public void initialize() {
69 logger.debug("Start initializing WlanThermo Nano!");
70 config = getConfigAs(WlanThermoNanoConfiguration.class);
72 updateStatus(ThingStatus.UNKNOWN);
74 if (config.getUsername() != null && !config.getUsername().isEmpty() && config.getPassword() != null
75 && !config.getPassword().isEmpty()) {
76 AuthenticationStore authStore = httpClient.getAuthenticationStore();
77 authStore.addAuthentication(new DigestAuthentication(config.getUri(), Authentication.ANY_REALM,
78 config.getUsername(), config.getPassword()));
80 scheduler.schedule(this::checkConnection, config.getPollingInterval(), TimeUnit.SECONDS);
82 logger.debug("Finished initializing WlanThermo Nano!");
83 } catch (URISyntaxException e) {
84 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
85 "Failed to initialize WlanThermo Nano!");
86 logger.debug("Failed to initialize WlanThermo Nano!", e);
90 private void checkConnection() {
92 if (httpClient.GET(config.getUri()).getStatus() == 200) {
93 updateStatus(ThingStatus.ONLINE);
94 ScheduledFuture<?> oldScheduler = pollingScheduler;
95 if (oldScheduler != null) {
96 oldScheduler.cancel(false);
98 pollingScheduler = scheduler.scheduleWithFixedDelay(this::update, 0, config.getPollingInterval(),
101 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
102 "WlanThermo not found under given address.");
104 } catch (URISyntaxException | InterruptedException | ExecutionException | TimeoutException e) {
105 logger.debug("Failed to connect.", e);
106 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
107 "Could not connect to WlanThermo at " + config.getIpAddress());
108 ScheduledFuture<?> oldScheduler = pollingScheduler;
109 if (oldScheduler != null) {
110 oldScheduler.cancel(false);
112 pollingScheduler = scheduler.schedule(this::checkConnection, config.getPollingInterval(), TimeUnit.SECONDS);
117 public void handleCommand(ChannelUID channelUID, Command command) {
118 if (command instanceof RefreshType) {
119 State s = wlanThermoNanoCommandHandler.getState(channelUID, data, settings);
121 updateState(channelUID, s);
123 if (wlanThermoNanoCommandHandler.setState(channelUID, command, data)) {
124 logger.debug("Data updated, pushing changes");
127 logger.debug("Could not handle command of type {} for channel {}!",
128 command.getClass().toGenericString(), channelUID.getId());
133 private void update() {
135 // Update objects with data from device
136 String json = httpClient.GET(config.getUri("/data")).getContentAsString();
137 data = gson.fromJson(json, Data.class);
138 logger.debug("Received at /data: {}", json);
139 json = httpClient.GET(config.getUri("/settings")).getContentAsString();
140 settings = gson.fromJson(json, Settings.class);
141 logger.debug("Received at /settings: {}", json);
144 for (Channel channel : thing.getChannels()) {
145 State state = wlanThermoNanoCommandHandler.getState(channel.getUID(), data, settings);
147 updateState(channel.getUID(), state);
149 // if we could not obtain a state, try trigger instead
150 String trigger = wlanThermoNanoCommandHandler.getTrigger(channel.getUID(), data);
151 if (trigger != null) {
152 triggerChannel(channel.getUID(), trigger);
156 } catch (URISyntaxException | InterruptedException | ExecutionException | TimeoutException e) {
157 logger.debug("Update failed, checking connection", e);
158 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Update failed, reconnecting...");
159 ScheduledFuture<?> oldScheduler = pollingScheduler;
160 if (oldScheduler != null) {
161 oldScheduler.cancel(false);
163 for (Channel channel : thing.getChannels()) {
164 updateState(channel.getUID(), UnDefType.UNDEF);
170 private void push() {
171 data.getChannel().forEach(c -> {
173 String json = gson.toJson(c);
174 logger.debug("Pushing: {}", json);
175 URI uri = config.getUri("/setchannels");
176 int status = httpClient.POST(uri).content(new StringContentProvider(json), "application/json")
177 .timeout(5, TimeUnit.SECONDS).send().getStatus();
179 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR,
180 "No or wrong login credentials provided. Please configure username/password for write access to WlanThermo!");
181 } else if (status != 200) {
182 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Failed to update channel "
183 + c.getName() + " on device, Statuscode " + status + " on URI " + uri.toString());
185 updateStatus(ThingStatus.ONLINE);
187 } catch (InterruptedException | TimeoutException | ExecutionException | URISyntaxException e) {
188 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR,
189 "Failed to update channel " + c.getName() + " on device!");
190 logger.debug("Failed to update channel {} on device", c.getName(), e);
196 public void dispose() {
197 ScheduledFuture<?> oldScheduler = pollingScheduler;
198 if (oldScheduler != null) {
199 boolean stopped = oldScheduler.cancel(true);
200 logger.debug("Stopped polling: {}", stopped);
202 pollingScheduler = null;