2 * Copyright (c) 2010-2022 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.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;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import javax.measure.Quantity;
24 import javax.measure.Unit;
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;
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
55 * @author Massimo Valla - Initial contribution
58 public abstract class OpenWebNetThingHandler extends BaseThingHandler {
60 private final Logger logger = LoggerFactory.getLogger(OpenWebNetThingHandler.class);
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
66 protected @Nullable ScheduledFuture<?> requestChannelStateTimeout;
67 protected @Nullable ScheduledFuture<?> refreshTimeout;
69 private static final int ALL_DEVICES_REFRESH_INTERVAL_MSEC = 60_000; // interval before sending another
70 // refreshAllDevices request
72 public OpenWebNetThingHandler(Thing thing) {
77 public void initialize() {
78 Bridge bridge = getBridge();
80 OpenWebNetBridgeHandler brH = (OpenWebNetBridgeHandler) bridge.getHandler();
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");
91 if (brH.isBusGateway()) {
92 w = buildBusWhere(configDeviceWhere);
94 w = new WhereZigBee(configDeviceWhere);
96 } catch (IllegalArgumentException ia) {
97 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
98 "@text/offline.conf-error-where");
102 final String oid = brH.ownIdFromDeviceWhere(w, this);
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");
113 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
114 "@text/offline.conf-error-no-bridge");
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);
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());
137 if (deviceWhere == null) {
138 logger.info("Cannot handle {} command for {}: 'where' parameter is not configured or is invalid",
139 command, thing.getUID());
142 if (command instanceof RefreshType) {
143 requestChannelState(channel);
145 handleChannelCommand(channel, command);
148 logger.debug("Thing {} is not associated to any gateway, skipping command", getThing().getUID());
153 * Handles a command for the specific channel for this thing.
154 * It must be further implemented by each specific device handler.
156 * @param channel the {@link ChannelUID}
157 * @param command the Command to be executed
159 protected abstract void handleChannelCommand(ChannelUID channel, Command command);
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.
165 * @param msg the message to handle
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);
175 * Helper method to send OWN messages from handler.
177 * @param msg the OpenMessage to be sent
179 public @Nullable Response send(OpenMessage msg) throws OWNException {
180 OpenWebNetBridgeHandler bh = bridgeHandler;
182 OpenGateway gw = bh.gateway;
187 logger.warn("Couldn't send message {}: handler or gateway is null", msg);
192 * Helper method to send with high priority OWN messages from handler.
194 * @param msg the OpenMessage to be sent
196 protected @Nullable Response sendHighPriority(OpenMessage msg) throws OWNException {
197 OpenWebNetBridgeHandler handler = bridgeHandler;
198 if (handler != null) {
199 OpenGateway gw = handler.gateway;
201 return gw.sendHighPriority(msg);
208 * Request the state for the specified channel. If no answer is received within THING_STATE_REQ_TIMEOUT_SEC, it is
210 * The method must be further implemented by each specific handler.
212 * @param channel the {@link ChannelUID} to request the state for
214 protected void requestChannelState(ChannelUID channel) {
215 logger.debug("requestChannelState() {}", channel);
216 Where w = deviceWhere;
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");
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");
229 }, THING_STATE_REQ_TIMEOUT_SEC, TimeUnit.SECONDS);
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.
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.
239 protected abstract void refreshDevice(boolean refreshAll);
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.
246 * @return timestamp when last refreshAll command was sent, 0 if not requested yet, or -1 if it's not supported by
249 protected long getRefreshAllLastTS() {
254 * Refresh all devices for this handler
256 protected void refreshAllDevices() {
257 logger.debug("--- refreshAllDevices() for device {}", thing.getUID());
258 OpenWebNetBridgeHandler brH = bridgeHandler;
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());
268 logger.debug("--- refreshAllDevices() : refresh all devices just sent... ({})", thing.getUID());
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)) {
275 "--- refreshAllDevices() : schedule expired: --UNKNOWN-- status for {}. Refreshing it...",
277 refreshDevice(false);
279 logger.debug("--- refreshAllDevices() : schedule expired: ONLINE status for {}",
282 }, THING_STATE_REQ_TIMEOUT_SEC, TimeUnit.SECONDS);
283 } else { // USB device or AllDevicesRefresh not supported
284 refreshDevice(false);
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).
293 * @param wStr the WHERE string
295 protected abstract Where buildBusWhere(String wStr) throws IllegalArgumentException;
298 public void dispose() {
299 OpenWebNetBridgeHandler bh = bridgeHandler;
301 if (bh != null && oid != null) {
302 bh.unregisterDevice(oid);
304 ScheduledFuture<?> rcst = requestChannelStateTimeout;
308 ScheduledFuture<?> rt = refreshTimeout;
316 * Helper method to return a Quantity from a Number value or UnDefType.NULL if value is null
318 * @param value to be used
319 * @param unit to be used
322 protected <U extends Quantity<U>> State getAsQuantityTypeOrNull(@Nullable Number value, Unit<U> unit) {
323 return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
327 * Returns a prefix String for ownId specific for each handler. To be implemented by sub-classes.
331 protected abstract String ownIdPrefix();