2 * Copyright (c) 2010-2023 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.alarmdecoder.internal.handler;
15 import java.io.BufferedReader;
16 import java.io.BufferedWriter;
17 import java.io.IOException;
18 import java.io.InputStreamReader;
19 import java.io.OutputStreamWriter;
20 import java.net.Socket;
21 import java.net.UnknownHostException;
22 import java.util.Date;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.alarmdecoder.internal.config.IPBridgeConfig;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.ThingStatus;
31 import org.openhab.core.thing.ThingStatusDetail;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * Handler responsible for communicating via TCP with the Nu Tech Alarm Decoder device.
37 * Based on and including code from the original OH1 alarmdecoder binding.
39 * @author Bernd Pfrommer - Initial contribution (OH1 version)
40 * @author Bob Adair - Re-factored into OH2 binding
43 public class IPBridgeHandler extends ADBridgeHandler {
45 private final Logger logger = LoggerFactory.getLogger(IPBridgeHandler.class);
47 private IPBridgeConfig config = new IPBridgeConfig();
49 private @Nullable Socket socket = null;
51 public IPBridgeHandler(Bridge bridge) {
56 public void initialize() {
57 logger.debug("Initializing IP bridge handler");
58 config = getConfigAs(IPBridgeConfig.class);
59 discovery = config.discovery;
61 if (config.hostname == null) {
62 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "hostname not configured");
65 if (config.tcpPort <= 0 || config.tcpPort > 65535) {
66 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "invalid port number configured");
70 // set the thing status to UNKNOWN temporarily and let the background connect task decide the real status.
71 updateStatus(ThingStatus.UNKNOWN);
73 scheduler.submit(this::connect); // start the async connect task
77 protected synchronized void connect() {
78 disconnect(); // make sure we are disconnected
79 writeException = false;
81 Socket socket = new Socket(config.hostname, config.tcpPort);
83 reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), AD_CHARSET));
84 writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), AD_CHARSET));
85 logger.debug("connected to {}:{}", config.hostname, config.tcpPort);
86 panelReadyReceived = false;
88 updateStatus(ThingStatus.ONLINE);
90 // Start connection check job
91 logger.debug("Scheduling connection check job with interval {} minutes.", config.reconnect);
92 lastReceivedTime = new Date();
93 connectionCheckJob = scheduler.scheduleWithFixedDelay(this::connectionCheck, config.reconnect,
94 config.reconnect, TimeUnit.MINUTES);
95 } catch (UnknownHostException e) {
96 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "unknown host");
98 } catch (IOException e) {
99 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
101 scheduleConnectRetry(config.reconnect); // Possibly a retryable error. Try again later.
105 protected synchronized void connectionCheck() {
106 logger.trace("Connection check job running");
108 Thread mrThread = msgReaderThread;
109 if (mrThread != null && !mrThread.isAlive()) {
110 logger.debug("Reader thread has exited abnormally. Restarting.");
111 scheduler.submit(this::connect);
112 } else if (writeException) {
113 logger.debug("Write exception encountered. Resetting connection.");
114 scheduler.submit(this::connect);
116 Date now = new Date();
117 Date last = lastReceivedTime;
118 if (last != null && config.timeout > 0
119 && ((last.getTime() + (config.timeout * 60 * 1000)) < now.getTime())) {
120 logger.warn("Last valid message received at {}. Resetting connection.", last);
121 scheduler.submit(this::connect);
127 protected synchronized void disconnect() {
128 logger.trace("Disconnecting");
129 // stop scheduled connection check and retry jobs
130 ScheduledFuture<?> crJob = connectRetryJob;
132 // use cancel(false) so we don't kill ourselves when connect retry job calls disconnect()
134 connectRetryJob = null;
136 ScheduledFuture<?> ccJob = connectionCheckJob;
138 // use cancel(false) so we don't kill ourselves when reconnect job calls disconnect()
140 connectionCheckJob = null;
143 // Must close the socket first so the message reader thread will exit properly.
144 // The BufferedReader.readLine() call used in readerThread() is not interruptable.
149 } catch (IOException e) {
150 logger.debug("error closing socket: {}", e.getMessage());
158 BufferedWriter bw = writer;
162 BufferedReader br = reader;
166 } catch (IOException e) {
167 logger.debug("error closing reader/writer: {}", e.getMessage());