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