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