2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.zway.internal.handler;
15 import static org.openhab.binding.zway.internal.ZWayBindingConstants.*;
18 import java.util.concurrent.ScheduledFuture;
19 import java.util.concurrent.TimeUnit;
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;
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;
57 * The {@link ZWayBridgeHandler} manages the connection between Z-Way API and binding.
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
65 * @author Patrick Hecker - Initial contribution, remove observer mechanism
66 * @author Johannes Einig - Bridge now stores DeviceList
68 public class ZWayBridgeHandler extends BaseBridgeHandler implements IZWayApiCallbacks {
70 public static final ThingTypeUID SUPPORTED_THING_TYPE = THING_TYPE_BRIDGE;
72 private final Logger logger = LoggerFactory.getLogger(getClass());
74 private BridgePolling bridgePolling;
75 private ScheduledFuture<?> pollingJob;
77 private ResetInclusionExclusion resetInclusionExclusion;
78 private ScheduledFuture<?> resetInclusionExclusionJob;
80 private ZWayBridgeConfiguration mConfig;
81 private IZWayApi mZWayApi;
83 private DeviceList deviceList;
86 * Initializer authenticate the Z-Way API instance with bridge configuration.
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
92 private class Initializer implements Runnable {
96 logger.debug("Authenticate to the Z-Way server ...");
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.
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();
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(),
116 // Called when thing or bridge updated ...
117 logger.debug("Polling is allready active");
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();
128 logger.warn("Initializing device failed (DeviceHandler is null): {}", thing.getLabel());
132 logger.warn("Z-Way bridge couldn't authenticated");
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());
140 logger.error("Unexpected error");
142 if (getThing().getStatus() == ThingStatus.ONLINE) {
143 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
144 "Error occurred when initialize bridge.");
151 * Disposer clean up openHAB Connector configuration
153 private class Remover implements Runnable {
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();
165 logger.warn("Removing device failed (DeviceHandler is null): {}", thing.getLabel());
169 // status update will finally remove the thing
170 updateStatus(ThingStatus.REMOVED);
174 public ZWayBridgeHandler(Bridge bridge) {
177 bridgePolling = new BridgePolling();
178 resetInclusionExclusion = new ResetInclusionExclusion();
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());
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();
195 logger.warn("Refreshing device failed (DeviceHandler is null): {}", thing.getLabel());
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");
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);
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);
217 } else if (command.equals(OnOffType.OFF)) {
218 logger.debug("Handle bridge stop inclusion command ...");
219 mZWayApi.getZWaveInclusion(0);
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);
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);
231 } else if (command.equals(OnOffType.OFF)) {
232 logger.debug("Handle bridge stop exclusion command ...");
233 mZWayApi.getZWaveExclusion(0);
239 public void initialize() {
240 logger.info("Initializing Z-Way bridge ...");
242 // Set thing status to a valid status
243 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Checking configuration...");
245 // Configuration - thing status update with a error message
246 mConfig = loadAndCheckConfiguration();
248 if (mConfig != null) {
249 logger.debug("Configuration complete: {}", mConfig);
251 mZWayApi = new ZWayApiHttp(mConfig.getZWayIpAddress(), mConfig.getZWayPort(), mConfig.getZWayProtocol(),
252 mConfig.getZWayUsername(), mConfig.getZWayPassword(), -1, false, this);
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());
261 public void dispose() {
262 logger.debug("Disposing Z-Way bridge ...");
264 if (pollingJob != null && !pollingJob.isCancelled()) {
265 pollingJob.cancel(true);
269 if (resetInclusionExclusionJob != null && !resetInclusionExclusionJob.isCancelled()) {
270 resetInclusionExclusionJob.cancel(true);
271 resetInclusionExclusionJob = null;
277 private class BridgePolling implements Runnable {
280 logger.debug("Starting polling for bridge: {}", getThing().getLabel());
281 if (getThing().getStatus().equals(ThingStatus.ONLINE)) {
282 updateControllerData();
284 logger.debug("Polling not possible, bridge isn't ONLINE");
289 private void updateControllerData() {
290 // Add additional information as properties or update channels
292 ZWaveController zwaveController = mZWayApi.getZWaveController();
293 if (zwaveController != null) {
294 Map<String, String> properties = editProperties();
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());
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);
314 if (zwaveController.getData().getSecureInclusion().getValue().equals("true")) {
315 updateState(SECURE_INCLUSION_CHANNEL, OnOffType.ON);
317 updateState(SECURE_INCLUSION_CHANNEL, OnOffType.OFF);
323 * Inclusion/Exclusion must be reset manually, also channel states.
325 private class ResetInclusionExclusion implements Runnable {
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);
333 updateState(INCLUSION_CHANNEL, OnOffType.OFF);
334 updateState(EXCLUSION_CHANNEL, OnOffType.OFF);
336 logger.debug("Reset inclusion and exclusion not possible, bridge isn't ONLINE");
342 public void handleRemoval() {
343 logger.debug("Handle removal Z-Way bridge ...");
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());
349 // super.handleRemoval() called in every case in scheduled task ...
352 protected ZWayBridgeConfiguration getZWayBridgeConfiguration() {
356 /*******************************
357 ******* DeviceList handling*****
358 ********************************
359 * Updates the deviceList every time a
360 * ChildHandler is initialized or disposed
364 public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
369 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
373 private void updateDeviceList() {
374 if (mZWayApi != null) {
375 logger.debug("ChildHandler changed. Updating device List");
376 deviceList = mZWayApi.getDevices();
378 logger.debug("Bridge Handler not online. No update of device list performed.");
382 private ZWayBridgeConfiguration loadAndCheckConfiguration() {
383 ZWayBridgeConfiguration config = getConfigAs(ZWayBridgeConfiguration.class);
385 /****************************************
386 ****** Z-Way server configuration ******
387 ****************************************/
390 if (StringUtils.trimToNull(config.getZWayIpAddress()) == null) {
391 config.setZWayIpAddress("localhost"); // default value
395 if (config.getZWayPort() == null) {
396 config.setZWayPort(8083);
400 if (StringUtils.trimToNull(config.getZWayProtocol()) == null) {
401 config.setZWayProtocol("http");
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.");
412 if (StringUtils.trimToNull(config.getZWayUsername()) == null) {
413 config.setZWayUsername("admin"); // default value
416 /***********************************
417 ****** General configuration ******
418 **********************************/
421 if (config.getPollingInterval() == null) {
422 config.setPollingInterval(3600);
429 * @return Z-Way API instance
431 public IZWayApi getZWayApi() {
435 public DeviceList getDeviceList() {
439 /********************************
440 ****** Z-Way API callback ******
441 *******************************/
444 public void getStatusResponse(String message) {
448 public void getRestartResponse(Boolean status) {
452 public void getLoginResponse(String sessionId) {
453 logger.debug("New session id: {}", sessionId);
454 updateStatus(ThingStatus.ONLINE);
458 public void getNamespacesResponse(NamespaceList namespaces) {
462 public void getModulesResponse(ModuleList moduleList) {
466 public void getInstancesResponse(InstanceList instanceList) {
470 public void postInstanceResponse(Instance instance) {
474 public void getInstanceResponse(Instance instance) {
478 public void putInstanceResponse(Instance instance) {
482 public void deleteInstanceResponse(boolean status) {
486 public void getDevicesResponse(DeviceList deviceList) {
490 public void putDeviceResponse(Device device) {
494 public void getDeviceResponse(Device device) {
498 public void getDeviceCommandResponse(String message) {
502 public void getLocationsResponse(LocationList locationList) {
506 public void postLocationResponse(Location location) {
510 public void getLocationResponse(Location location) {
514 public void putLocationResponse(Location location) {
518 public void deleteLocationResponse(boolean status) {
522 public void getProfilesResponse(ProfileList profileList) {
526 public void postProfileResponse(Profile profile) {
530 public void getProfileResponse(Profile profile) {
534 public void putProfileResponse(Profile profile) {
538 public void deleteProfileResponse(boolean status) {
542 public void getNotificationsResponse(NotificationList notificationList) {
546 public void getNotificationResponse(Notification notification) {
550 public void putNotificationResponse(Notification notification) {
554 public void getDeviceHistoriesResponse(DeviceHistoryList deviceHistoryList) {
558 public void getDeviceHistoryResponse(DeviceHistory deviceHistory) {
562 public void apiError(String message, boolean invalidateState) {
563 if (invalidateState) {
564 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
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 + ").");
577 public void authenticationError() {
578 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
579 "Authentication error. Please check username and password.");
583 public void responseFormatError(String message, boolean invalidateApiState) {
584 if (invalidateApiState) {
585 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
590 public void message(int code, String message) {
594 public void getZWaveDeviceResponse(ZWaveDevice zwaveDevice) {
598 public void getZWaveControllerResponse(ZWaveController zwaveController) {