2 * Copyright (c) 2010-2024 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.openwebnet.internal.handler;
15 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.*;
17 import java.time.Duration;
18 import java.time.ZonedDateTime;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.List;
23 import java.util.Objects;
25 import java.util.concurrent.ConcurrentHashMap;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
32 import org.openhab.binding.openwebnet.internal.actions.OpenWebNetBridgeActions;
33 import org.openhab.binding.openwebnet.internal.discovery.OpenWebNetDeviceDiscoveryService;
34 import org.openhab.binding.openwebnet.internal.handler.config.OpenWebNetBusBridgeConfig;
35 import org.openhab.binding.openwebnet.internal.handler.config.OpenWebNetZigBeeBridgeConfig;
36 import org.openhab.binding.openwebnet.internal.serial.SerialPortProviderAdapter;
37 import org.openhab.core.config.core.status.ConfigStatusMessage;
38 import org.openhab.core.thing.Bridge;
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.ThingTypeUID;
44 import org.openhab.core.thing.binding.ConfigStatusBridgeHandler;
45 import org.openhab.core.thing.binding.ThingHandlerService;
46 import org.openhab.core.types.Command;
47 import org.openhab.core.types.RefreshType;
48 import org.openwebnet4j.BUSGateway;
49 import org.openwebnet4j.GatewayListener;
50 import org.openwebnet4j.OpenDeviceType;
51 import org.openwebnet4j.OpenGateway;
52 import org.openwebnet4j.USBGateway;
53 import org.openwebnet4j.communication.OWNAuthException;
54 import org.openwebnet4j.communication.OWNException;
55 import org.openwebnet4j.message.Alarm;
56 import org.openwebnet4j.message.Automation;
57 import org.openwebnet4j.message.Auxiliary;
58 import org.openwebnet4j.message.BaseOpenMessage;
59 import org.openwebnet4j.message.CEN;
60 import org.openwebnet4j.message.EnergyManagement;
61 import org.openwebnet4j.message.FrameException;
62 import org.openwebnet4j.message.GatewayMgmt;
63 import org.openwebnet4j.message.Lighting;
64 import org.openwebnet4j.message.OpenMessage;
65 import org.openwebnet4j.message.Scenario;
66 import org.openwebnet4j.message.Thermoregulation;
67 import org.openwebnet4j.message.What;
68 import org.openwebnet4j.message.Where;
69 import org.openwebnet4j.message.WhereZigBee;
70 import org.openwebnet4j.message.Who;
71 import org.slf4j.Logger;
72 import org.slf4j.LoggerFactory;
75 * The {@link OpenWebNetBridgeHandler} is responsible for handling communication
76 * with gateways and handling events.
78 * @author Massimo Valla - Initial contribution, Lighting, Automation, Scenario
79 * @author Andrea Conte - Energy management, Thermoregulation
80 * @author Gilberto Cocchi - Thermoregulation
81 * @author Giovanni Fabiani - Aux
84 public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implements GatewayListener {
86 private final Logger logger = LoggerFactory.getLogger(OpenWebNetBridgeHandler.class);
88 private static final int GATEWAY_ONLINE_TIMEOUT_SEC = 20; // Time to wait for the gateway to become connected
90 private static final int REFRESH_ALL_DEVICES_DELAY_MSEC = 500; // Delay to wait before trying again another all
91 // devices refresh request after a connect/reconnect
92 private static final int REFRESH_ALL_DEVICES_DELAY_MAX_MSEC = 15000; // Maximum delay to wait for all devices
93 // refresh after a connect/reconnect
95 private static final int REFRESH_ALL_CHECK_DELAY_SEC = 20; // Delay to wait to check which devices are
98 private static final int DATETIME_SYNCH_DIFF_SEC = 60; // Difference from BUS date time
100 private long lastRegisteredDeviceTS = -1; // timestamp when the last device has been associated to the bridge
101 private long refreshAllDevicesDelay = 0; // delay waited before starting all devices refresh
103 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.BRIDGE_SUPPORTED_THING_TYPES;
105 // ConcurrentHashMap of devices registered to this BridgeHandler
106 // association is: ownId (String) -> OpenWebNetThingHandler, with ownId =
108 private Map<String, @Nullable OpenWebNetThingHandler> registeredDevices = new ConcurrentHashMap<>();
109 private Map<String, Long> discoveringDevices = new ConcurrentHashMap<>();
111 protected @Nullable OpenGateway gateway;
112 private boolean isBusGateway = false;
114 private boolean isGatewayConnected = false;
116 public @Nullable OpenWebNetDeviceDiscoveryService deviceDiscoveryService;
117 private boolean reconnecting = false; // we are trying to reconnect to gateway
118 private @Nullable ScheduledFuture<?> refreshAllSchedule;
119 private @Nullable ScheduledFuture<?> connectSchedule;
121 private boolean scanIsActive = false; // a device scan has been activated by OpenWebNetDeviceDiscoveryService;
122 private boolean discoveryByActivation;
123 private boolean dateTimeSynch = false;
125 public OpenWebNetBridgeHandler(Bridge bridge) {
129 public boolean isBusGateway() {
134 public void initialize() {
135 ThingTypeUID thingType = getThing().getThingTypeUID();
137 if (thingType.equals(THING_TYPE_ZB_GATEWAY)) {
138 gw = initZigBeeGateway();
140 gw = initBusGateway();
146 if (gw.isConnected()) { // gateway is already connected, device can go ONLINE
147 isGatewayConnected = true;
148 updateStatus(ThingStatus.ONLINE);
150 updateStatus(ThingStatus.UNKNOWN);
151 logger.debug("Trying to connect gateway {}... ", gw);
154 connectSchedule = scheduler.schedule(() -> {
155 // if status is still UNKNOWN after timer ends, set the device OFFLINE
156 if (thing.getStatus().equals(ThingStatus.UNKNOWN)) {
157 logger.info("status still UNKNOWN. Setting device={} to OFFLINE", thing.getUID());
158 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
159 "@text/offline.comm-error-timeout");
161 }, GATEWAY_ONLINE_TIMEOUT_SEC, TimeUnit.SECONDS);
162 logger.debug("bridge {} initialization completed", thing.getUID());
163 } catch (OWNException e) {
164 logger.debug("gw.connect() returned OWNException: {}", e.getMessage());
165 // status is updated by callback onConnectionError()
172 * Init a Zigbee gateway based on config
174 private @Nullable OpenGateway initZigBeeGateway() {
175 logger.debug("Initializing Zigbee USB Gateway");
176 OpenWebNetZigBeeBridgeConfig zbBridgeConfig = getConfigAs(OpenWebNetZigBeeBridgeConfig.class);
177 String serialPort = zbBridgeConfig.getSerialPort();
178 if (serialPort == null || serialPort.isEmpty()) {
179 logger.warn("Cannot connect Zigbee USB Gateway. No serial port has been provided in Bridge configuration.");
180 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
181 "@text/offline.conf-error-no-serial-port");
184 USBGateway tmpUSBGateway = new USBGateway(serialPort);
185 tmpUSBGateway.setSerialPortProvider(new SerialPortProviderAdapter());
186 logger.debug("**** -SPI- **** OpenWebNetBridgeHandler :: setSerialPortProvider to: {}",
187 tmpUSBGateway.getSerialPortProvider());
188 return tmpUSBGateway;
193 * Init a BUS gateway based on config
195 private @Nullable OpenGateway initBusGateway() {
196 logger.debug("Initializing BUS gateway");
198 OpenWebNetBusBridgeConfig busBridgeConfig = getConfigAs(OpenWebNetBusBridgeConfig.class);
199 String host = busBridgeConfig.getHost();
200 if (host == null || host.isEmpty()) {
201 logger.warn("Cannot connect to BUS Gateway. No host/IP has been provided in Bridge configuration.");
202 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
203 "@text/offline.conf-error-no-ip-address");
206 int port = busBridgeConfig.getPort().intValue();
207 String passwd = busBridgeConfig.getPasswd();
209 if (passwd.length() >= 4) {
210 passwdMasked = "******" + passwd.substring(passwd.length() - 3, passwd.length());
212 passwdMasked = "******";
214 discoveryByActivation = busBridgeConfig.getDiscoveryByActivation();
215 dateTimeSynch = busBridgeConfig.getDateTimeSynch();
217 "Creating new BUS gateway with config properties: {}:{}, pwd={}, discoveryByActivation={}, dateTimeSynch={}",
218 host, port, passwdMasked, discoveryByActivation, dateTimeSynch);
219 return new BUSGateway(host, port, passwd);
224 public void handleCommand(ChannelUID channelUID, Command command) {
225 logger.debug("handleCommand (command={} - channel={})", command, channelUID);
226 OpenGateway gw = gateway;
227 if (gw == null || !gw.isConnected()) {
228 logger.warn("Gateway is NOT connected, skipping command");
231 if (command instanceof RefreshType) {
232 refreshAllBridgeDevices();
234 logger.warn("Command or channel not supported: channel={} command={}", channelUID, command);
240 public Collection<ConfigStatusMessage> getConfigStatus() {
241 return Collections.emptyList();
245 public void handleRemoval() {
247 super.handleRemoval();
251 public void dispose() {
252 ScheduledFuture<?> rSc = refreshAllSchedule;
256 ScheduledFuture<?> cs = connectSchedule;
264 private void disconnectGateway() {
265 OpenGateway gw = gateway;
267 gw.closeConnection();
268 gw.unsubscribe(this);
269 logger.debug("Gateway {} connection closed and unsubscribed", gw.toString());
272 reconnecting = false;
276 * Return the OpenGateway linked to this BridgeHandler
278 * @return the linked OpenGateway
280 public @Nullable OpenGateway getGateway() {
285 public Collection<Class<? extends ThingHandlerService>> getServices() {
286 return Set.of(OpenWebNetDeviceDiscoveryService.class, OpenWebNetBridgeActions.class);
290 * Search for devices connected to this bridge handler's gateway
292 public synchronized void searchDevices() {
294 logger.debug("------$$ scanIsActive={}", scanIsActive);
295 OpenGateway gw = gateway;
297 if (!gw.isDiscovering()) {
298 if (!gw.isConnected()) {
299 logger.debug("------$$ Gateway '{}' is NOT connected, cannot search for devices", gw);
302 logger.info("------$$ STARTED active SEARCH for devices on bridge '{}'", thing.getUID());
304 gw.discoverDevices();
305 } catch (OWNException e) {
306 logger.warn("------$$ OWNException while discovering devices on bridge '{}': {}", thing.getUID(),
310 logger.debug("------$$ Searching devices on bridge '{}' already activated", thing.getUID());
314 logger.warn("------$$ Cannot search devices: no gateway associated to this handler");
319 public void onNewDevice(@Nullable Where w, @Nullable OpenDeviceType deviceType, @Nullable BaseOpenMessage message) {
320 OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
321 if (discService != null) {
322 if (deviceType != null) {
323 discService.newDiscoveryResult(w, deviceType, message);
325 logger.warn("onNewDevice with null deviceType, msg={}", message);
328 logger.warn("onNewDevice but null deviceDiscoveryService");
333 public void onDiscoveryCompleted() {
334 logger.info("------$$ FINISHED active SEARCH for devices on bridge '{}'", thing.getUID());
338 * Notifies that the scan has been stopped/aborted by
339 * OpenWebNetDeviceDiscoveryService
341 public void scanStopped() {
342 scanIsActive = false;
343 logger.debug("------$$ scanIsActive={}", scanIsActive);
346 private void discoverByActivation(BaseOpenMessage baseMsg) {
347 logger.debug("discoverByActivation: msg={}", baseMsg);
348 OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
349 if (discService == null) {
350 logger.warn("discoverByActivation: null OpenWebNetDeviceDiscoveryService, ignoring msg={}", baseMsg);
353 // we support these types only
354 if (baseMsg instanceof Lighting || baseMsg instanceof Automation || baseMsg instanceof EnergyManagement
355 || baseMsg instanceof Thermoregulation || baseMsg instanceof CEN || baseMsg instanceof Scenario
356 || baseMsg instanceof Alarm) {
357 BaseOpenMessage bmsg = baseMsg;
358 if (baseMsg instanceof Lighting) {
359 What what = baseMsg.getWhat();
360 if (Lighting.WhatLighting.OFF.equals(what)) { // skipping OFF msg: cannot distinguish dimmer/switch
361 logger.debug("discoverByActivation: skipping OFF msg: cannot distinguish dimmer/switch");
364 if (Lighting.WhatLighting.ON.equals(what)) { // if not already done just now, request light status to
365 // distinguish dimmer from switch
366 if (discoveringDevices.containsKey(ownIdFromMessage(baseMsg))) {
368 "discoverByActivation: we just requested status for this device and it's ON -> it's a switch");
370 OpenGateway gw = gateway;
373 discoveringDevices.put(ownIdFromMessage(baseMsg),
374 Long.valueOf(System.currentTimeMillis()));
375 gw.send(Lighting.requestStatus(baseMsg.getWhere().value()));
377 } catch (OWNException e) {
378 logger.warn("discoverByActivation: Exception while requesting light state: {}",
385 discoveringDevices.remove(ownIdFromMessage(baseMsg));
387 OpenDeviceType type = null;
389 type = bmsg.detectDeviceType();
390 } catch (FrameException e) {
391 logger.warn("Exception while detecting device type: {}", e.getMessage());
394 discService.newDiscoveryResult(bmsg.getWhere(), type, bmsg);
396 logger.debug("discoverByActivation: no device type detected from msg: {}", bmsg);
402 * Register a device ThingHandler to this BridgHandler
404 * @param ownId the device OpenWebNet id
405 * @param thingHandler the thing handler to be registered
407 protected void registerDevice(String ownId, OpenWebNetThingHandler thingHandler) {
408 if (registeredDevices.containsKey(ownId)) {
409 logger.warn("registering device with an existing ownId={}", ownId);
411 registeredDevices.put(ownId, thingHandler);
412 lastRegisteredDeviceTS = System.currentTimeMillis();
413 logger.debug("registered device ownId={}, thing={}", ownId, thingHandler.getThing().getUID());
417 * Un-register a device from this bridge handler
419 * @param ownId the device OpenWebNet id
421 protected void unregisterDevice(String ownId) {
422 if (registeredDevices.remove(ownId) != null) {
423 logger.debug("un-registered device ownId={}", ownId);
425 logger.warn("could not un-register ownId={} (not found)", ownId);
430 * Get an already registered device on this bridge handler
432 * @param ownId the device OpenWebNet id
433 * @return the registered device Thing handler or null if the id cannot be found
435 public @Nullable OpenWebNetThingHandler getRegisteredDevice(String ownId) {
436 return registeredDevices.get(ownId);
439 private void refreshAllBridgeDevices() {
440 logger.debug("--- --- ABOUT TO REFRESH ALL devices for bridge {}", thing.getUID());
442 final List<Thing> things = getThing().getThings();
443 int total = things.size();
444 logger.debug("--- FOUND {} things by getThings()", total);
446 if (registeredDevices.isEmpty()) { // no registered device yet
447 if (refreshAllDevicesDelay < REFRESH_ALL_DEVICES_DELAY_MAX_MSEC) {
448 logger.debug("--- REGISTER device not started yet... re-scheduling refreshAllBridgeDevices()");
449 refreshAllDevicesDelay += REFRESH_ALL_DEVICES_DELAY_MSEC * 3;
450 refreshAllSchedule = scheduler.schedule(this::refreshAllBridgeDevices,
451 REFRESH_ALL_DEVICES_DELAY_MSEC * 3, TimeUnit.MILLISECONDS);
455 "--- --- NONE OF {} CHILD DEVICE(S) has REGISTERED with bridge {}: check Things configuration (stopping refreshAllBridgeDevices)",
456 total, thing.getUID());
457 refreshAllDevicesDelay = 0;
460 } else if (System.currentTimeMillis() - lastRegisteredDeviceTS < REFRESH_ALL_DEVICES_DELAY_MSEC) {
461 // a device has been registered with the bridge just now, let's wait for other
462 // devices: re-schedule
464 logger.debug("--- REGISTER device just called... re-scheduling refreshAllBridgeDevices()");
465 refreshAllSchedule = scheduler.schedule(this::refreshAllBridgeDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
466 TimeUnit.MILLISECONDS);
469 for (Thing ownThing : things) {
470 OpenWebNetThingHandler hndlr = (OpenWebNetThingHandler) ownThing.getHandler();
473 logger.debug("--- REFRESHING ALL DEVICES FOR thing #{}/{}: {}", howMany, total, ownThing.getUID());
474 hndlr.refreshAllDevices();
476 logger.warn("--- No handler for thing {}", ownThing.getUID());
479 logger.debug("--- --- COMPLETED REFRESH all devices for bridge {}", thing.getUID());
480 refreshAllDevicesDelay = 0;
481 // set a check that all things are Online
482 refreshAllSchedule = scheduler.schedule(() -> checkAllRefreshed(things), REFRESH_ALL_CHECK_DELAY_SEC,
485 logger.debug("--- --- NO CHILD DEVICE to REFRESH for bridge {}", thing.getUID());
489 private void checkAllRefreshed(List<Thing> things) {
491 int total = things.size();
492 boolean allOnline = true;
493 for (Thing ownThing : things) {
495 ThingStatus ts = ownThing.getStatus();
496 if (ThingStatus.ONLINE == ts) {
497 logger.debug("--- CHECKED ONLINE thing #{}/{}: {}", howMany, total, ownThing.getUID());
499 logger.debug("--- CHECKED ^^^OFFLINE^^^ thing #{}/{}: {}", howMany, total, ownThing.getUID());
504 logger.debug("--- --- REFRESH CHECK COMPLETED: all things ONLINE for bridge {}", thing.getUID());
506 logger.debug("--- --- REFRESH CHECK COMPLETED: NOT all things ONLINE for bridge {}", thing.getUID());
511 public void onEventMessage(@Nullable OpenMessage msg) {
512 logger.trace("RECEIVED <<<<< {}", msg);
514 logger.warn("received event msg is null");
517 if (msg.isACK() || msg.isNACK()) {
518 return; // we ignore ACKS/NACKS
520 // GATEWAY MANAGEMENT
521 if (msg instanceof GatewayMgmt gwMsg) {
522 if (dateTimeSynch && GatewayMgmt.DimGatewayMgmt.DATETIME.equals(gwMsg.getDim())) {
523 checkDateTimeDiff(gwMsg);
528 BaseOpenMessage baseMsg = (BaseOpenMessage) msg;
529 // let's try to get the Thing associated with this message...
530 if (baseMsg instanceof Lighting || baseMsg instanceof Automation || baseMsg instanceof EnergyManagement
531 || baseMsg instanceof Thermoregulation || baseMsg instanceof CEN || baseMsg instanceof Auxiliary
532 || baseMsg instanceof Scenario || baseMsg instanceof Alarm) {
533 String ownId = ownIdFromMessage(baseMsg);
534 logger.debug("ownIdFromMessage({}) --> {}", baseMsg, ownId);
535 OpenWebNetThingHandler deviceHandler = registeredDevices.get(ownId);
536 if (deviceHandler == null) {
537 OpenGateway gw = gateway;
538 if (isBusGateway && ((gw != null && !gw.isDiscovering() && scanIsActive)
539 || (discoveryByActivation && !scanIsActive))) {
540 discoverByActivation(baseMsg);
542 logger.debug("ownId={} has NO DEVICE associated to bridge {}: ignoring it", ownId, thing.getUID());
545 deviceHandler.handleMessage(baseMsg);
548 logger.debug("BridgeHandler ignoring frame {}. WHO={} is not supported by this binding", baseMsg,
553 private void checkDateTimeDiff(GatewayMgmt gwMsg) {
555 ZonedDateTime now = ZonedDateTime.now();
556 ZonedDateTime gwTime = GatewayMgmt.parseDateTime(gwMsg);
557 long diff = Math.abs(Duration.between(now, gwTime).toSeconds());
558 if (diff > DATETIME_SYNCH_DIFF_SEC) {
559 logger.debug("checkDateTimeDiff: difference is more than 60s: {}s", diff);
560 OpenGateway gw = gateway;
562 logger.debug("checkDateTimeDiff: synch DateTime to: {}", now);
564 gw.send(GatewayMgmt.requestSetDateTime(now));
565 } catch (OWNException e) {
566 logger.warn("checkDateTimeDiff: Exception while sending set DateTime command: {}",
571 logger.debug("checkDateTimeDiff: DateTime difference: {}s", diff);
573 } catch (FrameException e) {
574 logger.warn("checkDateTimeDiff: FrameException while parsing {}", e.getMessage());
579 public void onConnected() {
580 isGatewayConnected = true;
581 Map<String, String> properties = editProperties();
582 boolean propertiesChanged = false;
583 OpenGateway gw = gateway;
585 logger.warn("received onConnected() but gateway is null");
588 if (gw instanceof USBGateway usbGateway) {
589 logger.info("---- CONNECTED to Zigbee USB gateway bridge '{}' (serialPort: {})", thing.getUID(),
590 usbGateway.getSerialPortName());
592 logger.info("---- CONNECTED to BUS gateway bridge '{}' ({}:{})", thing.getUID(),
593 ((BUSGateway) gw).getHost(), ((BUSGateway) gw).getPort());
594 // update serial number property (with MAC address)
595 if (!Objects.equals(properties.get(PROPERTY_SERIAL_NO), gw.getMACAddr().toUpperCase())) {
596 properties.put(PROPERTY_SERIAL_NO, gw.getMACAddr().toUpperCase());
597 propertiesChanged = true;
598 logger.debug("updated property gw serialNumber: {}", properties.get(PROPERTY_SERIAL_NO));
601 if (!Objects.equals(properties.get(PROPERTY_FIRMWARE_VERSION), gw.getFirmwareVersion())) {
602 properties.put(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
603 propertiesChanged = true;
604 logger.debug("updated property gw firmware version: {}", properties.get(PROPERTY_FIRMWARE_VERSION));
606 if (propertiesChanged) {
607 updateProperties(properties);
608 logger.info("properties updated for bridge '{}'", thing.getUID());
610 updateStatus(ThingStatus.ONLINE);
611 // schedule a refresh for all devices
612 refreshAllSchedule = scheduler.schedule(this::refreshAllBridgeDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
613 TimeUnit.MILLISECONDS);
617 public void onConnectionError(@Nullable OWNException error) {
620 errMsg = "unknown error";
622 errMsg = error.getMessage();
624 logger.info("---- ON CONNECTION ERROR for gateway {}: {}", gateway, errMsg);
625 isGatewayConnected = false;
626 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
627 "@text/offline.comm-error-connection" + " (onConnectionError - " + errMsg + ")");
628 tryReconnectGateway();
632 public void onConnectionClosed() {
633 isGatewayConnected = false;
634 logger.debug("onConnectionClosed() - isGatewayConnected={}", isGatewayConnected);
635 // NOTE: cannot change to OFFLINE here because we are already in REMOVING state
639 public void onDisconnected(@Nullable OWNException e) {
640 isGatewayConnected = false;
643 errMsg = "unknown error";
645 errMsg = e.getMessage();
647 logger.info("---- DISCONNECTED from gateway {}. OWNException: {}", gateway, errMsg);
648 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
649 "@text/offline.comm-error-disconnected" + " (onDisconnected - " + errMsg + ")");
650 tryReconnectGateway();
653 private void tryReconnectGateway() {
654 OpenGateway gw = gateway;
658 logger.info("---- Starting RECONNECT cycle to gateway {}", gw);
661 } catch (OWNAuthException e) {
662 logger.info("---- AUTH error from gateway. Stopping re-connect");
663 reconnecting = false;
664 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
665 "@text/offline.conf-error-auth" + " (" + e + ")");
668 logger.debug("---- already reconnecting");
671 logger.warn("---- cannot start RECONNECT, gateway is null");
676 public void onReconnected() {
677 reconnecting = false;
678 OpenGateway gw = gateway;
679 logger.info("---- RE-CONNECTED to bridge {}", thing.getUID());
681 updateStatus(ThingStatus.ONLINE);
682 if (gw.getFirmwareVersion() != null) {
683 this.updateProperty(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
684 logger.debug("gw firmware version: {}", gw.getFirmwareVersion());
686 // schedule a refresh for all devices
687 refreshAllSchedule = scheduler.schedule(this::refreshAllBridgeDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
688 TimeUnit.MILLISECONDS);
693 * Return a ownId string (=WHO.WHERE) from the device Where address and handler
695 * @param where the Where address (to be normalized)
696 * @param handler the device handler
697 * @return the ownId String
699 protected String ownIdFromDeviceWhere(Where where, OpenWebNetThingHandler handler) {
700 return handler.ownIdPrefix() + "." + normalizeWhere(where);
704 * Returns a ownId string (=WHO.WHERE) from a Who and Where address
707 * @param where the Where address (to be normalized)
708 * @return the ownId String
710 public String ownIdFromWhoWhere(Who who, Where where) {
711 return who.value() + "." + normalizeWhere(where);
715 * Return a ownId string (=WHO.WHERE) from a BaseOpenMessage
717 * @param baseMsg the BaseOpenMessage
718 * @return the ownId String
720 public String ownIdFromMessage(BaseOpenMessage baseMsg) {
722 Where w = baseMsg.getWhere();
724 return baseMsg.getWho().value() + "." + normalizeWhere(w);
725 } else if (baseMsg instanceof Alarm) { // null and Alarm
726 return baseMsg.getWho().value() + "." + "0"; // Alarm System --> where=0
728 logger.warn("ownIdFromMessage with null where: {}", baseMsg);
734 * Transform a Where address into a Thing id string
736 * @param where the Where address
737 * @return the thing Id string
739 public String thingIdFromWhere(Where where) {
740 return normalizeWhere(where); // '#' cannot be used in ThingUID;
744 * Normalize a Where address to generate ownId and Thing id
746 * @param where the Where address
747 * @return the normalized address as String
749 public String normalizeWhere(Where where) {
750 String str = where.value();
751 if (where instanceof WhereZigBee whereZigBee) {
752 str = whereZigBee.valueWithUnit(WhereZigBee.UNIT_ALL); // 76543210X#9 --> 765432100#9
754 if (str.indexOf("#4#") == -1) { // skip APL#4#bus case
755 if (str.indexOf('#') == 0) { // Thermo central unit (#0) or zone via central unit (#Z, Z=[1-99]) --> Z,
756 // Alarm Zone (#Z) --> Z
757 str = str.substring(1);
758 } else if (str.indexOf('#') > 0 && str.charAt(0) != '0') { // Thermo zone Z and actuator N (Z#N,
759 // Z=[1-99], N=[1-9]) --> Z
760 str = str.substring(0, str.indexOf('#'));
764 return str.replace('#', 'h');