]> git.basschouten.com Git - openhab-addons.git/blob
27f068758846528e8e697138bc643dde273cf9e7
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.*;
16
17 import java.math.BigDecimal;
18
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;
42
43 /**
44  * Handler for things representing devices on an UPB network.
45  *
46  * @author Marcus Better - Initial contribution
47  *
48  */
49 @NonNullByDefault
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;
53
54     private final Logger logger = LoggerFactory.getLogger(UPBThingHandler.class);
55     private final @Nullable Byte defaultNetworkId;
56
57     protected volatile byte networkId;
58     protected volatile int unitId;
59     private volatile long lastRefreshMillis;
60
61     public UPBThingHandler(final Thing device, final @Nullable Byte defaultNetworkId) {
62         super(device);
63         this.defaultNetworkId = defaultNetworkId;
64     }
65
66     @Override
67     public void initialize() {
68         logger.debug("initializing UPB thing handler {}", getThing().getUID());
69
70         final BigDecimal val = (BigDecimal) getConfig().get(Constants.CONFIGURATION_NETWORK_ID);
71         if (val == null) {
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");
76                 return;
77             }
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");
81             return;
82         } else {
83             networkId = val.byteValue();
84         }
85
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");
89             return;
90         }
91         unitId = cfgUnitId.intValue();
92         if (unitId < 1 || unitId > 250) {
93             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "invalid unit ID");
94             return;
95         }
96
97         final Bridge bridge = getBridge();
98         if (bridge == null) {
99             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, Constants.OFFLINE_CTLR_OFFLINE);
100             return;
101         }
102         bridgeStatusChanged(bridge.getStatusInfo());
103     }
104
105     @Override
106     public void bridgeStatusChanged(final ThingStatusInfo bridgeStatusInfo) {
107         logger.debug("DEV {}: Controller status is {}", unitId, bridgeStatusInfo.getStatus());
108
109         if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
110             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, Constants.OFFLINE_CTLR_OFFLINE);
111             return;
112         }
113
114         logger.debug("DEV {}: Controller is ONLINE. Starting device initialisation.", unitId);
115
116         final Bridge bridge = getBridge();
117         if (bridge == null) {
118             logger.debug("DEV {}: bridge is null!", unitId);
119             return;
120         }
121         final PIMHandler bridgeHandler = (PIMHandler) bridge.getHandler();
122         if (bridgeHandler == null) {
123             logger.debug("DEV {}: bridge handler is null!", unitId);
124             return;
125         }
126         updateDeviceStatus(bridgeHandler);
127         pingDevice();
128     }
129
130     @Override
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);
135             return;
136         }
137
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();
147             return;
148         } else {
149             logger.warn("channel {}: unsupported cmd {}", channelUID, cmd);
150             return;
151         }
152
153         message.network(networkId).destination(getUnitId());
154         pimHandler.sendPacket(message).thenAccept(this::updateStatus);
155     }
156
157     public void onMessageReceived(final UPBMessage msg) {
158         updateStatus(ThingStatus.ONLINE);
159         if (msg.getControlWord().isLink()) {
160             handleLinkMessage(msg);
161         } else {
162             handleDirectMessage(msg);
163         }
164     }
165
166     private void handleDirectMessage(final UPBMessage msg) {
167         final State state;
168         switch (msg.getCommand()) {
169             case ACTIVATE:
170                 state = OnOffType.ON;
171                 break;
172
173             case DEACTIVATE:
174                 state = OnOffType.OFF;
175                 break;
176
177             case GOTO:
178                 if (msg.getArguments().length == 0) {
179                     logger.warn("DEV {}: malformed GOTO cmd", unitId);
180                     return;
181                 }
182                 final int level = msg.getArguments()[0];
183                 state = new PercentType(level);
184                 break;
185
186             default:
187                 logger.debug("DEV {}: Message {} ignored", unitId, msg.getCommand());
188                 return;
189         }
190         updateState(Constants.DIMMER_TYPE_ID, state);
191     }
192
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) {
201                     continue;
202                 }
203                 switch (msg.getCommand()) {
204                     case ACTIVATE:
205                     case DEACTIVATE:
206                         triggerChannel(ch.getUID(), msg.getCommand().name());
207                         break;
208
209                     default:
210                         logger.debug("DEV {}: Message {} ignored for link {}", unitId, linkId & 0xff, msg.getCommand());
211                         return;
212                 }
213             }
214         }
215     }
216
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);
221         } else {
222             switch (device.getState()) {
223                 case INITIALIZING:
224                 case ALIVE:
225                     updateStatus(ThingStatus.ONLINE);
226                     break;
227                 case DEAD:
228                 case FAILED:
229                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
230                             Constants.OFFLINE_NODE_DEAD);
231                     break;
232             }
233         }
234     }
235
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);
242         }
243     }
244
245     private void updateStatus(final CmdStatus result) {
246         switch (result) {
247             case WRITE_FAILED:
248                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, Constants.OFFLINE_NODE_DEAD);
249                 break;
250
251             case ACK:
252             case NAK:
253                 updateStatus(ThingStatus.ONLINE);
254                 break;
255         }
256     }
257
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) {
267                 pimHandler
268                         .sendPacket(MessageBuilder.forCommand(REPORT_STATE).network(networkId).destination(getUnitId()))
269                         .thenAccept(this::updateStatus);
270             }
271         }
272     }
273
274     protected @Nullable PIMHandler getPIMHandler() {
275         final Bridge bridge = getBridge();
276         if (bridge == null) {
277             logger.debug("DEV {}: bridge is null!", unitId);
278             return null;
279         }
280         final PIMHandler bridgeHandler = (PIMHandler) bridge.getHandler();
281         if (bridgeHandler == null) {
282             logger.debug("DEV {}: bridge handler is null!", unitId);
283             return null;
284         }
285         return bridgeHandler;
286     }
287
288     public byte getNetworkId() {
289         return networkId;
290     }
291
292     public byte getUnitId() {
293         return (byte) unitId;
294     }
295 }