From 3c27aeb6212b27a0c7a66f6b8d4def56a56dc4b5 Mon Sep 17 00:00:00 2001 From: Christoph Weitkamp Date: Sun, 17 Jan 2021 23:04:25 +0100 Subject: [PATCH] [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 --- .../internal/callmonitor/CallEvent.java | 50 ++++--- .../internal/callmonitor/CallMonitor.java | 130 +++++++++--------- .../avmfritz/internal/handler/BoxHandler.java | 20 ++- 3 files changed, 112 insertions(+), 88 deletions(-) diff --git a/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/callmonitor/CallEvent.java b/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/callmonitor/CallEvent.java index 8e44be123..1b72dd445 100644 --- a/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/callmonitor/CallEvent.java +++ b/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/callmonitor/CallEvent.java @@ -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); } } diff --git a/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/callmonitor/CallMonitor.java b/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/callmonitor/CallMonitor.java index e474ef3f7..17d3d180a 100644 --- a/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/callmonitor/CallMonitor.java +++ b/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/callmonitor/CallMonitor.java @@ -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(); - } } diff --git a/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/handler/BoxHandler.java b/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/handler/BoxHandler.java index e639a61f2..284d728d1 100644 --- a/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/handler/BoxHandler.java +++ b/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/handler/BoxHandler.java @@ -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(); + } + } }