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