[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
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 timestamp;
private final String callType;
@ -47,26 +52,31 @@ public class CallEvent {
callType = fields[1];
id = fields[2];
if (callType.equals("RING")) {
externalNo = fields[3];
internalNo = fields[4];
connectionType = fields[5];
} else if (callType.equals("CONNECT")) {
line = fields[3];
if (fields.length > 4) {
externalNo = fields[4];
} else {
externalNo = "Unknown";
}
} else if (callType.equals("CALL")) {
line = fields[3];
internalNo = fields[4];
externalNo = fields[5];
connectionType = fields[6];
} else if (callType.equals("DISCONNECT")) {
// no fields to set
} else {
throw new IllegalArgumentException("Invalid call type: " + callType);
switch (callType) {
case CALL_TYPE_RING:
externalNo = fields[3];
internalNo = fields[4];
connectionType = fields[5];
break;
case CALL_TYPE_CONNECT:
line = fields[3];
if (fields.length > 4) {
externalNo = fields[4];
} else {
externalNo = "Unknown";
}
break;
case CALL_TYPE_CALL:
line = fields[3];
internalNo = fields[4];
externalNo = fields[5];
connectionType = fields[6];
break;
case CALL_TYPE_DISCONNECT:
// no fields to set
break;
default:
throw new IllegalArgumentException("Invalid call type: " + callType);
}
}

View File

@ -12,6 +12,8 @@
*/
package org.openhab.binding.avmfritz.internal.callmonitor;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@ -22,7 +24,6 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants;
import org.openhab.binding.avmfritz.internal.handler.BoxHandler;
import org.openhab.core.library.types.StringListType;
import org.openhab.core.thing.ThingStatus;
@ -32,7 +33,7 @@ import org.slf4j.Logger;
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
*/
@ -41,8 +42,8 @@ public class CallMonitor {
protected final Logger logger = LoggerFactory.getLogger(CallMonitor.class);
// port number to connect to fritzbox
private final int MONITOR_PORT = 1012;
// port number to connect to FRITZ!Box
private static final int MONITOR_PORT = 1012;
private @Nullable CallMonitorThread monitorThread;
private final ScheduledFuture<?> reconnectJob;
@ -62,12 +63,23 @@ public class CallMonitor {
} catch (InterruptedException e) {
}
// create a new thread for listening to the FritzBox
CallMonitorThread thread = new CallMonitorThread();
thread.setName("OH-binding-" + handler.getThing().getUID().getAsString());
// create a new thread for listening to the FRITZ!Box
CallMonitorThread thread = new CallMonitorThread("OH-binding-" + handler.getThing().getUID().getAsString());
thread.start();
this.monitorThread = thread;
}, 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() {
reconnectJob.cancel(true);
CallMonitorThread monitorThread = this.monitorThread;
if (monitorThread != null) {
monitorThread.interrupt();
}
stopThread();
}
public class CallMonitorThread extends Thread {
@ -92,7 +101,11 @@ public class CallMonitor {
// time to wait before reconnecting
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
@ -100,16 +113,16 @@ public class CallMonitor {
while (!interrupted) {
BufferedReader reader = null;
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);
socket = new Socket(ip, MONITOR_PORT);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// reset the retry interval
reconnectTime = 60000L;
} catch (Exception e) {
} catch (IOException e) {
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"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",
"Cannot connect to FRITZ!Box Call Monitor - make sure to enable it by dialing '#96*5'!");
logger.debug("Error attempting to connect to FRITZ!Box. Retrying in {} seconds",
reconnectTime / 1000L, e);
try {
Thread.sleep(reconnectTime);
@ -120,24 +133,24 @@ public class CallMonitor {
reconnectTime += 60000L;
}
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);
while (!interrupted) {
try {
if (reader.ready()) {
String line = reader.readLine();
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);
handleCallEvent(ce);
}
}
} catch (IOException e) {
if (interrupted) {
logger.debug("Lost connection to Fritzbox because of an interrupt.");
logger.debug("Lost connection to FRITZ!Box because of an interrupt.");
} else {
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Lost connection to Fritz!Box: " + e.getMessage());
"Lost connection to FRITZ!Box: " + e.getMessage());
}
break;
} finally {
@ -161,12 +174,12 @@ public class CallMonitor {
if (socket != null) {
try {
socket.close();
logger.debug("Socket to FritzBox closed.");
logger.debug("Socket to FRITZ!Box closed.");
} catch (IOException e) {
logger.warn("Failed to close connection to FritzBox.", e);
logger.warn("Failed to close connection to FRITZ!Box.", e);
}
} 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
*/
private void handleCallEvent(CallEvent ce) {
if (ce.getCallType().equals("DISCONNECT")) {
// reset states of call monitor channels
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE,
AVMFritzBindingConstants.CALL_STATE_IDLE);
} else if (ce.getCallType().equals("RING")) { // first event when call is incoming
StringListType state = new StringListType(ce.getInternalNo(), ce.getExternalNo());
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, state);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE,
AVMFritzBindingConstants.CALL_STATE_RINGING);
} else if (ce.getCallType().equals("CONNECT")) { // when call is answered/running
StringListType state = new StringListType(ce.getExternalNo(), "");
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, state);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE,
AVMFritzBindingConstants.CALL_STATE_ACTIVE);
} else if (ce.getCallType().equals("CALL")) { // outgoing call
StringListType state = new StringListType(ce.getExternalNo(), ce.getInternalNo());
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, state);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE,
AVMFritzBindingConstants.CALL_STATE_DIALING);
switch (ce.getCallType()) {
case CallEvent.CALL_TYPE_DISCONNECT:
// reset states of Call Monitor channels
resetChannels();
break;
case CallEvent.CALL_TYPE_RING: // first event when call is incoming
handler.updateState(CHANNEL_CALL_INCOMING,
new StringListType(ce.getInternalNo(), ce.getExternalNo()));
handler.updateState(CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
handler.updateState(CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
handler.updateState(CHANNEL_CALL_STATE, CALL_STATE_RINGING);
break;
case CallEvent.CALL_TYPE_CONNECT: // when call is answered/running
handler.updateState(CHANNEL_CALL_ACTIVE, new StringListType(ce.getExternalNo(), ""));
handler.updateState(CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
handler.updateState(CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
handler.updateState(CHANNEL_CALL_STATE, CALL_STATE_ACTIVE);
break;
case CallEvent.CALL_TYPE_CALL: // outgoing call
handler.updateState(CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
handler.updateState(CHANNEL_CALL_OUTGOING,
new StringListType(ce.getExternalNo(), ce.getInternalNo()));
handler.updateState(CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
handler.updateState(CHANNEL_CALL_STATE, CALL_STATE_DIALING);
break;
}
}
}
public void stopThread() {
logger.debug("Stopping call monitor thread...");
if (monitorThread != null) {
monitorThread.interrupt();
logger.debug("Stopping Call Monitor thread...");
CallMonitorThread thread = this.monitorThread;
if (thread != null) {
thread.interrupt();
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
protected void manageConnections() {
AVMFritzBoxConfiguration config = getConfigAs(AVMFritzBoxConfiguration.class);
if (this.callMonitor == null && callChannelsLinked()) {
CallMonitor cm = this.callMonitor;
if (cm == null && callChannelsLinked()) {
this.callMonitor = new CallMonitor(config.ipAddress, this, scheduler);
} else if (this.callMonitor != null && !callChannelsLinked()) {
CallMonitor cm = this.callMonitor;
} else if (cm != null && !callChannelsLinked()) {
cm.dispose();
this.callMonitor = null;
}
@ -95,4 +95,18 @@ public class BoxHandler extends AVMFritzBaseBridgeHandler {
public void updateState(String channelID, State 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();
}
}
}