2 * Copyright (c) 2010-2021 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.handler;
15 import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.*;
18 import java.util.concurrent.TimeUnit;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
23 import org.openhab.core.library.types.IncreaseDecreaseType;
24 import org.openhab.core.library.types.OnOffType;
25 import org.openhab.core.library.types.PercentType;
26 import org.openhab.core.thing.ChannelUID;
27 import org.openhab.core.thing.Thing;
28 import org.openhab.core.thing.ThingStatus;
29 import org.openhab.core.thing.ThingTypeUID;
30 import org.openhab.core.types.Command;
31 import org.openwebnet4j.communication.OWNException;
32 import org.openwebnet4j.communication.Response;
33 import org.openwebnet4j.message.BaseOpenMessage;
34 import org.openwebnet4j.message.FrameException;
35 import org.openwebnet4j.message.Lighting;
36 import org.openwebnet4j.message.What;
37 import org.openwebnet4j.message.Where;
38 import org.openwebnet4j.message.WhereZigBee;
39 import org.openwebnet4j.message.Who;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
44 * The {@link OpenWebNetLightingHandler} is responsible for handling commands/messages for a Lighting OpenWebNet device.
45 * It extends the abstract {@link OpenWebNetThingHandler}.
47 * @author Massimo Valla - Initial contribution
50 public class OpenWebNetLightingHandler extends OpenWebNetThingHandler {
52 private final Logger logger = LoggerFactory.getLogger(OpenWebNetLightingHandler.class);
54 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.LIGHTING_SUPPORTED_THING_TYPES;
56 private static final int BRIGHTNESS_CHANGE_DELAY_MSEC = 1500; // delay before sending another brightness status
58 private static final int BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC = 900; // we must wait some time to be sure dimmer has
59 // reached final level before requesting its
61 private static final int UNKNOWN_STATE = 1000;
63 private long lastBrightnessChangeSentTS = 0; // timestamp when last brightness change was sent to the device
65 private int brightness = UNKNOWN_STATE; // current brightness percent value for this device
67 private int brightnessBeforeOff = UNKNOWN_STATE; // latest brightness before device was set to off
69 private int sw[] = { UNKNOWN_STATE, UNKNOWN_STATE }; // current switch(es) state
71 public OpenWebNetLightingHandler(Thing thing) {
76 protected void requestChannelState(ChannelUID channel) {
77 logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
78 requestStatus(channel.getId());
81 /** helper method to request light status based on channel */
82 private void requestStatus(String channelId) {
83 Where w = deviceWhere;
86 Response res = send(Lighting.requestStatus(toWhere(channelId)));
87 if (res != null && res.isSuccess()) {
88 // set thing online if not already
89 ThingStatus ts = getThing().getStatus();
90 if (ThingStatus.ONLINE != ts && ThingStatus.REMOVING != ts && ThingStatus.REMOVED != ts) {
91 updateStatus(ThingStatus.ONLINE);
94 } catch (OWNException e) {
95 logger.warn("requestStatus() Exception while requesting light state: {}", e.getMessage());
101 protected void handleChannelCommand(ChannelUID channel, Command command) {
102 switch (channel.getId()) {
103 case CHANNEL_BRIGHTNESS:
104 handleBrightnessCommand(command);
107 case CHANNEL_SWITCH_01:
108 case CHANNEL_SWITCH_02:
109 handleSwitchCommand(channel, command);
112 logger.warn("Unsupported ChannelUID {}", channel);
118 * Handles Lighting switch command for a channel
120 * @param channel the channel
121 * @param command the command
123 private void handleSwitchCommand(ChannelUID channel, Command command) {
124 logger.debug("handleSwitchCommand() (command={} - channel={})", command, channel);
125 if (command instanceof OnOffType) {
127 if (OnOffType.ON.equals(command)) {
128 send(Lighting.requestTurnOn(toWhere(channel.getId())));
129 } else if (OnOffType.OFF.equals(command)) {
130 send(Lighting.requestTurnOff(toWhere(channel.getId())));
132 } catch (OWNException e) {
133 logger.warn("Exception while processing command {}: {}", command, e.getMessage());
136 logger.warn("Unsupported command: {}", command);
141 * Handles Lighting brightness command (xx%, INCREASE, DECREASE, ON, OFF)
143 * @param command the command
145 private void handleBrightnessCommand(Command command) {
146 logger.debug("handleBrightnessCommand() command={}", command);
147 if (command instanceof PercentType) {
148 dimLightTo(((PercentType) command).intValue(), command);
149 } else if (command instanceof IncreaseDecreaseType) {
150 if (IncreaseDecreaseType.INCREASE.equals(command)) {
151 dimLightTo(brightness + 10, command);
153 dimLightTo(brightness - 10, command);
155 } else if (command instanceof OnOffType) {
156 if (OnOffType.ON.equals(command)) {
157 dimLightTo(brightnessBeforeOff, command);
159 dimLightTo(0, command);
162 logger.warn("Cannot handle command {} for thing {}", command, getThing().getUID());
167 * Helper method to dim light to given percent
169 private void dimLightTo(int percent, Command command) {
170 logger.debug(" DIM dimLightTo({}) bri={} briBeforeOff={}", percent, brightness, brightnessBeforeOff);
171 int newBrightness = percent;
172 if (newBrightness == UNKNOWN_STATE) {
173 // we do not know last brightness -> set dimmer to 100%
175 } else if (newBrightness <= 0) {
177 brightnessBeforeOff = brightness;
178 logger.debug(" DIM saved bri before sending bri=0 command to device");
179 } else if (newBrightness > 100) {
182 What newBrightnessWhat = Lighting.percentToWhat(newBrightness);
183 logger.debug(" DIM newBrightness={} newBrightnessWhat={}", newBrightness, newBrightnessWhat);
185 What brightnessWhat = null;
186 if (brightness != UNKNOWN_STATE) {
187 brightnessWhat = Lighting.percentToWhat(brightness);
189 if (brightnessWhat == null || !newBrightnessWhat.value().equals(brightnessWhat.value())) {
190 logger.debug(" DIM brightnessWhat {} --> {} WHAT level change needed", brightnessWhat,
192 Where w = deviceWhere;
195 lastBrightnessChangeSentTS = System.currentTimeMillis();
196 send(Lighting.requestDimTo(w.value(), newBrightnessWhat));
197 } catch (OWNException e) {
198 logger.warn("Exception while sending dimTo request for command {}: {}", command, e.getMessage());
202 logger.debug(" DIM brightnessWhat {} --> {} NO WHAT level change needed", brightnessWhat,
205 brightness = newBrightness;
206 updateState(CHANNEL_BRIGHTNESS, new PercentType(brightness));
207 logger.debug(" DIM---END bri={} briBeforeOff={}", brightness, brightnessBeforeOff);
211 protected String ownIdPrefix() {
212 return Who.LIGHTING.value().toString();
216 protected void handleMessage(BaseOpenMessage msg) {
217 super.handleMessage(msg);
218 logger.debug("handleMessage() for thing: {}", thing.getUID());
219 ThingTypeUID thingType = thing.getThingTypeUID();
220 if (THING_TYPE_ZB_DIMMER.equals(thingType) || THING_TYPE_BUS_DIMMER.equals(thingType)) {
221 updateBrightness((Lighting) msg);
223 updateOnOffState((Lighting) msg);
228 * Updates brightness based on OWN Lighting message received
230 * @param msg the Lighting message received
232 private synchronized void updateBrightness(Lighting msg) {
233 long now = System.currentTimeMillis();
234 logger.debug(" $BRI updateBrightness({}) || bri={} briBeforeOff={}", msg, brightness,
235 brightnessBeforeOff);
236 long delta = now - lastBrightnessChangeSentTS;
237 boolean belowThresh = delta < BRIGHTNESS_CHANGE_DELAY_MSEC;
238 logger.debug(" $BRI delta={}ms {}", delta, (belowThresh ? "< DELAY" : ""));
240 // we just sent a command from OH, so we can ignore this message from network
241 logger.debug(" $BRI a request was sent {} < {} ms --> no action needed", delta,
242 BRIGHTNESS_CHANGE_DELAY_MSEC);
245 logger.debug(" $BRI \"ON\" notification from network, scheduling requestStatus...");
246 // we must wait BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC to be sure dimmer has reached final level
247 scheduler.schedule(() -> {
248 requestStatus(CHANNEL_BRIGHTNESS);
249 }, BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC, TimeUnit.MILLISECONDS);
251 logger.debug(" $BRI update from network");
252 if (msg.getWhat() != null) {
253 updateBrightnessState(msg);
254 } else { // dimension notification
255 if (msg.getDim() == Lighting.DIM.DIMMER_LEVEL_100) {
258 newBrightness = msg.parseDimmerLevel100();
259 } catch (FrameException fe) {
260 logger.warn("updateBrightness() Wrong value for dimmerLevel100 in message: {}", msg);
263 logger.debug(" $BRI DIMMER_LEVEL_100 newBrightness={}", newBrightness);
264 updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
265 if (newBrightness == 0) {
266 brightnessBeforeOff = brightness;
268 brightness = newBrightness;
270 logger.warn("updateBrightness() Cannot handle message {} for thing {}", msg,
271 getThing().getUID());
277 logger.debug(" $BRI---END updateBrightness({}) || bri={} briBeforeOff={}", msg, brightness,
278 brightnessBeforeOff);
282 * Updates light brightness state based on a OWN Lighting message
284 * @param msg the Lighting message received
286 private void updateBrightnessState(Lighting msg) {
287 if (msg.getWhat() != null) {
288 int newBrightnessWhat = msg.getWhat().value();
289 int brightnessWhat = UNKNOWN_STATE;
290 if (brightness != UNKNOWN_STATE) {
291 brightnessWhat = Lighting.percentToWhat(brightness).value();
293 logger.debug(" $BRI brightnessWhat {} --> {}", brightnessWhat, newBrightnessWhat);
294 if (brightnessWhat != newBrightnessWhat) {
295 int newBrightness = Lighting.levelToPercent(newBrightnessWhat);
296 updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
298 brightnessBeforeOff = brightness;
300 brightness = newBrightness;
301 logger.debug(" $BRI brightness CHANGED to {}", brightness);
303 logger.debug(" $BRI no change");
309 * Updates light on/off state based on a OWN Lighting event message received
311 * @param msg the Lighting message received
313 private void updateOnOffState(Lighting msg) {
314 OpenWebNetBridgeHandler brH = bridgeHandler;
316 if (msg.isOn() || msg.isOff()) {
319 if (brH.isBusGateway()) {
320 channelId = CHANNEL_SWITCH;
322 WhereZigBee w = (WhereZigBee) (msg.getWhere());
323 if (WhereZigBee.UNIT_02.equals(w.getUnit())) {
324 channelId = CHANNEL_SWITCH_02;
327 channelId = CHANNEL_SWITCH_01;
330 int currentSt = sw[switchId];
331 int newSt = (msg.isOn() ? 1 : 0);
332 if (newSt != currentSt) {
333 updateState(channelId, (newSt == 1 ? OnOffType.ON : OnOffType.OFF));
334 sw[switchId] = newSt;
335 logger.debug(" {} ONOFF CHANGED to {}", ownId, newSt);
337 logger.debug(" {} ONOFF no change", ownId);
340 logger.debug("updateOnOffState() Ignoring unsupported WHAT for thing {}. Frame={}", getThing().getUID(),
341 msg.getFrameValue());
348 * Returns a WHERE address string based on channelId string
350 * @param channelId the channelId string
353 protected String toWhere(String channelId) {
354 Where w = deviceWhere;
356 OpenWebNetBridgeHandler brH = bridgeHandler;
358 if (brH.isBusGateway()) {
360 } else if (channelId.equals(CHANNEL_SWITCH_02)) {
361 return ((WhereZigBee) w).valueWithUnit(WhereZigBee.UNIT_02);
362 } else { // CHANNEL_SWITCH_01 or other channels
363 return ((WhereZigBee) w).valueWithUnit(WhereZigBee.UNIT_01);