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