[zoneminder] Rework discovery to not use the thing handler thread (#9600)

Signed-off-by: Mark Hilbush <mark@hilbush.com>
This commit is contained in:
Mark Hilbush 2020-12-31 07:16:23 -05:00 committed by GitHub
parent 55a956e48c
commit 4ef61c9949
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 116 additions and 141 deletions

View File

@ -37,7 +37,7 @@ import org.osgi.service.component.annotations.Reference;
* @author Mark Hilbush - Initial contribution * @author Mark Hilbush - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
@Component(configurationPid = "binding.zm", service = ThingHandlerFactory.class) @Component(configurationPid = "binding.zoneminder", service = ThingHandlerFactory.class)
public class ZmHandlerFactory extends BaseThingHandlerFactory { public class ZmHandlerFactory extends BaseThingHandlerFactory {
private final TimeZoneProvider timeZoneProvider; private final TimeZoneProvider timeZoneProvider;

View File

@ -50,16 +50,6 @@ public class ZmBridgeConfig {
*/ */
public @Nullable Integer refreshInterval; public @Nullable Integer refreshInterval;
/**
* Enable/disable monitor discovery
*/
public @Nullable Boolean discoveryEnabled;
/**
* Frequency at which the binding will try to discover monitors
*/
public @Nullable Integer discoveryInterval;
/** /**
* Alarm duration set on monitor things when they're discovered * Alarm duration set on monitor things when they're discovered
*/ */
@ -70,6 +60,11 @@ public class ZmBridgeConfig {
*/ */
public @Nullable Integer defaultImageRefreshInterval; public @Nullable Integer defaultImageRefreshInterval;
/**
* Enable/disable monitor discovery
*/
public Boolean discoveryEnabled = Boolean.TRUE;
/** /**
* Zoneminder user name * Zoneminder user name
*/ */

View File

@ -17,6 +17,8 @@ import static org.openhab.binding.zoneminder.internal.ZmBindingConstants.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Future;
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;
@ -44,17 +46,32 @@ public class MonitorDiscoveryService extends AbstractDiscoveryService implements
private final Logger logger = LoggerFactory.getLogger(MonitorDiscoveryService.class); private final Logger logger = LoggerFactory.getLogger(MonitorDiscoveryService.class);
private @Nullable ZmBridgeHandler bridgeHandler; private static final int DISCOVERY_INTERVAL_SECONDS = 300;
private static final int DISCOVERY_INITIAL_DELAY_SECONDS = 10;
private static final int DISCOVERY_TIMEOUT_SECONDS = 6;
private @NonNullByDefault({}) ZmBridgeHandler bridgeHandler;
private @Nullable Future<?> discoveryJob;
public MonitorDiscoveryService() { public MonitorDiscoveryService() {
super(30); super(SUPPORTED_MONITOR_THING_TYPES_UIDS, DISCOVERY_TIMEOUT_SECONDS, true);
}
@Override
public void activate() {
super.activate(null);
}
@Override
public void deactivate() {
super.deactivate();
} }
@Override @Override
public void setThingHandler(@Nullable ThingHandler handler) { public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof ZmBridgeHandler) { if (handler instanceof ZmBridgeHandler) {
((ZmBridgeHandler) handler).setDiscoveryService(this); bridgeHandler = (ZmBridgeHandler) handler;
this.bridgeHandler = (ZmBridgeHandler) handler;
} }
} }
@ -63,66 +80,69 @@ public class MonitorDiscoveryService extends AbstractDiscoveryService implements
return bridgeHandler; return bridgeHandler;
} }
@Override
public void activate() {
}
@Override
public void deactivate() {
}
@Override @Override
public Set<ThingTypeUID> getSupportedThingTypes() { public Set<ThingTypeUID> getSupportedThingTypes() {
return SUPPORTED_MONITOR_THING_TYPES_UIDS; return SUPPORTED_MONITOR_THING_TYPES_UIDS;
} }
@Override @Override
public void startBackgroundDiscovery() { protected void startBackgroundDiscovery() {
logger.trace("Discovery: Performing background discovery scan for {}", getBridgeUID()); Future<?> localDiscoveryJob = discoveryJob;
discoverMonitors(); if (localDiscoveryJob == null || localDiscoveryJob.isCancelled()) {
logger.debug("ZoneminderDiscovery: Starting background discovery job");
discoveryJob = scheduler.scheduleWithFixedDelay(this::backgroundDiscoverMonitors,
DISCOVERY_INITIAL_DELAY_SECONDS, DISCOVERY_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
}
@Override
protected void stopBackgroundDiscovery() {
Future<?> localDiscoveryJob = discoveryJob;
if (localDiscoveryJob != null) {
logger.debug("ZoneminderDiscovery: Stopping background discovery job");
localDiscoveryJob.cancel(true);
discoveryJob = null;
}
} }
@Override @Override
public void startScan() { public void startScan() {
logger.debug("Discovery: Starting monitor discovery scan for {}", getBridgeUID()); logger.debug("ZoneminderDiscovery: Running discovery scan");
discoverMonitors(); discoverMonitors();
} }
private @Nullable ThingUID getBridgeUID() { private void backgroundDiscoverMonitors() {
ZmBridgeHandler localBridgeHandler = bridgeHandler; if (!bridgeHandler.isBackgroundDiscoveryEnabled()) {
return localBridgeHandler != null ? localBridgeHandler.getThing().getUID() : null; return;
}
logger.debug("ZoneminderDiscovery: Running background discovery scan");
discoverMonitors();
} }
private synchronized void discoverMonitors() { private synchronized void discoverMonitors() {
ZmBridgeHandler localBridgeHandler = bridgeHandler; ThingUID bridgeUID = bridgeHandler.getThing().getUID();
ThingUID bridgeUID = getBridgeUID(); Integer alarmDuration = bridgeHandler.getDefaultAlarmDuration();
if (localBridgeHandler != null && bridgeUID != null) { Integer imageRefreshInterval = bridgeHandler.getDefaultImageRefreshInterval();
Integer alarmDuration = localBridgeHandler.getDefaultAlarmDuration(); for (Monitor monitor : bridgeHandler.getSavedMonitors()) {
Integer imageRefreshInterval = localBridgeHandler.getDefaultImageRefreshInterval(); String id = monitor.getId();
for (Monitor monitor : localBridgeHandler.getSavedMonitors()) { String name = monitor.getName();
String id = monitor.getId(); ThingUID thingUID = new ThingUID(UID_MONITOR, bridgeUID, monitor.getId());
String name = monitor.getName(); Map<String, Object> properties = new HashMap<>();
ThingUID thingUID = new ThingUID(UID_MONITOR, bridgeUID, monitor.getId()); properties.put(CONFIG_MONITOR_ID, id);
Map<String, Object> properties = new HashMap<>(); properties.put(CONFIG_ALARM_DURATION, alarmDuration);
properties.put(CONFIG_MONITOR_ID, id); if (imageRefreshInterval != null) {
properties.put(CONFIG_ALARM_DURATION, alarmDuration); properties.put(CONFIG_IMAGE_REFRESH_INTERVAL, imageRefreshInterval);
if (imageRefreshInterval != null) {
properties.put(CONFIG_IMAGE_REFRESH_INTERVAL, imageRefreshInterval);
}
thingDiscovered(createDiscoveryResult(thingUID, bridgeUID, id, name, properties));
logger.trace("Discovery: Monitor with id '{}' and name '{}' added to Inbox with UID '{}'",
monitor.getId(), monitor.getName(), thingUID);
} }
thingDiscovered(createDiscoveryResult(thingUID, bridgeUID, id, name, properties));
logger.debug("ZoneminderDiscovery: Monitor with id '{}' and name '{}' added to Inbox with UID '{}'",
monitor.getId(), monitor.getName(), thingUID);
} }
} }
private DiscoveryResult createDiscoveryResult(ThingUID monitorUID, ThingUID bridgeUID, String id, String name, private DiscoveryResult createDiscoveryResult(ThingUID monitorUID, ThingUID bridgeUID, String id, String name,
Map<String, Object> properties) { Map<String, Object> properties) {
return DiscoveryResultBuilder.create(monitorUID).withProperties(properties).withBridge(bridgeUID) return DiscoveryResultBuilder.create(monitorUID).withProperties(properties).withBridge(bridgeUID)
.withLabel(buildLabel(name)).withRepresentationProperty(CONFIG_MONITOR_ID).build(); .withLabel(String.format("Zoneminder Monitor %s", name)).withRepresentationProperty(CONFIG_MONITOR_ID)
} .build();
private String buildLabel(String name) {
return String.format("Zoneminder Monitor %s", name);
} }
} }

View File

@ -28,7 +28,6 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -81,14 +80,8 @@ import com.google.gson.JsonSyntaxException;
@NonNullByDefault @NonNullByDefault
public class ZmBridgeHandler extends BaseBridgeHandler { public class ZmBridgeHandler extends BaseBridgeHandler {
private static final int REFRESH_INTERVAL_SECONDS = 1; private static final int MONITOR_REFRESH_INTERVAL_SECONDS = 10;
private static final int REFRESH_STARTUP_DELAY_SECONDS = 3; private static final int MONITOR_REFRESH_STARTUP_DELAY_SECONDS = 5;
private static final int MONITORS_INTERVAL_SECONDS = 5;
private static final int MONITORS_INITIAL_DELAY_SECONDS = 3;
private static final int DISCOVERY_INTERVAL_SECONDS = 300;
private static final int DISCOVERY_INITIAL_DELAY_SECONDS = 10;
private static final int API_TIMEOUT_MSEC = 10000; private static final int API_TIMEOUT_MSEC = 10000;
@ -104,10 +97,6 @@ public class ZmBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(ZmBridgeHandler.class); private final Logger logger = LoggerFactory.getLogger(ZmBridgeHandler.class);
private @Nullable Future<?> refreshMonitorsJob; private @Nullable Future<?> refreshMonitorsJob;
private final AtomicInteger monitorsCounter = new AtomicInteger();
private @Nullable MonitorDiscoveryService discoveryService;
private final AtomicInteger discoveryCounter = new AtomicInteger();
private List<Monitor> savedMonitors = new ArrayList<>(); private List<Monitor> savedMonitors = new ArrayList<>();
@ -115,9 +104,8 @@ public class ZmBridgeHandler extends BaseBridgeHandler {
private boolean useSSL; private boolean useSSL;
private @Nullable String portNumber; private @Nullable String portNumber;
private String urlPath = DEFAULT_URL_PATH; private String urlPath = DEFAULT_URL_PATH;
private int monitorsInterval; private int monitorRefreshInterval;
private int discoveryInterval; private boolean backgroundDiscoveryEnabled;
private boolean discoveryEnabled;
private int defaultAlarmDuration; private int defaultAlarmDuration;
private @Nullable Integer defaultImageRefreshInterval; private @Nullable Integer defaultImageRefreshInterval;
@ -144,17 +132,15 @@ public class ZmBridgeHandler extends BaseBridgeHandler {
Integer value; Integer value;
value = config.refreshInterval; value = config.refreshInterval;
monitorsInterval = value == null ? MONITORS_INTERVAL_SECONDS : value; monitorRefreshInterval = value == null ? MONITOR_REFRESH_INTERVAL_SECONDS : value;
value = config.discoveryInterval;
discoveryInterval = value == null ? DISCOVERY_INTERVAL_SECONDS : value;
value = config.defaultAlarmDuration; value = config.defaultAlarmDuration;
defaultAlarmDuration = value == null ? DEFAULT_ALARM_DURATION_SECONDS : value; defaultAlarmDuration = value == null ? DEFAULT_ALARM_DURATION_SECONDS : value;
defaultImageRefreshInterval = config.defaultImageRefreshInterval; defaultImageRefreshInterval = config.defaultImageRefreshInterval;
discoveryEnabled = config.discoveryEnabled == null ? false : config.discoveryEnabled.booleanValue(); backgroundDiscoveryEnabled = config.discoveryEnabled;
logger.debug("Bridge: Background discovery is {}", backgroundDiscoveryEnabled == true ? "ENABLED" : "DISABLED");
host = config.host; host = config.host;
useSSL = config.useSSL.booleanValue(); useSSL = config.useSSL.booleanValue();
@ -222,12 +208,8 @@ public class ZmBridgeHandler extends BaseBridgeHandler {
return Collections.singleton(MonitorDiscoveryService.class); return Collections.singleton(MonitorDiscoveryService.class);
} }
public void setDiscoveryService(MonitorDiscoveryService discoveryService) { public boolean isBackgroundDiscoveryEnabled() {
this.discoveryService = discoveryService; return backgroundDiscoveryEnabled;
}
public boolean isDiscoveryEnabled() {
return discoveryEnabled;
} }
public Integer getDefaultAlarmDuration() { public Integer getDefaultAlarmDuration() {
@ -571,40 +553,14 @@ public class ZmBridgeHandler extends BaseBridgeHandler {
return false; return false;
} }
/*
* The refresh job is executed every second
* - updates the monitor handlers every monitorsInterval seconds, and
* - runs the monitor discovery every discoveryInterval seconds
*/
private void refresh() {
refreshMonitors();
discoverMonitors();
}
@SuppressWarnings("null") @SuppressWarnings("null")
private void refreshMonitors() { private void refreshMonitors() {
if (monitorsCounter.getAndDecrement() == 0) { List<Monitor> monitors = getMonitors();
monitorsCounter.set(monitorsInterval); savedMonitors = monitors;
List<Monitor> monitors = getMonitors(); for (Monitor monitor : monitors) {
savedMonitors = monitors; ZmMonitorHandler handler = monitorHandlers.get(monitor.getId());
for (Monitor monitor : monitors) { if (handler != null) {
ZmMonitorHandler handler = monitorHandlers.get(monitor.getId()); handler.updateStatus(monitor);
if (handler != null) {
handler.updateStatus(monitor);
}
}
}
}
private void discoverMonitors() {
if (isDiscoveryEnabled()) {
if (discoveryCounter.getAndDecrement() == 0) {
discoveryCounter.set(discoveryInterval);
MonitorDiscoveryService localDiscoveryService = discoveryService;
if (localDiscoveryService != null) {
logger.trace("Bridge: Running monitor discovery");
localDiscoveryService.startBackgroundDiscovery();
}
} }
} }
} }
@ -612,10 +568,8 @@ public class ZmBridgeHandler extends BaseBridgeHandler {
private void scheduleRefreshJob() { private void scheduleRefreshJob() {
logger.debug("Bridge: Scheduling monitors refresh job"); logger.debug("Bridge: Scheduling monitors refresh job");
cancelRefreshJob(); cancelRefreshJob();
monitorsCounter.set(MONITORS_INITIAL_DELAY_SECONDS); refreshMonitorsJob = scheduler.scheduleWithFixedDelay(this::refreshMonitors,
discoveryCounter.set(DISCOVERY_INITIAL_DELAY_SECONDS); MONITOR_REFRESH_STARTUP_DELAY_SECONDS, monitorRefreshInterval, TimeUnit.SECONDS);
refreshMonitorsJob = scheduler.scheduleWithFixedDelay(this::refresh, REFRESH_STARTUP_DELAY_SECONDS,
REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS);
} }
private void cancelRefreshJob() { private void cancelRefreshJob() {
@ -623,6 +577,7 @@ public class ZmBridgeHandler extends BaseBridgeHandler {
if (localRefreshThermostatsJob != null) { if (localRefreshThermostatsJob != null) {
localRefreshThermostatsJob.cancel(true); localRefreshThermostatsJob.cancel(true);
logger.debug("Bridge: Canceling monitors refresh job"); logger.debug("Bridge: Canceling monitors refresh job");
refreshMonitorsJob = null;
} }
} }
} }

View File

@ -254,6 +254,7 @@ public class ZmMonitorHandler extends BaseThingHandler {
} }
private void startImageRefreshJob() { private void startImageRefreshJob() {
stopImageRefreshJob();
Integer interval = imageRefreshIntervalSeconds; Integer interval = imageRefreshIntervalSeconds;
if (interval != null) { if (interval != null) {
long delay = getRandomDelay(interval); long delay = getRandomDelay(interval);

View File

@ -14,31 +14,8 @@
<parameter-group name="auth-info"> <parameter-group name="auth-info">
<label>Authentication Information</label> <label>Authentication Information</label>
</parameter-group> </parameter-group>
<parameter name="refreshInterval" type="integer" min="2" unit="s" required="true" groupName="config-info">
<label>Refresh Interval</label> <!-- Parameter Group url-info -->
<description>Interval in seconds at which monitor status is updated</description>
<default>5</default>
</parameter>
<parameter name="discoveryEnabled" type="boolean" required="true" groupName="config-info">
<label>Discovery Enabled</label>
<description>Enable/disable automatic discovery</description>
<default>true</default>
</parameter>
<parameter name="discoveryInterval" type="integer" min="60" unit="s" required="true" groupName="config-info">
<label>Monitor Discovery Interval</label>
<description>Specifies time in seconds in which the binding will attempt to discover monitors</description>
<default>300</default>
</parameter>
<parameter name="defaultAlarmDuration" type="integer" unit="s" required="false" groupName="config-info">
<label>Default Alarm Duration</label>
<description>Duration in seconds after which the alarm will be turned off</description>
<default>60</default>
</parameter>
<parameter name="defaultImageRefreshInterval" type="integer" unit="s" required="false"
groupName="config-info">
<label>Default Image Refresh Interval</label>
<description>Interval in seconds at which monitor image snapshot will be updated</description>
</parameter>
<parameter name="host" type="text" required="true" groupName="url-info"> <parameter name="host" type="text" required="true" groupName="url-info">
<label>Server</label> <label>Server</label>
<description>ZoneMinder server name or IP address</description> <description>ZoneMinder server name or IP address</description>
@ -58,6 +35,30 @@
<description>URL path (Default is /zm. Use / if Zoneminder installed under the root directory)</description> <description>URL path (Default is /zm. Use / if Zoneminder installed under the root directory)</description>
<default>/zm</default> <default>/zm</default>
</parameter> </parameter>
<!-- Parameter Group config-info -->
<parameter name="refreshInterval" type="integer" min="2" unit="s" required="true" groupName="config-info">
<label>Refresh Interval</label>
<description>Interval in seconds at which monitor status is updated</description>
<default>5</default>
</parameter>
<parameter name="defaultAlarmDuration" type="integer" unit="s" required="false" groupName="config-info">
<label>Default Alarm Duration</label>
<description>Duration in seconds after which the alarm will be turned off</description>
<default>60</default>
</parameter>
<parameter name="defaultImageRefreshInterval" type="integer" unit="s" required="false"
groupName="config-info">
<label>Default Image Refresh Interval</label>
<description>Interval in seconds at which monitor image snapshot will be updated</description>
</parameter>
<parameter name="discoveryEnabled" type="boolean" required="true" groupName="config-info">
<label>Background Discovery Enabled</label>
<description>Enable/disable background discovery of monitors</description>
<default>true</default>
</parameter>
<!-- Parameter Group auth-info -->
<parameter name="user" type="text" required="false" groupName="auth-info"> <parameter name="user" type="text" required="false" groupName="auth-info">
<label>User Name</label> <label>User Name</label>
<description>User name (if authentication enabled in ZoneMinder)</description> <description>User name (if authentication enabled in ZoneMinder)</description>

View File

@ -73,6 +73,9 @@
<channel id="eventAlarmFrames" typeId="eventAlarmFrames"/> <channel id="eventAlarmFrames" typeId="eventAlarmFrames"/>
<channel id="eventLength" typeId="eventLength"/> <channel id="eventLength" typeId="eventLength"/>
</channels> </channels>
<representation-property>monitorId</representation-property>
<config-description-ref uri="thing-type:zoneminder:monitor"/> <config-description-ref uri="thing-type:zoneminder:monitor"/>
</thing-type> </thing-type>