]> git.basschouten.com Git - openhab-addons.git/blob
ca28d3ed1d3e924290c2d0e60962e29657b2a219
[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.openwebnet.handler;
14
15 import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.*;
16
17 import java.util.Set;
18 import java.util.concurrent.TimeUnit;
19
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;
42
43 /**
44  * The {@link OpenWebNetLightingHandler} is responsible for handling commands/messages for a Lighting OpenWebNet device.
45  * It extends the abstract {@link OpenWebNetThingHandler}.
46  *
47  * @author Massimo Valla - Initial contribution
48  */
49 @NonNullByDefault
50 public class OpenWebNetLightingHandler extends OpenWebNetThingHandler {
51
52     private final Logger logger = LoggerFactory.getLogger(OpenWebNetLightingHandler.class);
53
54     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.LIGHTING_SUPPORTED_THING_TYPES;
55
56     private static final int BRIGHTNESS_CHANGE_DELAY_MSEC = 1500; // delay before sending another brightness status
57                                                                   // request
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
60                                                                          // status
61     private static final int UNKNOWN_STATE = 1000;
62
63     private long lastBrightnessChangeSentTS = 0; // timestamp when last brightness change was sent to the device
64
65     private int brightness = UNKNOWN_STATE; // current brightness percent value for this device
66
67     private int brightnessBeforeOff = UNKNOWN_STATE; // latest brightness before device was set to off
68
69     private int sw[] = { UNKNOWN_STATE, UNKNOWN_STATE }; // current switch(es) state
70
71     public OpenWebNetLightingHandler(Thing thing) {
72         super(thing);
73     }
74
75     @Override
76     protected void requestChannelState(ChannelUID channel) {
77         logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
78         requestStatus(channel.getId());
79     }
80
81     /** helper method to request light status based on channel */
82     private void requestStatus(String channelId) {
83         Where w = deviceWhere;
84         if (w != null) {
85             try {
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);
92                     }
93                 }
94             } catch (OWNException e) {
95                 logger.warn("requestStatus() Exception while requesting light state: {}", e.getMessage());
96             }
97         }
98     }
99
100     @Override
101     protected void handleChannelCommand(ChannelUID channel, Command command) {
102         switch (channel.getId()) {
103             case CHANNEL_BRIGHTNESS:
104                 handleBrightnessCommand(command);
105                 break;
106             case CHANNEL_SWITCH:
107             case CHANNEL_SWITCH_01:
108             case CHANNEL_SWITCH_02:
109                 handleSwitchCommand(channel, command);
110                 break;
111             default: {
112                 logger.warn("Unsupported ChannelUID {}", channel);
113             }
114         }
115     }
116
117     /**
118      * Handles Lighting switch command for a channel
119      *
120      * @param channel the channel
121      * @param command the command
122      */
123     private void handleSwitchCommand(ChannelUID channel, Command command) {
124         logger.debug("handleSwitchCommand() (command={} - channel={})", command, channel);
125         if (command instanceof OnOffType) {
126             try {
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())));
131                 }
132             } catch (OWNException e) {
133                 logger.warn("Exception while processing command {}: {}", command, e.getMessage());
134             }
135         } else {
136             logger.warn("Unsupported command: {}", command);
137         }
138     }
139
140     /**
141      * Handles Lighting brightness command (xx%, INCREASE, DECREASE, ON, OFF)
142      *
143      * @param command the command
144      */
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);
152             } else { // DECREASE
153                 dimLightTo(brightness - 10, command);
154             }
155         } else if (command instanceof OnOffType) {
156             if (OnOffType.ON.equals(command)) {
157                 dimLightTo(brightnessBeforeOff, command);
158             } else { // OFF
159                 dimLightTo(0, command);
160             }
161         } else {
162             logger.warn("Cannot handle command {} for thing {}", command, getThing().getUID());
163         }
164     }
165
166     /**
167      * Helper method to dim light to given percent
168      */
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%
174             newBrightness = 100;
175         } else if (newBrightness <= 0) {
176             newBrightness = 0;
177             brightnessBeforeOff = brightness;
178             logger.debug("   DIM saved bri before sending bri=0 command to device");
179         } else if (newBrightness > 100) {
180             newBrightness = 100;
181         }
182         What newBrightnessWhat = Lighting.percentToWhat(newBrightness);
183         logger.debug("   DIM newBrightness={} newBrightnessWhat={}", newBrightness, newBrightnessWhat);
184         @Nullable
185         What brightnessWhat = null;
186         if (brightness != UNKNOWN_STATE) {
187             brightnessWhat = Lighting.percentToWhat(brightness);
188         }
189         if (brightnessWhat == null || !newBrightnessWhat.value().equals(brightnessWhat.value())) {
190             logger.debug("   DIM brightnessWhat {} --> {}  WHAT level change needed", brightnessWhat,
191                     newBrightnessWhat);
192             Where w = deviceWhere;
193             if (w != null) {
194                 try {
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());
199                 }
200             }
201         } else {
202             logger.debug("   DIM brightnessWhat {} --> {}  NO WHAT level change needed", brightnessWhat,
203                     newBrightnessWhat);
204         }
205         brightness = newBrightness;
206         updateState(CHANNEL_BRIGHTNESS, new PercentType(brightness));
207         logger.debug("   DIM---END bri={} briBeforeOff={}", brightness, brightnessBeforeOff);
208     }
209
210     @Override
211     protected String ownIdPrefix() {
212         return Who.LIGHTING.value().toString();
213     }
214
215     @Override
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);
222         } else {
223             updateOnOffState((Lighting) msg);
224         }
225     }
226
227     /**
228      * Updates brightness based on OWN Lighting message received
229      *
230      * @param msg the Lighting message received
231      */
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" : ""));
239         if (belowThresh) {
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);
243         } else {
244             if (msg.isOn()) {
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);
250             } else {
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) {
256                         int newBrightness;
257                         try {
258                             newBrightness = msg.parseDimmerLevel100();
259                         } catch (FrameException fe) {
260                             logger.warn("updateBrightness() Wrong value for dimmerLevel100 in message: {}", msg);
261                             return;
262                         }
263                         logger.debug("  $BRI DIMMER_LEVEL_100 newBrightness={}", newBrightness);
264                         updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
265                         if (newBrightness == 0) {
266                             brightnessBeforeOff = brightness;
267                         }
268                         brightness = newBrightness;
269                     } else {
270                         logger.warn("updateBrightness() Cannot handle message {} for thing {}", msg,
271                                 getThing().getUID());
272                         return;
273                     }
274                 }
275             }
276         }
277         logger.debug("  $BRI---END updateBrightness({}) || bri={} briBeforeOff={}", msg, brightness,
278                 brightnessBeforeOff);
279     }
280
281     /**
282      * Updates light brightness state based on a OWN Lighting message
283      *
284      * @param msg the Lighting message received
285      */
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();
292             }
293             logger.debug("  $BRI brightnessWhat {} --> {}", brightnessWhat, newBrightnessWhat);
294             if (brightnessWhat != newBrightnessWhat) {
295                 int newBrightness = Lighting.levelToPercent(newBrightnessWhat);
296                 updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
297                 if (msg.isOff()) {
298                     brightnessBeforeOff = brightness;
299                 }
300                 brightness = newBrightness;
301                 logger.debug("  $BRI brightness CHANGED to {}", brightness);
302             } else {
303                 logger.debug("  $BRI no change");
304             }
305         }
306     }
307
308     /**
309      * Updates light on/off state based on a OWN Lighting event message received
310      *
311      * @param msg the Lighting message received
312      */
313     private void updateOnOffState(Lighting msg) {
314         OpenWebNetBridgeHandler brH = bridgeHandler;
315         if (brH != null) {
316             if (msg.isOn() || msg.isOff()) {
317                 String channelId;
318                 int switchId = 0;
319                 if (brH.isBusGateway()) {
320                     channelId = CHANNEL_SWITCH;
321                 } else {
322                     WhereZigBee w = (WhereZigBee) (msg.getWhere());
323                     if (WhereZigBee.UNIT_02.equals(w.getUnit())) {
324                         channelId = CHANNEL_SWITCH_02;
325                         switchId = 1;
326                     } else {
327                         channelId = CHANNEL_SWITCH_01;
328                     }
329                 }
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);
336                 } else {
337                     logger.debug("  {} ONOFF no change", ownId);
338                 }
339             } else {
340                 logger.debug("updateOnOffState() Ignoring unsupported WHAT for thing {}. Frame={}", getThing().getUID(),
341                         msg.getFrameValue());
342                 return;
343             }
344         }
345     }
346
347     /**
348      * Returns a WHERE address string based on channelId string
349      *
350      * @param channelId the channelId string
351      **/
352     @Nullable
353     protected String toWhere(String channelId) {
354         Where w = deviceWhere;
355         if (w != null) {
356             OpenWebNetBridgeHandler brH = bridgeHandler;
357             if (brH != null) {
358                 if (brH.isBusGateway()) {
359                     return w.value();
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);
364                 }
365             }
366         }
367         return null;
368     }
369 }