]> git.basschouten.com Git - openhab-addons.git/blob
b1ab3268c1a55fdbb9e02f795ad2eb90e88a6a74
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.zoneminder.internal.handler;
14
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;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
27 import javax.security.auth.login.FailedLoginException;
28
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;
53
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;
66
67 /**
68  * Handler for a ZoneMinder Server.
69  *
70  * @author Martin S. Eskildsen - Initial contribution
71  */
72 public class ZoneMinderServerBridgeHandler extends BaseBridgeHandler implements ZoneMinderHandler {
73
74     public static final int TELNET_TIMEOUT = 5000;
75
76     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections
77             .singleton(ZoneMinderConstants.THING_TYPE_BRIDGE_ZONEMINDER_SERVER);
78
79     /**
80      * Logger
81      */
82     private final Logger logger = LoggerFactory.getLogger(getClass());
83
84     private ZoneMinderDiscoveryService discoveryService;
85
86     private ScheduledFuture<?> taskWatchDog;
87     private int refreshFrequency;
88     private int refreshCycleCount;
89
90     /** Connection status for the bridge. */
91     private boolean connected;
92     private ThingStatus curBridgeStatus = ThingStatus.UNKNOWN;
93
94     protected boolean online;
95
96     private Runnable watchDogRunnable = new Runnable() {
97         private int watchDogCount = -1;
98
99         @Override
100         public void run() {
101             try {
102                 updateAvaliabilityStatus(zoneMinderConnection);
103
104                 if ((discoveryService != null) && (getBridgeConfig().getAutodiscoverThings())) {
105                     watchDogCount++;
106                     // Run every two minutes
107                     if ((watchDogCount % 8) == 0) {
108                         discoveryService.startBackgroundDiscovery();
109                         watchDogCount = 0;
110                     }
111                 }
112             } catch (Exception exception) {
113                 logger.error("[WATCHDOG]: Server run(): Exception: {}", exception.getMessage());
114             }
115         }
116     };
117
118     /**
119      * Local copies of last fetched values from ZM
120      */
121     private String channelCpuLoad = "";
122     private String channelDiskUsage = "";
123
124     private Boolean isInitialized = false;
125
126     private IZoneMinderSession zoneMinderSession;
127     private IZoneMinderConnectionInfo zoneMinderConnection;
128
129     private ScheduledFuture<?> taskRefreshData;
130     private ScheduledFuture<?> taskPriorityRefreshData;
131
132     private Runnable refreshDataRunnable = () -> {
133         try {
134             boolean fetchDiskUsage = false;
135
136             if (!isOnline()) {
137                 logger.debug("{}: Bridge '{}' is noit online skipping refresh", getLogIdentifier(), thing.getUID());
138             }
139
140             refreshCycleCount++;
141
142             int iMaxCycles;
143             boolean resetCount = false;
144             boolean doRefresh = false;
145
146             // Disk Usage is disabled
147             if (getBridgeConfig().getRefreshIntervalLowPriorityTask() == 0) {
148                 iMaxCycles = getBridgeConfig().getRefreshInterval();
149                 resetCount = true;
150                 doRefresh = true;
151             } else {
152                 iMaxCycles = getBridgeConfig().getRefreshIntervalLowPriorityTask() * 60;
153                 doRefresh = true;
154                 if ((refreshCycleCount * refreshFrequency) >= (getBridgeConfig().getRefreshIntervalLowPriorityTask()
155                         * 60)) {
156                     fetchDiskUsage = true;
157                     resetCount = true;
158                 }
159             }
160
161             logger.debug(
162                     "{}: Running Refresh data task count='{}', freq='{}', max='{}', interval='{}', intervalLow='{}'",
163                     getLogIdentifier(), refreshCycleCount, refreshFrequency, iMaxCycles,
164                     getBridgeConfig().getRefreshInterval(), getBridgeConfig().getRefreshIntervalLowPriorityTask());
165
166             if (doRefresh) {
167                 if (resetCount) {
168                     refreshCycleCount = 0;
169                 }
170
171                 logger.debug("{}: 'refreshDataRunnable()': (diskUsage='{}')", getLogIdentifier(), fetchDiskUsage);
172
173                 refreshThing(zoneMinderSession, fetchDiskUsage);
174             }
175         } catch (Exception exception) {
176             logger.error("{}: monitorRunnable::run(): Exception: ", getLogIdentifier(), exception);
177         }
178     };
179
180     private Runnable refreshPriorityDataRunnable = () -> {
181         try {
182             // Make sure priority updates is done
183             for (Thing thing : getThing().getThings()) {
184                 try {
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);
192                             }
193                         } else {
194                             logger.debug(
195                                     "[MONITOR]: refreshThing not called for monitor, since thingHandler is 'null'");
196                         }
197                     }
198                 } catch (NullPointerException ex) {
199                     // This isn't critical (unless it comes over and over). There seems to be a bug so that a
200                     // null
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: ",
204                             thing.getUID(), ex);
205                 } catch (Exception ex) {
206                     logger.error("[MONITOR]: Method 'refreshThing()' for Bridge failed for thing='{}' - Exception: ",
207                             thing.getUID(), ex);
208                 }
209             }
210         } catch (Exception exception) {
211             logger.error("[MONITOR]: monitorRunnable::run(): Exception: ", exception);
212         }
213     };
214
215     /**
216      * Constructor
217      *
218      * @param bridge Bridge object representing a ZoneMinder Server
219      */
220     public ZoneMinderServerBridgeHandler(Bridge bridge) {
221         super(bridge);
222
223         logger.info("{}: Starting ZoneMinder Server Bridge Handler (Bridge='{}')", getLogIdentifier(),
224                 bridge.getBridgeUID());
225     }
226
227     /**
228      * Initializes the bridge.
229      */
230     @Override
231     public void initialize() {
232         logger.debug("[BRIDGE]: About to initialize bridge " + ZoneMinderConstants.BRIDGE_ZONEMINDER_SERVER);
233         try {
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());
245
246             closeConnection();
247
248             zoneMinderConnection = ZoneMinderFactory.CreateConnection(getBridgeConfig().getProtocol(),
249                     getBridgeConfig().getHostName(), getBridgeConfig().getHttpPort(), getBridgeConfig().getTelnetPort(),
250                     getBridgeConfig().getServerBasePath(), getBridgeConfig().getUserName(),
251                     getBridgeConfig().getPassword(), 3000);
252
253             taskRefreshData = null;
254             taskPriorityRefreshData = null;
255         } catch (Exception ex) {
256             logger.error("[BRIDGE]: 'ZoneMinderServerBridgeHandler' failed to initialize. Exception='{}'",
257                     ex.getMessage());
258             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR);
259         } finally {
260             startWatchDogTask();
261             isInitialized = true;
262         }
263     }
264
265     public void setDiscoveryService(ZoneMinderDiscoveryService discoveryService) {
266         this.discoveryService = discoveryService;
267     }
268
269     /**
270      * Method to find the lowest possible refresh rate (based on configuration)
271      *
272      * @param refreshRate
273      * @return
274      */
275     protected int calculateCommonRefreshFrequency(int refreshRate) {
276         // Check if 30, 15, 10 or 5 seconds is possible
277         if ((refreshRate % 30) == 0) {
278             return 30;
279         } else if ((refreshRate % 15) == 0) {
280             return 15;
281         } else if ((refreshRate % 10) == 0) {
282             return 10;
283         } else if ((refreshRate % 5) == 0) {
284             return 5;
285         }
286
287         // Hmm, didn't find a obvious shared value. Run every second...
288         return 1;
289     }
290
291     protected void startWatchDogTask() {
292         taskWatchDog = startTask(watchDogRunnable, 0, 15, TimeUnit.SECONDS);
293     }
294
295     protected void stopWatchDogTask() {
296         stopTask(taskWatchDog);
297         taskWatchDog = null;
298     }
299
300     /**
301      */
302     @Override
303     public void dispose() {
304         try {
305             logger.debug("{}: Stop polling of ZoneMinder Server API", getLogIdentifier());
306
307             logger.info("{}: Stopping Discovery service", getLogIdentifier());
308             // Remove the discovery service
309             if (discoveryService != null) {
310                 discoveryService.deactivate();
311                 discoveryService = null;
312             }
313
314             logger.info("{}: Stopping WatchDog task", getLogIdentifier());
315             stopWatchDogTask();
316
317             logger.info("{}: Stopping refresh data task", getLogIdentifier());
318             stopTask(taskRefreshData);
319         } catch (Exception ex) {
320         }
321     }
322
323     protected String getThingId() {
324         return getThing().getUID().getId();
325     }
326
327     @Override
328     public String getZoneMinderId() {
329         return getThing().getUID().getAsString();
330     }
331
332     protected ArrayList<IZoneMinderMonitorData> getMonitors(IZoneMinderSession session) {
333         if (isConnected()) {
334             return ZoneMinderFactory.getServerProxy(session).getMonitors();
335         }
336
337         return new ArrayList<>();
338     }
339
340     protected ZoneMinderBridgeServerConfig getBridgeConfig() {
341         return this.getConfigAs(ZoneMinderBridgeServerConfig.class);
342     }
343
344     /**
345     *
346     */
347     public ZoneMinderBaseThingHandler getZoneMinderThingHandlerFromZoneMinderId(ThingTypeUID thingTypeUID,
348             String zoneMinderId) {
349         // Inform thing handlers of connection
350         List<Thing> things = getThing().getThings();
351
352         for (Thing thing : things) {
353             ZoneMinderBaseThingHandler thingHandler = (ZoneMinderBaseThingHandler) thing.getHandler();
354
355             if ((thingHandler != null) && (thingHandler.getZoneMinderId().equals(zoneMinderId))
356                     && (thing.getThingTypeUID().equals(thingTypeUID))) {
357                 return thingHandler;
358             }
359         }
360         return null;
361     }
362
363     @Override
364     public Collection<Class<? extends ThingHandlerService>> getServices() {
365         return Collections.singleton(ZoneMinderDiscoveryService.class);
366     }
367
368     @Override
369     public void handleCommand(ChannelUID channelUID, Command command) {
370         logger.debug("{}: Update '{}' with '{}'", getLogIdentifier(), channelUID.getAsString(), command.toString());
371     }
372
373     protected synchronized void refreshThing(IZoneMinderSession session, boolean fetchDiskUsage) {
374         logger.debug("{}: 'refreshThing()': Thing='{}'!", getLogIdentifier(), this.getThing().getUID());
375
376         List<Channel> channels = getThing().getChannels();
377         List<Thing> things = getThing().getThings();
378
379         IZoneMinderServer zoneMinderServerProxy = ZoneMinderFactory.getServerProxy(session);
380         if (zoneMinderServerProxy == null) {
381             logger.warn("{}:  Could not obtain ZonerMinderServerProxy ", getLogIdentifier());
382
383             // Make sure old data is cleared
384             channelCpuLoad = "";
385             channelDiskUsage = "";
386         } else if (isConnected()) {
387             /*
388              * Fetch data for Bridge
389              */
390             IZoneMinderHostLoad hostLoad = null;
391             try {
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);
398             }
399
400             if (hostLoad == null) {
401                 logger.warn("{}: ZoneMinderHostLoad dataset could not be obtained (received 'null')",
402                         getLogIdentifier());
403             } else if (hostLoad.getHttpResponseCode() != 200) {
404                 logger.warn(
405                         "BRIDGE [{}]: ZoneMinderHostLoad dataset could not be obtained (HTTP Response: Code='{}', Message='{}')",
406                         getThingId(), hostLoad.getHttpResponseCode(), hostLoad.getHttpResponseMessage());
407             } else {
408                 channelCpuLoad = hostLoad.getCpuLoad().toString();
409             }
410
411             if (fetchDiskUsage) {
412                 IZoneMinderDiskUsage diskUsage = null;
413                 try {
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);
420                 }
421
422                 if (diskUsage == null) {
423                     logger.warn("{}: ZoneMinderDiskUsage dataset could not be obtained (received 'null')",
424                             getLogIdentifier());
425                 } else if (diskUsage.getHttpResponseCode() != 200) {
426                     logger.warn(
427                             "{}: ZoneMinderDiskUsage dataset could not be obtained (HTTP Response: Code='{}', Message='{}')",
428                             getLogIdentifier(), diskUsage.getHttpResponseCode(), diskUsage.getHttpResponseMessage());
429                 } else {
430                     channelDiskUsage = diskUsage.getDiskUsage();
431                 }
432             }
433         } else {
434             online = false;
435             // Make sure old data is cleared
436             channelCpuLoad = "";
437             channelDiskUsage = "";
438         }
439
440         /*
441          * Update all channels on Bridge
442          */
443         for (Channel channel : channels) {
444             updateChannel(channel.getUID());
445         }
446
447         /*
448          * Request Things attached to Bridge to refresh
449          */
450         for (Thing thing : things) {
451             try {
452                 if (thing.getThingTypeUID().equals(ZoneMinderConstants.THING_TYPE_THING_ZONEMINDER_MONITOR)) {
453                     ZoneMinderBaseThingHandler thingHandler = (ZoneMinderBaseThingHandler) thing.getHandler();
454                     if (thingHandler == null) {
455                         continue;
456                     }
457                     thingHandler.refreshThing(session, DataRefreshPriorityEnum.SCHEDULED);
458                 }
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());
462             }
463         }
464     }
465
466     /**
467      * Returns connection status.
468      */
469     public synchronized Boolean isConnected() {
470         return connected;
471     }
472
473     public boolean isOnline() {
474         return online;
475     }
476
477     /**
478      * Set connection status.
479      *
480      * @param connected
481      */
482     private synchronized void setConnected(boolean connected) {
483         if (this.connected != connected) {
484             if (connected) {
485                 try {
486                     zoneMinderSession = ZoneMinderFactory.CreateSession(zoneMinderConnection);
487                 } catch (FailedLoginException | IllegalArgumentException | IOException
488                         | ZoneMinderUrlNotFoundException e) {
489                     logger.error("BRIDGE [{}]: Call to setConencted failed with exception '{}'", getThingId(),
490                             e.getMessage());
491                 }
492             } else {
493                 zoneMinderSession = null;
494             }
495             this.connected = connected;
496         }
497     }
498
499     /**
500      * Set channel 'bridge_connection'.
501      *
502      * @param connected
503      */
504     private void setBridgeConnectionStatus(boolean connected) {
505         logger.debug(" {}: setBridgeConnection(): Set Bridge to {}", getLogIdentifier(),
506                 connected ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
507
508         Bridge bridge = getBridge();
509         if (bridge != null) {
510             ThingStatus status = bridge.getStatus();
511             logger.debug("{}: Bridge ThingStatus is: {}", getLogIdentifier(), status);
512         }
513
514         setConnected(connected);
515     }
516
517     /**
518      * Runs when connection established.
519      *
520      * @throws ZoneMinderUrlNotFoundException
521      * @throws IOException
522      * @throws GeneralSecurityException
523      * @throws IllegalArgumentException
524      */
525     public void onConnected() {
526         logger.debug("BRIDGE [{}]: onConnected(): Bridge Connected!", getThingId());
527         setConnected(true);
528         onBridgeConnected(this, zoneMinderConnection);
529
530         // Inform thing handlers of connection
531         List<Thing> things = getThing().getThings();
532
533         for (Thing thing : things) {
534             ZoneMinderBaseThingHandler thingHandler = (ZoneMinderBaseThingHandler) thing.getHandler();
535
536             if (thingHandler != null) {
537                 try {
538                     thingHandler.onBridgeConnected(this, zoneMinderConnection);
539                 } catch (IllegalArgumentException | GeneralSecurityException | IOException
540                         | ZoneMinderUrlNotFoundException e) {
541                     logger.error("{}: onConnected() failed - Exceprion: {}", getLogIdentifier(), e.getMessage());
542                 }
543                 logger.debug("{}: onConnected(): Bridge - {}, Thing - {}, Thing Handler - {}", getLogIdentifier(),
544                         thing.getBridgeUID(), thing.getUID(), thingHandler);
545             }
546         }
547     }
548
549     /**
550      * Runs when disconnected.
551      */
552     private void onDisconnected() {
553         logger.debug("{}: onDisconnected(): Bridge Disconnected!", getLogIdentifier());
554         setConnected(false);
555         onBridgeDisconnected(this);
556
557         // Inform thing handlers of disconnection
558         List<Thing> things = getThing().getThings();
559
560         for (Thing thing : things) {
561             ZoneMinderBaseThingHandler thingHandler = (ZoneMinderBaseThingHandler) thing.getHandler();
562
563             if (thingHandler != null) {
564                 thingHandler.onBridgeDisconnected(this);
565                 logger.debug("{}: onDisconnected(): Bridge - {}, Thing - {}, Thing Handler - {}", getLogIdentifier(),
566                         thing.getBridgeUID(), thing.getUID(), thingHandler);
567             }
568         }
569     }
570
571     @Override
572     public void updateAvaliabilityStatus(IZoneMinderConnectionInfo connection) {
573         ThingStatus newStatus = ThingStatus.OFFLINE;
574         ThingStatusDetail statusDetail = ThingStatusDetail.NONE;
575         String statusDescription = "";
576
577         boolean isOnline = false;
578
579         ThingStatus prevStatus = getThing().getStatus();
580
581         try {
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);
589                     return;
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);
595
596                     return;
597                 }
598
599                 IZoneMinderServer serverProxy = ZoneMinderFactory.getServerProxy(zoneMinderSession);
600                 IZoneMinderDaemonStatus daemonStatus = serverProxy.getHostDaemonCheckState();
601
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";
607
608                     logger.debug("{}: {} (state='{}' and ResponseCode='{}')", getLogIdentifier(), statusDescription,
609                             daemonStatus.getStatus(), daemonStatus.getHttpResponseCode());
610                     updateBridgeStatus(newStatus, statusDetail, statusDescription);
611                     return;
612                 }
613
614                 // TODO:: Check other things without being harsh????
615
616                 newStatus = ThingStatus.ONLINE;
617                 statusDetail = ThingStatusDetail.NONE;
618                 statusDescription = "";
619             }
620             // If we are OFFLINE, check everything
621             else if (prevStatus == ThingStatus.OFFLINE) {
622                 // Just wait until we are finished initializing
623                 if (!isInitialized) {
624                     online = isOnline;
625                     return;
626                 }
627
628                 ZoneMinderBridgeServerConfig config = getBridgeConfig();
629
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);
636                     return;
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);
642                     return;
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);
648                     return;
649                 }
650
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);
656                     return;
657                 }
658
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);
664                     return;
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);
670                     return;
671                 }
672
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);
678                     return;
679                 }
680
681                 /*
682                  * Now we will try to establish a session
683                  */
684
685                 IZoneMinderSession curSession = null;
686                 try {
687                     curSession = ZoneMinderFactory.CreateSession(connection);
688                 } catch (FailedLoginException | IllegalArgumentException | IOException
689                         | ZoneMinderUrlNotFoundException ex) {
690                     logger.error("{}: Create Session failed with exception {}", getLogIdentifier(), ex.getMessage());
691
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());
698                     }
699                     updateBridgeStatus(newStatus, statusDetail, statusDescription);
700                     return;
701                 }
702                 IZoneMinderServer serverProxy = ZoneMinderFactory.getServerProxy(curSession);
703
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);
710                     return;
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);
716                     return;
717                 }
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);
724                     return;
725                 } else {
726                     // Seems like everything is as we want it :-)
727                     isOnline = true;
728                 }
729
730                 if (isOnline) {
731                     zoneMinderSession = curSession;
732                     online = isOnline;
733                     newStatus = ThingStatus.ONLINE;
734                     statusDetail = ThingStatusDetail.NONE;
735                     statusDescription = "";
736                 } else {
737                     zoneMinderSession = null;
738                     online = isOnline;
739                     newStatus = ThingStatus.OFFLINE;
740                 }
741             }
742         } catch (Exception ex) {
743             newStatus = ThingStatus.OFFLINE;
744             statusDetail = ThingStatusDetail.COMMUNICATION_ERROR;
745             logger.error("{}: Exception occurred in updateAvailabilityStatus Exception='{}'", getLogIdentifier(),
746                     ex.getMessage());
747             statusDescription = "Error occurred (Check log)";
748         }
749         updateBridgeStatus(newStatus, statusDetail, statusDescription);
750
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) {
755                 try {
756                     thingHandler.updateAvaliabilityStatus(connection);
757                 } catch (Exception ex) {
758                     logger.debug("{}: Failed to call 'updateAvailabilityStatus()' for '{}'", getLogIdentifier(),
759                             thingHandler.getThing().getUID());
760                 }
761             }
762         }
763     }
764
765     @SuppressWarnings("null")
766     protected void updateBridgeStatus(ThingStatus newStatus, ThingStatusDetail statusDetail, String statusDescription) {
767         ThingStatusInfo curStatusInfo = thing.getStatusInfo();
768
769         String curDescription = curStatusInfo.getDescription();
770         if (StringUtils.isBlank(curStatusInfo.getDescription())) {
771             curDescription = "";
772         }
773
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(),
779                     newStatus);
780
781             if ((newStatus == ThingStatus.ONLINE) && (curStatusInfo.getStatus() != ThingStatus.ONLINE)) {
782                 try {
783                     setBridgeConnectionStatus(true);
784                     onConnected();
785                 } catch (IllegalArgumentException e) {
786                     // Just ignore that here
787                 }
788             } else if ((newStatus == ThingStatus.OFFLINE) && (curStatusInfo.getStatus() != ThingStatus.OFFLINE)) {
789                 try {
790                     setBridgeConnectionStatus(false);
791                     onDisconnected();
792                 } catch (IllegalArgumentException e) {
793                     // Just ignore that here
794                 }
795             }
796             // Update Status correspondingly
797             if ((newStatus == ThingStatus.OFFLINE) && (statusDetail != ThingStatusDetail.NONE)) {
798                 updateStatus(newStatus, statusDetail, statusDescription);
799             } else {
800                 updateStatus(newStatus);
801             }
802
803             curBridgeStatus = newStatus;
804         }
805     }
806
807     protected boolean isZoneMinderLoginValid(IZoneMinderConnectionInfo connection) {
808         try {
809             return ZoneMinderFactory.validateLogin(connection);
810         } catch (Exception e) {
811             return false;
812         }
813     }
814
815     @Override
816     public void updateChannel(ChannelUID channel) {
817         State state = null;
818         try {
819             switch (channel.getId()) {
820                 case ZoneMinderConstants.CHANNEL_ONLINE:
821                     updateState(channel, (isOnline() ? OnOffType.ON : OnOffType.OFF));
822                     break;
823
824                 case ZoneMinderConstants.CHANNEL_SERVER_DISKUSAGE:
825                     state = getServerDiskUsageState();
826                     break;
827
828                 case ZoneMinderConstants.CHANNEL_SERVER_CPULOAD:
829                     state = getServerCpuLoadState();
830                     break;
831
832                 default:
833                     logger.warn("{}: updateChannel(): Server '{}': No handler defined for channel='{}'",
834                             getLogIdentifier(), thing.getLabel(), channel.getAsString());
835                     break;
836             }
837
838             if (state != null) {
839                 logger.debug("{}: BridgeHandler.updateChannel(): Updating channel '{}' to state='{}'",
840                         getLogIdentifier(), channel.getId(), state.toString());
841                 updateState(channel.getId(), state);
842             }
843         } catch (Exception ex) {
844             logger.error("{}: Error when 'updateChannel()' was called for thing='{}' (Exception='{}'",
845                     getLogIdentifier(), channel.getId(), ex.getMessage());
846         }
847     }
848
849     protected boolean openConnection() {
850         boolean connected = false;
851         if (!isConnected()) {
852             logger.debug("{}: Connecting Bridge to ZoneMinder Server", getLogIdentifier());
853
854             try {
855                 if (isConnected()) {
856                     closeConnection();
857                 }
858                 setConnected(connected);
859
860                 logger.info("{}: Connecting to ZoneMinder Server (result='{}'", getLogIdentifier(), connected);
861
862             } catch (Exception exception) {
863                 logger.error("{}: openConnection(): Exception: ", getLogIdentifier(), exception);
864                 setConnected(false);
865             } finally {
866                 if (!isConnected()) {
867                     closeConnection();
868                 }
869             }
870
871         }
872         return isConnected();
873     }
874
875     synchronized void closeConnection() {
876         try {
877             logger.debug("{}: closeConnection(): Closed HTTP Connection!", getLogIdentifier());
878             setConnected(false);
879
880         } catch (Exception exception) {
881             logger.error("{}: closeConnection(): Error closing connection - {}", getLogIdentifier(),
882                     exception.getMessage());
883         }
884     }
885
886     protected State getServerCpuLoadState() {
887         State state = UnDefType.UNDEF;
888
889         try {
890             if ((channelCpuLoad != "") && (isConnected())) {
891                 state = new DecimalType(new BigDecimal(channelCpuLoad));
892             }
893
894         } catch (Exception ex) {
895             // Deliberately kept as debug info!
896             logger.debug("{}: Exception='{}'", getLogIdentifier(), ex.getMessage());
897         }
898
899         return state;
900     }
901
902     protected State getServerDiskUsageState() {
903         State state = UnDefType.UNDEF;
904
905         try {
906             if ((channelDiskUsage != "") && (isConnected())) {
907                 state = new DecimalType(new BigDecimal(channelDiskUsage));
908             }
909         } catch (Exception ex) {
910             // Deliberately kept as debug info!
911             logger.debug("{}: Exception {}", getLogIdentifier(), ex.getMessage());
912         }
913
914         return state;
915     }
916
917     @Override
918     public void onBridgeConnected(ZoneMinderServerBridgeHandler bridge, IZoneMinderConnectionInfo connection) {
919         logger.info("{}: Brigde went ONLINE", getLogIdentifier());
920
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);
925
926             if (getBridgeConfig().getRefreshIntervalLowPriorityTask() != 0) {
927                 refreshFrequency = calculateCommonRefreshFrequency(getBridgeConfig().getRefreshInterval());
928             } else {
929                 refreshFrequency = getBridgeConfig().getRefreshInterval();
930             }
931             logger.info("BRIDGE [{}]: Calculated refresh inetrval to '{}'", getThingId(), refreshFrequency);
932
933             if (taskRefreshData != null) {
934                 taskRefreshData.cancel(true);
935                 taskRefreshData = null;
936             }
937
938             // Start job to handle next updates
939             taskRefreshData = startTask(refreshDataRunnable, refreshFrequency, refreshFrequency, TimeUnit.SECONDS);
940
941             if (taskPriorityRefreshData != null) {
942                 taskPriorityRefreshData.cancel(true);
943                 taskPriorityRefreshData = null;
944             }
945
946             // Only start if Priority Frequency is higher than ordinary
947             if (refreshFrequency > 1) {
948                 taskPriorityRefreshData = startTask(refreshPriorityDataRunnable, 0, 1, TimeUnit.SECONDS);
949             }
950         }
951
952         // Update properties
953         updateMonitorProperties(zoneMinderSession);
954     }
955
956     @Override
957     public void onBridgeDisconnected(ZoneMinderServerBridgeHandler bridge) {
958         logger.info("{}: Brigde went OFFLINE", getLogIdentifier());
959
960         // Deactivate discovery service
961         discoveryService.deactivate();
962
963         // Stopping refresh thread while OFFLINE
964         if (taskRefreshData != null) {
965             taskRefreshData.cancel(true);
966             taskRefreshData = null;
967             logger.debug("{}: Stopping DataRefresh task", getLogIdentifier());
968         }
969
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());
975         }
976
977         // Make sure everything gets refreshed
978         for (Channel ch : getThing().getChannels()) {
979             handleCommand(ch.getUID(), RefreshType.REFRESH);
980         }
981
982         // Inform thing handlers of disconnection
983         for (Thing thing : getThing().getThings()) {
984             ZoneMinderBaseThingHandler thingHandler = (ZoneMinderBaseThingHandler) thing.getHandler();
985
986             if (thingHandler != null) {
987                 thingHandler.onBridgeDisconnected(this);
988                 logger.debug("{}: onDisconnected(): Bridge - {}, Thing - {}, Thing Handler - {}", getLogIdentifier(),
989                         thing.getBridgeUID(), thing.getUID(), thingHandler);
990             }
991         }
992     }
993
994     /**
995      * Method to start a data refresh task.
996      */
997     protected ScheduledFuture<?> startTask(Runnable command, long delay, long interval, TimeUnit unit) {
998         logger.debug("BRIDGE [{}]: Starting ZoneMinder Bridge Monitor Task. Command='{}'", getThingId(),
999                 command.toString());
1000         if (interval == 0) {
1001             return null;
1002         }
1003
1004         return scheduler.scheduleWithFixedDelay(command, delay, interval, unit);
1005     }
1006
1007     /**
1008      * Method to stop the datarefresh task.
1009      */
1010     protected void stopTask(ScheduledFuture<?> task) {
1011         try {
1012             if (task != null && !task.isCancelled()) {
1013                 logger.debug("{}: Stopping ZoneMinder Bridge Monitor Task. Task='{}'", getLogIdentifier(),
1014                         task.toString());
1015                 task.cancel(true);
1016             }
1017         } catch (Exception ex) {
1018         }
1019     }
1020
1021     public ArrayList<IZoneMinderMonitorData> getMonitors() {
1022         if (isOnline()) {
1023             IZoneMinderServer serverProxy = ZoneMinderFactory.getServerProxy(zoneMinderSession);
1024             ArrayList<IZoneMinderMonitorData> result = serverProxy.getMonitors();
1025
1026             return result;
1027         }
1028         return new ArrayList<>();
1029     }
1030
1031     /*
1032      * This is experimental
1033      * Try to add different properties
1034      */
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);
1039
1040         IZoneMinderHostVersion hostVersion = null;
1041         try {
1042             hostVersion = serverProxy.getHostVersion();
1043             logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
1044                     serverProxy.getHttpUrl(), serverProxy.getHttpResponseCode(), serverProxy.getHttpResponseMessage());
1045
1046             ZoneMinderConfig configUseApi = serverProxy.getConfig(ZoneMinderConfigEnum.ZM_OPT_USE_API);
1047             logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
1048                     serverProxy.getHttpUrl(), serverProxy.getHttpResponseCode(), serverProxy.getHttpResponseMessage());
1049
1050             ZoneMinderConfig configUseAuth = serverProxy.getConfig(ZoneMinderConfigEnum.ZM_OPT_USE_AUTH);
1051             logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
1052                     serverProxy.getHttpUrl(), serverProxy.getHttpResponseCode(), serverProxy.getHttpResponseMessage());
1053
1054             ZoneMinderConfig configTrigerrs = serverProxy.getConfig(ZoneMinderConfigEnum.ZM_OPT_TRIGGERS);
1055             logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
1056                     configUseApi.getHttpUrl(), configUseApi.getHttpResponseCode(),
1057                     configUseApi.getHttpResponseMessage());
1058
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(),
1066                     e.getMessage());
1067         }
1068
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)))) {
1075                 update = true;
1076                 break;
1077             }
1078         }
1079
1080         if (update) {
1081             logger.info("{}: Properties synchronised, Thing id: {}", getLogIdentifier(), getThingId());
1082             updateProperties(properties);
1083         }
1084     }
1085
1086     @Override
1087     public String getLogIdentifier() {
1088         String result = "[BRIDGE]";
1089         try {
1090             result = String.format("[BRIDGE (%s)]", getThingId());
1091         } catch (Exception e) {
1092             result = "[BRIDGE (?)]";
1093         }
1094         return result;
1095     }
1096 }