]> git.basschouten.com Git - openhab-addons.git/blob
f6eda3fc086b02901c23a853aa41103f649d1a6c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.WhereLightAutom;
39 import org.openwebnet4j.message.WhereZigBee;
40 import org.openwebnet4j.message.Who;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * The {@link OpenWebNetLightingHandler} is responsible for handling commands/messages for a Lighting OpenWebNet device.
46  * It extends the abstract {@link OpenWebNetThingHandler}.
47  *
48  * @author Massimo Valla - Initial contribution
49  */
50 @NonNullByDefault
51 public class OpenWebNetLightingHandler extends OpenWebNetThingHandler {
52
53     private final Logger logger = LoggerFactory.getLogger(OpenWebNetLightingHandler.class);
54
55     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.LIGHTING_SUPPORTED_THING_TYPES;
56
57     // interval to interpret ON as response to requestStatus
58     private static final int BRIGHTNESS_STATUS_REQUEST_INTERVAL_MSEC = 250;
59
60     // time to wait before sending a statusRequest, to avoid repeated requests and ensure dimmer has reached its final
61     // level
62     private static final int BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC = 900;
63
64     private static final int UNKNOWN_STATE = 1000;
65
66     private long lastBrightnessChangeSentTS = 0; // timestamp when last brightness change was sent to the device
67
68     private long lastStatusRequestSentTS = 0; // timestamp when last status request was sent to the device
69
70     private int brightness = UNKNOWN_STATE; // current brightness percent value for this device
71
72     private int brightnessBeforeOff = UNKNOWN_STATE; // latest brightness before device was set to off
73
74     private int sw[] = { UNKNOWN_STATE, UNKNOWN_STATE }; // current switch(es) state
75
76     public OpenWebNetLightingHandler(Thing thing) {
77         super(thing);
78     }
79
80     @Override
81     protected void requestChannelState(ChannelUID channel) {
82         logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
83         requestStatus(channel.getId());
84     }
85
86     /** helper method to request light status based on channel */
87     private void requestStatus(String channelId) {
88         Where w = deviceWhere;
89         if (w != null) {
90             try {
91                 lastStatusRequestSentTS = System.currentTimeMillis();
92                 Response res = send(Lighting.requestStatus(toWhere(channelId)));
93                 if (res != null && res.isSuccess()) {
94                     // set thing online if not already
95                     ThingStatus ts = getThing().getStatus();
96                     if (ThingStatus.ONLINE != ts && ThingStatus.REMOVING != ts && ThingStatus.REMOVED != ts) {
97                         updateStatus(ThingStatus.ONLINE);
98                     }
99                 }
100             } catch (OWNException e) {
101                 logger.warn("requestStatus() Exception while requesting light state: {}", e.getMessage());
102             }
103         }
104     }
105
106     @Override
107     protected void handleChannelCommand(ChannelUID channel, Command command) {
108         switch (channel.getId()) {
109             case CHANNEL_BRIGHTNESS:
110                 handleBrightnessCommand(command);
111                 break;
112             case CHANNEL_SWITCH:
113             case CHANNEL_SWITCH_01:
114             case CHANNEL_SWITCH_02:
115                 handleSwitchCommand(channel, command);
116                 break;
117             default: {
118                 logger.warn("Unsupported ChannelUID {}", channel);
119             }
120         }
121     }
122
123     /**
124      * Handles Lighting switch command for a channel
125      *
126      * @param channel the channel
127      * @param command the command
128      */
129     private void handleSwitchCommand(ChannelUID channel, Command command) {
130         logger.debug("handleSwitchCommand() (command={} - channel={})", command, channel);
131         if (command instanceof OnOffType) {
132             try {
133                 if (OnOffType.ON.equals(command)) {
134                     send(Lighting.requestTurnOn(toWhere(channel.getId())));
135                 } else if (OnOffType.OFF.equals(command)) {
136                     send(Lighting.requestTurnOff(toWhere(channel.getId())));
137                 }
138             } catch (OWNException e) {
139                 logger.warn("Exception while processing command {}: {}", command, e.getMessage());
140             }
141         } else {
142             logger.warn("Unsupported command: {}", command);
143         }
144     }
145
146     /**
147      * Handles Lighting brightness command (xx%, INCREASE, DECREASE, ON, OFF)
148      *
149      * @param command the command
150      */
151     private void handleBrightnessCommand(Command command) {
152         logger.debug("handleBrightnessCommand() command={}", command);
153         if (command instanceof PercentType) {
154             dimLightTo(((PercentType) command).intValue(), command);
155         } else if (command instanceof IncreaseDecreaseType) {
156             if (IncreaseDecreaseType.INCREASE.equals(command)) {
157                 dimLightTo(brightness + 10, command);
158             } else { // DECREASE
159                 dimLightTo(brightness - 10, command);
160             }
161         } else if (command instanceof OnOffType) {
162             if (OnOffType.ON.equals(command)) {
163                 dimLightTo(brightnessBeforeOff, command);
164             } else { // OFF
165                 dimLightTo(0, command);
166             }
167         } else {
168             logger.warn("Cannot handle command {} for thing {}", command, getThing().getUID());
169         }
170     }
171
172     /**
173      * Helper method to dim light to given percent
174      */
175     private void dimLightTo(int percent, Command command) {
176         logger.debug("   DIM dimLightTo({}) bri={} briBeforeOff={}", percent, brightness, brightnessBeforeOff);
177         int newBrightness = percent;
178         if (newBrightness == UNKNOWN_STATE) {
179             // we do not know last brightness -> set dimmer to 100%
180             newBrightness = 100;
181         } else if (newBrightness <= 0) {
182             newBrightness = 0;
183             brightnessBeforeOff = brightness;
184             logger.debug("   DIM saved bri before sending bri=0 command to device");
185         } else if (newBrightness > 100) {
186             newBrightness = 100;
187         }
188         What newBrightnessWhat = Lighting.percentToWhat(newBrightness);
189         logger.debug("   DIM newBrightness={} newBrightnessWhat={}", newBrightness, newBrightnessWhat);
190         @Nullable
191         What brightnessWhat = null;
192         if (brightness != UNKNOWN_STATE) {
193             brightnessWhat = Lighting.percentToWhat(brightness);
194         }
195         if (brightnessWhat == null || !newBrightnessWhat.value().equals(brightnessWhat.value())) {
196             logger.debug("   DIM brightnessWhat {} --> {}  WHAT level change needed", brightnessWhat,
197                     newBrightnessWhat);
198             Where w = deviceWhere;
199             if (w != null) {
200                 try {
201                     lastBrightnessChangeSentTS = System.currentTimeMillis();
202                     send(Lighting.requestDimTo(w.value(), newBrightnessWhat));
203                 } catch (OWNException e) {
204                     logger.warn("Exception while sending dimTo request for command {}: {}", command, e.getMessage());
205                 }
206             }
207         } else {
208             logger.debug("   DIM brightnessWhat {} --> {}  NO WHAT level change needed", brightnessWhat,
209                     newBrightnessWhat);
210         }
211         brightness = newBrightness;
212         updateState(CHANNEL_BRIGHTNESS, new PercentType(brightness));
213         logger.debug("   DIM---END bri={} briBeforeOff={}", brightness, brightnessBeforeOff);
214     }
215
216     @Override
217     protected String ownIdPrefix() {
218         return Who.LIGHTING.value().toString();
219     }
220
221     @Override
222     protected void handleMessage(BaseOpenMessage msg) {
223         logger.debug("handleMessage({}) for thing: {}", msg, thing.getUID());
224         super.handleMessage(msg);
225         ThingTypeUID thingType = thing.getThingTypeUID();
226         if (THING_TYPE_ZB_DIMMER.equals(thingType) || THING_TYPE_BUS_DIMMER.equals(thingType)) {
227             updateBrightness((Lighting) msg);
228         } else {
229             updateOnOffState((Lighting) msg);
230         }
231     }
232
233     /**
234      * Updates brightness based on OWN Lighting message received
235      *
236      * @param msg the Lighting message received
237      */
238     private synchronized void updateBrightness(Lighting msg) {
239         logger.debug("  $BRI updateBrightness({})       || bri={} briBeforeOff={}", msg, brightness,
240                 brightnessBeforeOff);
241         long now = System.currentTimeMillis();
242         long delta = now - lastBrightnessChangeSentTS;
243         boolean belowThresh = delta < BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC;
244         logger.debug("  $BRI delta={}ms {}", delta, (belowThresh ? "< DELAY" : ""));
245         if (belowThresh) {
246             // we just sent a command from OH, so we can ignore this message from network
247             logger.debug("  $BRI a command was sent {} < {} ms --> no action needed", delta,
248                     BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC);
249         } else {
250             if (msg.isOn()) {
251                 // if we have not just sent a requestStatus, on ON event we send requestStatus to know current level
252                 long deltaStatusReq = now - lastStatusRequestSentTS;
253                 if (deltaStatusReq > BRIGHTNESS_STATUS_REQUEST_INTERVAL_MSEC) {
254                     logger.debug("  $BRI 'ON' is new notification from network, scheduling requestStatus...");
255                     // we must wait BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC to be sure dimmer has reached its final level
256                     scheduler.schedule(() -> {
257                         requestStatus(CHANNEL_BRIGHTNESS);
258                     }, BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC, TimeUnit.MILLISECONDS);
259                     return;
260                 } else {
261                     // otherwise we interpret this ON event as the requestStatus response event with level=1
262                     // so we proceed to call updateBrightnessState()
263                     logger.debug("  $BRI 'ON' is the requestStatus response level");
264                 }
265             }
266             logger.debug("  $BRI update from network");
267             if (msg.getWhat() != null) {
268                 updateBrightnessState(msg);
269             } else { // dimension notification
270                 if (msg.getDim() == Lighting.DIM.DIMMER_LEVEL_100) {
271                     int newBrightness;
272                     try {
273                         newBrightness = msg.parseDimmerLevel100();
274                     } catch (FrameException fe) {
275                         logger.warn("updateBrightness() Wrong value for dimmerLevel100 in message: {}", msg);
276                         return;
277                     }
278                     logger.debug("  $BRI DIMMER_LEVEL_100 newBrightness={}", newBrightness);
279                     updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
280                     if (newBrightness == 0) {
281                         brightnessBeforeOff = brightness;
282                     }
283                     brightness = newBrightness;
284                 } else {
285                     logger.warn("updateBrightness() Cannot handle message {} for thing {}", msg, getThing().getUID());
286                     return;
287                 }
288             }
289         }
290         logger.debug("  $BRI---END updateBrightness({}) || bri={} briBeforeOff={}", msg, brightness,
291                 brightnessBeforeOff);
292     }
293
294     /**
295      * Updates light brightness state based on a OWN Lighting message
296      *
297      * @param msg the Lighting message received
298      */
299     private void updateBrightnessState(Lighting msg) {
300         What w = msg.getWhat();
301         if (w != null) {
302             if (Lighting.WHAT.ON.equals(w)) {
303                 w = Lighting.WHAT.DIMMER_LEVEL_2; // levels start at 2
304             }
305             int newBrightnessWhat = w.value();
306             int brightnessWhat = UNKNOWN_STATE;
307             if (brightness != UNKNOWN_STATE) {
308                 brightnessWhat = Lighting.percentToWhat(brightness).value();
309             }
310             logger.debug("  $BRI brightnessWhat {} --> {}", brightnessWhat, newBrightnessWhat);
311             if (brightnessWhat != newBrightnessWhat) {
312                 int newBrightness = Lighting.levelToPercent(newBrightnessWhat);
313                 updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
314                 if (msg.isOff()) {
315                     brightnessBeforeOff = brightness;
316                 }
317                 brightness = newBrightness;
318                 logger.debug("  $BRI brightness CHANGED to {}", brightness);
319             } else {
320                 logger.debug("  $BRI no change");
321             }
322         }
323     }
324
325     /**
326      * Updates light on/off state based on a OWN Lighting event message received
327      *
328      * @param msg the Lighting message received
329      */
330     private void updateOnOffState(Lighting msg) {
331         OpenWebNetBridgeHandler brH = bridgeHandler;
332         if (brH != null) {
333             if (msg.isOn() || msg.isOff()) {
334                 String channelId;
335                 int switchId = 0;
336                 if (brH.isBusGateway()) {
337                     channelId = CHANNEL_SWITCH;
338                 } else {
339                     WhereZigBee w = (WhereZigBee) (msg.getWhere());
340                     if (WhereZigBee.UNIT_02.equals(w.getUnit())) {
341                         channelId = CHANNEL_SWITCH_02;
342                         switchId = 1;
343                     } else {
344                         channelId = CHANNEL_SWITCH_01;
345                     }
346                 }
347                 int currentSt = sw[switchId];
348                 int newSt = (msg.isOn() ? 1 : 0);
349                 if (newSt != currentSt) {
350                     updateState(channelId, (newSt == 1 ? OnOffType.ON : OnOffType.OFF));
351                     sw[switchId] = newSt;
352                     logger.debug("  {} ONOFF CHANGED to {}", ownId, newSt);
353                 } else {
354                     logger.debug("  {} ONOFF no change", ownId);
355                 }
356             } else {
357                 logger.debug("updateOnOffState() Ignoring unsupported WHAT for thing {}. Frame={}", getThing().getUID(),
358                         msg.getFrameValue());
359                 return;
360             }
361         }
362     }
363
364     @Override
365     protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
366         return new WhereLightAutom(wStr);
367     }
368
369     /**
370      * Returns a WHERE address string based on channelId string
371      *
372      * @param channelId the channelId string
373      **/
374     @Nullable
375     protected String toWhere(String channelId) {
376         Where w = deviceWhere;
377         if (w != null) {
378             OpenWebNetBridgeHandler brH = bridgeHandler;
379             if (brH != null) {
380                 if (brH.isBusGateway()) {
381                     return w.value();
382                 } else if (channelId.equals(CHANNEL_SWITCH_02)) {
383                     return ((WhereZigBee) w).valueWithUnit(WhereZigBee.UNIT_02);
384                 } else { // CHANNEL_SWITCH_01 or other channels
385                     return ((WhereZigBee) w).valueWithUnit(WhereZigBee.UNIT_01);
386                 }
387             }
388         }
389         return null;
390     }
391 }