[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
*/
@NonNullByDefault
@Component(configurationPid = "binding.zm", service = ThingHandlerFactory.class)
@Component(configurationPid = "binding.zoneminder", service = ThingHandlerFactory.class)
public class ZmHandlerFactory extends BaseThingHandlerFactory {
private final TimeZoneProvider timeZoneProvider;

View File

@ -50,16 +50,6 @@ public class ZmBridgeConfig {
*/
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
*/
@ -70,6 +60,11 @@ public class ZmBridgeConfig {
*/
public @Nullable Integer defaultImageRefreshInterval;
/**
* Enable/disable monitor discovery
*/
public Boolean discoveryEnabled = Boolean.TRUE;
/**
* Zoneminder user name
*/

View File

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

View File

@ -28,7 +28,6 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@ -81,14 +80,8 @@ import com.google.gson.JsonSyntaxException;
@NonNullByDefault
public class ZmBridgeHandler extends BaseBridgeHandler {
private static final int REFRESH_INTERVAL_SECONDS = 1;
private static final int REFRESH_STARTUP_DELAY_SECONDS = 3;
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 MONITOR_REFRESH_INTERVAL_SECONDS = 10;
private static final int MONITOR_REFRESH_STARTUP_DELAY_SECONDS = 5;
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 @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<>();
@ -115,9 +104,8 @@ public class ZmBridgeHandler extends BaseBridgeHandler {
private boolean useSSL;
private @Nullable String portNumber;
private String urlPath = DEFAULT_URL_PATH;
private int monitorsInterval;
private int discoveryInterval;
private boolean discoveryEnabled;
private int monitorRefreshInterval;
private boolean backgroundDiscoveryEnabled;
private int defaultAlarmDuration;
private @Nullable Integer defaultImageRefreshInterval;
@ -144,17 +132,15 @@ public class ZmBridgeHandler extends BaseBridgeHandler {
Integer value;
value = config.refreshInterval;
monitorsInterval = value == null ? MONITORS_INTERVAL_SECONDS : value;
value = config.discoveryInterval;
discoveryInterval = value == null ? DISCOVERY_INTERVAL_SECONDS : value;
monitorRefreshInterval = value == null ? MONITOR_REFRESH_INTERVAL_SECONDS : value;
value = config.defaultAlarmDuration;
defaultAlarmDuration = value == null ? DEFAULT_ALARM_DURATION_SECONDS : value;
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;
useSSL = config.useSSL.booleanValue();
@ -222,12 +208,8 @@ public class ZmBridgeHandler extends BaseBridgeHandler {
return Collections.singleton(MonitorDiscoveryService.class);
}
public void setDiscoveryService(MonitorDiscoveryService discoveryService) {
this.discoveryService = discoveryService;
}
public boolean isDiscoveryEnabled() {
return discoveryEnabled;
public boolean isBackgroundDiscoveryEnabled() {
return backgroundDiscoveryEnabled;
}
public Integer getDefaultAlarmDuration() {
@ -571,40 +553,14 @@ public class ZmBridgeHandler extends BaseBridgeHandler {
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")
private void refreshMonitors() {
if (monitorsCounter.getAndDecrement() == 0) {
monitorsCounter.set(monitorsInterval);
List<Monitor> monitors = getMonitors();
savedMonitors = monitors;
for (Monitor monitor : monitors) {
ZmMonitorHandler handler = monitorHandlers.get(monitor.getId());
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();
}
List<Monitor> monitors = getMonitors();
savedMonitors = monitors;
for (Monitor monitor : monitors) {
ZmMonitorHandler handler = monitorHandlers.get(monitor.getId());
if (handler != null) {
handler.updateStatus(monitor);
}
}
}
@ -612,10 +568,8 @@ public class ZmBridgeHandler extends BaseBridgeHandler {
private void scheduleRefreshJob() {
logger.debug("Bridge: Scheduling monitors refresh job");
cancelRefreshJob();
monitorsCounter.set(MONITORS_INITIAL_DELAY_SECONDS);
discoveryCounter.set(DISCOVERY_INITIAL_DELAY_SECONDS);
refreshMonitorsJob = scheduler.scheduleWithFixedDelay(this::refresh, REFRESH_STARTUP_DELAY_SECONDS,
REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS);
refreshMonitorsJob = scheduler.scheduleWithFixedDelay(this::refreshMonitors,
MONITOR_REFRESH_STARTUP_DELAY_SECONDS, monitorRefreshInterval, TimeUnit.SECONDS);
}
private void cancelRefreshJob() {
@ -623,6 +577,7 @@ public class ZmBridgeHandler extends BaseBridgeHandler {
if (localRefreshThermostatsJob != null) {
localRefreshThermostatsJob.cancel(true);
logger.debug("Bridge: Canceling monitors refresh job");
refreshMonitorsJob = null;
}
}
}

View File

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

View File

@ -14,31 +14,8 @@
<parameter-group name="auth-info">
<label>Authentication Information</label>
</parameter-group>
<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="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 Group url-info -->
<parameter name="host" type="text" required="true" groupName="url-info">
<label>Server</label>
<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>
<default>/zm</default>
</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">
<label>User Name</label>
<description>User name (if authentication enabled in ZoneMinder)</description>

View File

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