]> git.basschouten.com Git - openhab-addons.git/blob
ce97b2768cada0103b19c6fd63c984870961f7ac
[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.upb.internal.handler;
14
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;
20
21 import java.math.BigDecimal;
22
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;
46
47 /**
48  * Handler for things representing devices on an UPB network.
49  *
50  * @author Marcus Better - Initial contribution
51  *
52  */
53 @NonNullByDefault
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;
57
58     private final Logger logger = LoggerFactory.getLogger(UPBThingHandler.class);
59     private final @Nullable Byte defaultNetworkId;
60
61     protected volatile byte networkId;
62     protected volatile int unitId;
63     private volatile long lastRefreshMillis;
64
65     public UPBThingHandler(final Thing device, final @Nullable Byte defaultNetworkId) {
66         super(device);
67         this.defaultNetworkId = defaultNetworkId;
68     }
69
70     @Override
71     public void initialize() {
72         logger.debug("initializing UPB thing handler {}", getThing().getUID());
73
74         final BigDecimal val = (BigDecimal) getConfig().get(Constants.CONFIGURATION_NETWORK_ID);
75         if (val == null) {
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");
80                 return;
81             }
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");
85             return;
86         } else {
87             networkId = val.byteValue();
88         }
89
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");
93             return;
94         }
95         unitId = cfgUnitId.intValue();
96         if (unitId < 1 || unitId > 250) {
97             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "invalid unit ID");
98             return;
99         }
100
101         final Bridge bridge = getBridge();
102         if (bridge == null) {
103             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, Constants.OFFLINE_CTLR_OFFLINE);
104             return;
105         }
106         bridgeStatusChanged(bridge.getStatusInfo());
107     }
108
109     @Override
110     public void bridgeStatusChanged(final ThingStatusInfo bridgeStatusInfo) {
111         logger.debug("DEV {}: Controller status is {}", unitId, bridgeStatusInfo.getStatus());
112
113         if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
114             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, Constants.OFFLINE_CTLR_OFFLINE);
115             return;
116         }
117
118         logger.debug("DEV {}: Controller is ONLINE. Starting device initialisation.", unitId);
119
120         final Bridge bridge = getBridge();
121         if (bridge == null) {
122             logger.debug("DEV {}: bridge is null!", unitId);
123             return;
124         }
125         final PIMHandler bridgeHandler = (PIMHandler) bridge.getHandler();
126         if (bridgeHandler == null) {
127             logger.debug("DEV {}: bridge handler is null!", unitId);
128             return;
129         }
130         updateDeviceStatus(bridgeHandler);
131         pingDevice();
132     }
133
134     @Override
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);
139             return;
140         }
141
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 percentCommand) {
148             message = MessageBuilder.forCommand(GOTO).args(percentCommand.byteValue());
149         } else if (cmd == RefreshType.REFRESH) {
150             refreshDeviceState();
151             return;
152         } else {
153             logger.warn("channel {}: unsupported cmd {}", channelUID, cmd);
154             return;
155         }
156
157         message.network(networkId).destination(getUnitId());
158         pimHandler.sendPacket(message).thenAccept(this::updateStatus);
159     }
160
161     public void onMessageReceived(final UPBMessage msg) {
162         updateStatus(ThingStatus.ONLINE);
163         if (msg.getControlWord().isLink()) {
164             handleLinkMessage(msg);
165         } else {
166             handleDirectMessage(msg);
167         }
168     }
169
170     private void handleDirectMessage(final UPBMessage msg) {
171         final State state;
172         byte[] args = msg.getArguments();
173         switch (msg.getCommand()) {
174             case ACTIVATE:
175                 state = OnOffType.ON;
176                 break;
177
178             case DEACTIVATE:
179                 state = OnOffType.OFF;
180                 break;
181
182             case GOTO:
183             case DEVICE_STATE:
184                 if (args.length == 0) {
185                     logger.warn("DEV {}: malformed {} cmd", unitId, msg.getCommand());
186                     return;
187                 }
188                 state = new PercentType(args[0]);
189                 break;
190
191             default:
192                 logger.debug("DEV {}: Message {} ignored", unitId, msg.getCommand());
193                 return;
194         }
195         updateState(Constants.DIMMER_TYPE_ID, state);
196     }
197
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) {
206                     continue;
207                 }
208                 switch (msg.getCommand()) {
209                     case ACTIVATE:
210                     case DEACTIVATE:
211                         triggerChannel(ch.getUID(), msg.getCommand().name());
212                         break;
213
214                     default:
215                         logger.debug("DEV {}: Message {} ignored for link {}", unitId, linkId & 0xff, msg.getCommand());
216                         return;
217                 }
218             }
219         }
220     }
221
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);
226         } else {
227             switch (device.getState()) {
228                 case INITIALIZING:
229                 case ALIVE:
230                     updateStatus(ThingStatus.ONLINE);
231                     break;
232                 case DEAD:
233                 case FAILED:
234                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
235                             Constants.OFFLINE_NODE_DEAD);
236                     break;
237             }
238         }
239     }
240
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);
247         }
248     }
249
250     private void updateStatus(final CmdStatus result) {
251         switch (result) {
252             case WRITE_FAILED:
253                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, Constants.OFFLINE_NODE_DEAD);
254                 break;
255
256             case ACK:
257             case NAK:
258                 updateStatus(ThingStatus.ONLINE);
259                 break;
260         }
261     }
262
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) {
272                 pimHandler
273                         .sendPacket(MessageBuilder.forCommand(REPORT_STATE).network(networkId).destination(getUnitId()))
274                         .thenAccept(this::updateStatus);
275             }
276         }
277     }
278
279     protected @Nullable PIMHandler getPIMHandler() {
280         final Bridge bridge = getBridge();
281         if (bridge == null) {
282             logger.debug("DEV {}: bridge is null!", unitId);
283             return null;
284         }
285         final PIMHandler bridgeHandler = (PIMHandler) bridge.getHandler();
286         if (bridgeHandler == null) {
287             logger.debug("DEV {}: bridge handler is null!", unitId);
288             return null;
289         }
290         return bridgeHandler;
291     }
292
293     public byte getNetworkId() {
294         return networkId;
295     }
296
297     public byte getUnitId() {
298         return (byte) unitId;
299     }
300 }