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