]> git.basschouten.com Git - openhab-addons.git/blob
5b0dfb104c635f3ee40a680caaa0753d1e623af3
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.openwebnet.handler;
14
15 import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.*;
16
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.concurrent.ConcurrentHashMap;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
28 import org.openhab.binding.openwebnet.handler.config.OpenWebNetBusBridgeConfig;
29 import org.openhab.binding.openwebnet.handler.config.OpenWebNetZigBeeBridgeConfig;
30 import org.openhab.binding.openwebnet.internal.discovery.OpenWebNetDeviceDiscoveryService;
31 import org.openhab.core.config.core.status.ConfigStatusMessage;
32 import org.openhab.core.thing.Bridge;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.ThingTypeUID;
38 import org.openhab.core.thing.binding.ConfigStatusBridgeHandler;
39 import org.openhab.core.thing.binding.ThingHandlerService;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.RefreshType;
42 import org.openwebnet4j.BUSGateway;
43 import org.openwebnet4j.GatewayListener;
44 import org.openwebnet4j.OpenDeviceType;
45 import org.openwebnet4j.OpenGateway;
46 import org.openwebnet4j.USBGateway;
47 import org.openwebnet4j.communication.OWNAuthException;
48 import org.openwebnet4j.communication.OWNException;
49 import org.openwebnet4j.message.Automation;
50 import org.openwebnet4j.message.BaseOpenMessage;
51 import org.openwebnet4j.message.EnergyManagement;
52 import org.openwebnet4j.message.FrameException;
53 import org.openwebnet4j.message.GatewayMgmt;
54 import org.openwebnet4j.message.Lighting;
55 import org.openwebnet4j.message.OpenMessage;
56 import org.openwebnet4j.message.What;
57 import org.openwebnet4j.message.Where;
58 import org.openwebnet4j.message.WhereZigBee;
59 import org.openwebnet4j.message.Who;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 /**
64  * The {@link OpenWebNetBridgeHandler} is responsible for handling communication with gateways and handling events.
65  *
66  * @author Massimo Valla - Initial contribution
67  * @author Andrea Conte - Energy management
68  */
69 @NonNullByDefault
70 public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implements GatewayListener {
71
72     private final Logger logger = LoggerFactory.getLogger(OpenWebNetBridgeHandler.class);
73
74     private static final int GATEWAY_ONLINE_TIMEOUT_SEC = 20; // Time to wait for the gateway to become connected
75
76     private static final int REFRESH_ALL_DEVICES_DELAY_MSEC = 500; // Delay to wait before sending all devices refresh
77                                                                    // request after a connect/reconnect
78
79     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.BRIDGE_SUPPORTED_THING_TYPES;
80
81     // ConcurrentHashMap of devices registered to this BridgeHandler
82     // association is: ownId (String) -> OpenWebNetThingHandler, with ownId = WHO.WHERE
83     private Map<String, @Nullable OpenWebNetThingHandler> registeredDevices = new ConcurrentHashMap<>();
84     private Map<String, Long> discoveringDevices = new ConcurrentHashMap<>();
85
86     protected @Nullable OpenGateway gateway;
87     private boolean isBusGateway = false;
88
89     private boolean isGatewayConnected = false;
90
91     public @Nullable OpenWebNetDeviceDiscoveryService deviceDiscoveryService;
92     private boolean reconnecting = false; // we are trying to reconnect to gateway
93     private @Nullable ScheduledFuture<?> refreshSchedule;
94
95     private boolean scanIsActive = false; // a device scan has been activated by OpenWebNetDeviceDiscoveryService;
96     private boolean discoveryByActivation;
97
98     public OpenWebNetBridgeHandler(Bridge bridge) {
99         super(bridge);
100     }
101
102     public boolean isBusGateway() {
103         return isBusGateway;
104     }
105
106     @Override
107     public void initialize() {
108         ThingTypeUID thingType = getThing().getThingTypeUID();
109         OpenGateway gw;
110         if (thingType.equals(THING_TYPE_ZB_GATEWAY)) {
111             gw = initZigBeeGateway();
112         } else {
113             gw = initBusGateway();
114             isBusGateway = true;
115         }
116         if (gw != null) {
117             gateway = gw;
118             gw.subscribe(this);
119             if (gw.isConnected()) { // gateway is already connected, device can go ONLINE
120                 isGatewayConnected = true;
121                 updateStatus(ThingStatus.ONLINE);
122             } else {
123                 updateStatus(ThingStatus.UNKNOWN);
124                 logger.debug("Trying to connect gateway {}... ", gw);
125                 try {
126                     gw.connect();
127                     scheduler.schedule(() -> {
128                         // if status is still UNKNOWN after timer ends, set the device as OFFLINE
129                         if (thing.getStatus().equals(ThingStatus.UNKNOWN)) {
130                             logger.info("status still UNKNOWN. Setting device={} to OFFLINE", thing.getUID());
131                             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
132                                     "@text/offline.comm-error-timeout");
133                         }
134                     }, GATEWAY_ONLINE_TIMEOUT_SEC, TimeUnit.SECONDS);
135                     logger.debug("bridge {} initialization completed", thing.getUID());
136                 } catch (OWNException e) {
137                     logger.debug("gw.connect() returned OWNException: {}", e.getMessage());
138                     // status is updated by callback onConnectionError()
139                 }
140             }
141         }
142     }
143
144     /**
145      * Init a ZigBee gateway based on config
146      */
147     private @Nullable OpenGateway initZigBeeGateway() {
148         logger.debug("Initializing ZigBee USB Gateway");
149         OpenWebNetZigBeeBridgeConfig zbBridgeConfig = getConfigAs(OpenWebNetZigBeeBridgeConfig.class);
150         String serialPort = zbBridgeConfig.getSerialPort();
151         if (serialPort == null || serialPort.isEmpty()) {
152             logger.warn("Cannot connect ZigBee USB Gateway. No serial port has been provided in Bridge configuration.");
153             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
154                     "@text/offline.conf-error-no-serial-port");
155             return null;
156         } else {
157             return new USBGateway(serialPort);
158         }
159     }
160
161     /**
162      * Init a BUS gateway based on config
163      */
164     private @Nullable OpenGateway initBusGateway() {
165         logger.debug("Initializing BUS gateway");
166         OpenWebNetBusBridgeConfig busBridgeConfig = getConfigAs(OpenWebNetBusBridgeConfig.class);
167         String host = busBridgeConfig.getHost();
168         if (host == null || host.isEmpty()) {
169             logger.warn("Cannot connect to BUS Gateway. No host/IP has been provided in Bridge configuration.");
170             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
171                     "@text/offline.conf-error-no-ip-address");
172             return null;
173         } else {
174             int port = busBridgeConfig.getPort().intValue();
175             String passwd = busBridgeConfig.getPasswd();
176             String passwdMasked;
177             if (passwd.length() >= 4) {
178                 passwdMasked = "******" + passwd.substring(passwd.length() - 3, passwd.length());
179             } else {
180                 passwdMasked = "******";
181             }
182             discoveryByActivation = busBridgeConfig.getDiscoveryByActivation();
183             logger.debug("Creating new BUS gateway with config properties: {}:{}, pwd={}, discoveryByActivation={}",
184                     host, port, passwdMasked, discoveryByActivation);
185             return new BUSGateway(host, port, passwd);
186         }
187     }
188
189     @Override
190     public void handleCommand(ChannelUID channelUID, Command command) {
191         logger.debug("handleCommand (command={} - channel={})", command, channelUID);
192         OpenGateway gw = gateway;
193         if (gw == null || !gw.isConnected()) {
194             logger.warn("Gateway is NOT connected, skipping command");
195             return;
196         } else {
197             if (command instanceof RefreshType) {
198                 refreshAllDevices();
199             } else {
200                 logger.warn("Command or channel not supported: channel={} command={}", channelUID, command);
201             }
202         }
203     }
204
205     @Override
206     public Collection<ConfigStatusMessage> getConfigStatus() {
207         return Collections.emptyList();
208     }
209
210     @Override
211     public void handleRemoval() {
212         disconnectGateway();
213         super.handleRemoval();
214     }
215
216     @Override
217     public void dispose() {
218         ScheduledFuture<?> rSc = refreshSchedule;
219         if (rSc != null) {
220             rSc.cancel(true);
221         }
222         disconnectGateway();
223         super.dispose();
224     }
225
226     private void disconnectGateway() {
227         OpenGateway gw = gateway;
228         if (gw != null) {
229             gw.closeConnection();
230             gw.unsubscribe(this);
231             logger.debug("Gateway {} connection closed and unsubscribed", gw.toString());
232             gateway = null;
233         }
234         reconnecting = false;
235     }
236
237     @Override
238     public Collection<Class<? extends ThingHandlerService>> getServices() {
239         return Collections.singleton(OpenWebNetDeviceDiscoveryService.class);
240     }
241
242     /**
243      * Search for devices connected to this bridge handler's gateway
244      *
245      * @param listener to receive device found notifications
246      */
247     public synchronized void searchDevices() {
248         scanIsActive = true;
249         logger.debug("------$$ scanIsActive={}", scanIsActive);
250         OpenGateway gw = gateway;
251         if (gw != null) {
252             if (!gw.isDiscovering()) {
253                 if (!gw.isConnected()) {
254                     logger.debug("------$$ Gateway '{}' is NOT connected, cannot search for devices", gw);
255                     return;
256                 }
257                 logger.info("------$$ STARTED active SEARCH for devices on bridge '{}'", thing.getUID());
258                 try {
259                     gw.discoverDevices();
260                 } catch (OWNException e) {
261                     logger.warn("------$$ OWNException while discovering devices on bridge '{}': {}", thing.getUID(),
262                             e.getMessage());
263                 }
264             } else {
265                 logger.debug("------$$ Searching devices on bridge '{}' already activated", thing.getUID());
266                 return;
267             }
268         } else {
269             logger.warn("------$$ Cannot search devices: no gateway associated to this handler");
270         }
271     }
272
273     @Override
274     public void onNewDevice(@Nullable Where w, @Nullable OpenDeviceType deviceType, @Nullable BaseOpenMessage message) {
275         OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
276         if (discService != null) {
277             if (w != null && deviceType != null) {
278                 discService.newDiscoveryResult(w, deviceType, message);
279             } else {
280                 logger.warn("onNewDevice with null where/deviceType, msg={}", message);
281             }
282         } else {
283             logger.warn("onNewDevice but null deviceDiscoveryService");
284         }
285     }
286
287     @Override
288     public void onDiscoveryCompleted() {
289         logger.info("------$$ FINISHED active SEARCH for devices on bridge '{}'", thing.getUID());
290     }
291
292     /**
293      * Notifies that the scan has been stopped/aborted by OpenWebNetDeviceDiscoveryService
294      */
295     public void scanStopped() {
296         scanIsActive = false;
297         logger.debug("------$$ scanIsActive={}", scanIsActive);
298     }
299
300     private void discoverByActivation(BaseOpenMessage baseMsg) {
301         logger.debug("discoverByActivation: msg={}", baseMsg);
302         OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
303         if (discService == null) {
304             logger.warn("discoverByActivation: null OpenWebNetDeviceDiscoveryService, ignoring msg={}", baseMsg);
305             return;
306         }
307         if (baseMsg instanceof Lighting || baseMsg instanceof Automation || baseMsg instanceof EnergyManagement) { // we
308                                                                                                                    // support
309                                                                                                                    // these
310                                                                                                                    // types
311                                                                                                                    // only
312             BaseOpenMessage bmsg = baseMsg;
313             if (baseMsg instanceof Lighting) {
314                 What what = baseMsg.getWhat();
315                 if (Lighting.WHAT.OFF.equals(what)) { // skipping OFF msg: cannot distinguish dimmer/switch
316                     logger.debug("discoverByActivation: skipping OFF msg: cannot distinguish dimmer/switch");
317                     return;
318                 }
319                 if (Lighting.WHAT.ON.equals(what)) { // if not already done just now, request light status to
320                                                      // distinguish dimmer from switch
321                     if (discoveringDevices.containsKey(ownIdFromMessage(baseMsg))) {
322                         logger.debug(
323                                 "discoverByActivation: we just requested status for this device and it's ON -> it's a switch");
324                     } else {
325                         OpenGateway gw = gateway;
326                         if (gw != null) {
327                             try {
328                                 discoveringDevices.put(ownIdFromMessage(baseMsg),
329                                         Long.valueOf(System.currentTimeMillis()));
330                                 gw.send(Lighting.requestStatus(baseMsg.getWhere().value()));
331                                 return;
332                             } catch (OWNException e) {
333                                 logger.warn("discoverByActivation: Exception while requesting light state: {}",
334                                         e.getMessage());
335                                 return;
336                             }
337                         }
338                     }
339                 }
340                 discoveringDevices.remove(ownIdFromMessage(baseMsg));
341             }
342             OpenDeviceType type = null;
343             try {
344                 type = bmsg.detectDeviceType();
345             } catch (FrameException e) {
346                 logger.warn("Exception while detecting device type: {}", e.getMessage());
347             }
348             if (type != null) {
349                 discService.newDiscoveryResult(bmsg.getWhere(), type, bmsg);
350             } else {
351                 logger.debug("discoverByActivation: no device type detected from msg: {}", bmsg);
352             }
353         }
354     }
355
356     /**
357      * Register a device ThingHandler to this BridgHandler
358      *
359      * @param ownId the device OpenWebNet id
360      * @param thingHandler the thing handler to be registered
361      */
362     protected void registerDevice(String ownId, OpenWebNetThingHandler thingHandler) {
363         if (registeredDevices.containsKey(ownId)) {
364             logger.warn("registering device with an existing ownId={}", ownId);
365         }
366         registeredDevices.put(ownId, thingHandler);
367         logger.debug("registered device ownId={}, thing={}", ownId, thingHandler.getThing().getUID());
368     }
369
370     /**
371      * Un-register a device from this bridge handler
372      *
373      * @param ownId the device OpenWebNet id
374      */
375     protected void unregisterDevice(String ownId) {
376         if (registeredDevices.remove(ownId) != null) {
377             logger.debug("un-registered device ownId={}", ownId);
378         } else {
379             logger.warn("could not un-register ownId={} (not found)", ownId);
380         }
381     }
382
383     /**
384      * Get an already registered device on this bridge handler
385      *
386      * @param ownId the device OpenWebNet id
387      * @return the registered device Thing handler or null if the id cannot be found
388      */
389     public @Nullable OpenWebNetThingHandler getRegisteredDevice(String ownId) {
390         return registeredDevices.get(ownId);
391     }
392
393     private void refreshAllDevices() {
394         logger.debug("Refreshing all devices for bridge {}", thing.getUID());
395         for (Thing ownThing : getThing().getThings()) {
396             OpenWebNetThingHandler hndlr = (OpenWebNetThingHandler) ownThing.getHandler();
397             if (hndlr != null) {
398                 hndlr.refreshDevice(true);
399             }
400         }
401     }
402
403     @Override
404     public void onEventMessage(@Nullable OpenMessage msg) {
405         logger.trace("RECEIVED <<<<< {}", msg);
406         if (msg == null) {
407             logger.warn("received event msg is null");
408             return;
409         }
410         if (msg.isACK() || msg.isNACK()) {
411             return; // we ignore ACKS/NACKS
412         }
413         // GATEWAY MANAGEMENT
414         if (msg instanceof GatewayMgmt) {
415             // noop
416             return;
417         }
418
419         BaseOpenMessage baseMsg = (BaseOpenMessage) msg;
420         // let's try to get the Thing associated with this message...
421         if (baseMsg instanceof Lighting || baseMsg instanceof Automation || baseMsg instanceof EnergyManagement) {
422             String ownId = ownIdFromMessage(baseMsg);
423             logger.debug("ownIdFromMessage({}) --> {}", baseMsg, ownId);
424             OpenWebNetThingHandler deviceHandler = registeredDevices.get(ownId);
425             if (deviceHandler == null) {
426                 OpenGateway gw = gateway;
427                 if (isBusGateway && ((gw != null && !gw.isDiscovering() && scanIsActive)
428                         || (discoveryByActivation && !scanIsActive))) {
429                     discoverByActivation(baseMsg);
430                 } else {
431                     logger.debug("ownId={} has NO DEVICE associated, ignoring it", ownId);
432                 }
433             } else {
434                 deviceHandler.handleMessage(baseMsg);
435             }
436         } else {
437             logger.debug("BridgeHandler ignoring frame {}. WHO={} is not supported by this binding", baseMsg,
438                     baseMsg.getWho());
439         }
440     }
441
442     @Override
443     public void onConnected() {
444         isGatewayConnected = true;
445         Map<String, String> properties = editProperties();
446         boolean propertiesChanged = false;
447         OpenGateway gw = gateway;
448         if (gw == null) {
449             logger.warn("received onConnected() but gateway is null");
450             return;
451         }
452         if (gw instanceof USBGateway) {
453             logger.info("---- CONNECTED to ZigBee USB gateway bridge '{}' (serialPort: {})", thing.getUID(),
454                     ((USBGateway) gw).getSerialPortName());
455         } else {
456             logger.info("---- CONNECTED to BUS gateway bridge '{}' ({}:{})", thing.getUID(),
457                     ((BUSGateway) gw).getHost(), ((BUSGateway) gw).getPort());
458             // update serial number property (with MAC address)
459             if (properties.get(PROPERTY_SERIAL_NO) != gw.getMACAddr().toUpperCase()) {
460                 properties.put(PROPERTY_SERIAL_NO, gw.getMACAddr().toUpperCase());
461                 propertiesChanged = true;
462                 logger.debug("updated property gw serialNumber: {}", properties.get(PROPERTY_SERIAL_NO));
463             }
464         }
465         if (properties.get(PROPERTY_FIRMWARE_VERSION) != gw.getFirmwareVersion()) {
466             properties.put(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
467             propertiesChanged = true;
468             logger.debug("updated property gw firmware version: {}", properties.get(PROPERTY_FIRMWARE_VERSION));
469         }
470         if (propertiesChanged) {
471             updateProperties(properties);
472             logger.info("properties updated for bridge '{}'", thing.getUID());
473         }
474         updateStatus(ThingStatus.ONLINE);
475         // schedule a refresh for all devices
476         refreshSchedule = scheduler.schedule(this::refreshAllDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
477                 TimeUnit.MILLISECONDS);
478     }
479
480     @Override
481     public void onConnectionError(@Nullable OWNException error) {
482         String errMsg;
483         if (error == null) {
484             errMsg = "unknown error";
485         } else {
486             errMsg = error.getMessage();
487         }
488         logger.info("---- ON CONNECTION ERROR for gateway {}: {}", gateway, errMsg);
489         isGatewayConnected = false;
490         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
491                 "@text/offline.comm-error-connection" + " (onConnectionError - " + errMsg + ")");
492         tryReconnectGateway();
493     }
494
495     @Override
496     public void onConnectionClosed() {
497         isGatewayConnected = false;
498         logger.debug("onConnectionClosed() - isGatewayConnected={}", isGatewayConnected);
499         // NOTE: cannot change to OFFLINE here because we are already in REMOVING state
500     }
501
502     @Override
503     public void onDisconnected(@Nullable OWNException e) {
504         isGatewayConnected = false;
505         String errMsg;
506         if (e == null) {
507             errMsg = "unknown error";
508         } else {
509             errMsg = e.getMessage();
510         }
511         logger.info("---- DISCONNECTED from gateway {}. OWNException: {}", gateway, errMsg);
512         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
513                 "@text/offline.comm-error-disconnected" + " (onDisconnected - " + errMsg + ")");
514         tryReconnectGateway();
515     }
516
517     private void tryReconnectGateway() {
518         OpenGateway gw = gateway;
519         if (gw != null) {
520             if (!reconnecting) {
521                 reconnecting = true;
522                 logger.info("---- Starting RECONNECT cycle to gateway {}", gw);
523                 try {
524                     gw.reconnect();
525                 } catch (OWNAuthException e) {
526                     logger.info("---- AUTH error from gateway. Stopping re-connect");
527                     reconnecting = false;
528                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
529                             "@text/offline.conf-error-auth" + " (" + e + ")");
530                 }
531             } else {
532                 logger.debug("---- reconnecting=true");
533             }
534         } else {
535             logger.warn("---- cannot start RECONNECT, gateway is null");
536         }
537     }
538
539     @Override
540     public void onReconnected() {
541         reconnecting = false;
542         OpenGateway gw = gateway;
543         logger.info("---- RE-CONNECTED to bridge {}", thing.getUID());
544         if (gw != null) {
545             updateStatus(ThingStatus.ONLINE);
546             if (gw.getFirmwareVersion() != null) {
547                 this.updateProperty(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
548                 logger.debug("gw firmware version: {}", gw.getFirmwareVersion());
549             }
550
551             // schedule a refresh for all devices
552             refreshSchedule = scheduler.schedule(this::refreshAllDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
553                     TimeUnit.MILLISECONDS);
554         }
555     }
556
557     /**
558      * Return a ownId string (=WHO.WHERE) from the device Where address and handler
559      *
560      * @param where the Where address (to be normalized)
561      * @param handler the device handler
562      * @return the ownId String
563      */
564     protected String ownIdFromDeviceWhere(Where where, OpenWebNetThingHandler handler) {
565         return handler.ownIdPrefix() + "." + normalizeWhere(where);
566     }
567
568     /**
569      * Returns a ownId string (=WHO.WHERE) from a Who and Where address
570      *
571      * @param who the Who
572      * @param where the Where address (to be normalized)
573      * @return the ownId String
574      */
575     public String ownIdFromWhoWhere(Who who, Where where) {
576         return who.value() + "." + normalizeWhere(where);
577     }
578
579     /**
580      * Return a ownId string (=WHO.WHERE) from a BaseOpenMessage
581      *
582      * @param baseMsg the BaseOpenMessage
583      * @return the ownId String
584      */
585     public String ownIdFromMessage(BaseOpenMessage baseMsg) {
586         return baseMsg.getWho().value() + "." + normalizeWhere(baseMsg.getWhere());
587     }
588
589     /**
590      * Transform a Where address into a Thing id string
591      *
592      * @param where the Where address
593      * @return the thing Id string
594      */
595     public String thingIdFromWhere(Where where) {
596         return normalizeWhere(where); // '#' cannot be used in ThingUID;
597     }
598
599     /**
600      * Normalize a Where address
601      *
602      * @param where the Where address
603      * @return the normalized address as String
604      */
605     public String normalizeWhere(Where where) {
606         String str = where.value();
607         if (where instanceof WhereZigBee) {
608             str = ((WhereZigBee) where).valueWithUnit(WhereZigBee.UNIT_ALL); // 76543210X#9 --> 765432100#9
609         } else {
610             if (str.indexOf("#4#") == -1) { // skip APL#4#bus case
611                 if (str.indexOf('#') == 0) { // Thermo central unit (#0) or zone via central unit (#Z, Z=[1-99]) --> Z
612                     str = str.substring(1);
613                 } else if (str.indexOf('#') > 0) { // Thermo zone Z and actuator N (Z#N, Z=[1-99], N=[1-9]) --> Z
614                     str = str.substring(0, str.indexOf('#'));
615                 }
616             }
617         }
618         return str.replace('#', 'h');
619     }
620 }