2 * Copyright (c) 2010-2021 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 java.io.BufferedReader;
16 import java.io.IOException;
17 import java.io.InputStreamReader;
18 import java.net.Socket;
19 import java.util.concurrent.ScheduledExecutorService;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants;
26 import org.openhab.binding.avmfritz.internal.handler.BoxHandler;
27 import org.openhab.core.library.types.StringListType;
28 import org.openhab.core.thing.ThingStatus;
29 import org.openhab.core.thing.ThingStatusDetail;
30 import org.openhab.core.types.UnDefType;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
35 * This class handles all communication with the call monitor port of the fritzbox.
37 * @author Kai Kreuzer - Initial contribution
40 public class CallMonitor {
42 protected final Logger logger = LoggerFactory.getLogger(CallMonitor.class);
44 // port number to connect to fritzbox
45 private final int MONITOR_PORT = 1012;
47 private @Nullable CallMonitorThread monitorThread;
48 private final ScheduledFuture<?> reconnectJob;
50 private final String ip;
51 private final BoxHandler handler;
53 public CallMonitor(String ip, BoxHandler handler, ScheduledExecutorService scheduler) {
55 this.handler = handler;
56 reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
59 // Wait before reconnect
62 } catch (InterruptedException e) {
65 // create a new thread for listening to the FritzBox
66 CallMonitorThread thread = new CallMonitorThread();
67 thread.setName("OH-binding-" + handler.getThing().getUID().getAsString());
69 this.monitorThread = thread;
70 }, 0, 2, TimeUnit.HOURS);
74 * Cancel the reconnect job.
76 public void dispose() {
77 reconnectJob.cancel(true);
78 CallMonitorThread monitorThread = this.monitorThread;
79 if (monitorThread != null) {
80 monitorThread.interrupt();
84 public class CallMonitorThread extends Thread {
87 private @Nullable Socket socket;
89 // Thread control flag
90 private boolean interrupted = false;
92 // time to wait before reconnecting
93 private long reconnectTime = 60000L;
95 public CallMonitorThread() {
100 while (!interrupted) {
101 BufferedReader reader = null;
103 logger.debug("Callmonitor thread [{}] attempting connection to FritzBox on {}:{}.",
104 Thread.currentThread().getId(), ip, MONITOR_PORT);
105 socket = new Socket(ip, MONITOR_PORT);
106 reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
107 // reset the retry interval
108 reconnectTime = 60000L;
109 } catch (Exception e) {
110 handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
111 "Cannot connect to Fritz!Box call monitor - make sure to enable it by dialing '#96*5'!");
112 logger.debug("Error attempting to connect to FritzBox. Retrying in {} seconds",
113 reconnectTime / 1000L, e);
115 Thread.sleep(reconnectTime);
116 } catch (InterruptedException ex) {
119 // wait another more minute the next time
120 reconnectTime += 60000L;
122 if (reader != null) {
123 logger.debug("Connected to FritzBox call monitor at {}:{}.", ip, MONITOR_PORT);
124 handler.setStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
125 while (!interrupted) {
127 if (reader.ready()) {
128 String line = reader.readLine();
130 logger.debug("Received raw call string from fbox: {}", line);
131 CallEvent ce = new CallEvent(line);
135 } catch (IOException e) {
137 logger.debug("Lost connection to Fritzbox because of an interrupt.");
139 handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
140 "Lost connection to Fritz!Box: " + e.getMessage());
146 } catch (InterruptedException e) {
156 * Close socket and stop running thread.
159 public void interrupt() {
161 if (socket != null) {
164 logger.debug("Socket to FritzBox closed.");
165 } catch (IOException e) {
166 logger.warn("Failed to close connection to FritzBox.", e);
169 logger.debug("Socket to FritzBox not open, therefore not closing it.");
174 * Handle call event and update item as required.
176 * @param ce call event to process
178 private void handleCallEvent(CallEvent ce) {
179 if (ce.getCallType().equals("DISCONNECT")) {
180 // reset states of call monitor channels
181 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
182 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
183 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
184 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE,
185 AVMFritzBindingConstants.CALL_STATE_IDLE);
186 } else if (ce.getCallType().equals("RING")) { // first event when call is incoming
187 StringListType state = new StringListType(ce.getInternalNo(), ce.getExternalNo());
188 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, state);
189 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
190 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
191 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE,
192 AVMFritzBindingConstants.CALL_STATE_RINGING);
193 } else if (ce.getCallType().equals("CONNECT")) { // when call is answered/running
194 StringListType state = new StringListType(ce.getExternalNo(), "");
195 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, state);
196 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
197 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
198 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE,
199 AVMFritzBindingConstants.CALL_STATE_ACTIVE);
200 } else if (ce.getCallType().equals("CALL")) { // outgoing call
201 StringListType state = new StringListType(ce.getExternalNo(), ce.getInternalNo());
202 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
203 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, state);
204 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
205 handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE,
206 AVMFritzBindingConstants.CALL_STATE_DIALING);
211 public void stopThread() {
212 logger.debug("Stopping call monitor thread...");
213 if (monitorThread != null) {
214 monitorThread.interrupt();
215 monitorThread = null;
219 public void startThread() {
220 logger.debug("Starting call monitor thread...");
221 if (monitorThread != null) {
222 monitorThread.interrupt();
223 monitorThread = null;
225 // create a new thread for listening to the FritzBox
226 monitorThread = new CallMonitorThread();
227 monitorThread.start();