]> git.basschouten.com Git - openhab-addons.git/blob
0a22e5538d91943aa1066c9fc5dd72fc27427aa5
[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.zway.internal.handler;
14
15 import static org.openhab.binding.zway.internal.ZWayBindingConstants.*;
16
17 import java.util.Map;
18 import java.util.concurrent.ScheduledFuture;
19 import java.util.concurrent.TimeUnit;
20
21 import org.apache.commons.lang3.StringUtils;
22 import org.openhab.binding.zway.internal.config.ZWayBridgeConfiguration;
23 import org.openhab.core.library.types.OnOffType;
24 import org.openhab.core.thing.Bridge;
25 import org.openhab.core.thing.ChannelUID;
26 import org.openhab.core.thing.Thing;
27 import org.openhab.core.thing.ThingStatus;
28 import org.openhab.core.thing.ThingStatusDetail;
29 import org.openhab.core.thing.ThingTypeUID;
30 import org.openhab.core.thing.binding.BaseBridgeHandler;
31 import org.openhab.core.thing.binding.ThingHandler;
32 import org.openhab.core.types.Command;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 import de.fh_zwickau.informatik.sensor.IZWayApi;
37 import de.fh_zwickau.informatik.sensor.IZWayApiCallbacks;
38 import de.fh_zwickau.informatik.sensor.ZWayApiHttp;
39 import de.fh_zwickau.informatik.sensor.model.devicehistory.DeviceHistory;
40 import de.fh_zwickau.informatik.sensor.model.devicehistory.DeviceHistoryList;
41 import de.fh_zwickau.informatik.sensor.model.devices.Device;
42 import de.fh_zwickau.informatik.sensor.model.devices.DeviceList;
43 import de.fh_zwickau.informatik.sensor.model.instances.Instance;
44 import de.fh_zwickau.informatik.sensor.model.instances.InstanceList;
45 import de.fh_zwickau.informatik.sensor.model.locations.Location;
46 import de.fh_zwickau.informatik.sensor.model.locations.LocationList;
47 import de.fh_zwickau.informatik.sensor.model.modules.ModuleList;
48 import de.fh_zwickau.informatik.sensor.model.namespaces.NamespaceList;
49 import de.fh_zwickau.informatik.sensor.model.notifications.Notification;
50 import de.fh_zwickau.informatik.sensor.model.notifications.NotificationList;
51 import de.fh_zwickau.informatik.sensor.model.profiles.Profile;
52 import de.fh_zwickau.informatik.sensor.model.profiles.ProfileList;
53 import de.fh_zwickau.informatik.sensor.model.zwaveapi.controller.ZWaveController;
54 import de.fh_zwickau.informatik.sensor.model.zwaveapi.devices.ZWaveDevice;
55
56 /**
57  * The {@link ZWayBridgeHandler} manages the connection between Z-Way API and binding.
58  *
59  * During the initialization the following tasks are performed:
60  * - load and check configuration
61  * - instantiate a Z-Way API that used in the whole binding
62  * - authenticate to the Z-Way server
63  * - initialize all containing device things
64  *
65  * @author Patrick Hecker - Initial contribution, remove observer mechanism
66  * @author Johannes Einig - Bridge now stores DeviceList
67  */
68 public class ZWayBridgeHandler extends BaseBridgeHandler implements IZWayApiCallbacks {
69
70     public static final ThingTypeUID SUPPORTED_THING_TYPE = THING_TYPE_BRIDGE;
71
72     private final Logger logger = LoggerFactory.getLogger(getClass());
73
74     private BridgePolling bridgePolling;
75     private ScheduledFuture<?> pollingJob;
76
77     private ResetInclusionExclusion resetInclusionExclusion;
78     private ScheduledFuture<?> resetInclusionExclusionJob;
79
80     private ZWayBridgeConfiguration mConfig;
81     private IZWayApi mZWayApi;
82
83     private DeviceList deviceList;
84
85     /**
86      * Initializer authenticate the Z-Way API instance with bridge configuration.
87      *
88      * If Z-Way API successfully authenticated:
89      * - check existence of openHAB Connector in Z-Way server and configure openHAB server
90      * - initialize all containing device things
91      */
92     private class Initializer implements Runnable {
93
94         @Override
95         public void run() {
96             logger.debug("Authenticate to the Z-Way server ...");
97
98             // https://community.openhab.org/t/oh2-major-bug-with-scheduled-jobs/12350/11
99             // If any execution of the task encounters an exception, subsequent executions are
100             // suppressed. Otherwise, the task will only terminate via cancellation or
101             // termination of the executor.
102             try {
103                 // Authenticate - thing status update with a error message
104                 if (mZWayApi.getLogin() != null) {
105                     // Thing status set to online in login callback
106                     logger.info("Z-Way bridge successfully authenticated");
107                     // Gets the latest deviceList from zWay during bridge initialization
108                     deviceList = mZWayApi.getDevices();
109
110                     // Initialize bridge polling
111                     if (pollingJob == null || pollingJob.isCancelled()) {
112                         logger.debug("Starting polling job at intervall {}", mConfig.getPollingInterval());
113                         pollingJob = scheduler.scheduleWithFixedDelay(bridgePolling, 10, mConfig.getPollingInterval(),
114                                 TimeUnit.SECONDS);
115                     } else {
116                         // Called when thing or bridge updated ...
117                         logger.debug("Polling is allready active");
118                     }
119
120                     // Initializing all containing device things
121                     logger.debug("Initializing all configured devices ...");
122                     for (Thing thing : getThing().getThings()) {
123                         ThingHandler handler = thing.getHandler();
124                         if (handler != null) {
125                             logger.debug("Initializing device: {}", thing.getLabel());
126                             handler.initialize();
127                         } else {
128                             logger.warn("Initializing device failed (DeviceHandler is null): {}", thing.getLabel());
129                         }
130                     }
131                 } else {
132                     logger.warn("Z-Way bridge couldn't authenticated");
133                 }
134             } catch (Throwable t) {
135                 if (t instanceof Exception) {
136                     logger.error("{}", t.getMessage());
137                 } else if (t instanceof Error) {
138                     logger.error("{}", t.getMessage());
139                 } else {
140                     logger.error("Unexpected error");
141                 }
142                 if (getThing().getStatus() == ThingStatus.ONLINE) {
143                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
144                             "Error occurred when initialize bridge.");
145                 }
146             }
147         }
148     }
149
150     /**
151      * Disposer clean up openHAB Connector configuration
152      */
153     private class Remover implements Runnable {
154
155         @Override
156         public void run() {
157             // Removing all containing device things
158             logger.debug("Removing all configured devices ...");
159             for (Thing thing : getThing().getThings()) {
160                 ThingHandler handler = thing.getHandler();
161                 if (handler != null) {
162                     logger.debug("Removing device: {}", thing.getLabel());
163                     handler.handleRemoval();
164                 } else {
165                     logger.warn("Removing device failed (DeviceHandler is null): {}", thing.getLabel());
166                 }
167             }
168
169             // status update will finally remove the thing
170             updateStatus(ThingStatus.REMOVED);
171         }
172     }
173
174     public ZWayBridgeHandler(Bridge bridge) {
175         super(bridge);
176
177         bridgePolling = new BridgePolling();
178         resetInclusionExclusion = new ResetInclusionExclusion();
179     }
180
181     @Override
182     public void handleCommand(ChannelUID channelUID, Command command) {
183         // possible commands: check Z-Way server, check openHAB Connector, reconnect, ...
184         logger.debug("Handle command for channel: {} with command: {}", channelUID.getId(), command.toString());
185
186         if (channelUID.getId().equals(ACTIONS_CHANNEL)) {
187             if (command.toString().equals(ACTIONS_CHANNEL_OPTION_REFRESH)) {
188                 logger.debug("Handle bridge refresh command for all configured devices ...");
189                 for (Thing thing : getThing().getThings()) {
190                     ZWayDeviceHandler handler = (ZWayDeviceHandler) thing.getHandler();
191                     if (handler != null) {
192                         logger.debug("Refreshing device: {}", thing.getLabel());
193                         handler.refreshAllChannels();
194                     } else {
195                         logger.warn("Refreshing device failed (DeviceHandler is null): {}", thing.getLabel());
196                     }
197                 }
198             }
199         } else if (channelUID.getId().equals(SECURE_INCLUSION_CHANNEL)) {
200             if (command.equals(OnOffType.ON)) {
201                 logger.debug("Enable bridge secure inclusion ...");
202                 mZWayApi.updateControllerData("secureInclusion", "true");
203             } else if (command.equals(OnOffType.OFF)) {
204                 logger.debug("Disable bridge secure inclusion ...");
205                 mZWayApi.updateControllerData("secureInclusion", "false");
206             }
207         } else if (channelUID.getId().equals(INCLUSION_CHANNEL)) {
208             if (command.equals(OnOffType.ON)) {
209                 logger.debug("Handle bridge start inclusion command ...");
210                 mZWayApi.getZWaveInclusion(1);
211
212                 // Start reset job
213                 if (resetInclusionExclusionJob == null || resetInclusionExclusionJob.isCancelled()) {
214                     logger.debug("Starting reset inclusion and exclusion job in 30 seconds");
215                     resetInclusionExclusionJob = scheduler.schedule(resetInclusionExclusion, 30, TimeUnit.SECONDS);
216                 }
217             } else if (command.equals(OnOffType.OFF)) {
218                 logger.debug("Handle bridge stop inclusion command ...");
219                 mZWayApi.getZWaveInclusion(0);
220             }
221         } else if (channelUID.getId().equals(EXCLUSION_CHANNEL)) {
222             if (command.equals(OnOffType.ON)) {
223                 logger.debug("Handle bridge start exclusion command ...");
224                 mZWayApi.getZWaveExclusion(1);
225
226                 // Start reset job
227                 if (resetInclusionExclusionJob == null || resetInclusionExclusionJob.isCancelled()) {
228                     logger.debug("Starting reset inclusion and exclusion job in 30 seconds");
229                     resetInclusionExclusionJob = scheduler.schedule(resetInclusionExclusion, 30, TimeUnit.SECONDS);
230                 }
231             } else if (command.equals(OnOffType.OFF)) {
232                 logger.debug("Handle bridge stop exclusion command ...");
233                 mZWayApi.getZWaveExclusion(0);
234             }
235         }
236     }
237
238     @Override
239     public void initialize() {
240         logger.info("Initializing Z-Way bridge ...");
241
242         // Set thing status to a valid status
243         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Checking configuration...");
244
245         // Configuration - thing status update with a error message
246         mConfig = loadAndCheckConfiguration();
247
248         if (mConfig != null) {
249             logger.debug("Configuration complete: {}", mConfig);
250
251             mZWayApi = new ZWayApiHttp(mConfig.getZWayIpAddress(), mConfig.getZWayPort(), mConfig.getZWayProtocol(),
252                     mConfig.getZWayUsername(), mConfig.getZWayPassword(), -1, false, this);
253
254             // Start an extra thread, because it takes sometimes more
255             // than 5000 milliseconds and the handler will suspend (ThingStatus.UNINITIALIZED).
256             scheduler.execute(new Initializer());
257         }
258     }
259
260     @Override
261     public void dispose() {
262         logger.debug("Disposing Z-Way bridge ...");
263
264         if (pollingJob != null && !pollingJob.isCancelled()) {
265             pollingJob.cancel(true);
266             pollingJob = null;
267         }
268
269         if (resetInclusionExclusionJob != null && !resetInclusionExclusionJob.isCancelled()) {
270             resetInclusionExclusionJob.cancel(true);
271             resetInclusionExclusionJob = null;
272         }
273
274         super.dispose();
275     }
276
277     private class BridgePolling implements Runnable {
278         @Override
279         public void run() {
280             logger.debug("Starting polling for bridge: {}", getThing().getLabel());
281             if (getThing().getStatus().equals(ThingStatus.ONLINE)) {
282                 updateControllerData();
283             } else {
284                 logger.debug("Polling not possible, bridge isn't ONLINE");
285             }
286         }
287     }
288
289     private void updateControllerData() {
290         // Add additional information as properties or update channels
291
292         ZWaveController zwaveController = mZWayApi.getZWaveController();
293         if (zwaveController != null) {
294             Map<String, String> properties = editProperties();
295             // ESH default properties
296             properties.put(Thing.PROPERTY_FIRMWARE_VERSION, zwaveController.getData().getAPIVersion().getValue());
297             properties.put(Thing.PROPERTY_HARDWARE_VERSION, zwaveController.getData().getZWaveChip().getValue());
298             // Thing.PROPERTY_MODEL_ID not available, only manufacturerProductId
299             properties.put(Thing.PROPERTY_SERIAL_NUMBER, zwaveController.getData().getUuid().getValue());
300             properties.put(Thing.PROPERTY_VENDOR, zwaveController.getData().getVendor().getValue());
301
302             // Custom properties
303             properties.put(BRIDGE_PROP_SOFTWARE_REVISION_VERSION,
304                     zwaveController.getData().getSoftwareRevisionVersion().getValue());
305             properties.put(BRIDGE_PROP_SOFTWARE_REVISION_DATE,
306                     zwaveController.getData().getSoftwareRevisionDate().getValue());
307             properties.put(BRIDGE_PROP_SDK, zwaveController.getData().getSDK().getValue());
308             properties.put(BRIDGE_PROP_MANUFACTURER_ID, zwaveController.getData().getManufacturerId().getValue());
309             properties.put(BRIDGE_PROP_SECURE_INCLUSION, zwaveController.getData().getSecureInclusion().getValue());
310             properties.put(BRIDGE_PROP_FREQUENCY, zwaveController.getData().getFrequency().getValue());
311             updateProperties(properties);
312
313             // Update channels
314             if (zwaveController.getData().getSecureInclusion().getValue().equals("true")) {
315                 updateState(SECURE_INCLUSION_CHANNEL, OnOffType.ON);
316             } else {
317                 updateState(SECURE_INCLUSION_CHANNEL, OnOffType.OFF);
318             }
319         }
320     }
321
322     /**
323      * Inclusion/Exclusion must be reset manually, also channel states.
324      */
325     private class ResetInclusionExclusion implements Runnable {
326         @Override
327         public void run() {
328             logger.debug("Reset inclusion and exclusion for bridge: {}", getThing().getLabel());
329             if (getThing().getStatus().equals(ThingStatus.ONLINE)) {
330                 mZWayApi.getZWaveInclusion(0);
331                 mZWayApi.getZWaveExclusion(0);
332
333                 updateState(INCLUSION_CHANNEL, OnOffType.OFF);
334                 updateState(EXCLUSION_CHANNEL, OnOffType.OFF);
335             } else {
336                 logger.debug("Reset inclusion and exclusion not possible, bridge isn't ONLINE");
337             }
338         }
339     }
340
341     @Override
342     public void handleRemoval() {
343         logger.debug("Handle removal Z-Way bridge ...");
344
345         // Start an extra thread, because it takes sometimes more
346         // than 5000 milliseconds and the handler will suspend (ThingStatus.UNINITIALIZED).
347         scheduler.execute(new Remover());
348
349         // super.handleRemoval() called in every case in scheduled task ...
350     }
351
352     protected ZWayBridgeConfiguration getZWayBridgeConfiguration() {
353         return mConfig;
354     }
355
356     /*******************************
357      ******* DeviceList handling*****
358      ********************************
359      * Updates the deviceList every time a
360      * ChildHandler is initialized or disposed
361      */
362
363     @Override
364     public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
365         updateDeviceList();
366     }
367
368     @Override
369     public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
370         updateDeviceList();
371     }
372
373     private void updateDeviceList() {
374         if (mZWayApi != null) {
375             logger.debug("ChildHandler changed. Updating device List");
376             deviceList = mZWayApi.getDevices();
377         } else {
378             logger.debug("Bridge Handler not online. No update of device list performed.");
379         }
380     }
381
382     private ZWayBridgeConfiguration loadAndCheckConfiguration() {
383         ZWayBridgeConfiguration config = getConfigAs(ZWayBridgeConfiguration.class);
384
385         /****************************************
386          ****** Z-Way server configuration ******
387          ****************************************/
388
389         // Z-Way IP address
390         if (StringUtils.trimToNull(config.getZWayIpAddress()) == null) {
391             config.setZWayIpAddress("localhost"); // default value
392         }
393
394         // Z-Way Port
395         if (config.getZWayPort() == null) {
396             config.setZWayPort(8083);
397         }
398
399         // Z-Way Protocol
400         if (StringUtils.trimToNull(config.getZWayProtocol()) == null) {
401             config.setZWayProtocol("http");
402         }
403
404         // Z-Way Password
405         if (StringUtils.trimToNull(config.getZWayPassword()) == null) {
406             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
407                     "The connection to the Z-Way Server can't established, because the Z-Way password is missing. Please set a Z-Way password.");
408             return null;
409         }
410
411         // Z-Way Username
412         if (StringUtils.trimToNull(config.getZWayUsername()) == null) {
413             config.setZWayUsername("admin"); // default value
414         }
415
416         /***********************************
417          ****** General configuration ******
418          **********************************/
419
420         // Polling interval
421         if (config.getPollingInterval() == null) {
422             config.setPollingInterval(3600);
423         }
424
425         return config;
426     }
427
428     /**
429      * @return Z-Way API instance
430      */
431     public IZWayApi getZWayApi() {
432         return mZWayApi;
433     }
434
435     public DeviceList getDeviceList() {
436         return deviceList;
437     }
438
439     /********************************
440      ****** Z-Way API callback ******
441      *******************************/
442
443     @Override
444     public void getStatusResponse(String message) {
445     }
446
447     @Override
448     public void getRestartResponse(Boolean status) {
449     }
450
451     @Override
452     public void getLoginResponse(String sessionId) {
453         logger.debug("New session id: {}", sessionId);
454         updateStatus(ThingStatus.ONLINE);
455     }
456
457     @Override
458     public void getNamespacesResponse(NamespaceList namespaces) {
459     }
460
461     @Override
462     public void getModulesResponse(ModuleList moduleList) {
463     }
464
465     @Override
466     public void getInstancesResponse(InstanceList instanceList) {
467     }
468
469     @Override
470     public void postInstanceResponse(Instance instance) {
471     }
472
473     @Override
474     public void getInstanceResponse(Instance instance) {
475     }
476
477     @Override
478     public void putInstanceResponse(Instance instance) {
479     }
480
481     @Override
482     public void deleteInstanceResponse(boolean status) {
483     }
484
485     @Override
486     public void getDevicesResponse(DeviceList deviceList) {
487     }
488
489     @Override
490     public void putDeviceResponse(Device device) {
491     }
492
493     @Override
494     public void getDeviceResponse(Device device) {
495     }
496
497     @Override
498     public void getDeviceCommandResponse(String message) {
499     }
500
501     @Override
502     public void getLocationsResponse(LocationList locationList) {
503     }
504
505     @Override
506     public void postLocationResponse(Location location) {
507     }
508
509     @Override
510     public void getLocationResponse(Location location) {
511     }
512
513     @Override
514     public void putLocationResponse(Location location) {
515     }
516
517     @Override
518     public void deleteLocationResponse(boolean status) {
519     }
520
521     @Override
522     public void getProfilesResponse(ProfileList profileList) {
523     }
524
525     @Override
526     public void postProfileResponse(Profile profile) {
527     }
528
529     @Override
530     public void getProfileResponse(Profile profile) {
531     }
532
533     @Override
534     public void putProfileResponse(Profile profile) {
535     }
536
537     @Override
538     public void deleteProfileResponse(boolean status) {
539     }
540
541     @Override
542     public void getNotificationsResponse(NotificationList notificationList) {
543     }
544
545     @Override
546     public void getNotificationResponse(Notification notification) {
547     }
548
549     @Override
550     public void putNotificationResponse(Notification notification) {
551     }
552
553     @Override
554     public void getDeviceHistoriesResponse(DeviceHistoryList deviceHistoryList) {
555     }
556
557     @Override
558     public void getDeviceHistoryResponse(DeviceHistory deviceHistory) {
559     }
560
561     @Override
562     public void apiError(String message, boolean invalidateState) {
563         if (invalidateState) {
564             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
565         }
566     }
567
568     @Override
569     public void httpStatusError(int httpStatus, String message, boolean invalidateState) {
570         if (invalidateState) {
571             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
572                     message + "(HTTP status code: " + httpStatus + ").");
573         }
574     }
575
576     @Override
577     public void authenticationError() {
578         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
579                 "Authentication error. Please check username and password.");
580     }
581
582     @Override
583     public void responseFormatError(String message, boolean invalidateApiState) {
584         if (invalidateApiState) {
585             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
586         }
587     }
588
589     @Override
590     public void message(int code, String message) {
591     }
592
593     @Override
594     public void getZWaveDeviceResponse(ZWaveDevice zwaveDevice) {
595     }
596
597     @Override
598     public void getZWaveControllerResponse(ZWaveController zwaveController) {
599     }
600 }