]> git.basschouten.com Git - openhab-addons.git/blob
19e5dcad485c1cd121a459434a164a3c6d8e71ab
[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.milight.internal.handler;
14
15 import java.net.DatagramSocket;
16 import java.net.InetAddress;
17
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.binding.milight.internal.MilightBindingConstants;
22 import org.openhab.binding.milight.internal.MilightThingState;
23 import org.openhab.binding.milight.internal.protocol.QueuedSend;
24 import org.openhab.core.library.types.DecimalType;
25 import org.openhab.core.library.types.HSBType;
26 import org.openhab.core.library.types.IncreaseDecreaseType;
27 import org.openhab.core.library.types.OnOffType;
28 import org.openhab.core.library.types.PercentType;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.openhab.core.thing.ThingStatusInfo;
35 import org.openhab.core.thing.binding.BaseThingHandler;
36 import org.openhab.core.types.Command;
37 import org.openhab.core.types.RefreshType;
38 import org.openhab.core.types.UnDefType;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * The {@link AbstractLedHandler} is responsible for handling commands, which are
44  * sent to one of the channels.
45  *
46  * @author David Graeff - Initial contribution
47  */
48 @NonNullByDefault
49 public abstract class AbstractLedHandler extends BaseThingHandler implements LedHandlerInterface {
50     private final Logger logger = LoggerFactory.getLogger(AbstractLedHandler.class);
51
52     protected final QueuedSend sendQueue;
53     /** Each bulb type including zone has to be unique. -> Each type has an offset. */
54     protected final int typeOffset;
55     protected final MilightThingState state = new MilightThingState();
56     protected LedHandlerConfig config = new LedHandlerConfig();
57     protected int port = 0;
58
59     protected @NonNullByDefault({}) InetAddress address;
60     protected @NonNullByDefault({}) DatagramSocket socket;
61
62     protected int delayTimeMS = 50;
63     protected int repeatTimes = 3;
64
65     protected int bridgeOffset;
66
67     /**
68      * A bulb always belongs to a zone in the milight universe and we need a way to queue commands for being send.
69      *
70      * @param thing
71      * @param sendQueue The send queue.
72      * @param typeOffset Each bulb type including its zone has to be unique. To realise this, each type has an offset.
73      */
74     public AbstractLedHandler(Thing thing, QueuedSend sendQueue, int typeOffset) {
75         super(thing);
76         this.typeOffset = typeOffset;
77         this.sendQueue = sendQueue;
78     }
79
80     @Override
81     public void handleCommand(ChannelUID channelUID, Command command) {
82         if (command instanceof RefreshType) {
83             switch (channelUID.getId()) {
84                 case MilightBindingConstants.CHANNEL_COLOR:
85                     updateState(channelUID, new HSBType(new DecimalType(state.hue360),
86                             new PercentType(state.saturation), new PercentType(state.brightness)));
87                     break;
88                 case MilightBindingConstants.CHANNEL_BRIGHTNESS:
89                     updateState(channelUID, new PercentType(state.brightness));
90                     break;
91                 case MilightBindingConstants.CHANNEL_SATURATION:
92                     updateState(channelUID, new PercentType(state.saturation));
93                     break;
94                 case MilightBindingConstants.CHANNEL_TEMP:
95                     updateState(channelUID, new PercentType(state.colorTemperature));
96                     break;
97                 case MilightBindingConstants.CHANNEL_ANIMATION_MODE:
98                     updateState(channelUID, new DecimalType(state.animationMode));
99                     break;
100             }
101             return;
102         }
103
104         switch (channelUID.getId()) {
105             case MilightBindingConstants.CHANNEL_COLOR: {
106                 if (command instanceof HSBType hsb) {
107                     this.setHSB(hsb.getHue().intValue(), hsb.getSaturation().intValue(), hsb.getBrightness().intValue(),
108                             state);
109                     updateState(MilightBindingConstants.CHANNEL_SATURATION, new PercentType(state.saturation));
110                 } else if (command instanceof OnOffType hsb) {
111                     this.setPower(hsb == OnOffType.ON, state);
112                 } else if (command instanceof PercentType p) {
113                     this.setBrightness(p.intValue(), state);
114                 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
115                     this.changeBrightness(increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? 1 : -1, state);
116                 } else {
117                     logger.error(
118                             "CHANNEL_COLOR channel only supports OnOffType/IncreaseDecreaseType/HSBType/PercentType");
119                 }
120                 updateState(MilightBindingConstants.CHANNEL_BRIGHTNESS, new PercentType(state.brightness));
121                 break;
122             }
123             case MilightBindingConstants.CHANNEL_NIGHTMODE: {
124                 this.nightMode(state);
125                 updateState(channelUID, UnDefType.UNDEF);
126                 break;
127             }
128             case MilightBindingConstants.CHANNEL_WHITEMODE: {
129                 this.whiteMode(state);
130                 updateState(channelUID, UnDefType.UNDEF);
131                 break;
132             }
133             case MilightBindingConstants.CHANNEL_BRIGHTNESS: {
134                 if (command instanceof OnOffType onOffCommand) {
135                     this.setPower(onOffCommand == OnOffType.ON, state);
136                 } else if (command instanceof DecimalType decimalCommand) {
137                     this.setBrightness(decimalCommand.intValue(), state);
138                 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
139                     this.changeBrightness(increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? 1 : -1, state);
140                 } else {
141                     logger.error("CHANNEL_BRIGHTNESS channel only supports OnOffType/IncreaseDecreaseType/DecimalType");
142                 }
143                 updateState(MilightBindingConstants.CHANNEL_COLOR, new HSBType(new DecimalType(state.hue360),
144                         new PercentType(state.saturation), new PercentType(state.brightness)));
145
146                 break;
147             }
148             case MilightBindingConstants.CHANNEL_SATURATION: {
149                 if (command instanceof OnOffType onOffCommand) {
150                     this.setSaturation((onOffCommand == OnOffType.ON) ? 100 : 0, state);
151                 } else if (command instanceof DecimalType decimalCommand) {
152                     this.setSaturation(decimalCommand.intValue(), state);
153                 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
154                     this.changeSaturation(increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? 1 : -1, state);
155                 } else {
156                     logger.error("CHANNEL_SATURATION channel only supports OnOffType/IncreaseDecreaseType/DecimalType");
157                 }
158                 updateState(MilightBindingConstants.CHANNEL_COLOR, new HSBType(new DecimalType(state.hue360),
159                         new PercentType(state.saturation), new PercentType(state.brightness)));
160
161                 break;
162             }
163             case MilightBindingConstants.CHANNEL_TEMP: {
164                 if (command instanceof OnOffType onOffCommand) {
165                     this.setColorTemperature((onOffCommand == OnOffType.ON) ? 100 : 0, state);
166                 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
167                     this.changeColorTemperature(increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? 1 : -1,
168                             state);
169                 } else if (command instanceof DecimalType d) {
170                     this.setColorTemperature(d.intValue(), state);
171                 } else {
172                     logger.error("CHANNEL_TEMP channel only supports OnOffType/IncreaseDecreaseType/DecimalType");
173                 }
174                 break;
175             }
176             case MilightBindingConstants.CHANNEL_SPEED_REL: {
177                 if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
178                     if (increaseDecreaseCommand == IncreaseDecreaseType.INCREASE) {
179                         this.changeSpeed(1, state);
180                     } else if (increaseDecreaseCommand == IncreaseDecreaseType.DECREASE) {
181                         this.changeSpeed(-1, state);
182                     }
183                 } else {
184                     logger.error("CHANNEL_SPEED channel only supports IncreaseDecreaseType");
185                 }
186                 break;
187             }
188             case MilightBindingConstants.CHANNEL_ANIMATION_MODE: {
189                 if (command instanceof DecimalType decimalCommand) {
190                     this.setLedMode(decimalCommand.intValue(), state);
191                 } else {
192                     logger.error("Animation mode channel only supports DecimalType");
193                 }
194                 break;
195             }
196             case MilightBindingConstants.CHANNEL_ANIMATION_MODE_REL: {
197                 if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
198                     if (increaseDecreaseCommand == IncreaseDecreaseType.INCREASE) {
199                         this.nextAnimationMode(state);
200                     } else if (increaseDecreaseCommand == IncreaseDecreaseType.DECREASE) {
201                         this.previousAnimationMode(state);
202                     }
203                 } else {
204                     logger.error("Relative animation mode channel only supports IncreaseDecreaseType");
205                 }
206                 break;
207             }
208             default:
209                 logger.error("Channel unknown {}", channelUID.getId());
210         }
211     }
212
213     /**
214      * Return the bride handler.
215      */
216     public @Nullable AbstractBridgeHandler getBridgeHandler() {
217         Bridge bridge = getBridge();
218         if (bridge == null) {
219             return null;
220         }
221         return (AbstractBridgeHandler) bridge.getHandler();
222     }
223
224     /**
225      * Return the bridge status.
226      */
227     public ThingStatusInfo getBridgeStatus() {
228         Bridge b = getBridge();
229         if (b != null) {
230             return b.getStatusInfo();
231         } else {
232             return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null);
233         }
234     }
235
236     /**
237      * Generates a unique command id for the {@see QueuedSend}. It incorporates the bridge, zone, bulb type and command
238      * category.
239      *
240      * @param commandCategory The category of the command.
241      *
242      * @return
243      */
244     public int uidc(int commandCategory) {
245         return (bridgeOffset + config.zone + typeOffset + 1) * 64 + commandCategory;
246     }
247
248     protected void start(AbstractBridgeHandler handler) {
249     }
250
251     @Override
252     public void bridgeStatusChanged(@NonNull ThingStatusInfo bridgeStatusInfo) {
253         if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
254             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
255             return;
256         }
257         if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
258             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
259             return;
260         }
261
262         AbstractBridgeHandler h = getBridgeHandler();
263         if (h == null) {
264             logger.warn("Bridge handler not found!");
265             return;
266         }
267         final InetAddress inetAddress = h.address;
268         if (inetAddress == null) {
269             logger.warn("Bridge handler has not yet determined the IP address!");
270             return;
271         }
272
273         state.reset();
274         configUpdated(h, inetAddress);
275
276         if (h.getThing().getStatus() == ThingStatus.ONLINE) {
277             updateStatus(ThingStatus.ONLINE);
278             start(h);
279         } else {
280             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
281         }
282     }
283
284     /**
285      * Called by the bridge if a configuration update happened after initialisation has been done
286      *
287      * @param h The bridge handler
288      */
289     public void configUpdated(AbstractBridgeHandler h, InetAddress address) {
290         this.port = h.config.port;
291         this.address = address;
292         this.socket = h.socket;
293         this.delayTimeMS = h.config.delayTime;
294         this.repeatTimes = h.config.repeat;
295         this.bridgeOffset = h.bridgeOffset;
296     }
297
298     @Override
299     public void initialize() {
300         config = getConfigAs(LedHandlerConfig.class);
301         bridgeStatusChanged(getBridgeStatus());
302     }
303 }