2 * Copyright (c) 2010-2023 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.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;
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;
56 * The {@link ZWayBridgeHandler} manages the connection between Z-Way API and binding.
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
64 * @author Patrick Hecker - Initial contribution, remove observer mechanism
65 * @author Johannes Einig - Bridge now stores DeviceList
67 public class ZWayBridgeHandler extends BaseBridgeHandler implements IZWayApiCallbacks {
69 public static final ThingTypeUID SUPPORTED_THING_TYPE = THING_TYPE_BRIDGE;
71 private final Logger logger = LoggerFactory.getLogger(getClass());
73 private BridgePolling bridgePolling;
74 private ScheduledFuture<?> pollingJob;
76 private ResetInclusionExclusion resetInclusionExclusion;
77 private ScheduledFuture<?> resetInclusionExclusionJob;
79 private ZWayBridgeConfiguration mConfig;
80 private IZWayApi mZWayApi;
82 private DeviceList deviceList;
85 * Initializer authenticate the Z-Way API instance with bridge configuration.
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
91 private class Initializer implements Runnable {
95 logger.debug("Authenticate to the Z-Way server ...");
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.
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();
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(),
115 // Called when thing or bridge updated ...
116 logger.debug("Polling is allready active");
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();
127 logger.warn("Initializing device failed (DeviceHandler is null): {}", thing.getLabel());
131 logger.warn("Z-Way bridge couldn't authenticated");
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());
139 logger.error("Unexpected error");
141 if (getThing().getStatus() == ThingStatus.ONLINE) {
142 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
143 "Error occurred when initialize bridge.");
150 * Disposer clean up openHAB Connector configuration
152 private class Remover implements Runnable {
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();
164 logger.warn("Removing device failed (DeviceHandler is null): {}", thing.getLabel());
168 // status update will finally remove the thing
169 updateStatus(ThingStatus.REMOVED);
173 public ZWayBridgeHandler(Bridge bridge) {
176 bridgePolling = new BridgePolling();
177 resetInclusionExclusion = new ResetInclusionExclusion();
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());
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();
194 logger.warn("Refreshing device failed (DeviceHandler is null): {}", thing.getLabel());
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");
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);
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);
216 } else if (command.equals(OnOffType.OFF)) {
217 logger.debug("Handle bridge stop inclusion command ...");
218 mZWayApi.getZWaveInclusion(0);
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);
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);
230 } else if (command.equals(OnOffType.OFF)) {
231 logger.debug("Handle bridge stop exclusion command ...");
232 mZWayApi.getZWaveExclusion(0);
238 public void initialize() {
239 logger.info("Initializing Z-Way bridge ...");
241 // Set thing status to a valid status
242 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Checking configuration...");
244 // Configuration - thing status update with an error message
245 mConfig = loadAndCheckConfiguration();
247 if (mConfig != null) {
248 logger.debug("Configuration complete: {}", mConfig);
250 mZWayApi = new ZWayApiHttp(mConfig.getZWayIpAddress(), mConfig.getZWayPort(), mConfig.getZWayProtocol(),
251 mConfig.getZWayUsername(), mConfig.getZWayPassword(), -1, false, this);
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());
260 public void dispose() {
261 logger.debug("Disposing Z-Way bridge ...");
263 if (pollingJob != null && !pollingJob.isCancelled()) {
264 pollingJob.cancel(true);
268 if (resetInclusionExclusionJob != null && !resetInclusionExclusionJob.isCancelled()) {
269 resetInclusionExclusionJob.cancel(true);
270 resetInclusionExclusionJob = null;
276 private class BridgePolling implements Runnable {
279 logger.debug("Starting polling for bridge: {}", getThing().getLabel());
280 if (getThing().getStatus().equals(ThingStatus.ONLINE)) {
281 updateControllerData();
283 logger.debug("Polling not possible, bridge isn't ONLINE");
288 private void updateControllerData() {
289 // Add additional information as properties or update channels
291 ZWaveController zwaveController = mZWayApi.getZWaveController();
292 if (zwaveController != null) {
293 Map<String, String> properties = editProperties();
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());
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);
313 if ("true".equals(zwaveController.getData().getSecureInclusion().getValue())) {
314 updateState(SECURE_INCLUSION_CHANNEL, OnOffType.ON);
316 updateState(SECURE_INCLUSION_CHANNEL, OnOffType.OFF);
322 * Inclusion/Exclusion must be reset manually, also channel states.
324 private class ResetInclusionExclusion implements Runnable {
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);
332 updateState(INCLUSION_CHANNEL, OnOffType.OFF);
333 updateState(EXCLUSION_CHANNEL, OnOffType.OFF);
335 logger.debug("Reset inclusion and exclusion not possible, bridge isn't ONLINE");
341 public void handleRemoval() {
342 logger.debug("Handle removal Z-Way bridge ...");
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());
348 // super.handleRemoval() called in every case in scheduled task ...
351 protected ZWayBridgeConfiguration getZWayBridgeConfiguration() {
355 /*******************************
356 ******* DeviceList handling*****
357 ********************************
358 * Updates the deviceList every time a
359 * ChildHandler is initialized or disposed
363 public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
368 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
372 private void updateDeviceList() {
373 if (mZWayApi != null) {
374 logger.debug("ChildHandler changed. Updating device List");
375 deviceList = mZWayApi.getDevices();
377 logger.debug("Bridge Handler not online. No update of device list performed.");
381 private ZWayBridgeConfiguration loadAndCheckConfiguration() {
382 ZWayBridgeConfiguration config = getConfigAs(ZWayBridgeConfiguration.class);
384 /****************************************
385 ****** Z-Way server configuration ******
386 ****************************************/
389 String zWayIpAddress = config.getZWayIpAddress();
390 if (zWayIpAddress.isBlank()) {
391 config.setZWayIpAddress("localhost"); // default value
395 String zWayProtocol = config.getZWayProtocol();
396 if (zWayProtocol.isBlank()) {
397 config.setZWayProtocol("http");
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.");
409 String zWayUsername = config.getZWayUsername();
410 if (zWayUsername.isBlank()) {
411 config.setZWayUsername("admin"); // default value
418 * @return Z-Way API instance
420 public IZWayApi getZWayApi() {
424 public DeviceList getDeviceList() {
428 /********************************
429 ****** Z-Way API callback ******
430 *******************************/
433 public void getStatusResponse(String message) {
437 public void getRestartResponse(Boolean status) {
441 public void getLoginResponse(String sessionId) {
442 logger.debug("New session id: {}", sessionId);
443 updateStatus(ThingStatus.ONLINE);
447 public void getNamespacesResponse(NamespaceList namespaces) {
451 public void getModulesResponse(ModuleList moduleList) {
455 public void getInstancesResponse(InstanceList instanceList) {
459 public void postInstanceResponse(Instance instance) {
463 public void getInstanceResponse(Instance instance) {
467 public void putInstanceResponse(Instance instance) {
471 public void deleteInstanceResponse(boolean status) {
475 public void getDevicesResponse(DeviceList deviceList) {
479 public void putDeviceResponse(Device device) {
483 public void getDeviceResponse(Device device) {
487 public void getDeviceCommandResponse(String message) {
491 public void getLocationsResponse(LocationList locationList) {
495 public void postLocationResponse(Location location) {
499 public void getLocationResponse(Location location) {
503 public void putLocationResponse(Location location) {
507 public void deleteLocationResponse(boolean status) {
511 public void getProfilesResponse(ProfileList profileList) {
515 public void postProfileResponse(Profile profile) {
519 public void getProfileResponse(Profile profile) {
523 public void putProfileResponse(Profile profile) {
527 public void deleteProfileResponse(boolean status) {
531 public void getNotificationsResponse(NotificationList notificationList) {
535 public void getNotificationResponse(Notification notification) {
539 public void putNotificationResponse(Notification notification) {
543 public void getDeviceHistoriesResponse(DeviceHistoryList deviceHistoryList) {
547 public void getDeviceHistoryResponse(DeviceHistory deviceHistory) {
551 public void apiError(String message, boolean invalidateState) {
552 if (invalidateState) {
553 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
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 + ").");
566 public void authenticationError() {
567 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
568 "Authentication error. Please check username and password.");
572 public void responseFormatError(String message, boolean invalidateApiState) {
573 if (invalidateApiState) {
574 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
579 public void message(int code, String message) {
583 public void getZWaveDeviceResponse(ZWaveDevice zwaveDevice) {
587 public void getZWaveControllerResponse(ZWaveController zwaveController) {