2 * Copyright (c) 2010-2020 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.upb.internal.handler;
15 import static org.openhab.binding.upb.internal.message.Command.*;
17 import java.math.BigDecimal;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.binding.upb.internal.Constants;
22 import org.openhab.binding.upb.internal.UPBDevice;
23 import org.openhab.binding.upb.internal.handler.UPBIoHandler.CmdStatus;
24 import org.openhab.binding.upb.internal.message.MessageBuilder;
25 import org.openhab.binding.upb.internal.message.UPBMessage;
26 import org.openhab.core.library.types.OnOffType;
27 import org.openhab.core.library.types.PercentType;
28 import org.openhab.core.thing.Bridge;
29 import org.openhab.core.thing.Channel;
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.thing.type.ChannelTypeUID;
37 import org.openhab.core.types.Command;
38 import org.openhab.core.types.RefreshType;
39 import org.openhab.core.types.State;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
44 * Handler for things representing devices on an UPB network.
46 * @author Marcus Better - Initial contribution
50 public class UPBThingHandler extends BaseThingHandler {
51 // Time to wait between attempts to poll the device to refresh state
52 private static final long REFRESH_INTERVAL_MS = 3_000;
54 private final Logger logger = LoggerFactory.getLogger(UPBThingHandler.class);
55 private final @Nullable Byte defaultNetworkId;
57 protected volatile byte networkId;
58 protected volatile int unitId;
59 private volatile long lastRefreshMillis;
61 public UPBThingHandler(final Thing device, final @Nullable Byte defaultNetworkId) {
63 this.defaultNetworkId = defaultNetworkId;
67 public void initialize() {
68 logger.debug("initializing UPB thing handler {}", getThing().getUID());
70 final BigDecimal val = (BigDecimal) getConfig().get(Constants.CONFIGURATION_NETWORK_ID);
72 // use value from binding config
73 final Byte defaultNetworkId = this.defaultNetworkId;
74 if (defaultNetworkId == null) {
75 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "missing network ID");
78 networkId = defaultNetworkId.byteValue();
79 } else if (val.compareTo(BigDecimal.ZERO) < 0 || val.compareTo(BigDecimal.valueOf(255)) > 0) {
80 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "invalid network ID");
83 networkId = val.byteValue();
86 final BigDecimal cfgUnitId = (BigDecimal) getConfig().get(Constants.CONFIGURATION_UNIT_ID);
87 if (cfgUnitId == null) {
88 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "missing unit ID");
91 unitId = cfgUnitId.intValue();
92 if (unitId < 1 || unitId > 250) {
93 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "invalid unit ID");
97 final Bridge bridge = getBridge();
99 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, Constants.OFFLINE_CTLR_OFFLINE);
102 bridgeStatusChanged(bridge.getStatusInfo());
106 public void bridgeStatusChanged(final ThingStatusInfo bridgeStatusInfo) {
107 logger.debug("DEV {}: Controller status is {}", unitId, bridgeStatusInfo.getStatus());
109 if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
110 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, Constants.OFFLINE_CTLR_OFFLINE);
114 logger.debug("DEV {}: Controller is ONLINE. Starting device initialisation.", unitId);
116 final Bridge bridge = getBridge();
117 if (bridge == null) {
118 logger.debug("DEV {}: bridge is null!", unitId);
121 final PIMHandler bridgeHandler = (PIMHandler) bridge.getHandler();
122 if (bridgeHandler == null) {
123 logger.debug("DEV {}: bridge handler is null!", unitId);
126 updateDeviceStatus(bridgeHandler);
131 public void handleCommand(final ChannelUID channelUID, final Command cmd) {
132 final PIMHandler pimHandler = getPIMHandler();
133 if (pimHandler == null) {
134 logger.warn("DEV {}: received cmd {} but no bridge handler", unitId, cmd);
138 final MessageBuilder message;
139 if (cmd == OnOffType.ON) {
140 message = MessageBuilder.forCommand(ACTIVATE);
141 } else if (cmd == OnOffType.OFF) {
142 message = MessageBuilder.forCommand(DEACTIVATE);
143 } else if (cmd instanceof PercentType) {
144 message = MessageBuilder.forCommand(GOTO).args(((PercentType) cmd).byteValue());
145 } else if (cmd == RefreshType.REFRESH) {
146 refreshDeviceState();
149 logger.warn("channel {}: unsupported cmd {}", channelUID, cmd);
153 message.network(networkId).destination(getUnitId());
154 pimHandler.sendPacket(message).thenAccept(this::updateStatus);
157 public void onMessageReceived(final UPBMessage msg) {
158 updateStatus(ThingStatus.ONLINE);
159 if (msg.getControlWord().isLink()) {
160 handleLinkMessage(msg);
162 handleDirectMessage(msg);
166 private void handleDirectMessage(final UPBMessage msg) {
168 switch (msg.getCommand()) {
170 state = OnOffType.ON;
174 state = OnOffType.OFF;
178 if (msg.getArguments().length == 0) {
179 logger.warn("DEV {}: malformed GOTO cmd", unitId);
182 final int level = msg.getArguments()[0];
183 state = new PercentType(level);
187 logger.debug("DEV {}: Message {} ignored", unitId, msg.getCommand());
190 updateState(Constants.DIMMER_TYPE_ID, state);
193 private void handleLinkMessage(final UPBMessage msg) {
194 final byte linkId = msg.getDestination();
195 for (final Channel ch : getThing().getChannels()) {
196 ChannelTypeUID channelTypeUID = ch.getChannelTypeUID();
197 if (channelTypeUID != null && Constants.SCENE_CHANNEL_TYPE_ID.equals(channelTypeUID.getId())) {
198 final BigDecimal channelLinkId = (BigDecimal) ch.getConfiguration()
199 .get(Constants.CONFIGURATION_LINK_ID);
200 if (channelLinkId == null || channelLinkId.byteValue() != linkId) {
203 switch (msg.getCommand()) {
206 triggerChannel(ch.getUID(), msg.getCommand().name());
210 logger.debug("DEV {}: Message {} ignored for link {}", unitId, linkId & 0xff, msg.getCommand());
217 private void updateDeviceStatus(final PIMHandler bridgeHandler) {
218 final UPBDevice device = bridgeHandler.getDevice(getNetworkId(), getUnitId());
219 if (device == null) {
220 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, Constants.OFFLINE_NODE_NOTFOUND);
222 switch (device.getState()) {
225 updateStatus(ThingStatus.ONLINE);
229 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
230 Constants.OFFLINE_NODE_DEAD);
236 protected void pingDevice() {
237 final PIMHandler pimHandler = getPIMHandler();
238 if (pimHandler != null) {
239 pimHandler.sendPacket(
240 MessageBuilder.forCommand(NULL).ackMessage(true).network(networkId).destination((byte) unitId))
241 .thenAccept(this::updateStatus);
245 private void updateStatus(final CmdStatus result) {
248 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, Constants.OFFLINE_NODE_DEAD);
253 updateStatus(ThingStatus.ONLINE);
258 private void refreshDeviceState() {
259 // This polls the device to see if it is alive. Since the REFRESH command is sent
260 // for each channel and we want to avoid unnecessary traffic, we only ping the device
261 // if some time has elapsed since the last refresh.
262 final long now = System.currentTimeMillis();
263 if (now - lastRefreshMillis > REFRESH_INTERVAL_MS) {
264 lastRefreshMillis = now;
265 final PIMHandler pimHandler = getPIMHandler();
266 if (pimHandler != null) {
268 .sendPacket(MessageBuilder.forCommand(REPORT_STATE).network(networkId).destination(getUnitId()))
269 .thenAccept(this::updateStatus);
274 protected @Nullable PIMHandler getPIMHandler() {
275 final Bridge bridge = getBridge();
276 if (bridge == null) {
277 logger.debug("DEV {}: bridge is null!", unitId);
280 final PIMHandler bridgeHandler = (PIMHandler) bridge.getHandler();
281 if (bridgeHandler == null) {
282 logger.debug("DEV {}: bridge handler is null!", unitId);
285 return bridgeHandler;
288 public byte getNetworkId() {
292 public byte getUnitId() {
293 return (byte) unitId;