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.discovery.OpenWebNetDeviceDiscoveryService;
33 import org.openhab.binding.openwebnet.internal.handler.config.OpenWebNetBusBridgeConfig;
34 import org.openhab.binding.openwebnet.internal.handler.config.OpenWebNetZigBeeBridgeConfig;
35 import org.openhab.binding.openwebnet.internal.serial.SerialPortProviderAdapter;
36 import org.openhab.core.config.core.status.ConfigStatusMessage;
37 import org.openhab.core.thing.Bridge;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.Thing;
40 import org.openhab.core.thing.ThingStatus;
41 import org.openhab.core.thing.ThingStatusDetail;
42 import org.openhab.core.thing.ThingTypeUID;
43 import org.openhab.core.thing.binding.ConfigStatusBridgeHandler;
44 import org.openhab.core.thing.binding.ThingHandlerService;
45 import org.openhab.core.types.Command;
46 import org.openhab.core.types.RefreshType;
47 import org.openwebnet4j.BUSGateway;
48 import org.openwebnet4j.GatewayListener;
49 import org.openwebnet4j.OpenDeviceType;
50 import org.openwebnet4j.OpenGateway;
51 import org.openwebnet4j.USBGateway;
52 import org.openwebnet4j.communication.OWNAuthException;
53 import org.openwebnet4j.communication.OWNException;
54 import org.openwebnet4j.message.Alarm;
55 import org.openwebnet4j.message.Automation;
56 import org.openwebnet4j.message.Auxiliary;
57 import org.openwebnet4j.message.BaseOpenMessage;
58 import org.openwebnet4j.message.CEN;
59 import org.openwebnet4j.message.EnergyManagement;
60 import org.openwebnet4j.message.FrameException;
61 import org.openwebnet4j.message.GatewayMgmt;
62 import org.openwebnet4j.message.Lighting;
63 import org.openwebnet4j.message.OpenMessage;
64 import org.openwebnet4j.message.Scenario;
65 import org.openwebnet4j.message.Thermoregulation;
66 import org.openwebnet4j.message.What;
67 import org.openwebnet4j.message.Where;
68 import org.openwebnet4j.message.WhereZigBee;
69 import org.openwebnet4j.message.Who;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
74 * The {@link OpenWebNetBridgeHandler} is responsible for handling communication
75 * with gateways and handling events.
77 * @author Massimo Valla - Initial contribution, Lighting, Automation, Scenario
78 * @author Andrea Conte - Energy management, Thermoregulation
79 * @author Gilberto Cocchi - Thermoregulation
80 * @author Giovanni Fabiani - Aux
83 public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implements GatewayListener {
85 private final Logger logger = LoggerFactory.getLogger(OpenWebNetBridgeHandler.class);
87 private static final int GATEWAY_ONLINE_TIMEOUT_SEC = 20; // Time to wait for the gateway to become connected
89 private static final int REFRESH_ALL_DEVICES_DELAY_MSEC = 500; // Delay to wait before trying again another all
90 // devices refresh request after a connect/reconnect
91 private static final int REFRESH_ALL_DEVICES_DELAY_MAX_MSEC = 15000; // Maximum delay to wait for all devices
92 // refresh after a connect/reconnect
94 private static final int REFRESH_ALL_CHECK_DELAY_SEC = 20; // Delay to wait to check which devices are
97 private static final int DATETIME_SYNCH_DIFF_SEC = 60; // Difference from BUS date time
99 private long lastRegisteredDeviceTS = -1; // timestamp when the last device has been associated to the bridge
100 private long refreshAllDevicesDelay = 0; // delay waited before starting all devices refresh
102 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.BRIDGE_SUPPORTED_THING_TYPES;
104 // ConcurrentHashMap of devices registered to this BridgeHandler
105 // association is: ownId (String) -> OpenWebNetThingHandler, with ownId =
107 private Map<String, @Nullable OpenWebNetThingHandler> registeredDevices = new ConcurrentHashMap<>();
108 private Map<String, Long> discoveringDevices = new ConcurrentHashMap<>();
110 protected @Nullable OpenGateway gateway;
111 private boolean isBusGateway = false;
113 private boolean isGatewayConnected = false;
115 public @Nullable OpenWebNetDeviceDiscoveryService deviceDiscoveryService;
116 private boolean reconnecting = false; // we are trying to reconnect to gateway
117 private @Nullable ScheduledFuture<?> refreshAllSchedule;
118 private @Nullable ScheduledFuture<?> connectSchedule;
120 private boolean scanIsActive = false; // a device scan has been activated by OpenWebNetDeviceDiscoveryService;
121 private boolean discoveryByActivation;
122 private boolean dateTimeSynch = false;
124 public OpenWebNetBridgeHandler(Bridge bridge) {
128 public boolean isBusGateway() {
133 public void initialize() {
134 ThingTypeUID thingType = getThing().getThingTypeUID();
136 if (thingType.equals(THING_TYPE_ZB_GATEWAY)) {
137 gw = initZigBeeGateway();
139 gw = initBusGateway();
145 if (gw.isConnected()) { // gateway is already connected, device can go ONLINE
146 isGatewayConnected = true;
147 updateStatus(ThingStatus.ONLINE);
149 updateStatus(ThingStatus.UNKNOWN);
150 logger.debug("Trying to connect gateway {}... ", gw);
153 connectSchedule = scheduler.schedule(() -> {
154 // if status is still UNKNOWN after timer ends, set the device OFFLINE
155 if (thing.getStatus().equals(ThingStatus.UNKNOWN)) {
156 logger.info("status still UNKNOWN. Setting device={} to OFFLINE", thing.getUID());
157 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
158 "@text/offline.comm-error-timeout");
160 }, GATEWAY_ONLINE_TIMEOUT_SEC, TimeUnit.SECONDS);
161 logger.debug("bridge {} initialization completed", thing.getUID());
162 } catch (OWNException e) {
163 logger.debug("gw.connect() returned OWNException: {}", e.getMessage());
164 // status is updated by callback onConnectionError()
171 * Init a Zigbee gateway based on config
173 private @Nullable OpenGateway initZigBeeGateway() {
174 logger.debug("Initializing Zigbee USB Gateway");
175 OpenWebNetZigBeeBridgeConfig zbBridgeConfig = getConfigAs(OpenWebNetZigBeeBridgeConfig.class);
176 String serialPort = zbBridgeConfig.getSerialPort();
177 if (serialPort == null || serialPort.isEmpty()) {
178 logger.warn("Cannot connect Zigbee USB Gateway. No serial port has been provided in Bridge configuration.");
179 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
180 "@text/offline.conf-error-no-serial-port");
183 USBGateway tmpUSBGateway = new USBGateway(serialPort);
184 tmpUSBGateway.setSerialPortProvider(new SerialPortProviderAdapter());
185 logger.debug("**** -SPI- **** OpenWebNetBridgeHandler :: setSerialPortProvider to: {}",
186 tmpUSBGateway.getSerialPortProvider());
187 return tmpUSBGateway;
192 * Init a BUS gateway based on config
194 private @Nullable OpenGateway initBusGateway() {
195 logger.debug("Initializing BUS gateway");
197 OpenWebNetBusBridgeConfig busBridgeConfig = getConfigAs(OpenWebNetBusBridgeConfig.class);
198 String host = busBridgeConfig.getHost();
199 if (host == null || host.isEmpty()) {
200 logger.warn("Cannot connect to BUS Gateway. No host/IP has been provided in Bridge configuration.");
201 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
202 "@text/offline.conf-error-no-ip-address");
205 int port = busBridgeConfig.getPort().intValue();
206 String passwd = busBridgeConfig.getPasswd();
208 if (passwd.length() >= 4) {
209 passwdMasked = "******" + passwd.substring(passwd.length() - 3, passwd.length());
211 passwdMasked = "******";
213 discoveryByActivation = busBridgeConfig.getDiscoveryByActivation();
214 dateTimeSynch = busBridgeConfig.getDateTimeSynch();
216 "Creating new BUS gateway with config properties: {}:{}, pwd={}, discoveryByActivation={}, dateTimeSynch={}",
217 host, port, passwdMasked, discoveryByActivation, dateTimeSynch);
218 return new BUSGateway(host, port, passwd);
223 public void handleCommand(ChannelUID channelUID, Command command) {
224 logger.debug("handleCommand (command={} - channel={})", command, channelUID);
225 OpenGateway gw = gateway;
226 if (gw == null || !gw.isConnected()) {
227 logger.warn("Gateway is NOT connected, skipping command");
230 if (command instanceof RefreshType) {
231 refreshAllBridgeDevices();
233 logger.warn("Command or channel not supported: channel={} command={}", channelUID, command);
239 public Collection<ConfigStatusMessage> getConfigStatus() {
240 return Collections.emptyList();
244 public void handleRemoval() {
246 super.handleRemoval();
250 public void dispose() {
251 ScheduledFuture<?> rSc = refreshAllSchedule;
255 ScheduledFuture<?> cs = connectSchedule;
263 private void disconnectGateway() {
264 OpenGateway gw = gateway;
266 gw.closeConnection();
267 gw.unsubscribe(this);
268 logger.debug("Gateway {} connection closed and unsubscribed", gw.toString());
271 reconnecting = false;
275 public Collection<Class<? extends ThingHandlerService>> getServices() {
276 return Set.of(OpenWebNetDeviceDiscoveryService.class);
280 * Search for devices connected to this bridge handler's gateway
282 public synchronized void searchDevices() {
284 logger.debug("------$$ scanIsActive={}", scanIsActive);
285 OpenGateway gw = gateway;
287 if (!gw.isDiscovering()) {
288 if (!gw.isConnected()) {
289 logger.debug("------$$ Gateway '{}' is NOT connected, cannot search for devices", gw);
292 logger.info("------$$ STARTED active SEARCH for devices on bridge '{}'", thing.getUID());
294 gw.discoverDevices();
295 } catch (OWNException e) {
296 logger.warn("------$$ OWNException while discovering devices on bridge '{}': {}", thing.getUID(),
300 logger.debug("------$$ Searching devices on bridge '{}' already activated", thing.getUID());
304 logger.warn("------$$ Cannot search devices: no gateway associated to this handler");
309 public void onNewDevice(@Nullable Where w, @Nullable OpenDeviceType deviceType, @Nullable BaseOpenMessage message) {
310 OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
311 if (discService != null) {
312 if (deviceType != null) {
313 discService.newDiscoveryResult(w, deviceType, message);
315 logger.warn("onNewDevice with null deviceType, msg={}", message);
318 logger.warn("onNewDevice but null deviceDiscoveryService");
323 public void onDiscoveryCompleted() {
324 logger.info("------$$ FINISHED active SEARCH for devices on bridge '{}'", thing.getUID());
328 * Notifies that the scan has been stopped/aborted by
329 * OpenWebNetDeviceDiscoveryService
331 public void scanStopped() {
332 scanIsActive = false;
333 logger.debug("------$$ scanIsActive={}", scanIsActive);
336 private void discoverByActivation(BaseOpenMessage baseMsg) {
337 logger.debug("discoverByActivation: msg={}", baseMsg);
338 OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
339 if (discService == null) {
340 logger.warn("discoverByActivation: null OpenWebNetDeviceDiscoveryService, ignoring msg={}", baseMsg);
343 // we support these types only
344 if (baseMsg instanceof Lighting || baseMsg instanceof Automation || baseMsg instanceof EnergyManagement
345 || baseMsg instanceof Thermoregulation || baseMsg instanceof CEN || baseMsg instanceof Scenario
346 || baseMsg instanceof Alarm) {
347 BaseOpenMessage bmsg = baseMsg;
348 if (baseMsg instanceof Lighting) {
349 What what = baseMsg.getWhat();
350 if (Lighting.WhatLighting.OFF.equals(what)) { // skipping OFF msg: cannot distinguish dimmer/switch
351 logger.debug("discoverByActivation: skipping OFF msg: cannot distinguish dimmer/switch");
354 if (Lighting.WhatLighting.ON.equals(what)) { // if not already done just now, request light status to
355 // distinguish dimmer from switch
356 if (discoveringDevices.containsKey(ownIdFromMessage(baseMsg))) {
358 "discoverByActivation: we just requested status for this device and it's ON -> it's a switch");
360 OpenGateway gw = gateway;
363 discoveringDevices.put(ownIdFromMessage(baseMsg),
364 Long.valueOf(System.currentTimeMillis()));
365 gw.send(Lighting.requestStatus(baseMsg.getWhere().value()));
367 } catch (OWNException e) {
368 logger.warn("discoverByActivation: Exception while requesting light state: {}",
375 discoveringDevices.remove(ownIdFromMessage(baseMsg));
377 OpenDeviceType type = null;
379 type = bmsg.detectDeviceType();
380 } catch (FrameException e) {
381 logger.warn("Exception while detecting device type: {}", e.getMessage());
384 discService.newDiscoveryResult(bmsg.getWhere(), type, bmsg);
386 logger.debug("discoverByActivation: no device type detected from msg: {}", bmsg);
392 * Register a device ThingHandler to this BridgHandler
394 * @param ownId the device OpenWebNet id
395 * @param thingHandler the thing handler to be registered
397 protected void registerDevice(String ownId, OpenWebNetThingHandler thingHandler) {
398 if (registeredDevices.containsKey(ownId)) {
399 logger.warn("registering device with an existing ownId={}", ownId);
401 registeredDevices.put(ownId, thingHandler);
402 lastRegisteredDeviceTS = System.currentTimeMillis();
403 logger.debug("registered device ownId={}, thing={}", ownId, thingHandler.getThing().getUID());
407 * Un-register a device from this bridge handler
409 * @param ownId the device OpenWebNet id
411 protected void unregisterDevice(String ownId) {
412 if (registeredDevices.remove(ownId) != null) {
413 logger.debug("un-registered device ownId={}", ownId);
415 logger.warn("could not un-register ownId={} (not found)", ownId);
420 * Get an already registered device on this bridge handler
422 * @param ownId the device OpenWebNet id
423 * @return the registered device Thing handler or null if the id cannot be found
425 public @Nullable OpenWebNetThingHandler getRegisteredDevice(String ownId) {
426 return registeredDevices.get(ownId);
429 private void refreshAllBridgeDevices() {
430 logger.debug("--- --- ABOUT TO REFRESH ALL devices for bridge {}", thing.getUID());
432 final List<Thing> things = getThing().getThings();
433 int total = things.size();
434 logger.debug("--- FOUND {} things by getThings()", total);
436 if (registeredDevices.isEmpty()) { // no registered device yet
437 if (refreshAllDevicesDelay < REFRESH_ALL_DEVICES_DELAY_MAX_MSEC) {
438 logger.debug("--- REGISTER device not started yet... re-scheduling refreshAllBridgeDevices()");
439 refreshAllDevicesDelay += REFRESH_ALL_DEVICES_DELAY_MSEC * 3;
440 refreshAllSchedule = scheduler.schedule(this::refreshAllBridgeDevices,
441 REFRESH_ALL_DEVICES_DELAY_MSEC * 3, TimeUnit.MILLISECONDS);
445 "--- --- NONE OF {} CHILD DEVICE(S) has REGISTERED with bridge {}: check Things configuration (stopping refreshAllBridgeDevices)",
446 total, thing.getUID());
447 refreshAllDevicesDelay = 0;
450 } else if (System.currentTimeMillis() - lastRegisteredDeviceTS < REFRESH_ALL_DEVICES_DELAY_MSEC) {
451 // a device has been registered with the bridge just now, let's wait for other
452 // devices: re-schedule
454 logger.debug("--- REGISTER device just called... re-scheduling refreshAllBridgeDevices()");
455 refreshAllSchedule = scheduler.schedule(this::refreshAllBridgeDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
456 TimeUnit.MILLISECONDS);
459 for (Thing ownThing : things) {
460 OpenWebNetThingHandler hndlr = (OpenWebNetThingHandler) ownThing.getHandler();
463 logger.debug("--- REFRESHING ALL DEVICES FOR thing #{}/{}: {}", howMany, total, ownThing.getUID());
464 hndlr.refreshAllDevices();
466 logger.warn("--- No handler for thing {}", ownThing.getUID());
469 logger.debug("--- --- COMPLETED REFRESH all devices for bridge {}", thing.getUID());
470 refreshAllDevicesDelay = 0;
471 // set a check that all things are Online
472 refreshAllSchedule = scheduler.schedule(() -> checkAllRefreshed(things), REFRESH_ALL_CHECK_DELAY_SEC,
475 logger.debug("--- --- NO CHILD DEVICE to REFRESH for bridge {}", thing.getUID());
479 private void checkAllRefreshed(List<Thing> things) {
481 int total = things.size();
482 boolean allOnline = true;
483 for (Thing ownThing : things) {
485 ThingStatus ts = ownThing.getStatus();
486 if (ThingStatus.ONLINE == ts) {
487 logger.debug("--- CHECKED ONLINE thing #{}/{}: {}", howMany, total, ownThing.getUID());
489 logger.debug("--- CHECKED ^^^OFFLINE^^^ thing #{}/{}: {}", howMany, total, ownThing.getUID());
494 logger.debug("--- --- REFRESH CHECK COMPLETED: all things ONLINE for bridge {}", thing.getUID());
496 logger.debug("--- --- REFRESH CHECK COMPLETED: NOT all things ONLINE for bridge {}", thing.getUID());
501 public void onEventMessage(@Nullable OpenMessage msg) {
502 logger.trace("RECEIVED <<<<< {}", msg);
504 logger.warn("received event msg is null");
507 if (msg.isACK() || msg.isNACK()) {
508 return; // we ignore ACKS/NACKS
510 // GATEWAY MANAGEMENT
511 if (msg instanceof GatewayMgmt gwMsg) {
512 if (dateTimeSynch && GatewayMgmt.DimGatewayMgmt.DATETIME.equals(gwMsg.getDim())) {
513 checkDateTimeDiff(gwMsg);
518 BaseOpenMessage baseMsg = (BaseOpenMessage) msg;
519 // let's try to get the Thing associated with this message...
520 if (baseMsg instanceof Lighting || baseMsg instanceof Automation || baseMsg instanceof EnergyManagement
521 || baseMsg instanceof Thermoregulation || baseMsg instanceof CEN || baseMsg instanceof Auxiliary
522 || baseMsg instanceof Scenario || baseMsg instanceof Alarm) {
523 String ownId = ownIdFromMessage(baseMsg);
524 logger.debug("ownIdFromMessage({}) --> {}", baseMsg, ownId);
525 OpenWebNetThingHandler deviceHandler = registeredDevices.get(ownId);
526 if (deviceHandler == null) {
527 OpenGateway gw = gateway;
528 if (isBusGateway && ((gw != null && !gw.isDiscovering() && scanIsActive)
529 || (discoveryByActivation && !scanIsActive))) {
530 discoverByActivation(baseMsg);
532 logger.debug("ownId={} has NO DEVICE associated to bridge {}: ignoring it", ownId, thing.getUID());
535 deviceHandler.handleMessage(baseMsg);
538 logger.debug("BridgeHandler ignoring frame {}. WHO={} is not supported by this binding", baseMsg,
543 private void checkDateTimeDiff(GatewayMgmt gwMsg) {
545 ZonedDateTime now = ZonedDateTime.now();
546 ZonedDateTime gwTime = GatewayMgmt.parseDateTime(gwMsg);
547 long diff = Math.abs(Duration.between(now, gwTime).toSeconds());
548 if (diff > DATETIME_SYNCH_DIFF_SEC) {
549 logger.debug("checkDateTimeDiff: difference is more than 60s: {}s", diff);
550 OpenGateway gw = gateway;
552 logger.debug("checkDateTimeDiff: synch DateTime to: {}", now);
554 gw.send(GatewayMgmt.requestSetDateTime(now));
555 } catch (OWNException e) {
556 logger.warn("checkDateTimeDiff: Exception while sending set DateTime command: {}",
561 logger.debug("checkDateTimeDiff: DateTime difference: {}s", diff);
563 } catch (FrameException e) {
564 logger.warn("checkDateTimeDiff: FrameException while parsing {}", e.getMessage());
569 public void onConnected() {
570 isGatewayConnected = true;
571 Map<String, String> properties = editProperties();
572 boolean propertiesChanged = false;
573 OpenGateway gw = gateway;
575 logger.warn("received onConnected() but gateway is null");
578 if (gw instanceof USBGateway usbGateway) {
579 logger.info("---- CONNECTED to Zigbee USB gateway bridge '{}' (serialPort: {})", thing.getUID(),
580 usbGateway.getSerialPortName());
582 logger.info("---- CONNECTED to BUS gateway bridge '{}' ({}:{})", thing.getUID(),
583 ((BUSGateway) gw).getHost(), ((BUSGateway) gw).getPort());
584 // update serial number property (with MAC address)
585 if (!Objects.equals(properties.get(PROPERTY_SERIAL_NO), gw.getMACAddr().toUpperCase())) {
586 properties.put(PROPERTY_SERIAL_NO, gw.getMACAddr().toUpperCase());
587 propertiesChanged = true;
588 logger.debug("updated property gw serialNumber: {}", properties.get(PROPERTY_SERIAL_NO));
591 if (!Objects.equals(properties.get(PROPERTY_FIRMWARE_VERSION), gw.getFirmwareVersion())) {
592 properties.put(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
593 propertiesChanged = true;
594 logger.debug("updated property gw firmware version: {}", properties.get(PROPERTY_FIRMWARE_VERSION));
596 if (propertiesChanged) {
597 updateProperties(properties);
598 logger.info("properties updated for bridge '{}'", thing.getUID());
600 updateStatus(ThingStatus.ONLINE);
601 // schedule a refresh for all devices
602 refreshAllSchedule = scheduler.schedule(this::refreshAllBridgeDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
603 TimeUnit.MILLISECONDS);
607 public void onConnectionError(@Nullable OWNException error) {
610 errMsg = "unknown error";
612 errMsg = error.getMessage();
614 logger.info("---- ON CONNECTION ERROR for gateway {}: {}", gateway, errMsg);
615 isGatewayConnected = false;
616 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
617 "@text/offline.comm-error-connection" + " (onConnectionError - " + errMsg + ")");
618 tryReconnectGateway();
622 public void onConnectionClosed() {
623 isGatewayConnected = false;
624 logger.debug("onConnectionClosed() - isGatewayConnected={}", isGatewayConnected);
625 // NOTE: cannot change to OFFLINE here because we are already in REMOVING state
629 public void onDisconnected(@Nullable OWNException e) {
630 isGatewayConnected = false;
633 errMsg = "unknown error";
635 errMsg = e.getMessage();
637 logger.info("---- DISCONNECTED from gateway {}. OWNException: {}", gateway, errMsg);
638 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
639 "@text/offline.comm-error-disconnected" + " (onDisconnected - " + errMsg + ")");
640 tryReconnectGateway();
643 private void tryReconnectGateway() {
644 OpenGateway gw = gateway;
648 logger.info("---- Starting RECONNECT cycle to gateway {}", gw);
651 } catch (OWNAuthException e) {
652 logger.info("---- AUTH error from gateway. Stopping re-connect");
653 reconnecting = false;
654 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
655 "@text/offline.conf-error-auth" + " (" + e + ")");
658 logger.debug("---- already reconnecting");
661 logger.warn("---- cannot start RECONNECT, gateway is null");
666 public void onReconnected() {
667 reconnecting = false;
668 OpenGateway gw = gateway;
669 logger.info("---- RE-CONNECTED to bridge {}", thing.getUID());
671 updateStatus(ThingStatus.ONLINE);
672 if (gw.getFirmwareVersion() != null) {
673 this.updateProperty(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
674 logger.debug("gw firmware version: {}", gw.getFirmwareVersion());
676 // schedule a refresh for all devices
677 refreshAllSchedule = scheduler.schedule(this::refreshAllBridgeDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
678 TimeUnit.MILLISECONDS);
683 * Return a ownId string (=WHO.WHERE) from the device Where address and handler
685 * @param where the Where address (to be normalized)
686 * @param handler the device handler
687 * @return the ownId String
689 protected String ownIdFromDeviceWhere(Where where, OpenWebNetThingHandler handler) {
690 return handler.ownIdPrefix() + "." + normalizeWhere(where);
694 * Returns a ownId string (=WHO.WHERE) from a Who and Where address
697 * @param where the Where address (to be normalized)
698 * @return the ownId String
700 public String ownIdFromWhoWhere(Who who, Where where) {
701 return who.value() + "." + normalizeWhere(where);
705 * Return a ownId string (=WHO.WHERE) from a BaseOpenMessage
707 * @param baseMsg the BaseOpenMessage
708 * @return the ownId String
710 public String ownIdFromMessage(BaseOpenMessage baseMsg) {
712 Where w = baseMsg.getWhere();
714 return baseMsg.getWho().value() + "." + normalizeWhere(w);
715 } else if (baseMsg instanceof Alarm) { // null and Alarm
716 return baseMsg.getWho().value() + "." + "0"; // Alarm System --> where=0
718 logger.warn("ownIdFromMessage with null where: {}", baseMsg);
724 * Transform a Where address into a Thing id string
726 * @param where the Where address
727 * @return the thing Id string
729 public String thingIdFromWhere(Where where) {
730 return normalizeWhere(where); // '#' cannot be used in ThingUID;
734 * Normalize a Where address to generate ownId and Thing id
736 * @param where the Where address
737 * @return the normalized address as String
739 public String normalizeWhere(Where where) {
740 String str = where.value();
741 if (where instanceof WhereZigBee whereZigBee) {
742 str = whereZigBee.valueWithUnit(WhereZigBee.UNIT_ALL); // 76543210X#9 --> 765432100#9
744 if (str.indexOf("#4#") == -1) { // skip APL#4#bus case
745 if (str.indexOf('#') == 0) { // Thermo central unit (#0) or zone via central unit (#Z, Z=[1-99]) --> Z,
746 // Alarm Zone (#Z) --> Z
747 str = str.substring(1);
748 } else if (str.indexOf('#') > 0 && str.charAt(0) != '0') { // Thermo zone Z and actuator N (Z#N,
749 // Z=[1-99], N=[1-9]) --> Z
750 str = str.substring(0, str.indexOf('#'));
754 return str.replace('#', 'h');