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.avmfritz.internal.callmonitor;
15 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
17 import java.io.BufferedReader;
18 import java.io.IOException;
19 import java.io.InputStreamReader;
20 import java.net.Socket;
21 import java.util.concurrent.ScheduledExecutorService;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.avmfritz.internal.handler.BoxHandler;
28 import org.openhab.core.library.types.StringListType;
29 import org.openhab.core.thing.ThingStatus;
30 import org.openhab.core.thing.ThingStatusDetail;
31 import org.openhab.core.types.UnDefType;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * This class handles all communication with the Call Monitor port of the FRITZ!Box.
38 * @author Kai Kreuzer - Initial contribution
41 public class CallMonitor {
43 protected final Logger logger = LoggerFactory.getLogger(CallMonitor.class);
45 // port number to connect to FRITZ!Box
46 private static final int MONITOR_PORT = 1012;
48 private @Nullable CallMonitorThread monitorThread;
49 private final ScheduledFuture<?> reconnectJob;
51 private final String ip;
52 private final BoxHandler handler;
54 public CallMonitor(String ip, BoxHandler handler, ScheduledExecutorService scheduler) {
56 this.handler = handler;
57 reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
60 // Wait before reconnect
63 } catch (InterruptedException e) {
66 // create a new thread for listening to the FRITZ!Box
67 CallMonitorThread thread = new CallMonitorThread("OH-binding-" + handler.getThing().getUID().getAsString());
69 this.monitorThread = thread;
70 }, 0, 2, TimeUnit.HOURS);
71 // initialize states of Call Monitor channels
78 public void resetChannels() {
79 handler.updateState(CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
80 handler.updateState(CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
81 handler.updateState(CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
82 handler.updateState(CHANNEL_CALL_STATE, CALL_STATE_IDLE);
86 * Cancel the reconnect job.
88 public void dispose() {
89 reconnectJob.cancel(true);
93 public class CallMonitorThread extends Thread {
96 private @Nullable Socket socket;
98 // Thread control flag
99 private boolean interrupted = false;
101 // time to wait before reconnecting
102 private long reconnectTime = 60000L;
104 public CallMonitorThread(String threadName) {
106 setUncaughtExceptionHandler((thread, throwable) -> {
107 logger.warn("Lost connection to FRITZ!Box because of an uncaught exception: ", throwable);
113 while (!interrupted) {
114 BufferedReader reader = null;
116 logger.debug("Call Monitor thread [{}] attempting connection to FRITZ!Box on {}:{}.",
117 Thread.currentThread().getId(), ip, MONITOR_PORT);
118 socket = new Socket(ip, MONITOR_PORT);
119 reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
120 // reset the retry interval
121 reconnectTime = 60000L;
122 } catch (IOException e) {
123 handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
124 "Cannot connect to FRITZ!Box Call Monitor - make sure to enable it by dialing '#96*5*'!");
125 logger.debug("Error attempting to connect to FRITZ!Box. Retrying in {} seconds",
126 reconnectTime / 1000L, e);
128 Thread.sleep(reconnectTime);
129 } catch (InterruptedException ex) {
132 // wait another more minute the next time
133 reconnectTime += 60000L;
135 if (reader != null) {
136 logger.debug("Connected to FRITZ!Box Call Monitor at {}:{}.", ip, MONITOR_PORT);
137 handler.setStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
138 while (!interrupted) {
140 if (reader.ready()) {
141 String line = reader.readLine();
143 logger.debug("Received raw call string from FRITZ!Box: {}", line);
144 CallEvent ce = new CallEvent(line);
148 } catch (IOException e) {
150 logger.debug("Lost connection to FRITZ!Box because of an interrupt.");
152 handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
153 "Lost connection to FRITZ!Box: " + e.getMessage());
159 } catch (InterruptedException e) {
169 * Close socket and stop running thread.
172 public void interrupt() {
174 if (socket != null) {
177 logger.debug("Socket to FRITZ!Box closed.");
178 } catch (IOException e) {
179 logger.warn("Failed to close connection to FRITZ!Box.", e);
182 logger.debug("Socket to FRITZ!Box not open, therefore not closing it.");
187 * Handle call event and update item as required.
189 * @param ce call event to process
191 private void handleCallEvent(CallEvent ce) {
192 switch (ce.getCallType()) {
193 case CallEvent.CALL_TYPE_DISCONNECT:
194 // reset states of Call Monitor channels
197 case CallEvent.CALL_TYPE_RING: // first event when call is incoming
198 handler.updateState(CHANNEL_CALL_INCOMING,
199 new StringListType(ce.getInternalNo(), ce.getExternalNo()));
200 handler.updateState(CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
201 handler.updateState(CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
202 handler.updateState(CHANNEL_CALL_STATE, CALL_STATE_RINGING);
204 case CallEvent.CALL_TYPE_CONNECT: // when call is answered/running
205 handler.updateState(CHANNEL_CALL_ACTIVE, new StringListType(ce.getExternalNo(), ""));
206 handler.updateState(CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
207 handler.updateState(CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
208 handler.updateState(CHANNEL_CALL_STATE, CALL_STATE_ACTIVE);
210 case CallEvent.CALL_TYPE_CALL: // outgoing call
211 handler.updateState(CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
212 handler.updateState(CHANNEL_CALL_OUTGOING,
213 new StringListType(ce.getExternalNo(), ce.getInternalNo()));
214 handler.updateState(CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
215 handler.updateState(CHANNEL_CALL_STATE, CALL_STATE_DIALING);
221 public void stopThread() {
222 logger.debug("Stopping Call Monitor thread...");
223 CallMonitorThread thread = this.monitorThread;
224 if (thread != null) {
226 monitorThread = null;