]> git.basschouten.com Git - openhab-addons.git/blob
b44946b2e521e6f8bd8998f55eb845ce52b98f45
[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) {
109                     HSBType hsb = (HSBType) command;
110                     this.setHSB(hsb.getHue().intValue(), hsb.getSaturation().intValue(), hsb.getBrightness().intValue(),
111                             state);
112                     updateState(MilightBindingConstants.CHANNEL_SATURATION, new PercentType(state.saturation));
113                 } else if (command instanceof OnOffType) {
114                     OnOffType hsb = (OnOffType) command;
115                     this.setPower(hsb == OnOffType.ON, state);
116                 } else if (command instanceof PercentType) {
117                     PercentType p = (PercentType) command;
118                     this.setBrightness(p.intValue(), state);
119                 } else if (command instanceof IncreaseDecreaseType) {
120                     this.changeBrightness((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE ? 1 : -1,
121                             state);
122                 } else {
123                     logger.error(
124                             "CHANNEL_COLOR channel only supports OnOffType/IncreaseDecreaseType/HSBType/PercentType");
125                 }
126                 updateState(MilightBindingConstants.CHANNEL_BRIGHTNESS, new PercentType(state.brightness));
127                 break;
128             }
129             case MilightBindingConstants.CHANNEL_NIGHTMODE: {
130                 this.nightMode(state);
131                 updateState(channelUID, UnDefType.UNDEF);
132                 break;
133             }
134             case MilightBindingConstants.CHANNEL_WHITEMODE: {
135                 this.whiteMode(state);
136                 updateState(channelUID, UnDefType.UNDEF);
137                 break;
138             }
139             case MilightBindingConstants.CHANNEL_BRIGHTNESS: {
140                 if (command instanceof OnOffType) {
141                     OnOffType hsb = (OnOffType) command;
142                     this.setPower(hsb == OnOffType.ON, state);
143                 } else if (command instanceof DecimalType) {
144                     DecimalType d = (DecimalType) command;
145                     this.setBrightness(d.intValue(), state);
146                 } else if (command instanceof IncreaseDecreaseType) {
147                     this.changeBrightness((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE ? 1 : -1,
148                             state);
149                 } else {
150                     logger.error("CHANNEL_BRIGHTNESS channel only supports OnOffType/IncreaseDecreaseType/DecimalType");
151                 }
152                 updateState(MilightBindingConstants.CHANNEL_COLOR, new HSBType(new DecimalType(state.hue360),
153                         new PercentType(state.saturation), new PercentType(state.brightness)));
154
155                 break;
156             }
157             case MilightBindingConstants.CHANNEL_SATURATION: {
158                 if (command instanceof OnOffType) {
159                     OnOffType s = (OnOffType) command;
160                     this.setSaturation((s == OnOffType.ON) ? 100 : 0, state);
161                 } else if (command instanceof DecimalType) {
162                     DecimalType d = (DecimalType) command;
163                     this.setSaturation(d.intValue(), state);
164                 } else if (command instanceof IncreaseDecreaseType) {
165                     this.changeSaturation((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE ? 1 : -1,
166                             state);
167                 } else {
168                     logger.error("CHANNEL_SATURATION channel only supports OnOffType/IncreaseDecreaseType/DecimalType");
169                 }
170                 updateState(MilightBindingConstants.CHANNEL_COLOR, new HSBType(new DecimalType(state.hue360),
171                         new PercentType(state.saturation), new PercentType(state.brightness)));
172
173                 break;
174             }
175             case MilightBindingConstants.CHANNEL_TEMP: {
176                 if (command instanceof OnOffType) {
177                     OnOffType s = (OnOffType) command;
178                     this.setColorTemperature((s == OnOffType.ON) ? 100 : 0, state);
179                 } else if (command instanceof IncreaseDecreaseType) {
180                     this.changeColorTemperature(
181                             (IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE ? 1 : -1, state);
182                 } else if (command instanceof DecimalType) {
183                     DecimalType d = (DecimalType) command;
184                     this.setColorTemperature(d.intValue(), state);
185                 } else {
186                     logger.error("CHANNEL_TEMP channel only supports OnOffType/IncreaseDecreaseType/DecimalType");
187                 }
188                 break;
189             }
190             case MilightBindingConstants.CHANNEL_SPEED_REL: {
191                 if (command instanceof IncreaseDecreaseType) {
192                     IncreaseDecreaseType id = (IncreaseDecreaseType) command;
193                     if (id == IncreaseDecreaseType.INCREASE) {
194                         this.changeSpeed(1, state);
195                     } else if (id == IncreaseDecreaseType.DECREASE) {
196                         this.changeSpeed(-1, state);
197                     }
198                 } else {
199                     logger.error("CHANNEL_SPEED channel only supports IncreaseDecreaseType");
200                 }
201                 break;
202             }
203             case MilightBindingConstants.CHANNEL_ANIMATION_MODE: {
204                 if (command instanceof DecimalType) {
205                     DecimalType d = (DecimalType) command;
206                     this.setLedMode(d.intValue(), state);
207                 } else {
208                     logger.error("Animation mode channel only supports DecimalType");
209                 }
210                 break;
211             }
212             case MilightBindingConstants.CHANNEL_ANIMATION_MODE_REL: {
213                 if (command instanceof IncreaseDecreaseType) {
214                     IncreaseDecreaseType id = (IncreaseDecreaseType) command;
215                     if (id == IncreaseDecreaseType.INCREASE) {
216                         this.nextAnimationMode(state);
217                     } else if (id == IncreaseDecreaseType.DECREASE) {
218                         this.previousAnimationMode(state);
219                     }
220                 } else {
221                     logger.error("Relative animation mode channel only supports IncreaseDecreaseType");
222                 }
223                 break;
224             }
225             default:
226                 logger.error("Channel unknown {}", channelUID.getId());
227         }
228     }
229
230     /**
231      * Return the bride handler.
232      */
233     public @Nullable AbstractBridgeHandler getBridgeHandler() {
234         Bridge bridge = getBridge();
235         if (bridge == null) {
236             return null;
237         }
238         return (AbstractBridgeHandler) bridge.getHandler();
239     }
240
241     /**
242      * Return the bridge status.
243      */
244     public ThingStatusInfo getBridgeStatus() {
245         Bridge b = getBridge();
246         if (b != null) {
247             return b.getStatusInfo();
248         } else {
249             return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null);
250         }
251     }
252
253     /**
254      * Generates a unique command id for the {@see QueuedSend}. It incorporates the bridge, zone, bulb type and command
255      * category.
256      *
257      * @param commandCategory The category of the command.
258      *
259      * @return
260      */
261     public int uidc(int commandCategory) {
262         return (bridgeOffset + config.zone + typeOffset + 1) * 64 + commandCategory;
263     }
264
265     protected void start(AbstractBridgeHandler handler) {
266     }
267
268     @Override
269     public void bridgeStatusChanged(@NonNull ThingStatusInfo bridgeStatusInfo) {
270         if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
271             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
272             return;
273         }
274         if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
275             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
276             return;
277         }
278
279         AbstractBridgeHandler h = getBridgeHandler();
280         if (h == null) {
281             logger.warn("Bridge handler not found!");
282             return;
283         }
284         final InetAddress inetAddress = h.address;
285         if (inetAddress == null) {
286             logger.warn("Bridge handler has not yet determined the IP address!");
287             return;
288         }
289
290         state.reset();
291         configUpdated(h, inetAddress);
292
293         if (h.getThing().getStatus() == ThingStatus.ONLINE) {
294             updateStatus(ThingStatus.ONLINE);
295             start(h);
296         } else {
297             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
298         }
299     }
300
301     /**
302      * Called by the bridge if a configuration update happened after initialisation has been done
303      *
304      * @param h The bridge handler
305      */
306     public void configUpdated(AbstractBridgeHandler h, InetAddress address) {
307         this.port = h.config.port;
308         this.address = address;
309         this.socket = h.socket;
310         this.delayTimeMS = h.config.delayTime;
311         this.repeatTimes = h.config.repeat;
312         this.bridgeOffset = h.bridgeOffset;
313     }
314
315     @Override
316     public void initialize() {
317         config = getConfigAs(LedHandlerConfig.class);
318         bridgeStatusChanged(getBridgeStatus());
319     }
320 }