]> git.basschouten.com Git - openhab-addons.git/blob
2f6a732930ceae69ff6acd9f186f55db27cc214a
[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.alarmdecoder.internal.handler;
14
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;
25
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;
34
35 /**
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.
38  *
39  * @author Bernd Pfrommer - Initial contribution (OH1 version)
40  * @author Bob Adair - Re-factored into OH2 binding
41  */
42 @NonNullByDefault
43 public class IPBridgeHandler extends ADBridgeHandler {
44
45     private final Logger logger = LoggerFactory.getLogger(IPBridgeHandler.class);
46
47     private IPBridgeConfig config = new IPBridgeConfig();
48
49     private @Nullable Socket socket = null;
50
51     public IPBridgeHandler(Bridge bridge) {
52         super(bridge);
53     }
54
55     @Override
56     public void initialize() {
57         logger.debug("Initializing IP bridge handler");
58         config = getConfigAs(IPBridgeConfig.class);
59         discovery = config.discovery;
60
61         if (config.hostname == null) {
62             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "hostname not configured");
63             return;
64         }
65         if (config.tcpPort <= 0 || config.tcpPort > 65535) {
66             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "invalid port number configured");
67             return;
68         }
69
70         // set the thing status to UNKNOWN temporarily and let the background connect task decide the real status.
71         updateStatus(ThingStatus.UNKNOWN);
72
73         scheduler.submit(this::connect); // start the async connect task
74     }
75
76     @Override
77     protected synchronized void connect() {
78         disconnect(); // make sure we are disconnected
79         writeException = false;
80         try {
81             Socket socket = new Socket(config.hostname, config.tcpPort);
82             this.socket = socket;
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;
87             startMsgReader();
88             updateStatus(ThingStatus.ONLINE);
89
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");
97             disconnect();
98         } catch (IOException e) {
99             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
100             disconnect();
101             scheduleConnectRetry(config.reconnect); // Possibly a retryable error. Try again later.
102         }
103     }
104
105     protected synchronized void connectionCheck() {
106         logger.trace("Connection check job running");
107
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);
115         } else {
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);
122             }
123         }
124     }
125
126     @Override
127     protected synchronized void disconnect() {
128         logger.trace("Disconnecting");
129         // stop scheduled connection check and retry jobs
130         ScheduledFuture<?> crJob = connectRetryJob;
131         if (crJob != null) {
132             // use cancel(false) so we don't kill ourselves when connect retry job calls disconnect()
133             crJob.cancel(false);
134             connectRetryJob = null;
135         }
136         ScheduledFuture<?> ccJob = connectionCheckJob;
137         if (ccJob != null) {
138             // use cancel(false) so we don't kill ourselves when reconnect job calls disconnect()
139             ccJob.cancel(false);
140             connectionCheckJob = null;
141         }
142
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.
145         Socket s = socket;
146         if (s != null) {
147             try {
148                 s.close();
149             } catch (IOException e) {
150                 logger.debug("error closing socket: {}", e.getMessage());
151             }
152         }
153         socket = null;
154
155         stopMsgReader();
156
157         try {
158             BufferedWriter bw = writer;
159             if (bw != null) {
160                 bw.close();
161             }
162             BufferedReader br = reader;
163             if (br != null) {
164                 br.close();
165             }
166         } catch (IOException e) {
167             logger.debug("error closing reader/writer: {}", e.getMessage());
168         }
169         writer = null;
170         reader = null;
171     }
172 }