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