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