]> git.basschouten.com Git - openhab-addons.git/blob
cf196880937a00c16ac2192b6e50e8106dcd6858
[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.avmfritz.internal.callmonitor;
14
15 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
16
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;
24
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;
34
35 /**
36  * This class handles all communication with the Call Monitor port of the FRITZ!Box.
37  *
38  * @author Kai Kreuzer - Initial contribution
39  */
40 @NonNullByDefault
41 public class CallMonitor {
42
43     protected final Logger logger = LoggerFactory.getLogger(CallMonitor.class);
44
45     // port number to connect to FRITZ!Box
46     private static final int MONITOR_PORT = 1012;
47
48     private @Nullable CallMonitorThread monitorThread;
49     private final ScheduledFuture<?> reconnectJob;
50
51     private final String ip;
52     private final BoxHandler handler;
53
54     public CallMonitor(String ip, BoxHandler handler, ScheduledExecutorService scheduler) {
55         this.ip = ip;
56         this.handler = handler;
57         reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
58             stopThread();
59
60             // Wait before reconnect
61             try {
62                 Thread.sleep(5000L);
63             } catch (InterruptedException e) {
64             }
65
66             // create a new thread for listening to the FRITZ!Box
67             CallMonitorThread thread = new CallMonitorThread("OH-binding-" + handler.getThing().getUID().getAsString());
68             thread.start();
69             this.monitorThread = thread;
70         }, 0, 2, TimeUnit.HOURS);
71         // initialize states of Call Monitor channels
72         resetChannels();
73     }
74
75     /**
76      * Reset channels.
77      */
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);
83     }
84
85     /**
86      * Cancel the reconnect job.
87      */
88     public void dispose() {
89         reconnectJob.cancel(true);
90         stopThread();
91     }
92
93     public class CallMonitorThread extends Thread {
94
95         // Socket to connect
96         private @Nullable Socket socket;
97
98         // Thread control flag
99         private boolean interrupted = false;
100
101         // time to wait before reconnecting
102         private long reconnectTime = 60000L;
103
104         public CallMonitorThread(String threadName) {
105             super(threadName);
106             setUncaughtExceptionHandler((thread, throwable) -> {
107                 logger.warn("Lost connection to FRITZ!Box because of an uncaught exception: ", throwable);
108             });
109         }
110
111         @Override
112         public void run() {
113             while (!interrupted) {
114                 BufferedReader reader = null;
115                 try {
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);
127                     try {
128                         Thread.sleep(reconnectTime);
129                     } catch (InterruptedException ex) {
130                         interrupted = true;
131                     }
132                     // wait another more minute the next time
133                     reconnectTime += 60000L;
134                 }
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) {
139                         try {
140                             if (reader.ready()) {
141                                 String line = reader.readLine();
142                                 if (line != null) {
143                                     logger.debug("Received raw call string from FRITZ!Box: {}", line);
144                                     CallEvent ce = new CallEvent(line);
145                                     handleCallEvent(ce);
146                                 }
147                             }
148                         } catch (IOException e) {
149                             if (interrupted) {
150                                 logger.debug("Lost connection to FRITZ!Box because of an interrupt.");
151                             } else {
152                                 handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
153                                         "Lost connection to FRITZ!Box: " + e.getMessage());
154                             }
155                             break;
156                         } finally {
157                             try {
158                                 sleep(500L);
159                             } catch (InterruptedException e) {
160                                 interrupted = true;
161                             }
162                         }
163                     }
164                 }
165             }
166         }
167
168         /**
169          * Close socket and stop running thread.
170          */
171         @Override
172         public void interrupt() {
173             interrupted = true;
174             if (socket != null) {
175                 try {
176                     socket.close();
177                     logger.debug("Socket to FRITZ!Box closed.");
178                 } catch (IOException e) {
179                     logger.warn("Failed to close connection to FRITZ!Box.", e);
180                 }
181             } else {
182                 logger.debug("Socket to FRITZ!Box not open, therefore not closing it.");
183             }
184         }
185
186         /**
187          * Handle call event and update item as required.
188          *
189          * @param ce call event to process
190          */
191         private void handleCallEvent(CallEvent ce) {
192             switch (ce.getCallType()) {
193                 case CallEvent.CALL_TYPE_DISCONNECT:
194                     // reset states of Call Monitor channels
195                     resetChannels();
196                     break;
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);
203                     break;
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);
209                     break;
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);
216                     break;
217             }
218         }
219     }
220
221     public void stopThread() {
222         logger.debug("Stopping Call Monitor thread...");
223         CallMonitorThread thread = this.monitorThread;
224         if (thread != null) {
225             thread.interrupt();
226             monitorThread = null;
227         }
228     }
229 }