2 * Copyright (c) 2010-2023 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.ACTIVATE;
16 import static org.openhab.binding.upb.internal.message.Command.DEACTIVATE;
17 import static org.openhab.binding.upb.internal.message.Command.GOTO;
18 import static org.openhab.binding.upb.internal.message.Command.NULL;
19 import static org.openhab.binding.upb.internal.message.Command.REPORT_STATE;
21 import java.math.BigDecimal;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.upb.internal.Constants;
26 import org.openhab.binding.upb.internal.UPBDevice;
27 import org.openhab.binding.upb.internal.handler.UPBIoHandler.CmdStatus;
28 import org.openhab.binding.upb.internal.message.MessageBuilder;
29 import org.openhab.binding.upb.internal.message.UPBMessage;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.PercentType;
32 import org.openhab.core.thing.Bridge;
33 import org.openhab.core.thing.Channel;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.thing.ThingStatus;
37 import org.openhab.core.thing.ThingStatusDetail;
38 import org.openhab.core.thing.ThingStatusInfo;
39 import org.openhab.core.thing.binding.BaseThingHandler;
40 import org.openhab.core.thing.type.ChannelTypeUID;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.RefreshType;
43 import org.openhab.core.types.State;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * Handler for things representing devices on an UPB network.
50 * @author Marcus Better - Initial contribution
54 public class UPBThingHandler extends BaseThingHandler {
55 // Time to wait between attempts to poll the device to refresh state
56 private static final long REFRESH_INTERVAL_MS = 3_000;
58 private final Logger logger = LoggerFactory.getLogger(UPBThingHandler.class);
59 private final @Nullable Byte defaultNetworkId;
61 protected volatile byte networkId;
62 protected volatile int unitId;
63 private volatile long lastRefreshMillis;
65 public UPBThingHandler(final Thing device, final @Nullable Byte defaultNetworkId) {
67 this.defaultNetworkId = defaultNetworkId;
71 public void initialize() {
72 logger.debug("initializing UPB thing handler {}", getThing().getUID());
74 final BigDecimal val = (BigDecimal) getConfig().get(Constants.CONFIGURATION_NETWORK_ID);
76 // use value from binding config
77 final Byte defaultNetworkId = this.defaultNetworkId;
78 if (defaultNetworkId == null) {
79 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "missing network ID");
82 networkId = defaultNetworkId.byteValue();
83 } else if (val.compareTo(BigDecimal.ZERO) < 0 || val.compareTo(BigDecimal.valueOf(255)) > 0) {
84 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "invalid network ID");
87 networkId = val.byteValue();
90 final BigDecimal cfgUnitId = (BigDecimal) getConfig().get(Constants.CONFIGURATION_UNIT_ID);
91 if (cfgUnitId == null) {
92 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "missing unit ID");
95 unitId = cfgUnitId.intValue();
96 if (unitId < 1 || unitId > 250) {
97 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "invalid unit ID");
101 final Bridge bridge = getBridge();
102 if (bridge == null) {
103 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, Constants.OFFLINE_CTLR_OFFLINE);
106 bridgeStatusChanged(bridge.getStatusInfo());
110 public void bridgeStatusChanged(final ThingStatusInfo bridgeStatusInfo) {
111 logger.debug("DEV {}: Controller status is {}", unitId, bridgeStatusInfo.getStatus());
113 if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
114 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, Constants.OFFLINE_CTLR_OFFLINE);
118 logger.debug("DEV {}: Controller is ONLINE. Starting device initialisation.", unitId);
120 final Bridge bridge = getBridge();
121 if (bridge == null) {
122 logger.debug("DEV {}: bridge is null!", unitId);
125 final PIMHandler bridgeHandler = (PIMHandler) bridge.getHandler();
126 if (bridgeHandler == null) {
127 logger.debug("DEV {}: bridge handler is null!", unitId);
130 updateDeviceStatus(bridgeHandler);
135 public void handleCommand(final ChannelUID channelUID, final Command cmd) {
136 final PIMHandler pimHandler = getPIMHandler();
137 if (pimHandler == null) {
138 logger.warn("DEV {}: received cmd {} but no bridge handler", unitId, cmd);
142 final MessageBuilder message;
143 if (cmd == OnOffType.ON) {
144 message = MessageBuilder.forCommand(ACTIVATE);
145 } else if (cmd == OnOffType.OFF) {
146 message = MessageBuilder.forCommand(DEACTIVATE);
147 } else if (cmd instanceof PercentType) {
148 message = MessageBuilder.forCommand(GOTO).args(((PercentType) cmd).byteValue());
149 } else if (cmd == RefreshType.REFRESH) {
150 refreshDeviceState();
153 logger.warn("channel {}: unsupported cmd {}", channelUID, cmd);
157 message.network(networkId).destination(getUnitId());
158 pimHandler.sendPacket(message).thenAccept(this::updateStatus);
161 public void onMessageReceived(final UPBMessage msg) {
162 updateStatus(ThingStatus.ONLINE);
163 if (msg.getControlWord().isLink()) {
164 handleLinkMessage(msg);
166 handleDirectMessage(msg);
170 private void handleDirectMessage(final UPBMessage msg) {
172 byte[] args = msg.getArguments();
173 switch (msg.getCommand()) {
175 state = OnOffType.ON;
179 state = OnOffType.OFF;
184 if (args.length == 0) {
185 logger.warn("DEV {}: malformed {} cmd", unitId, msg.getCommand());
188 state = new PercentType(args[0]);
192 logger.debug("DEV {}: Message {} ignored", unitId, msg.getCommand());
195 updateState(Constants.DIMMER_TYPE_ID, state);
198 private void handleLinkMessage(final UPBMessage msg) {
199 final byte linkId = msg.getDestination();
200 for (final Channel ch : getThing().getChannels()) {
201 ChannelTypeUID channelTypeUID = ch.getChannelTypeUID();
202 if (channelTypeUID != null && Constants.SCENE_CHANNEL_TYPE_ID.equals(channelTypeUID.getId())) {
203 final BigDecimal channelLinkId = (BigDecimal) ch.getConfiguration()
204 .get(Constants.CONFIGURATION_LINK_ID);
205 if (channelLinkId == null || channelLinkId.byteValue() != linkId) {
208 switch (msg.getCommand()) {
211 triggerChannel(ch.getUID(), msg.getCommand().name());
215 logger.debug("DEV {}: Message {} ignored for link {}", unitId, linkId & 0xff, msg.getCommand());
222 private void updateDeviceStatus(final PIMHandler bridgeHandler) {
223 final UPBDevice device = bridgeHandler.getDevice(getNetworkId(), getUnitId());
224 if (device == null) {
225 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, Constants.OFFLINE_NODE_NOTFOUND);
227 switch (device.getState()) {
230 updateStatus(ThingStatus.ONLINE);
234 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
235 Constants.OFFLINE_NODE_DEAD);
241 protected void pingDevice() {
242 final PIMHandler pimHandler = getPIMHandler();
243 if (pimHandler != null) {
244 pimHandler.sendPacket(
245 MessageBuilder.forCommand(NULL).ackMessage(true).network(networkId).destination((byte) unitId))
246 .thenAccept(this::updateStatus);
250 private void updateStatus(final CmdStatus result) {
253 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, Constants.OFFLINE_NODE_DEAD);
258 updateStatus(ThingStatus.ONLINE);
263 private void refreshDeviceState() {
264 // This polls the device to see if it is alive. Since the REFRESH command is sent
265 // for each channel and we want to avoid unnecessary traffic, we only ping the device
266 // if some time has elapsed since the last refresh.
267 final long now = System.currentTimeMillis();
268 if (now - lastRefreshMillis > REFRESH_INTERVAL_MS) {
269 lastRefreshMillis = now;
270 final PIMHandler pimHandler = getPIMHandler();
271 if (pimHandler != null) {
273 .sendPacket(MessageBuilder.forCommand(REPORT_STATE).network(networkId).destination(getUnitId()))
274 .thenAccept(this::updateStatus);
279 protected @Nullable PIMHandler getPIMHandler() {
280 final Bridge bridge = getBridge();
281 if (bridge == null) {
282 logger.debug("DEV {}: bridge is null!", unitId);
285 final PIMHandler bridgeHandler = (PIMHandler) bridge.getHandler();
286 if (bridgeHandler == null) {
287 logger.debug("DEV {}: bridge handler is null!", unitId);
290 return bridgeHandler;
293 public byte getNetworkId() {
297 public byte getUnitId() {
298 return (byte) unitId;