[avmfritz] Added initial refresh of Call Monitor channels and improved thread handling (#9734)

* Added initial refresh of Call Monitor channels

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
This commit is contained in:
Christoph Weitkamp 2021-01-17 23:04:25 +01:00 committed by GitHub
parent 2a5bdf3b47
commit 3c27aeb621
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 112 additions and 88 deletions

View File

@ -26,6 +26,11 @@ import org.eclipse.jdt.annotation.Nullable;
@NonNullByDefault @NonNullByDefault
public class CallEvent { public class CallEvent {
public static final String CALL_TYPE_CALL = "CALL";
public static final String CALL_TYPE_CONNECT = "CONNECT";
public static final String CALL_TYPE_RING = "RING";
public static final String CALL_TYPE_DISCONNECT = "DISCONNECT";
private final String rawEvent; private final String rawEvent;
private final String timestamp; private final String timestamp;
private final String callType; private final String callType;
@ -47,25 +52,30 @@ public class CallEvent {
callType = fields[1]; callType = fields[1];
id = fields[2]; id = fields[2];
if (callType.equals("RING")) { switch (callType) {
case CALL_TYPE_RING:
externalNo = fields[3]; externalNo = fields[3];
internalNo = fields[4]; internalNo = fields[4];
connectionType = fields[5]; connectionType = fields[5];
} else if (callType.equals("CONNECT")) { break;
case CALL_TYPE_CONNECT:
line = fields[3]; line = fields[3];
if (fields.length > 4) { if (fields.length > 4) {
externalNo = fields[4]; externalNo = fields[4];
} else { } else {
externalNo = "Unknown"; externalNo = "Unknown";
} }
} else if (callType.equals("CALL")) { break;
case CALL_TYPE_CALL:
line = fields[3]; line = fields[3];
internalNo = fields[4]; internalNo = fields[4];
externalNo = fields[5]; externalNo = fields[5];
connectionType = fields[6]; connectionType = fields[6];
} else if (callType.equals("DISCONNECT")) { break;
case CALL_TYPE_DISCONNECT:
// no fields to set // no fields to set
} else { break;
default:
throw new IllegalArgumentException("Invalid call type: " + callType); throw new IllegalArgumentException("Invalid call type: " + callType);
} }
} }

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.avmfritz.internal.callmonitor; package org.openhab.binding.avmfritz.internal.callmonitor;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -22,7 +24,6 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants;
import org.openhab.binding.avmfritz.internal.handler.BoxHandler; import org.openhab.binding.avmfritz.internal.handler.BoxHandler;
import org.openhab.core.library.types.StringListType; import org.openhab.core.library.types.StringListType;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
@ -32,7 +33,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* This class handles all communication with the call monitor port of the fritzbox. * This class handles all communication with the Call Monitor port of the FRITZ!Box.
* *
* @author Kai Kreuzer - Initial contribution * @author Kai Kreuzer - Initial contribution
*/ */
@ -41,8 +42,8 @@ public class CallMonitor {
protected final Logger logger = LoggerFactory.getLogger(CallMonitor.class); protected final Logger logger = LoggerFactory.getLogger(CallMonitor.class);
// port number to connect to fritzbox // port number to connect to FRITZ!Box
private final int MONITOR_PORT = 1012; private static final int MONITOR_PORT = 1012;
private @Nullable CallMonitorThread monitorThread; private @Nullable CallMonitorThread monitorThread;
private final ScheduledFuture<?> reconnectJob; private final ScheduledFuture<?> reconnectJob;
@ -62,12 +63,23 @@ public class CallMonitor {
} catch (InterruptedException e) { } catch (InterruptedException e) {
} }
// create a new thread for listening to the FritzBox // create a new thread for listening to the FRITZ!Box
CallMonitorThread thread = new CallMonitorThread(); CallMonitorThread thread = new CallMonitorThread("OH-binding-" + handler.getThing().getUID().getAsString());
thread.setName("OH-binding-" + handler.getThing().getUID().getAsString());
thread.start(); thread.start();
this.monitorThread = thread; this.monitorThread = thread;
}, 0, 2, TimeUnit.HOURS); }, 0, 2, TimeUnit.HOURS);
// initialize states of Call Monitor channels
resetChannels();
}
/**
* Reset channels.
*/
public void resetChannels() {
handler.updateState(CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
handler.updateState(CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
handler.updateState(CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
handler.updateState(CHANNEL_CALL_STATE, CALL_STATE_IDLE);
} }
/** /**
@ -75,10 +87,7 @@ public class CallMonitor {
*/ */
public void dispose() { public void dispose() {
reconnectJob.cancel(true); reconnectJob.cancel(true);
CallMonitorThread monitorThread = this.monitorThread; stopThread();
if (monitorThread != null) {
monitorThread.interrupt();
}
} }
public class CallMonitorThread extends Thread { public class CallMonitorThread extends Thread {
@ -92,7 +101,11 @@ public class CallMonitor {
// time to wait before reconnecting // time to wait before reconnecting
private long reconnectTime = 60000L; private long reconnectTime = 60000L;
public CallMonitorThread() { public CallMonitorThread(String threadName) {
super(threadName);
setUncaughtExceptionHandler((thread, throwable) -> {
logger.warn("Lost connection to FRITZ!Box because of an uncaught exception: ", throwable);
});
} }
@Override @Override
@ -100,16 +113,16 @@ public class CallMonitor {
while (!interrupted) { while (!interrupted) {
BufferedReader reader = null; BufferedReader reader = null;
try { try {
logger.debug("Callmonitor thread [{}] attempting connection to FritzBox on {}:{}.", logger.debug("Call Monitor thread [{}] attempting connection to FRITZ!Box on {}:{}.",
Thread.currentThread().getId(), ip, MONITOR_PORT); Thread.currentThread().getId(), ip, MONITOR_PORT);
socket = new Socket(ip, MONITOR_PORT); socket = new Socket(ip, MONITOR_PORT);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// reset the retry interval // reset the retry interval
reconnectTime = 60000L; reconnectTime = 60000L;
} catch (Exception e) { } catch (IOException e) {
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Cannot connect to Fritz!Box call monitor - make sure to enable it by dialing '#96*5'!"); "Cannot connect to FRITZ!Box Call Monitor - make sure to enable it by dialing '#96*5'!");
logger.debug("Error attempting to connect to FritzBox. Retrying in {} seconds", logger.debug("Error attempting to connect to FRITZ!Box. Retrying in {} seconds",
reconnectTime / 1000L, e); reconnectTime / 1000L, e);
try { try {
Thread.sleep(reconnectTime); Thread.sleep(reconnectTime);
@ -120,24 +133,24 @@ public class CallMonitor {
reconnectTime += 60000L; reconnectTime += 60000L;
} }
if (reader != null) { if (reader != null) {
logger.debug("Connected to FritzBox call monitor at {}:{}.", ip, MONITOR_PORT); logger.debug("Connected to FRITZ!Box Call Monitor at {}:{}.", ip, MONITOR_PORT);
handler.setStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null); handler.setStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
while (!interrupted) { while (!interrupted) {
try { try {
if (reader.ready()) { if (reader.ready()) {
String line = reader.readLine(); String line = reader.readLine();
if (line != null) { if (line != null) {
logger.debug("Received raw call string from fbox: {}", line); logger.debug("Received raw call string from FRITZ!Box: {}", line);
CallEvent ce = new CallEvent(line); CallEvent ce = new CallEvent(line);
handleCallEvent(ce); handleCallEvent(ce);
} }
} }
} catch (IOException e) { } catch (IOException e) {
if (interrupted) { if (interrupted) {
logger.debug("Lost connection to Fritzbox because of an interrupt."); logger.debug("Lost connection to FRITZ!Box because of an interrupt.");
} else { } else {
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Lost connection to Fritz!Box: " + e.getMessage()); "Lost connection to FRITZ!Box: " + e.getMessage());
} }
break; break;
} finally { } finally {
@ -161,12 +174,12 @@ public class CallMonitor {
if (socket != null) { if (socket != null) {
try { try {
socket.close(); socket.close();
logger.debug("Socket to FritzBox closed."); logger.debug("Socket to FRITZ!Box closed.");
} catch (IOException e) { } catch (IOException e) {
logger.warn("Failed to close connection to FritzBox.", e); logger.warn("Failed to close connection to FRITZ!Box.", e);
} }
} else { } else {
logger.debug("Socket to FritzBox not open, therefore not closing it."); logger.debug("Socket to FRITZ!Box not open, therefore not closing it.");
} }
} }
@ -176,54 +189,41 @@ public class CallMonitor {
* @param ce call event to process * @param ce call event to process
*/ */
private void handleCallEvent(CallEvent ce) { private void handleCallEvent(CallEvent ce) {
if (ce.getCallType().equals("DISCONNECT")) { switch (ce.getCallType()) {
// reset states of call monitor channels case CallEvent.CALL_TYPE_DISCONNECT:
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, UnDefType.UNDEF); // reset states of Call Monitor channels
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, UnDefType.UNDEF); resetChannels();
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, UnDefType.UNDEF); break;
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE, case CallEvent.CALL_TYPE_RING: // first event when call is incoming
AVMFritzBindingConstants.CALL_STATE_IDLE); handler.updateState(CHANNEL_CALL_INCOMING,
} else if (ce.getCallType().equals("RING")) { // first event when call is incoming new StringListType(ce.getInternalNo(), ce.getExternalNo()));
StringListType state = new StringListType(ce.getInternalNo(), ce.getExternalNo()); handler.updateState(CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, state); handler.updateState(CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, UnDefType.UNDEF); handler.updateState(CHANNEL_CALL_STATE, CALL_STATE_RINGING);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, UnDefType.UNDEF); break;
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE, case CallEvent.CALL_TYPE_CONNECT: // when call is answered/running
AVMFritzBindingConstants.CALL_STATE_RINGING); handler.updateState(CHANNEL_CALL_ACTIVE, new StringListType(ce.getExternalNo(), ""));
} else if (ce.getCallType().equals("CONNECT")) { // when call is answered/running handler.updateState(CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
StringListType state = new StringListType(ce.getExternalNo(), ""); handler.updateState(CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, state); handler.updateState(CHANNEL_CALL_STATE, CALL_STATE_ACTIVE);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, UnDefType.UNDEF); break;
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, UnDefType.UNDEF); case CallEvent.CALL_TYPE_CALL: // outgoing call
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE, handler.updateState(CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
AVMFritzBindingConstants.CALL_STATE_ACTIVE); handler.updateState(CHANNEL_CALL_OUTGOING,
} else if (ce.getCallType().equals("CALL")) { // outgoing call new StringListType(ce.getExternalNo(), ce.getInternalNo()));
StringListType state = new StringListType(ce.getExternalNo(), ce.getInternalNo()); handler.updateState(CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, UnDefType.UNDEF); handler.updateState(CHANNEL_CALL_STATE, CALL_STATE_DIALING);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, state); break;
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE,
AVMFritzBindingConstants.CALL_STATE_DIALING);
} }
} }
} }
public void stopThread() { public void stopThread() {
logger.debug("Stopping call monitor thread..."); logger.debug("Stopping Call Monitor thread...");
if (monitorThread != null) { CallMonitorThread thread = this.monitorThread;
monitorThread.interrupt(); if (thread != null) {
thread.interrupt();
monitorThread = null; monitorThread = null;
} }
} }
public void startThread() {
logger.debug("Starting call monitor thread...");
if (monitorThread != null) {
monitorThread.interrupt();
monitorThread = null;
}
// create a new thread for listening to the FritzBox
monitorThread = new CallMonitorThread();
monitorThread.start();
}
} }

View File

@ -56,10 +56,10 @@ public class BoxHandler extends AVMFritzBaseBridgeHandler {
@Override @Override
protected void manageConnections() { protected void manageConnections() {
AVMFritzBoxConfiguration config = getConfigAs(AVMFritzBoxConfiguration.class); AVMFritzBoxConfiguration config = getConfigAs(AVMFritzBoxConfiguration.class);
if (this.callMonitor == null && callChannelsLinked()) {
this.callMonitor = new CallMonitor(config.ipAddress, this, scheduler);
} else if (this.callMonitor != null && !callChannelsLinked()) {
CallMonitor cm = this.callMonitor; CallMonitor cm = this.callMonitor;
if (cm == null && callChannelsLinked()) {
this.callMonitor = new CallMonitor(config.ipAddress, this, scheduler);
} else if (cm != null && !callChannelsLinked()) {
cm.dispose(); cm.dispose();
this.callMonitor = null; this.callMonitor = null;
} }
@ -95,4 +95,18 @@ public class BoxHandler extends AVMFritzBaseBridgeHandler {
public void updateState(String channelID, State state) { public void updateState(String channelID, State state) {
super.updateState(channelID, state); super.updateState(channelID, state);
} }
@Override
public void handleRefreshCommand() {
refreshCallMonitorChannels();
super.handleRefreshCommand();
}
private void refreshCallMonitorChannels() {
CallMonitor cm = this.callMonitor;
if (cm != null) {
// initialize states of call monitor channels
cm.resetChannels();
}
}
} }