]> git.basschouten.com Git - openhab-addons.git/blob
8d4ceb13370520d66c98cc0edff3c2c02c334bd2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.internal.handler;
14
15 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.*;
16
17 import java.util.Map;
18 import java.util.concurrent.ScheduledFuture;
19 import java.util.concurrent.TimeUnit;
20
21 import javax.measure.Quantity;
22 import javax.measure.Unit;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.core.library.types.QuantityType;
27 import org.openhab.core.thing.Bridge;
28 import org.openhab.core.thing.ChannelUID;
29 import org.openhab.core.thing.Thing;
30 import org.openhab.core.thing.ThingStatus;
31 import org.openhab.core.thing.ThingStatusDetail;
32 import org.openhab.core.thing.ThingStatusInfo;
33 import org.openhab.core.thing.binding.BaseThingHandler;
34 import org.openhab.core.types.Command;
35 import org.openhab.core.types.RefreshType;
36 import org.openhab.core.types.State;
37 import org.openhab.core.types.UnDefType;
38 import org.openwebnet4j.OpenGateway;
39 import org.openwebnet4j.communication.OWNException;
40 import org.openwebnet4j.communication.Response;
41 import org.openwebnet4j.message.BaseOpenMessage;
42 import org.openwebnet4j.message.OpenMessage;
43 import org.openwebnet4j.message.Where;
44 import org.openwebnet4j.message.WhereZigBee;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * The {@link OpenWebNetThingHandler} is responsible for handling commands for
50  * an OpenWebNet device.
51  * It's the abstract class for all OpenWebNet things. It should be extended by
52  * each specific OpenWebNet category of device (WHO).
53  *
54  * @author Massimo Valla - Initial contribution
55  */
56 @NonNullByDefault
57 public abstract class OpenWebNetThingHandler extends BaseThingHandler {
58
59     private final Logger logger = LoggerFactory.getLogger(OpenWebNetThingHandler.class);
60
61     protected @Nullable OpenWebNetBridgeHandler bridgeHandler;
62     protected @Nullable String ownId; // OpenWebNet identifier for this device: WHO.WHERE
63     protected @Nullable Where deviceWhere; // this device WHERE address
64
65     protected @Nullable ScheduledFuture<?> requestChannelStateTimeout;
66     protected @Nullable ScheduledFuture<?> refreshTimeout;
67
68     private static final int ALL_DEVICES_REFRESH_INTERVAL_MSEC = 60_000; // interval before sending another
69                                                                          // refreshAllDevices request
70
71     public OpenWebNetThingHandler(Thing thing) {
72         super(thing);
73     }
74
75     @Override
76     public void initialize() {
77         Bridge bridge = getBridge();
78         if (bridge != null) {
79             OpenWebNetBridgeHandler brH = (OpenWebNetBridgeHandler) bridge.getHandler();
80             if (brH != null) {
81                 bridgeHandler = brH;
82
83                 final String configDeviceWhere = (String) getConfig().get(CONFIG_PROPERTY_WHERE);
84                 if (configDeviceWhere == null) {
85                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
86                             "@text/offline.conf-error-where");
87                 } else {
88                     Where w;
89                     try {
90                         if (brH.isBusGateway()) {
91                             w = buildBusWhere(configDeviceWhere);
92                         } else {
93                             w = new WhereZigBee(configDeviceWhere);
94                         }
95                     } catch (IllegalArgumentException ia) {
96                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
97                                 "@text/offline.conf-error-where");
98                         return;
99                     }
100                     deviceWhere = w;
101                     final String oid = brH.ownIdFromDeviceWhere(w, this);
102                     ownId = oid;
103                     Map<String, String> properties = editProperties();
104                     properties.put(PROPERTY_OWNID, oid);
105                     updateProperties(properties);
106                     brH.registerDevice(oid, this);
107                     logger.debug("associated thing to bridge with ownId={}", ownId);
108                     updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "@text/unknown.waiting-state");
109                 }
110             }
111         } else {
112             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
113                     "@text/offline.conf-error-no-bridge");
114         }
115     }
116
117     @Override
118     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
119         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
120             updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "@text/unknown.waiting-state");
121         } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
122             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "@text/offline.bridge-offline");
123         }
124     }
125
126     @Override
127     public void handleCommand(ChannelUID channel, Command command) {
128         logger.debug("handleCommand() (command={} - channel={})", command, channel);
129         OpenWebNetBridgeHandler handler = bridgeHandler;
130         if (handler != null) {
131             OpenGateway gw = handler.gateway;
132             if (gw != null && !gw.isConnected()) {
133                 logger.info("Cannot handle {} command for {}: gateway is not connected", command, thing.getUID());
134                 return;
135             }
136             if (deviceWhere == null) {
137                 logger.info("Cannot handle {} command for {}: 'where' parameter is not configured or is invalid",
138                         command, thing.getUID());
139                 return;
140             }
141             if (command instanceof RefreshType) {
142                 requestChannelState(channel);
143             } else {
144                 handleChannelCommand(channel, command);
145             }
146         } else {
147             logger.debug("Thing {} is not associated to any gateway, skipping command", getThing().getUID());
148         }
149     }
150
151     /**
152      * Handles a command for the specific channel for this thing.
153      * It must be further implemented by each specific device handler.
154      *
155      * @param channel the {@link ChannelUID}
156      * @param command the Command to be executed
157      */
158     protected abstract void handleChannelCommand(ChannelUID channel, Command command);
159
160     /**
161      * Handle incoming message from OWN network via bridge Thing, directed to this
162      * device. It should be further implemented by each specific device handler.
163      *
164      * @param msg the message to handle
165      */
166     protected void handleMessage(BaseOpenMessage msg) {
167         ThingStatus ts = getThing().getStatus();
168         if (ThingStatus.ONLINE != ts && ThingStatus.REMOVING != ts && ThingStatus.REMOVED != ts) {
169             updateStatus(ThingStatus.ONLINE);
170         }
171     }
172
173     /**
174      * Helper method to send OWN messages from handler.
175      *
176      * @param msg the OpenMessage to be sent
177      */
178     public @Nullable Response send(OpenMessage msg) throws OWNException {
179         OpenWebNetBridgeHandler bh = bridgeHandler;
180         if (bh != null) {
181             OpenGateway gw = bh.gateway;
182             if (gw != null) {
183                 return gw.send(msg);
184             }
185         }
186         logger.warn("Couldn't send message {}: handler or gateway is null", msg);
187         return null;
188     }
189
190     /**
191      * Helper method to send with high priority OWN messages from handler.
192      *
193      * @param msg the OpenMessage to be sent
194      */
195     protected @Nullable Response sendHighPriority(OpenMessage msg) throws OWNException {
196         OpenWebNetBridgeHandler handler = bridgeHandler;
197         if (handler != null) {
198             OpenGateway gw = handler.gateway;
199             if (gw != null) {
200                 return gw.sendHighPriority(msg);
201             }
202         }
203         return null;
204     }
205
206     /**
207      * Request the state for the specified channel. If no answer is received within
208      * THING_STATE_REQ_TIMEOUT_SEC, it is put OFFLINE.
209      * The method must be further implemented by each specific handler.
210      *
211      * @param channel the {@link ChannelUID} to request the state for
212      */
213     protected void requestChannelState(ChannelUID channel) {
214         logger.debug("requestChannelState() {}", channel);
215         Where w = deviceWhere;
216         if (w == null) {
217             logger.warn("Could not requestChannelState(): deviceWhere is null for thing {}", thing.getUID());
218             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-where");
219             return;
220         }
221         // set a schedule to put device OFFLINE if no answer is received after
222         // THING_STATE_REQ_TIMEOUT_SEC
223         requestChannelStateTimeout = scheduler.schedule(() -> {
224             if (thing.getStatus().equals(ThingStatus.UNKNOWN)) {
225                 logger.debug("requestChannelState() TIMEOUT for thing {}", thing.getUID());
226                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
227                         "@text/offline.comm-error-state");
228             }
229         }, THING_STATE_REQ_TIMEOUT_SEC, TimeUnit.SECONDS);
230     }
231
232     /**
233      * Refresh a device, possibly using a single OWN command if refreshAll=true and
234      * if supported. The method must be further implemented by each specific
235      * handler.
236      *
237      * @param refreshAll true if all devices for this handler must be refreshed with
238      *            a single OWN command, if supported, otherwise just refresh
239      *            the single device.
240      */
241     protected abstract void refreshDevice(boolean refreshAll);
242
243     /**
244      * If the subclass supports refreshing all devices with a single OWN command,
245      * returns the last TS when a refreshAll was requested, or 0 if not requested
246      * yet. If not supported return -1 (default).
247      * It must be implemented by each subclass that supports all devices refresh.
248      *
249      * @return timestamp when last refreshAll command was sent, 0 if not requested
250      *         yet, or -1 if it's not supported by subclass.
251      */
252     protected long getRefreshAllLastTS() {
253         return -1;
254     };
255
256     /**
257      * Refresh all devices for this handler
258      */
259     protected void refreshAllDevices() {
260         logger.debug("--- refreshAllDevices() for device {}", thing.getUID());
261         OpenWebNetBridgeHandler brH = bridgeHandler;
262         if (brH != null) {
263             long refAllTS = getRefreshAllLastTS();
264             logger.debug("{} support = {}", thing.getUID(), refAllTS >= 0);
265             if (brH.isBusGateway() && refAllTS >= 0) {
266                 long now = System.currentTimeMillis();
267                 if (now - refAllTS > ALL_DEVICES_REFRESH_INTERVAL_MSEC) {
268                     logger.debug("--- refreshAllDevices() : refreshing ALL devices... ({})", thing.getUID());
269                     refreshDevice(true);
270                 } else {
271                     logger.debug("--- refreshAllDevices() : refresh all devices sent {}msec ago, skipping... ({})",
272                             ALL_DEVICES_REFRESH_INTERVAL_MSEC, thing.getUID());
273                 }
274                 // sometimes GENERAL (e.g. #*1*0##) refresh requests do not return state for all
275                 // devices, so let's schedule another single refresh device, just in case
276                 refreshTimeout = scheduler.schedule(() -> {
277                     if (thing.getStatus().equals(ThingStatus.UNKNOWN)) {
278                         logger.debug(
279                                 "--- refreshAllDevices() : schedule expired: --UNKNOWN-- status for {}. Refreshing it...",
280                                 thing.getUID());
281                         refreshDevice(false);
282                     } else {
283                         logger.debug("--- refreshAllDevices() : schedule expired: ONLINE status for {}",
284                                 thing.getUID());
285                     }
286                 }, THING_STATE_REQ_TIMEOUT_SEC, TimeUnit.SECONDS);
287             } else { // USB device or AllDevicesRefresh not supported
288                 refreshDevice(false);
289             }
290         }
291     }
292
293     /**
294      * Abstract builder for device Where address, to be implemented by each subclass
295      * to choose the right Where subclass (the method is used only if the Thing is
296      * associated to a BUS gateway).
297      *
298      * @param wStr the WHERE string
299      */
300     protected abstract Where buildBusWhere(String wStr) throws IllegalArgumentException;
301
302     @Override
303     public void dispose() {
304         OpenWebNetBridgeHandler bh = bridgeHandler;
305         String oid = ownId;
306         if (bh != null && oid != null) {
307             bh.unregisterDevice(oid);
308         }
309         ScheduledFuture<?> rcst = requestChannelStateTimeout;
310         if (rcst != null) {
311             rcst.cancel(true);
312         }
313         ScheduledFuture<?> rt = refreshTimeout;
314         if (rt != null) {
315             rt.cancel(true);
316         }
317         super.dispose();
318     }
319
320     /**
321      * Helper method to return a Quantity from a Number value or UnDefType.NULL if
322      * value is null
323      *
324      * @param value to be used
325      * @param unit to be used
326      * @return Quantity
327      */
328     protected <U extends Quantity<U>> State getAsQuantityTypeOrNull(@Nullable Number value, Unit<U> unit) {
329         return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
330     }
331
332     /**
333      * Returns a prefix String for ownId specific for each handler.
334      * To be implemented by sub-classes.
335      *
336      * @return
337      */
338     protected abstract String ownIdPrefix();
339 }