]> git.basschouten.com Git - openhab-addons.git/blob
4055e93c82967092349430631fa8155a7178d3a4
[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.jablotron.internal.handler;
14
15 import static org.openhab.binding.jablotron.JablotronBindingConstants.*;
16
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.List;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23 import java.util.concurrent.TimeoutException;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.eclipse.jetty.client.HttpClient;
28 import org.eclipse.jetty.client.api.ContentResponse;
29 import org.eclipse.jetty.client.api.Request;
30 import org.eclipse.jetty.client.util.StringContentProvider;
31 import org.eclipse.jetty.http.HttpHeader;
32 import org.eclipse.jetty.http.HttpMethod;
33 import org.openhab.binding.jablotron.internal.config.JablotronBridgeConfig;
34 import org.openhab.binding.jablotron.internal.discovery.JablotronDiscoveryService;
35 import org.openhab.binding.jablotron.internal.model.JablotronControlResponse;
36 import org.openhab.binding.jablotron.internal.model.JablotronDataUpdateResponse;
37 import org.openhab.binding.jablotron.internal.model.JablotronDiscoveredService;
38 import org.openhab.binding.jablotron.internal.model.JablotronGetEventHistoryResponse;
39 import org.openhab.binding.jablotron.internal.model.JablotronGetServiceResponse;
40 import org.openhab.binding.jablotron.internal.model.JablotronHistoryDataEvent;
41 import org.openhab.binding.jablotron.internal.model.JablotronLoginResponse;
42 import org.openhab.binding.jablotron.internal.model.ja100f.JablotronGetPGResponse;
43 import org.openhab.binding.jablotron.internal.model.ja100f.JablotronGetSectionsResponse;
44 import org.openhab.binding.jablotron.internal.model.ja100f.JablotronGetThermoDevicesResponse;
45 import org.openhab.core.thing.Bridge;
46 import org.openhab.core.thing.ChannelUID;
47 import org.openhab.core.thing.Thing;
48 import org.openhab.core.thing.ThingStatus;
49 import org.openhab.core.thing.ThingStatusDetail;
50 import org.openhab.core.thing.binding.BaseBridgeHandler;
51 import org.openhab.core.thing.binding.ThingHandlerService;
52 import org.openhab.core.types.Command;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 import com.google.gson.Gson;
57 import com.google.gson.JsonSyntaxException;
58
59 /**
60  * The {@link JablotronBridgeHandler} is responsible for handling commands, which are
61  * sent to one of the channels.
62  *
63  * @author Ondrej Pecta - Initial contribution
64  */
65 @NonNullByDefault
66 public class JablotronBridgeHandler extends BaseBridgeHandler {
67
68     private final Logger logger = LoggerFactory.getLogger(JablotronBridgeHandler.class);
69
70     private final Gson gson = new Gson();
71
72     final HttpClient httpClient;
73
74     private @Nullable ScheduledFuture<?> future = null;
75
76     /**
77      * Our configuration
78      */
79     public JablotronBridgeConfig bridgeConfig = new JablotronBridgeConfig();
80
81     public JablotronBridgeHandler(Bridge thing, HttpClient httpClient) {
82         super(thing);
83         this.httpClient = httpClient;
84     }
85
86     @Override
87     public Collection<Class<? extends ThingHandlerService>> getServices() {
88         return Collections.singleton(JablotronDiscoveryService.class);
89     }
90
91     @Override
92     public void handleCommand(ChannelUID channelUID, Command command) {
93     }
94
95     @Override
96     public void initialize() {
97         bridgeConfig = getConfigAs(JablotronBridgeConfig.class);
98         scheduler.execute(this::login);
99         future = scheduler.scheduleWithFixedDelay(this::updateAlarmThings, 30, bridgeConfig.getRefresh(),
100                 TimeUnit.SECONDS);
101     }
102
103     private void updateAlarmThings() {
104         logger.debug("Updating overall alarm's statuses...");
105         @Nullable
106         List<JablotronDiscoveredService> services = discoverServices();
107         if (services != null) {
108             Bridge localBridge = getThing();
109             if (localBridge != null && ThingStatus.ONLINE != localBridge.getStatus()) {
110                 updateStatus(ThingStatus.ONLINE);
111             }
112             for (JablotronDiscoveredService service : services) {
113                 updateAlarmThing(service);
114             }
115         }
116     }
117
118     private void updateAlarmThing(JablotronDiscoveredService service) {
119         for (Thing th : getThing().getThings()) {
120             if (ThingStatus.ONLINE != th.getStatus()) {
121                 logger.debug("Thing {} is not online", th.getUID());
122                 continue;
123             }
124
125             JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
126
127             if (handler == null) {
128                 logger.debug("Thing handler is null");
129                 continue;
130             }
131
132             if (String.valueOf(service.getId()).equals(handler.thingConfig.getServiceId())) {
133                 if ("ENABLED".equals(service.getStatus())) {
134                     if (!service.getWarning().isEmpty()) {
135                         logger.debug("Alarm with service id: {} warning: {}", service.getId(), service.getWarning());
136                     }
137                     handler.setStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, service.getWarning());
138                     if ("ALARM".equals(service.getWarning()) || "TAMPER".equals(service.getWarning())) {
139                         handler.triggerAlarm(service);
140                     }
141                     handler.setInService("SERVICE".equals(service.getWarning()));
142                 } else {
143                     logger.debug("Alarm with service id: {} is offline", service.getId());
144                     handler.setStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, service.getStatus());
145                 }
146             }
147         }
148     }
149
150     @Override
151     public void dispose() {
152         super.dispose();
153         ScheduledFuture<?> localFuture = future;
154         if (localFuture != null) {
155             localFuture.cancel(true);
156         }
157         logout();
158     }
159
160     private @Nullable <T> T sendJsonMessage(String url, String urlParameters, Class<T> classOfT) {
161         return sendMessage(url, urlParameters, classOfT, APPLICATION_JSON, true);
162     }
163
164     private @Nullable <T> T sendJsonMessage(String url, String urlParameters, Class<T> classOfT, boolean relogin) {
165         return sendMessage(url, urlParameters, classOfT, APPLICATION_JSON, relogin);
166     }
167
168     private @Nullable <T> T sendUrlEncodedMessage(String url, String urlParameters, Class<T> classOfT) {
169         return sendMessage(url, urlParameters, classOfT, WWW_FORM_URLENCODED, true);
170     }
171
172     private @Nullable <T> T sendMessage(String url, String urlParameters, Class<T> classOfT, String encoding,
173             boolean relogin) {
174         String line = "";
175         try {
176             ContentResponse resp = createRequest(url).content(new StringContentProvider(urlParameters), encoding)
177                     .send();
178
179             logger.trace("Request: {} with data: {}", url, urlParameters);
180             line = resp.getContentAsString();
181             logger.trace("Response: {}", line);
182             return gson.fromJson(line, classOfT);
183         } catch (TimeoutException e) {
184             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
185                     "Timeout during calling url: " + url);
186         } catch (InterruptedException e) {
187             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
188                     "Interrupt during calling url: " + url);
189             Thread.currentThread().interrupt();
190         } catch (JsonSyntaxException e) {
191             logger.debug("Invalid JSON received: {}", line);
192             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
193                     "Syntax error during calling url: " + url);
194         } catch (ExecutionException e) {
195             if (relogin) {
196                 if (e.getMessage().contains(AUTHENTICATION_CHALLENGE)) {
197                     relogin();
198                     return null;
199                 }
200             }
201             logger.debug("Error during calling url: {}", url, e);
202             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
203                     "Error during calling url: " + url);
204         }
205         return null;
206     }
207
208     protected synchronized void login() {
209         String url = JABLOTRON_API_URL + "userAuthorize.json";
210         String urlParameters = "{\"login\":\"" + bridgeConfig.getLogin() + "\", \"password\":\""
211                 + bridgeConfig.getPassword() + "\"}";
212         JablotronLoginResponse response = sendJsonMessage(url, urlParameters, JablotronLoginResponse.class, false);
213
214         if (response == null) {
215             return;
216         }
217
218         if (response.getHttpCode() != 200) {
219             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
220                     "Http error: " + response.getHttpCode());
221         } else {
222             updateStatus(ThingStatus.ONLINE);
223         }
224     }
225
226     protected void logout() {
227         String url = JABLOTRON_API_URL + "logout.json";
228         String urlParameters = "system=" + SYSTEM;
229
230         try {
231             ContentResponse resp = createRequest(url)
232                     .content(new StringContentProvider(urlParameters), WWW_FORM_URLENCODED).send();
233
234             if (logger.isTraceEnabled()) {
235                 String line = resp.getContentAsString();
236                 logger.trace("logout response: {}", line);
237             }
238         } catch (ExecutionException | TimeoutException | InterruptedException e) {
239             // Silence
240         }
241     }
242
243     public @Nullable List<JablotronDiscoveredService> discoverServices() {
244         String url = JABLOTRON_API_URL + "serviceListGet.json";
245         String urlParameters = "{\"list-type\": \"EXTENDED\",\"visibility\": \"VISIBLE\"}";
246         JablotronGetServiceResponse response = sendJsonMessage(url, urlParameters, JablotronGetServiceResponse.class);
247
248         if (response == null) {
249             return null;
250         }
251
252         if (response.getHttpCode() != 200) {
253             logger.debug("Error during service discovery, got http code: {}", response.getHttpCode());
254         }
255
256         return response.getData().getServices();
257     }
258
259     protected @Nullable JablotronControlResponse sendUserCode(Thing th, String section, String key, String status,
260             String code) {
261         JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
262
263         if (handler == null) {
264             logger.debug("Thing handler is null");
265             return null;
266         }
267
268         if (handler.isInService()) {
269             logger.debug("Cannot send command because the alarm is in the service mode");
270             return null;
271         }
272
273         String url = JABLOTRON_API_URL + "controlSegment.json";
274         String urlParameters = "service=" + th.getThingTypeUID().getId() + "&serviceId="
275                 + handler.thingConfig.getServiceId() + "&segmentId=" + section + "&segmentKey=" + key
276                 + "&expected_status=" + status + "&control_time=0&control_code=" + code + "&system=" + SYSTEM;
277
278         JablotronControlResponse response = sendUrlEncodedMessage(url, urlParameters, JablotronControlResponse.class);
279
280         if (response == null) {
281             return null;
282         }
283
284         if (!response.isStatus()) {
285             logger.debug("Error during sending user code: {}", response.getErrorMessage());
286         }
287         return response;
288     }
289
290     protected @Nullable List<JablotronHistoryDataEvent> sendGetEventHistory(Thing th, String alarm) {
291         String url = JABLOTRON_API_URL + alarm + "/eventHistoryGet.json";
292         JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
293
294         if (handler == null) {
295             logger.debug("Thing handler is null");
296             return null;
297         }
298
299         String urlParameters = "{\"limit\":1, \"service-id\":" + handler.thingConfig.getServiceId() + "}";
300         JablotronGetEventHistoryResponse response = sendJsonMessage(url, urlParameters,
301                 JablotronGetEventHistoryResponse.class);
302
303         if (response == null) {
304             return null;
305         }
306
307         if (200 != response.getHttpCode()) {
308             logger.debug("Got error while getting history with http code: {}", response.getHttpCode());
309         }
310         return response.getData().getEvents();
311     }
312
313     protected @Nullable JablotronDataUpdateResponse sendGetStatusRequest(Thing th) {
314         String url = JABLOTRON_API_URL + "dataUpdate.json";
315         JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
316
317         if (handler == null) {
318             logger.debug("Thing handler is null");
319             return null;
320         }
321
322         String urlParameters = "data=[{ \"filter_data\":[{\"data_type\":\"section\"},{\"data_type\":\"pgm\"},{\"data_type\":\"thermometer\"},{\"data_type\":\"thermostat\"}],\"service_type\":\""
323                 + th.getThingTypeUID().getId() + "\",\"service_id\":" + handler.thingConfig.getServiceId()
324                 + ",\"data_group\":\"serviceData\"}]&system=" + SYSTEM;
325
326         return sendUrlEncodedMessage(url, urlParameters, JablotronDataUpdateResponse.class);
327     }
328
329     protected @Nullable JablotronGetPGResponse sendGetProgrammableGates(Thing th, String alarm) {
330         String url = JABLOTRON_API_URL + alarm + "/programmableGatesGet.json";
331         JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
332
333         if (handler == null) {
334             logger.debug("Thing handler is null");
335             return null;
336         }
337
338         String urlParameters = getCommonUrlParameters(handler.thingConfig.getServiceId());
339
340         return sendJsonMessage(url, urlParameters, JablotronGetPGResponse.class);
341     }
342
343     private String getCommonUrlParameters(String serviceId) {
344         return "{\"connect-device\":false,\"list-type\":\"FULL\",\"service-id\":" + serviceId
345                 + ",\"service-states\":true}";
346     }
347
348     protected @Nullable JablotronGetThermoDevicesResponse sendGetThermometers(Thing th, String alarm) {
349         String url = JABLOTRON_API_URL + alarm + "/thermoDevicesGet.json";
350         JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
351
352         if (handler == null) {
353             logger.debug("Thing handler is null");
354             return null;
355         }
356
357         String urlParameters = getCommonUrlParameters(handler.thingConfig.getServiceId());
358
359         return sendJsonMessage(url, urlParameters, JablotronGetThermoDevicesResponse.class);
360     }
361
362     protected @Nullable JablotronGetSectionsResponse sendGetSections(Thing th, String alarm) {
363         String url = JABLOTRON_API_URL + alarm + "/sectionsGet.json";
364         JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
365
366         if (handler == null) {
367             logger.debug("Thing handler is null");
368             return null;
369         }
370
371         String urlParameters = getCommonUrlParameters(handler.thingConfig.getServiceId());
372
373         return sendJsonMessage(url, urlParameters, JablotronGetSectionsResponse.class);
374     }
375
376     protected @Nullable JablotronGetSectionsResponse controlComponent(Thing th, String code, String action,
377             String value, String componentId) throws SecurityException {
378         JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
379
380         if (handler == null) {
381             logger.debug("Thing handler is null");
382             return null;
383         }
384
385         if (handler.isInService()) {
386             logger.debug("Cannot control component because the alarm is in the service mode");
387             return null;
388         }
389
390         String url = JABLOTRON_API_URL + handler.getAlarmName() + "/controlComponent.json";
391         String urlParameters = "{\"authorization\":{\"authorization-code\":\"" + code
392                 + "\"},\"control-components\":[{\"actions\":{\"action\":\"" + action + "\",\"value\":\"" + value
393                 + "\"},\"component-id\":\"" + componentId + "\"}],\"service-id\":" + handler.thingConfig.getServiceId()
394                 + "}";
395
396         return sendJsonMessage(url, urlParameters, JablotronGetSectionsResponse.class);
397     }
398
399     private Request createRequest(String url) {
400         return httpClient.newRequest(url).method(HttpMethod.POST).header(HttpHeader.ACCEPT, APPLICATION_JSON)
401                 .header(HttpHeader.ACCEPT_LANGUAGE, bridgeConfig.getLang()).header(HttpHeader.ACCEPT_ENCODING, "*")
402                 .header("x-vendor-id", VENDOR).agent(AGENT).timeout(TIMEOUT_SEC, TimeUnit.SECONDS);
403     }
404
405     private void relogin() {
406         logger.debug("doing relogin");
407         login();
408     }
409 }