]> git.basschouten.com Git - openhab-addons.git/blob
8a73b9049a3c384d5ea694430a485ee785084f17
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.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;
43
44 import com.google.gson.Gson;
45
46 /**
47  * The {@link WlanThermoHandler} is responsible for handling commands, which are
48  * sent to one of the channels.
49  *
50  * @author Christian Schlipp - Initial contribution
51  */
52 @NonNullByDefault
53 public abstract class WlanThermoHandler extends BaseThingHandler {
54
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;
61
62     public WlanThermoHandler(Thing thing, HttpClient httpClient, boolean extendedConfig) {
63         super(thing);
64         this.httpClient = httpClient;
65         this.extendedConfig = extendedConfig;
66     }
67
68     @Override
69     public void initialize() {
70         updateStatus(ThingStatus.UNKNOWN);
71         try {
72             if (extendedConfig) {
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()));
79                 }
80             } else {
81                 config = getConfigAs(WlanThermoConfiguration.class);
82             }
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());
88         }
89     }
90
91     @Override
92     public void dispose() {
93         ScheduledFuture<?> oldScheduler = pollingScheduler;
94         if (oldScheduler != null) {
95             boolean stopped = oldScheduler.cancel(true);
96             logger.debug("Stopped polling: {}", stopped);
97         }
98         pollingScheduler = null;
99     }
100
101     protected void checkConnectionAndUpdate() {
102         if (this.thing.getStatus() != ThingStatus.ONLINE) {
103             try {
104                 if (httpClient.GET(config.getUri()).getStatus() == 200) {
105                     updateStatus(ThingStatus.ONLINE);
106                     // rerun immediately to update state
107                     checkConnectionAndUpdate();
108                 } else {
109                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
110                             "WlanThermo not found under given address.");
111                 }
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());
117             }
118         } else {
119             pull();
120         }
121     }
122
123     protected boolean doPost(String endpoint, String json) throws InterruptedException {
124         try {
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();
128             if (status == 401) {
129                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
130                         "No or wrong login credentials provided. Please configure username/password for write access to WlanThermo!");
131                 return false;
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
137                 return true;
138             } else {
139                 updateStatus(ThingStatus.ONLINE);
140                 return true;
141             }
142         } catch (TimeoutException | ExecutionException | URISyntaxException e) {
143             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
144                     "Failed to update channel on device: " + e.getMessage());
145             return false;
146         }
147     }
148
149     protected <T> T doGet(String endpoint, Class<T> object) throws InterruptedException, WlanThermoException {
150         try {
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);
159             }
160             throw new WlanThermoException(e);
161         }
162     }
163
164     @Override
165     public void handleCommand(ChannelUID channelUID, Command command) {
166         if (command instanceof RefreshType) {
167             try {
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());
173             }
174         } else {
175             if (setState(channelUID, command) && thing.getStatus() == ThingStatus.ONLINE) {
176                 logger.debug("Data updated, pushing changes");
177                 scheduler.execute(this::push);
178             } else {
179                 logger.debug("Could not handle command of type {} for channel {}!",
180                         command.getClass().toGenericString(), channelUID.getId());
181             }
182         }
183     }
184
185     protected abstract void push();
186
187     protected abstract void pull();
188
189     protected abstract State getState(ChannelUID channelUID)
190             throws WlanThermoInputException, WlanThermoUnknownChannelException;
191
192     protected abstract boolean setState(ChannelUID channelUID, Command command);
193 }