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