2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.wifiled.internal.handler;
15 import java.io.DataOutputStream;
16 import java.io.IOException;
17 import java.net.Socket;
18 import java.util.concurrent.ExecutionException;
19 import java.util.concurrent.Executors;
20 import java.util.concurrent.Future;
21 import java.util.concurrent.ScheduledExecutorService;
22 import java.util.concurrent.Semaphore;
23 import java.util.concurrent.TimeUnit;
24 import java.util.concurrent.locks.Lock;
25 import java.util.concurrent.locks.ReentrantLock;
26 import java.util.function.BiFunction;
27 import java.util.function.Function;
29 import org.openhab.core.library.types.HSBType;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.PercentType;
32 import org.openhab.core.library.types.StringType;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
37 * The {@link FadingWiFiLEDDriver} class is responsible for the communication with the WiFi LED controller.
38 * It utilizes color fading when changing colors or turning the light on of off.
40 * @author Stefan Endrullis - Initial contribution
41 * @author Ries van Twisk
43 public class FadingWiFiLEDDriver extends AbstractWiFiLEDDriver {
45 public static final int DEFAULT_FADE_DURATION_IN_MS = 1000;
46 public static final int DEFAULT_FADE_STEPS = 100;
47 public static final int KEEP_COMMUNICATION_OPEN_FOR_MS = 1000;
49 private static final InternalLedState BLACK_STATE = new InternalLedState();
51 private boolean power = false;
52 private InternalLedState currentState = new InternalLedState(); // Use to not update the controller with the same
54 private InternalLedState currentFaderState = new InternalLedState();
55 private InternalLedState targetState = new InternalLedState();
56 private LEDStateDTO dtoState = LEDStateDTO.valueOf(0, 0, 0, 0, 0, 0, 0, 0);
57 private final ScheduledExecutorService waiterExecutor = Executors.newSingleThreadScheduledExecutor();
58 private final ScheduledExecutorService faderExecutor = Executors.newSingleThreadScheduledExecutor();
59 private LEDFaderRunner ledfaderThread = null;
60 private final Semaphore ledUpdateSyncSemaphore = new Semaphore(1, false);
61 private final int fadeDurationInMs;
62 private final int totalFadingSteps;
64 public FadingWiFiLEDDriver(String host, int port, AbstractWiFiLEDDriver.Protocol protocol, int fadeDurationInMs,
65 int totalFadingSteps) {
66 super(host, port, protocol);
67 this.fadeDurationInMs = fadeDurationInMs < 10 ? 10 : fadeDurationInMs;
68 this.totalFadingSteps = totalFadingSteps < 1 ? 1 : totalFadingSteps;
72 public void init() throws IOException {
74 LEDState s = getLEDState();
75 dtoState = LEDStateDTO.valueOf(s.state, s.program, s.programSpeed, s.red, s.green, s.blue, s.white,
77 power = (s.state & 0x01) != 0;
78 currentState = InternalLedState.fromRGBW(s.red, s.green, s.blue, s.white, s.white2);
79 currentFaderState = currentState;
80 } catch (IOException e) {
81 logger.warn("IOException", e);
86 public void shutdown() {
87 waiterExecutor.shutdown();
88 faderExecutor.shutdown();
90 if (!waiterExecutor.awaitTermination((fadeDurationInMs / totalFadingSteps) * 2, TimeUnit.MILLISECONDS)) {
91 waiterExecutor.shutdownNow();
93 if (!faderExecutor.awaitTermination((fadeDurationInMs / totalFadingSteps) * 2, TimeUnit.MILLISECONDS)) {
94 faderExecutor.shutdownNow();
96 } catch (InterruptedException e) {
102 public void setColor(HSBType color) throws IOException {
103 dtoState = dtoState.withColor(color);
104 changeState(targetState.withColor(color));
108 public void setBrightness(PercentType brightness) throws IOException {
109 dtoState = dtoState.withBrightness(brightness);
110 changeState(targetState.withBrightness(brightness.doubleValue() / 100));
114 public void incBrightness(int step) throws IOException {
115 dtoState = dtoState.withIncrementedBrightness(step);
116 changeState(targetState.withBrightness(targetState.getBrightness() + ((double) step / 100)));
120 public void decBrightness(int step) throws IOException {
121 dtoState = dtoState.withIncrementedBrightness(-step);
122 changeState(targetState.withBrightness(targetState.getBrightness() - ((double) step / 100)));
126 public void setWhite(PercentType white) throws IOException {
127 dtoState = dtoState.withWhite(white);
128 changeState(targetState.withWhite(white.doubleValue() / 100));
132 public void incWhite(int step) throws IOException {
133 dtoState = dtoState.withIncrementedWhite(step);
134 changeState(targetState.withWhite(targetState.getWhite() + ((double) step / 100)));
138 public void setWhite2(PercentType white2) throws IOException {
139 dtoState = dtoState.withWhite2(white2);
140 changeState(targetState.withWhite2(white2.doubleValue() / 100));
144 public void incWhite2(int step) throws IOException {
145 dtoState = dtoState.withIncrementedWhite2(step);
146 changeState(targetState.withWhite2(targetState.getWhite2() + ((double) step / 100)));
150 public void setProgram(StringType program) throws IOException {
154 public void setProgramSpeed(PercentType speed) throws IOException {
158 public void incProgramSpeed(int step) throws IOException {
162 public void setPower(OnOffType command) throws IOException {
163 dtoState = dtoState.withPower(command);
164 power = command == OnOffType.ON;
165 fadeToState(power ? targetState : BLACK_STATE);
169 public LEDStateDTO getLEDStateDTO() throws IOException {
173 private void changeState(final InternalLedState newState) throws IOException {
174 targetState = newState;
176 fadeToState(targetState);
181 * Runnable that takes care of fading of the LED's
183 static final class LEDFaderRunner implements Runnable {
184 private final Logger logger = LoggerFactory.getLogger(LEDFaderRunner.class);
188 private InternalLedState fromState;
189 private InternalLedState toState;
190 private final int totalFadingSteps;
191 private final long keepCommOpenForMS;
192 private final Function<DataOutputStream, Boolean> powerOnFunc;
193 private final BiFunction<DataOutputStream, InternalLedState, Boolean> ledSender;
195 private long lastCommunicationTime = 0;
197 private int currentFadingStep = 1;
199 private final Lock lock = new ReentrantLock();
201 private InternalLedState currentFadeState;
202 private Socket socket;
203 private DataOutputStream outputStream;
205 public LEDFaderRunner(String host, int port, InternalLedState fromState, InternalLedState toState,
206 int totalFadingSteps, int keepCommOpenForMS, Function<DataOutputStream, Boolean> powerOnFunc,
207 BiFunction<DataOutputStream, InternalLedState, Boolean> ledSender) {
210 this.fromState = fromState;
211 this.toState = toState;
212 this.totalFadingSteps = totalFadingSteps;
213 this.keepCommOpenForMS = keepCommOpenForMS;
214 this.powerOnFunc = powerOnFunc;
215 this.ledSender = ledSender;
219 * Call before starting a thre`ad, it will initialise a socket and power on the LEDs
221 * @throws IOException
223 public void init() throws IOException {
224 socket = new Socket(host, port);
225 socket.setSoTimeout(DEFAULT_SOCKET_TIMEOUT);
226 outputStream = new DataOutputStream(socket.getOutputStream());
227 logger.debug("Connected to '{}'", socket);
228 powerOnFunc.apply(outputStream);
229 currentFadeState = fromState;
232 public void setToState(InternalLedState toState) {
234 this.fromState = currentFadeState;
235 this.toState = toState;
236 this.currentFadingStep = 1;
243 if (currentFadingStep <= totalFadingSteps) {
244 currentFadeState = fromState.fade(toState, (double) currentFadingStep / totalFadingSteps);
247 logger.debug("currentFadeState: {}", currentFadeState);
248 if (!ledSender.apply(outputStream, currentFadeState)) {
249 logger.warn("Failed sending at step {}", currentFadingStep);
250 throw new IllegalStateException("Failed sending at step " + currentFadingStep);
252 lastCommunicationTime = System.currentTimeMillis();
255 if (lastCommunicationTime < (System.currentTimeMillis() - keepCommOpenForMS)) {
256 throw new IllegalStateException("Reached end step");
262 public void shutdown() {
263 if (socket != null) {
266 } catch (IOException e) {
273 private synchronized void fadeToState(final InternalLedState newTargetState) throws IOException {
274 if (ledUpdateSyncSemaphore.tryAcquire(1)) {
275 // Create and Execute a new LED Fader
276 ledfaderThread = new LEDFaderRunner(host, port, currentFaderState, newTargetState, totalFadingSteps,
277 KEEP_COMMUNICATION_OPEN_FOR_MS, (outputStream) -> {
279 sendRaw(getBytesForPower(true), outputStream);
281 } catch (IOException e) {
282 logger.warn("IOException", e);
285 }, (outputStream, fs) -> {
287 sendLEDData(fs, outputStream);
288 currentFaderState = fs;
289 logger.trace("Current: {} {} {} {}", fs.getR(), fs.getG(), fs.getB(), fs.getWhite());
291 } catch (IOException e) {
292 logger.warn("IOException", e);
296 ledfaderThread.init();
297 final int period = fadeDurationInMs / totalFadingSteps;
298 final Future<?> future = faderExecutor.scheduleAtFixedRate(ledfaderThread, 0, period < 1 ? 1 : period,
299 TimeUnit.MILLISECONDS);
301 // Wait untill LED Thread has finished, when so shutdown fader
302 waiterExecutor.schedule(() -> {
305 } catch (InterruptedException | ExecutionException e) {
307 } catch (Exception e) {
308 logger.warn("Exception", e);
310 ledfaderThread.shutdown();
311 ledfaderThread = null;
312 ledUpdateSyncSemaphore.release(1);
313 }, 0, TimeUnit.MILLISECONDS);
315 ledfaderThread.setToState(newTargetState);
319 private void sendLEDData(InternalLedState ledState, DataOutputStream out) throws IOException {
320 logger.debug("Setting LED State to {}", ledState);
322 if (!ledState.equals(currentState)) {
323 // "normal" program: set color etc.
324 byte r = (byte) (ledState.getR() & 0xFF);
325 byte g = (byte) (ledState.getG() & 0xFF);
326 byte b = (byte) (ledState.getB() & 0xFF);
327 byte w = (byte) (ledState.getW() & 0xFF);
328 byte w2 = (byte) (ledState.getW2() & 0xFF);
330 logger.debug("RGBW: {}, {}, {}, {}, {}", r, g, b, w, w2);
332 byte[] bytes = getBytesForColor(r, g, b, w, w2);
336 currentState = ledState;