2 * Copyright (c) 2010-2024 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.jablotron.internal.handler;
15 import static org.openhab.binding.jablotron.JablotronBindingConstants.*;
17 import java.util.Collection;
18 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;
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;
56 import com.google.gson.Gson;
57 import com.google.gson.JsonSyntaxException;
60 * The {@link JablotronBridgeHandler} is responsible for handling commands, which are
61 * sent to one of the channels.
63 * @author Ondrej Pecta - Initial contribution
66 public class JablotronBridgeHandler extends BaseBridgeHandler {
68 private final Logger logger = LoggerFactory.getLogger(JablotronBridgeHandler.class);
70 private final Gson gson = new Gson();
72 final HttpClient httpClient;
74 private @Nullable ScheduledFuture<?> future = null;
79 public JablotronBridgeConfig bridgeConfig = new JablotronBridgeConfig();
81 public JablotronBridgeHandler(Bridge thing, HttpClient httpClient) {
83 this.httpClient = httpClient;
87 public Collection<Class<? extends ThingHandlerService>> getServices() {
88 return Set.of(JablotronDiscoveryService.class);
92 public void handleCommand(ChannelUID channelUID, Command command) {
96 public void initialize() {
97 bridgeConfig = getConfigAs(JablotronBridgeConfig.class);
98 scheduler.execute(this::login);
99 future = scheduler.scheduleWithFixedDelay(this::updateAlarmThings, 30, bridgeConfig.getRefresh(),
103 private void updateAlarmThings() {
104 logger.debug("Updating overall alarm's statuses...");
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);
112 for (JablotronDiscoveredService service : services) {
113 updateAlarmThing(service);
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());
125 JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
127 if (handler == null) {
128 logger.debug("Thing handler is null");
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());
137 handler.setStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, service.getWarning());
138 if ("ALARM".equals(service.getWarning()) || "TAMPER".equals(service.getWarning())) {
139 handler.triggerAlarm(service);
141 handler.setInService("SERVICE".equals(service.getWarning()));
143 logger.debug("Alarm with service id: {} is offline", service.getId());
144 handler.setStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, service.getStatus());
151 public void dispose() {
153 ScheduledFuture<?> localFuture = future;
154 if (localFuture != null) {
155 localFuture.cancel(true);
160 private @Nullable <T> T sendJsonMessage(String url, String urlParameters, Class<T> classOfT) {
161 return sendMessage(url, urlParameters, classOfT, APPLICATION_JSON, true);
164 private @Nullable <T> T sendJsonMessage(String url, String urlParameters, Class<T> classOfT, boolean relogin) {
165 return sendMessage(url, urlParameters, classOfT, APPLICATION_JSON, relogin);
168 private @Nullable <T> T sendUrlEncodedMessage(String url, String urlParameters, Class<T> classOfT) {
169 return sendMessage(url, urlParameters, classOfT, WWW_FORM_URLENCODED, true);
172 private @Nullable <T> T sendMessage(String url, String urlParameters, Class<T> classOfT, String encoding,
176 ContentResponse resp = createRequest(url).content(new StringContentProvider(urlParameters), encoding)
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) {
196 if (e.getMessage().contains(AUTHENTICATION_CHALLENGE)) {
201 logger.debug("Error during calling url: {}", url, e);
202 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
203 "Error during calling url: " + url);
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);
214 if (response == null) {
218 if (response.getHttpCode() != 200) {
219 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
220 "Http error: " + response.getHttpCode());
222 updateStatus(ThingStatus.ONLINE);
226 protected void logout() {
227 String url = JABLOTRON_API_URL + "logout.json";
228 String urlParameters = "system=" + SYSTEM;
231 ContentResponse resp = createRequest(url)
232 .content(new StringContentProvider(urlParameters), WWW_FORM_URLENCODED).send();
234 if (logger.isTraceEnabled()) {
235 String line = resp.getContentAsString();
236 logger.trace("logout response: {}", line);
238 } catch (ExecutionException | TimeoutException | InterruptedException e) {
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);
248 if (response == null) {
252 if (response.getHttpCode() != 200) {
253 logger.debug("Error during service discovery, got http code: {}", response.getHttpCode());
256 return response.getData().getServices();
259 protected @Nullable JablotronControlResponse sendUserCode(Thing th, String section, String key, String status,
261 JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
263 if (handler == null) {
264 logger.debug("Thing handler is null");
268 if (handler.isInService()) {
269 logger.debug("Cannot send command because the alarm is in the service mode");
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;
278 JablotronControlResponse response = sendUrlEncodedMessage(url, urlParameters, JablotronControlResponse.class);
280 if (response == null) {
284 if (!response.isStatus()) {
285 logger.debug("Error during sending user code: {}", response.getErrorMessage());
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();
294 if (handler == null) {
295 logger.debug("Thing handler is null");
299 String urlParameters = "{\"limit\":1, \"service-id\":" + handler.thingConfig.getServiceId() + "}";
300 JablotronGetEventHistoryResponse response = sendJsonMessage(url, urlParameters,
301 JablotronGetEventHistoryResponse.class);
303 if (response == null) {
307 if (200 != response.getHttpCode()) {
308 logger.debug("Got error while getting history with http code: {}", response.getHttpCode());
310 return response.getData().getEvents();
313 protected @Nullable JablotronDataUpdateResponse sendGetStatusRequest(Thing th) {
314 String url = JABLOTRON_API_URL + "dataUpdate.json";
315 JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
317 if (handler == null) {
318 logger.debug("Thing handler is null");
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;
326 return sendUrlEncodedMessage(url, urlParameters, JablotronDataUpdateResponse.class);
329 protected @Nullable JablotronGetPGResponse sendGetProgrammableGates(Thing th, String alarm) {
330 String url = JABLOTRON_API_URL + alarm + "/programmableGatesGet.json";
331 JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
333 if (handler == null) {
334 logger.debug("Thing handler is null");
338 String urlParameters = getCommonUrlParameters(handler.thingConfig.getServiceId());
340 return sendJsonMessage(url, urlParameters, JablotronGetPGResponse.class);
343 private String getCommonUrlParameters(String serviceId) {
344 return "{\"connect-device\":false,\"list-type\":\"FULL\",\"service-id\":" + serviceId
345 + ",\"service-states\":true}";
348 protected @Nullable JablotronGetThermoDevicesResponse sendGetThermometers(Thing th, String alarm) {
349 String url = JABLOTRON_API_URL + alarm + "/thermoDevicesGet.json";
350 JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
352 if (handler == null) {
353 logger.debug("Thing handler is null");
357 String urlParameters = getCommonUrlParameters(handler.thingConfig.getServiceId());
359 return sendJsonMessage(url, urlParameters, JablotronGetThermoDevicesResponse.class);
362 protected @Nullable JablotronGetSectionsResponse sendGetSections(Thing th, String alarm) {
363 String url = JABLOTRON_API_URL + alarm + "/sectionsGet.json";
364 JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
366 if (handler == null) {
367 logger.debug("Thing handler is null");
371 String urlParameters = getCommonUrlParameters(handler.thingConfig.getServiceId());
373 return sendJsonMessage(url, urlParameters, JablotronGetSectionsResponse.class);
376 protected @Nullable JablotronGetSectionsResponse controlComponent(Thing th, String code, String action,
377 String value, String componentId) throws SecurityException {
378 JablotronAlarmHandler handler = (JablotronAlarmHandler) th.getHandler();
380 if (handler == null) {
381 logger.debug("Thing handler is null");
385 if (handler.isInService()) {
386 logger.debug("Cannot control component because the alarm is in the service mode");
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()
396 return sendJsonMessage(url, urlParameters, JablotronGetSectionsResponse.class);
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);
405 private void relogin() {
406 logger.debug("doing relogin");