]> git.basschouten.com Git - openhab-addons.git/blob
6905c9aa491094e94cc1607066f20822c0cc5242
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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 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;
22
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;
33
34 /**
35  * This class handles all communication with the call monitor port of the fritzbox.
36  *
37  * @author Kai Kreuzer - Initial contribution
38  */
39 @NonNullByDefault
40 public class CallMonitor {
41
42     protected final Logger logger = LoggerFactory.getLogger(CallMonitor.class);
43
44     // port number to connect to fritzbox
45     private final int MONITOR_PORT = 1012;
46
47     private @Nullable CallMonitorThread monitorThread;
48     private ScheduledFuture<?> reconnectJob;
49
50     private String ip;
51     private BoxHandler handler;
52
53     public CallMonitor(String ip, BoxHandler handler, ScheduledExecutorService scheduler) {
54         this.ip = ip;
55         this.handler = handler;
56         reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
57             stopThread();
58
59             // Wait before reconnect
60             try {
61                 Thread.sleep(5000L);
62             } catch (InterruptedException e) {
63             }
64
65             // create a new thread for listening to the FritzBox
66             CallMonitorThread thread = new CallMonitorThread();
67             thread.setName("OH-binding-" + handler.getThing().getUID().getAsString());
68             thread.start();
69             this.monitorThread = thread;
70         }, 0, 2, TimeUnit.HOURS);
71     }
72
73     /**
74      * Cancel the reconnect job.
75      */
76     public void dispose() {
77         reconnectJob.cancel(true);
78         CallMonitorThread monitorThread = this.monitorThread;
79         if (monitorThread != null) {
80             monitorThread.interrupt();
81         }
82     }
83
84     public class CallMonitorThread extends Thread {
85
86         // Socket to connect
87         private @Nullable Socket socket;
88
89         // Thread control flag
90         private boolean interrupted = false;
91
92         // time to wait before reconnecting
93         private long reconnectTime = 60000L;
94
95         public CallMonitorThread() {
96         }
97
98         @Override
99         public void run() {
100             while (!interrupted) {
101                 BufferedReader reader = null;
102                 try {
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);
114                     try {
115                         Thread.sleep(reconnectTime);
116                     } catch (InterruptedException ex) {
117                         interrupted = true;
118                     }
119                     // wait another more minute the next time
120                     reconnectTime += 60000L;
121                 }
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) {
126                         try {
127                             if (reader.ready()) {
128                                 String line = reader.readLine();
129                                 if (line != null) {
130                                     logger.debug("Received raw call string from fbox: {}", line);
131                                     CallEvent ce = new CallEvent(line);
132                                     handleCallEvent(ce);
133                                 }
134                             }
135                         } catch (IOException e) {
136                             if (interrupted) {
137                                 logger.debug("Lost connection to Fritzbox because of an interrupt.");
138                             } else {
139                                 handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
140                                         "Lost connection to Fritz!Box: " + e.getMessage());
141                             }
142                             break;
143                         } finally {
144                             try {
145                                 sleep(500L);
146                             } catch (InterruptedException e) {
147                                 interrupted = true;
148                             }
149                         }
150                     }
151                 }
152             }
153         }
154
155         /**
156          * Close socket and stop running thread.
157          */
158         @Override
159         public void interrupt() {
160             interrupted = true;
161             if (socket != null) {
162                 try {
163                     socket.close();
164                     logger.debug("Socket to FritzBox closed.");
165                 } catch (IOException e) {
166                     logger.warn("Failed to close connection to FritzBox.", e);
167                 }
168             } else {
169                 logger.debug("Socket to FritzBox not open, therefore not closing it.");
170             }
171         }
172
173         /**
174          * Handle call event and update item as required.
175          *
176          * @param ce call event to process
177          */
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);
207             }
208         }
209     }
210
211     public void stopThread() {
212         logger.debug("Stopping call monitor thread...");
213         if (monitorThread != null) {
214             monitorThread.interrupt();
215             monitorThread = null;
216         }
217     }
218
219     public void startThread() {
220         logger.debug("Starting call monitor thread...");
221         if (monitorThread != null) {
222             monitorThread.interrupt();
223             monitorThread = null;
224         }
225         // create a new thread for listening to the FritzBox
226         monitorThread = new CallMonitorThread();
227         monitorThread.start();
228     }
229 }