2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.zoneminder.internal.handler;
15 import java.io.IOException;
16 import java.math.BigDecimal;
17 import java.security.GeneralSecurityException;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.List;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
27 import javax.security.auth.login.FailedLoginException;
29 import org.apache.commons.lang.StringUtils;
30 import org.openhab.binding.zoneminder.internal.DataRefreshPriorityEnum;
31 import org.openhab.binding.zoneminder.internal.ZoneMinderConstants;
32 import org.openhab.binding.zoneminder.internal.ZoneMinderProperties;
33 import org.openhab.binding.zoneminder.internal.config.ZoneMinderBridgeServerConfig;
34 import org.openhab.binding.zoneminder.internal.discovery.ZoneMinderDiscoveryService;
35 import org.openhab.core.library.types.DecimalType;
36 import org.openhab.core.library.types.OnOffType;
37 import org.openhab.core.thing.Bridge;
38 import org.openhab.core.thing.Channel;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.thing.ThingStatusInfo;
44 import org.openhab.core.thing.ThingTypeUID;
45 import org.openhab.core.thing.binding.BaseBridgeHandler;
46 import org.openhab.core.thing.binding.ThingHandlerService;
47 import org.openhab.core.types.Command;
48 import org.openhab.core.types.RefreshType;
49 import org.openhab.core.types.State;
50 import org.openhab.core.types.UnDefType;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
54 import name.eskildsen.zoneminder.IZoneMinderConnectionInfo;
55 import name.eskildsen.zoneminder.IZoneMinderDaemonStatus;
56 import name.eskildsen.zoneminder.IZoneMinderDiskUsage;
57 import name.eskildsen.zoneminder.IZoneMinderHostLoad;
58 import name.eskildsen.zoneminder.IZoneMinderHostVersion;
59 import name.eskildsen.zoneminder.IZoneMinderMonitorData;
60 import name.eskildsen.zoneminder.IZoneMinderServer;
61 import name.eskildsen.zoneminder.IZoneMinderSession;
62 import name.eskildsen.zoneminder.ZoneMinderFactory;
63 import name.eskildsen.zoneminder.api.config.ZoneMinderConfig;
64 import name.eskildsen.zoneminder.api.config.ZoneMinderConfigEnum;
65 import name.eskildsen.zoneminder.exception.ZoneMinderUrlNotFoundException;
68 * Handler for a ZoneMinder Server.
70 * @author Martin S. Eskildsen - Initial contribution
72 public class ZoneMinderServerBridgeHandler extends BaseBridgeHandler implements ZoneMinderHandler {
74 public static final int TELNET_TIMEOUT = 5000;
76 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections
77 .singleton(ZoneMinderConstants.THING_TYPE_BRIDGE_ZONEMINDER_SERVER);
82 private final Logger logger = LoggerFactory.getLogger(getClass());
84 private ZoneMinderDiscoveryService discoveryService;
86 private ScheduledFuture<?> taskWatchDog;
87 private int refreshFrequency;
88 private int refreshCycleCount;
90 /** Connection status for the bridge. */
91 private boolean connected;
92 private ThingStatus curBridgeStatus = ThingStatus.UNKNOWN;
94 protected boolean online;
96 private Runnable watchDogRunnable = new Runnable() {
97 private int watchDogCount = -1;
102 updateAvaliabilityStatus(zoneMinderConnection);
104 if ((discoveryService != null) && (getBridgeConfig().getAutodiscoverThings())) {
106 // Run every two minutes
107 if ((watchDogCount % 8) == 0) {
108 discoveryService.startBackgroundDiscovery();
112 } catch (Exception exception) {
113 logger.error("[WATCHDOG]: Server run(): Exception: {}", exception.getMessage());
119 * Local copies of last fetched values from ZM
121 private String channelCpuLoad = "";
122 private String channelDiskUsage = "";
124 private Boolean isInitialized = false;
126 private IZoneMinderSession zoneMinderSession;
127 private IZoneMinderConnectionInfo zoneMinderConnection;
129 private ScheduledFuture<?> taskRefreshData;
130 private ScheduledFuture<?> taskPriorityRefreshData;
132 private Runnable refreshDataRunnable = () -> {
134 boolean fetchDiskUsage = false;
137 logger.debug("{}: Bridge '{}' is noit online skipping refresh", getLogIdentifier(), thing.getUID());
143 boolean resetCount = false;
144 boolean doRefresh = false;
146 // Disk Usage is disabled
147 if (getBridgeConfig().getRefreshIntervalLowPriorityTask() == 0) {
148 iMaxCycles = getBridgeConfig().getRefreshInterval();
152 iMaxCycles = getBridgeConfig().getRefreshIntervalLowPriorityTask() * 60;
154 if ((refreshCycleCount * refreshFrequency) >= (getBridgeConfig().getRefreshIntervalLowPriorityTask()
156 fetchDiskUsage = true;
162 "{}: Running Refresh data task count='{}', freq='{}', max='{}', interval='{}', intervalLow='{}'",
163 getLogIdentifier(), refreshCycleCount, refreshFrequency, iMaxCycles,
164 getBridgeConfig().getRefreshInterval(), getBridgeConfig().getRefreshIntervalLowPriorityTask());
168 refreshCycleCount = 0;
171 logger.debug("{}: 'refreshDataRunnable()': (diskUsage='{}')", getLogIdentifier(), fetchDiskUsage);
173 refreshThing(zoneMinderSession, fetchDiskUsage);
175 } catch (Exception exception) {
176 logger.error("{}: monitorRunnable::run(): Exception: ", getLogIdentifier(), exception);
180 private Runnable refreshPriorityDataRunnable = () -> {
182 // Make sure priority updates is done
183 for (Thing thing : getThing().getThings()) {
185 if (thing.getThingTypeUID().equals(ZoneMinderConstants.THING_TYPE_THING_ZONEMINDER_MONITOR)) {
186 ZoneMinderBaseThingHandler thingHandler = (ZoneMinderBaseThingHandler) thing.getHandler();
187 if (thingHandler != null) {
188 if (thingHandler.getRefreshPriority() == DataRefreshPriorityEnum.HIGH_PRIORITY) {
189 logger.debug("[MONITOR-{}]: RefreshPriority is High Priority",
190 thingHandler.getZoneMinderId());
191 thingHandler.refreshThing(zoneMinderSession, DataRefreshPriorityEnum.HIGH_PRIORITY);
195 "[MONITOR]: refreshThing not called for monitor, since thingHandler is 'null'");
198 } catch (NullPointerException ex) {
199 // This isn't critical (unless it comes over and over). There seems to be a bug so that a
201 // pointer exception is coming every now and then.
202 // HAve to find the reason for that. Until thenm, don't Spamm
203 logger.error("[MONITOR]: Method 'refreshThing()' for Bridge failed for thing='{}' - Exception: ",
205 } catch (Exception ex) {
206 logger.error("[MONITOR]: Method 'refreshThing()' for Bridge failed for thing='{}' - Exception: ",
210 } catch (Exception exception) {
211 logger.error("[MONITOR]: monitorRunnable::run(): Exception: ", exception);
218 * @param bridge Bridge object representing a ZoneMinder Server
220 public ZoneMinderServerBridgeHandler(Bridge bridge) {
223 logger.info("{}: Starting ZoneMinder Server Bridge Handler (Bridge='{}')", getLogIdentifier(),
224 bridge.getBridgeUID());
228 * Initializes the bridge.
231 public void initialize() {
232 logger.debug("[BRIDGE]: About to initialize bridge " + ZoneMinderConstants.BRIDGE_ZONEMINDER_SERVER);
234 updateStatus(ThingStatus.OFFLINE);
235 logger.info("BRIDGE: ZoneMinder Server Bridge Handler Initialized");
236 logger.debug("BRIDGE: HostName: {}", getBridgeConfig().getHostName());
237 logger.debug("BRIDGE: Protocol: {}", getBridgeConfig().getProtocol());
238 logger.debug("BRIDGE: Port HTTP(S) {}", getBridgeConfig().getHttpPort());
239 logger.debug("BRIDGE: Port Telnet {}", getBridgeConfig().getTelnetPort());
240 logger.debug("BRIDGE: Server Path {}", getBridgeConfig().getServerBasePath());
241 logger.debug("BRIDGE: User: {}", getBridgeConfig().getUserName());
242 logger.debug("BRIDGE: Refresh interval: {}", getBridgeConfig().getRefreshInterval());
243 logger.debug("BRIDGE: Low prio. refresh: {}", getBridgeConfig().getRefreshIntervalLowPriorityTask());
244 logger.debug("BRIDGE: Autodiscovery: {}", getBridgeConfig().getAutodiscoverThings());
248 zoneMinderConnection = ZoneMinderFactory.CreateConnection(getBridgeConfig().getProtocol(),
249 getBridgeConfig().getHostName(), getBridgeConfig().getHttpPort(), getBridgeConfig().getTelnetPort(),
250 getBridgeConfig().getServerBasePath(), getBridgeConfig().getUserName(),
251 getBridgeConfig().getPassword(), 3000);
253 taskRefreshData = null;
254 taskPriorityRefreshData = null;
255 } catch (Exception ex) {
256 logger.error("[BRIDGE]: 'ZoneMinderServerBridgeHandler' failed to initialize. Exception='{}'",
258 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR);
261 isInitialized = true;
265 public void setDiscoveryService(ZoneMinderDiscoveryService discoveryService) {
266 this.discoveryService = discoveryService;
270 * Method to find the lowest possible refresh rate (based on configuration)
275 protected int calculateCommonRefreshFrequency(int refreshRate) {
276 // Check if 30, 15, 10 or 5 seconds is possible
277 if ((refreshRate % 30) == 0) {
279 } else if ((refreshRate % 15) == 0) {
281 } else if ((refreshRate % 10) == 0) {
283 } else if ((refreshRate % 5) == 0) {
287 // Hmm, didn't find a obvious shared value. Run every second...
291 protected void startWatchDogTask() {
292 taskWatchDog = startTask(watchDogRunnable, 0, 15, TimeUnit.SECONDS);
295 protected void stopWatchDogTask() {
296 stopTask(taskWatchDog);
303 public void dispose() {
305 logger.debug("{}: Stop polling of ZoneMinder Server API", getLogIdentifier());
307 logger.info("{}: Stopping Discovery service", getLogIdentifier());
308 // Remove the discovery service
309 if (discoveryService != null) {
310 discoveryService.deactivate();
311 discoveryService = null;
314 logger.info("{}: Stopping WatchDog task", getLogIdentifier());
317 logger.info("{}: Stopping refresh data task", getLogIdentifier());
318 stopTask(taskRefreshData);
319 } catch (Exception ex) {
323 protected String getThingId() {
324 return getThing().getUID().getId();
328 public String getZoneMinderId() {
329 return getThing().getUID().getAsString();
332 protected ArrayList<IZoneMinderMonitorData> getMonitors(IZoneMinderSession session) {
334 return ZoneMinderFactory.getServerProxy(session).getMonitors();
337 return new ArrayList<>();
340 protected ZoneMinderBridgeServerConfig getBridgeConfig() {
341 return this.getConfigAs(ZoneMinderBridgeServerConfig.class);
347 public ZoneMinderBaseThingHandler getZoneMinderThingHandlerFromZoneMinderId(ThingTypeUID thingTypeUID,
348 String zoneMinderId) {
349 // Inform thing handlers of connection
350 List<Thing> things = getThing().getThings();
352 for (Thing thing : things) {
353 ZoneMinderBaseThingHandler thingHandler = (ZoneMinderBaseThingHandler) thing.getHandler();
355 if ((thingHandler != null) && (thingHandler.getZoneMinderId().equals(zoneMinderId))
356 && (thing.getThingTypeUID().equals(thingTypeUID))) {
364 public Collection<Class<? extends ThingHandlerService>> getServices() {
365 return Collections.singleton(ZoneMinderDiscoveryService.class);
369 public void handleCommand(ChannelUID channelUID, Command command) {
370 logger.debug("{}: Update '{}' with '{}'", getLogIdentifier(), channelUID.getAsString(), command.toString());
373 protected synchronized void refreshThing(IZoneMinderSession session, boolean fetchDiskUsage) {
374 logger.debug("{}: 'refreshThing()': Thing='{}'!", getLogIdentifier(), this.getThing().getUID());
376 List<Channel> channels = getThing().getChannels();
377 List<Thing> things = getThing().getThings();
379 IZoneMinderServer zoneMinderServerProxy = ZoneMinderFactory.getServerProxy(session);
380 if (zoneMinderServerProxy == null) {
381 logger.warn("{}: Could not obtain ZonerMinderServerProxy ", getLogIdentifier());
383 // Make sure old data is cleared
385 channelDiskUsage = "";
386 } else if (isConnected()) {
388 * Fetch data for Bridge
390 IZoneMinderHostLoad hostLoad = null;
392 hostLoad = zoneMinderServerProxy.getHostCpuLoad();
393 logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
394 zoneMinderServerProxy.getHttpUrl(), zoneMinderServerProxy.getHttpResponseCode(),
395 zoneMinderServerProxy.getHttpResponseMessage());
396 } catch (FailedLoginException | ZoneMinderUrlNotFoundException | IOException ex) {
397 logger.error("{}: Exception thrown in call to ZoneMinderHostLoad: ", getLogIdentifier(), ex);
400 if (hostLoad == null) {
401 logger.warn("{}: ZoneMinderHostLoad dataset could not be obtained (received 'null')",
403 } else if (hostLoad.getHttpResponseCode() != 200) {
405 "BRIDGE [{}]: ZoneMinderHostLoad dataset could not be obtained (HTTP Response: Code='{}', Message='{}')",
406 getThingId(), hostLoad.getHttpResponseCode(), hostLoad.getHttpResponseMessage());
408 channelCpuLoad = hostLoad.getCpuLoad().toString();
411 if (fetchDiskUsage) {
412 IZoneMinderDiskUsage diskUsage = null;
414 diskUsage = zoneMinderServerProxy.getHostDiskUsage();
415 logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
416 zoneMinderServerProxy.getHttpUrl(), zoneMinderServerProxy.getHttpResponseCode(),
417 zoneMinderServerProxy.getHttpResponseMessage());
418 } catch (Exception ex) {
419 logger.error("{}: Exception thrown in call to ZoneMinderDiskUsage: ", getLogIdentifier(), ex);
422 if (diskUsage == null) {
423 logger.warn("{}: ZoneMinderDiskUsage dataset could not be obtained (received 'null')",
425 } else if (diskUsage.getHttpResponseCode() != 200) {
427 "{}: ZoneMinderDiskUsage dataset could not be obtained (HTTP Response: Code='{}', Message='{}')",
428 getLogIdentifier(), diskUsage.getHttpResponseCode(), diskUsage.getHttpResponseMessage());
430 channelDiskUsage = diskUsage.getDiskUsage();
435 // Make sure old data is cleared
437 channelDiskUsage = "";
441 * Update all channels on Bridge
443 for (Channel channel : channels) {
444 updateChannel(channel.getUID());
448 * Request Things attached to Bridge to refresh
450 for (Thing thing : things) {
452 if (thing.getThingTypeUID().equals(ZoneMinderConstants.THING_TYPE_THING_ZONEMINDER_MONITOR)) {
453 ZoneMinderBaseThingHandler thingHandler = (ZoneMinderBaseThingHandler) thing.getHandler();
454 if (thingHandler == null) {
457 thingHandler.refreshThing(session, DataRefreshPriorityEnum.SCHEDULED);
459 } catch (Exception ex) { // Other exceptions has to be shown as errors
460 logger.warn("{}: Method 'refreshThing()' for Bridge {} failed for thing='{}' - Exception='{}'",
461 getLogIdentifier(), this.getZoneMinderId(), thing.getUID(), ex.getMessage());
467 * Returns connection status.
469 public synchronized Boolean isConnected() {
473 public boolean isOnline() {
478 * Set connection status.
482 private synchronized void setConnected(boolean connected) {
483 if (this.connected != connected) {
486 zoneMinderSession = ZoneMinderFactory.CreateSession(zoneMinderConnection);
487 } catch (FailedLoginException | IllegalArgumentException | IOException
488 | ZoneMinderUrlNotFoundException e) {
489 logger.error("BRIDGE [{}]: Call to setConencted failed with exception '{}'", getThingId(),
493 zoneMinderSession = null;
495 this.connected = connected;
500 * Set channel 'bridge_connection'.
504 private void setBridgeConnectionStatus(boolean connected) {
505 logger.debug(" {}: setBridgeConnection(): Set Bridge to {}", getLogIdentifier(),
506 connected ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
508 Bridge bridge = getBridge();
509 if (bridge != null) {
510 ThingStatus status = bridge.getStatus();
511 logger.debug("{}: Bridge ThingStatus is: {}", getLogIdentifier(), status);
514 setConnected(connected);
518 * Runs when connection established.
520 * @throws ZoneMinderUrlNotFoundException
521 * @throws IOException
522 * @throws GeneralSecurityException
523 * @throws IllegalArgumentException
525 public void onConnected() {
526 logger.debug("BRIDGE [{}]: onConnected(): Bridge Connected!", getThingId());
528 onBridgeConnected(this, zoneMinderConnection);
530 // Inform thing handlers of connection
531 List<Thing> things = getThing().getThings();
533 for (Thing thing : things) {
534 ZoneMinderBaseThingHandler thingHandler = (ZoneMinderBaseThingHandler) thing.getHandler();
536 if (thingHandler != null) {
538 thingHandler.onBridgeConnected(this, zoneMinderConnection);
539 } catch (IllegalArgumentException | GeneralSecurityException | IOException
540 | ZoneMinderUrlNotFoundException e) {
541 logger.error("{}: onConnected() failed - Exceprion: {}", getLogIdentifier(), e.getMessage());
543 logger.debug("{}: onConnected(): Bridge - {}, Thing - {}, Thing Handler - {}", getLogIdentifier(),
544 thing.getBridgeUID(), thing.getUID(), thingHandler);
550 * Runs when disconnected.
552 private void onDisconnected() {
553 logger.debug("{}: onDisconnected(): Bridge Disconnected!", getLogIdentifier());
555 onBridgeDisconnected(this);
557 // Inform thing handlers of disconnection
558 List<Thing> things = getThing().getThings();
560 for (Thing thing : things) {
561 ZoneMinderBaseThingHandler thingHandler = (ZoneMinderBaseThingHandler) thing.getHandler();
563 if (thingHandler != null) {
564 thingHandler.onBridgeDisconnected(this);
565 logger.debug("{}: onDisconnected(): Bridge - {}, Thing - {}, Thing Handler - {}", getLogIdentifier(),
566 thing.getBridgeUID(), thing.getUID(), thingHandler);
572 public void updateAvaliabilityStatus(IZoneMinderConnectionInfo connection) {
573 ThingStatus newStatus = ThingStatus.OFFLINE;
574 ThingStatusDetail statusDetail = ThingStatusDetail.NONE;
575 String statusDescription = "";
577 boolean isOnline = false;
579 ThingStatus prevStatus = getThing().getStatus();
582 // Just perform a health check to see if we are still connected
583 if (prevStatus == ThingStatus.ONLINE) {
584 if (zoneMinderSession == null) {
585 newStatus = ThingStatus.ONLINE;
586 statusDetail = ThingStatusDetail.NONE;
587 statusDescription = "";
588 updateBridgeStatus(newStatus, statusDetail, statusDescription);
590 } else if (!zoneMinderSession.isConnected()) {
591 newStatus = ThingStatus.OFFLINE;
592 statusDetail = ThingStatusDetail.COMMUNICATION_ERROR;
593 statusDescription = "Session lost connection to ZoneMinder Server";
594 updateBridgeStatus(newStatus, statusDetail, statusDescription);
599 IZoneMinderServer serverProxy = ZoneMinderFactory.getServerProxy(zoneMinderSession);
600 IZoneMinderDaemonStatus daemonStatus = serverProxy.getHostDaemonCheckState();
602 // If service isn't running OR we revceived a http responsecode other than 200, assume we are offline
603 if ((!daemonStatus.getStatus()) || (daemonStatus.getHttpResponseCode() != 200)) {
604 newStatus = ThingStatus.OFFLINE;
605 statusDetail = ThingStatusDetail.COMMUNICATION_ERROR;
606 statusDescription = "ZoneMinder Server Daemon not running";
608 logger.debug("{}: {} (state='{}' and ResponseCode='{}')", getLogIdentifier(), statusDescription,
609 daemonStatus.getStatus(), daemonStatus.getHttpResponseCode());
610 updateBridgeStatus(newStatus, statusDetail, statusDescription);
614 // TODO:: Check other things without being harsh????
616 newStatus = ThingStatus.ONLINE;
617 statusDetail = ThingStatusDetail.NONE;
618 statusDescription = "";
620 // If we are OFFLINE, check everything
621 else if (prevStatus == ThingStatus.OFFLINE) {
622 // Just wait until we are finished initializing
623 if (!isInitialized) {
628 ZoneMinderBridgeServerConfig config = getBridgeConfig();
630 // Check if server Bridge configuration is valid
631 if (config == null) {
632 newStatus = ThingStatus.OFFLINE;
633 statusDetail = ThingStatusDetail.CONFIGURATION_ERROR;
634 statusDescription = "Configuration not found";
635 updateBridgeStatus(newStatus, statusDetail, statusDescription);
637 } else if (config.getHostName() == null) {
638 newStatus = ThingStatus.OFFLINE;
639 statusDetail = ThingStatusDetail.CONFIGURATION_ERROR;
640 statusDescription = "Host not found in configuration";
641 updateBridgeStatus(newStatus, statusDetail, statusDescription);
643 } else if (config.getProtocol() == null) {
644 newStatus = ThingStatus.OFFLINE;
645 statusDetail = ThingStatusDetail.CONFIGURATION_ERROR;
646 statusDescription = "Unknown protocol in configuration";
647 updateBridgeStatus(newStatus, statusDetail, statusDescription);
651 else if (config.getHttpPort() == null) {
652 newStatus = ThingStatus.OFFLINE;
653 statusDetail = ThingStatusDetail.CONFIGURATION_ERROR;
654 statusDescription = "Invalid HTTP port";
655 updateBridgeStatus(newStatus, statusDetail, statusDescription);
659 else if (config.getTelnetPort() == null) {
660 newStatus = ThingStatus.OFFLINE;
661 statusDetail = ThingStatusDetail.CONFIGURATION_ERROR;
662 statusDescription = "Invalid telnet port";
663 updateBridgeStatus(newStatus, statusDetail, statusDescription);
665 } else if (!ZoneMinderFactory.isZoneMinderUrl(connection)) {
666 newStatus = ThingStatus.OFFLINE;
667 statusDetail = ThingStatusDetail.CONFIGURATION_ERROR;
668 statusDescription = "URL not a ZoneMinder Server";
669 updateBridgeStatus(newStatus, statusDetail, statusDescription);
673 if (!isZoneMinderLoginValid(connection)) {
674 newStatus = ThingStatus.OFFLINE;
675 statusDetail = ThingStatusDetail.CONFIGURATION_ERROR;
676 statusDescription = "Cannot access ZoneMinder Server. Check provided usercredentials";
677 updateBridgeStatus(newStatus, statusDetail, statusDescription);
682 * Now we will try to establish a session
685 IZoneMinderSession curSession = null;
687 curSession = ZoneMinderFactory.CreateSession(connection);
688 } catch (FailedLoginException | IllegalArgumentException | IOException
689 | ZoneMinderUrlNotFoundException ex) {
690 logger.error("{}: Create Session failed with exception {}", getLogIdentifier(), ex.getMessage());
692 newStatus = ThingStatus.OFFLINE;
693 statusDetail = ThingStatusDetail.COMMUNICATION_ERROR;
694 statusDescription = "Failed to connect. (Check Log)";
695 if (curBridgeStatus != ThingStatus.OFFLINE) {
696 logger.error("{}: Bridge OFFLINE because of '{}' Exception='{}'", getLogIdentifier(),
697 statusDescription, ex.getMessage());
699 updateBridgeStatus(newStatus, statusDetail, statusDescription);
702 IZoneMinderServer serverProxy = ZoneMinderFactory.getServerProxy(curSession);
704 // Check if server API can be accessed
705 if (!serverProxy.isApiEnabled()) {
706 newStatus = ThingStatus.OFFLINE;
707 statusDetail = ThingStatusDetail.CONFIGURATION_ERROR;
708 statusDescription = "ZoneMinder Server 'OPT_USE_API' not enabled";
709 updateBridgeStatus(newStatus, statusDetail, statusDescription);
711 } else if (!serverProxy.getHostDaemonCheckState().getStatus()) {
712 newStatus = ThingStatus.OFFLINE;
713 statusDetail = ThingStatusDetail.CONFIGURATION_ERROR;
714 statusDescription = "ZoneMinder Server Daemon not running";
715 updateBridgeStatus(newStatus, statusDetail, statusDescription);
718 // Verify that 'OPT_TRIGGER' is set to true in ZoneMinder
719 else if (!serverProxy.isTriggerOptionEnabled()) {
720 newStatus = ThingStatus.OFFLINE;
721 statusDetail = ThingStatusDetail.CONFIGURATION_ERROR;
722 statusDescription = "ZoneMinder Server option 'OPT_TRIGGERS' not enabled";
723 updateBridgeStatus(newStatus, statusDetail, statusDescription);
726 // Seems like everything is as we want it :-)
731 zoneMinderSession = curSession;
733 newStatus = ThingStatus.ONLINE;
734 statusDetail = ThingStatusDetail.NONE;
735 statusDescription = "";
737 zoneMinderSession = null;
739 newStatus = ThingStatus.OFFLINE;
742 } catch (Exception ex) {
743 newStatus = ThingStatus.OFFLINE;
744 statusDetail = ThingStatusDetail.COMMUNICATION_ERROR;
745 logger.error("{}: Exception occurred in updateAvailabilityStatus Exception='{}'", getLogIdentifier(),
747 statusDescription = "Error occurred (Check log)";
749 updateBridgeStatus(newStatus, statusDetail, statusDescription);
751 // Ask child things to update their Availability Status
752 for (Thing thing : getThing().getThings()) {
753 ZoneMinderBaseThingHandler thingHandler = (ZoneMinderBaseThingHandler) thing.getHandler();
754 if (thingHandler instanceof ZoneMinderThingMonitorHandler) {
756 thingHandler.updateAvaliabilityStatus(connection);
757 } catch (Exception ex) {
758 logger.debug("{}: Failed to call 'updateAvailabilityStatus()' for '{}'", getLogIdentifier(),
759 thingHandler.getThing().getUID());
765 @SuppressWarnings("null")
766 protected void updateBridgeStatus(ThingStatus newStatus, ThingStatusDetail statusDetail, String statusDescription) {
767 ThingStatusInfo curStatusInfo = thing.getStatusInfo();
769 String curDescription = curStatusInfo.getDescription();
770 if (StringUtils.isBlank(curStatusInfo.getDescription())) {
774 // Status changed, curDescription is null checked
775 if ((curStatusInfo.getStatus() != newStatus) || (curStatusInfo.getStatusDetail() != statusDetail)
776 || (!curDescription.equals(statusDescription))) {
777 // if (thing.getStatus() != newStatus) {
778 logger.info("{}: Bridge status changed from '{}' to '{}'", getLogIdentifier(), thing.getStatus(),
781 if ((newStatus == ThingStatus.ONLINE) && (curStatusInfo.getStatus() != ThingStatus.ONLINE)) {
783 setBridgeConnectionStatus(true);
785 } catch (IllegalArgumentException e) {
786 // Just ignore that here
788 } else if ((newStatus == ThingStatus.OFFLINE) && (curStatusInfo.getStatus() != ThingStatus.OFFLINE)) {
790 setBridgeConnectionStatus(false);
792 } catch (IllegalArgumentException e) {
793 // Just ignore that here
796 // Update Status correspondingly
797 if ((newStatus == ThingStatus.OFFLINE) && (statusDetail != ThingStatusDetail.NONE)) {
798 updateStatus(newStatus, statusDetail, statusDescription);
800 updateStatus(newStatus);
803 curBridgeStatus = newStatus;
807 protected boolean isZoneMinderLoginValid(IZoneMinderConnectionInfo connection) {
809 return ZoneMinderFactory.validateLogin(connection);
810 } catch (Exception e) {
816 public void updateChannel(ChannelUID channel) {
819 switch (channel.getId()) {
820 case ZoneMinderConstants.CHANNEL_ONLINE:
821 updateState(channel, (isOnline() ? OnOffType.ON : OnOffType.OFF));
824 case ZoneMinderConstants.CHANNEL_SERVER_DISKUSAGE:
825 state = getServerDiskUsageState();
828 case ZoneMinderConstants.CHANNEL_SERVER_CPULOAD:
829 state = getServerCpuLoadState();
833 logger.warn("{}: updateChannel(): Server '{}': No handler defined for channel='{}'",
834 getLogIdentifier(), thing.getLabel(), channel.getAsString());
839 logger.debug("{}: BridgeHandler.updateChannel(): Updating channel '{}' to state='{}'",
840 getLogIdentifier(), channel.getId(), state.toString());
841 updateState(channel.getId(), state);
843 } catch (Exception ex) {
844 logger.error("{}: Error when 'updateChannel()' was called for thing='{}' (Exception='{}'",
845 getLogIdentifier(), channel.getId(), ex.getMessage());
849 protected boolean openConnection() {
850 boolean connected = false;
851 if (!isConnected()) {
852 logger.debug("{}: Connecting Bridge to ZoneMinder Server", getLogIdentifier());
858 setConnected(connected);
860 logger.info("{}: Connecting to ZoneMinder Server (result='{}'", getLogIdentifier(), connected);
862 } catch (Exception exception) {
863 logger.error("{}: openConnection(): Exception: ", getLogIdentifier(), exception);
866 if (!isConnected()) {
872 return isConnected();
875 synchronized void closeConnection() {
877 logger.debug("{}: closeConnection(): Closed HTTP Connection!", getLogIdentifier());
880 } catch (Exception exception) {
881 logger.error("{}: closeConnection(): Error closing connection - {}", getLogIdentifier(),
882 exception.getMessage());
886 protected State getServerCpuLoadState() {
887 State state = UnDefType.UNDEF;
890 if ((channelCpuLoad != "") && (isConnected())) {
891 state = new DecimalType(new BigDecimal(channelCpuLoad));
894 } catch (Exception ex) {
895 // Deliberately kept as debug info!
896 logger.debug("{}: Exception='{}'", getLogIdentifier(), ex.getMessage());
902 protected State getServerDiskUsageState() {
903 State state = UnDefType.UNDEF;
906 if ((channelDiskUsage != "") && (isConnected())) {
907 state = new DecimalType(new BigDecimal(channelDiskUsage));
909 } catch (Exception ex) {
910 // Deliberately kept as debug info!
911 logger.debug("{}: Exception {}", getLogIdentifier(), ex.getMessage());
918 public void onBridgeConnected(ZoneMinderServerBridgeHandler bridge, IZoneMinderConnectionInfo connection) {
919 logger.info("{}: Brigde went ONLINE", getLogIdentifier());
921 if (taskRefreshData == null) {
922 // Perform first refresh manually (we want to force update of DiskUsage)
923 boolean updateDiskUsage = (getBridgeConfig().getRefreshIntervalLowPriorityTask() > 0) ? true : false;
924 refreshThing(zoneMinderSession, updateDiskUsage);
926 if (getBridgeConfig().getRefreshIntervalLowPriorityTask() != 0) {
927 refreshFrequency = calculateCommonRefreshFrequency(getBridgeConfig().getRefreshInterval());
929 refreshFrequency = getBridgeConfig().getRefreshInterval();
931 logger.info("BRIDGE [{}]: Calculated refresh inetrval to '{}'", getThingId(), refreshFrequency);
933 if (taskRefreshData != null) {
934 taskRefreshData.cancel(true);
935 taskRefreshData = null;
938 // Start job to handle next updates
939 taskRefreshData = startTask(refreshDataRunnable, refreshFrequency, refreshFrequency, TimeUnit.SECONDS);
941 if (taskPriorityRefreshData != null) {
942 taskPriorityRefreshData.cancel(true);
943 taskPriorityRefreshData = null;
946 // Only start if Priority Frequency is higher than ordinary
947 if (refreshFrequency > 1) {
948 taskPriorityRefreshData = startTask(refreshPriorityDataRunnable, 0, 1, TimeUnit.SECONDS);
953 updateMonitorProperties(zoneMinderSession);
957 public void onBridgeDisconnected(ZoneMinderServerBridgeHandler bridge) {
958 logger.info("{}: Brigde went OFFLINE", getLogIdentifier());
960 // Deactivate discovery service
961 discoveryService.deactivate();
963 // Stopping refresh thread while OFFLINE
964 if (taskRefreshData != null) {
965 taskRefreshData.cancel(true);
966 taskRefreshData = null;
967 logger.debug("{}: Stopping DataRefresh task", getLogIdentifier());
970 // Stopping High priority thread while OFFLINE
971 if (taskPriorityRefreshData != null) {
972 taskPriorityRefreshData.cancel(true);
973 taskPriorityRefreshData = null;
974 logger.debug("{}: Stopping Priority DataRefresh task", getLogIdentifier());
977 // Make sure everything gets refreshed
978 for (Channel ch : getThing().getChannels()) {
979 handleCommand(ch.getUID(), RefreshType.REFRESH);
982 // Inform thing handlers of disconnection
983 for (Thing thing : getThing().getThings()) {
984 ZoneMinderBaseThingHandler thingHandler = (ZoneMinderBaseThingHandler) thing.getHandler();
986 if (thingHandler != null) {
987 thingHandler.onBridgeDisconnected(this);
988 logger.debug("{}: onDisconnected(): Bridge - {}, Thing - {}, Thing Handler - {}", getLogIdentifier(),
989 thing.getBridgeUID(), thing.getUID(), thingHandler);
995 * Method to start a data refresh task.
997 protected ScheduledFuture<?> startTask(Runnable command, long delay, long interval, TimeUnit unit) {
998 logger.debug("BRIDGE [{}]: Starting ZoneMinder Bridge Monitor Task. Command='{}'", getThingId(),
1000 if (interval == 0) {
1004 return scheduler.scheduleWithFixedDelay(command, delay, interval, unit);
1008 * Method to stop the datarefresh task.
1010 protected void stopTask(ScheduledFuture<?> task) {
1012 if (task != null && !task.isCancelled()) {
1013 logger.debug("{}: Stopping ZoneMinder Bridge Monitor Task. Task='{}'", getLogIdentifier(),
1017 } catch (Exception ex) {
1021 public ArrayList<IZoneMinderMonitorData> getMonitors() {
1023 IZoneMinderServer serverProxy = ZoneMinderFactory.getServerProxy(zoneMinderSession);
1024 ArrayList<IZoneMinderMonitorData> result = serverProxy.getMonitors();
1028 return new ArrayList<>();
1032 * This is experimental
1033 * Try to add different properties
1035 private void updateMonitorProperties(IZoneMinderSession session) {
1036 // Update property information about this device
1037 Map<String, String> properties = editProperties();
1038 IZoneMinderServer serverProxy = ZoneMinderFactory.getServerProxy(session);
1040 IZoneMinderHostVersion hostVersion = null;
1042 hostVersion = serverProxy.getHostVersion();
1043 logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
1044 serverProxy.getHttpUrl(), serverProxy.getHttpResponseCode(), serverProxy.getHttpResponseMessage());
1046 ZoneMinderConfig configUseApi = serverProxy.getConfig(ZoneMinderConfigEnum.ZM_OPT_USE_API);
1047 logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
1048 serverProxy.getHttpUrl(), serverProxy.getHttpResponseCode(), serverProxy.getHttpResponseMessage());
1050 ZoneMinderConfig configUseAuth = serverProxy.getConfig(ZoneMinderConfigEnum.ZM_OPT_USE_AUTH);
1051 logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
1052 serverProxy.getHttpUrl(), serverProxy.getHttpResponseCode(), serverProxy.getHttpResponseMessage());
1054 ZoneMinderConfig configTrigerrs = serverProxy.getConfig(ZoneMinderConfigEnum.ZM_OPT_TRIGGERS);
1055 logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
1056 configUseApi.getHttpUrl(), configUseApi.getHttpResponseCode(),
1057 configUseApi.getHttpResponseMessage());
1059 properties.put(ZoneMinderProperties.PROPERTY_SERVER_VERSION, hostVersion.getVersion());
1060 properties.put(ZoneMinderProperties.PROPERTY_SERVER_API_VERSION, hostVersion.getApiVersion());
1061 properties.put(ZoneMinderProperties.PROPERTY_SERVER_USE_API, configUseApi.getValueAsString());
1062 properties.put(ZoneMinderProperties.PROPERTY_SERVER_USE_AUTHENTIFICATION, configUseAuth.getValueAsString());
1063 properties.put(ZoneMinderProperties.PROPERTY_SERVER_TRIGGERS_ENABLED, configTrigerrs.getValueAsString());
1064 } catch (FailedLoginException | ZoneMinderUrlNotFoundException | IOException e) {
1065 logger.warn("{}: Exception occurred when updating monitor properties (Exception='{}'", getLogIdentifier(),
1069 // Must loop over the new properties since we might have added data
1070 boolean update = false;
1071 Map<String, String> originalProperties = editProperties();
1072 for (String property : properties.keySet()) {
1073 if ((originalProperties.get(property) == null
1074 || !originalProperties.get(property).equals(properties.get(property)))) {
1081 logger.info("{}: Properties synchronised, Thing id: {}", getLogIdentifier(), getThingId());
1082 updateProperties(properties);
1087 public String getLogIdentifier() {
1088 String result = "[BRIDGE]";
1090 result = String.format("[BRIDGE (%s)]", getThingId());
1091 } catch (Exception e) {
1092 result = "[BRIDGE (?)]";