]> git.basschouten.com Git - openhab-addons.git/blob
ff9dd2e7d68acf81375e44b1ebc48c4e89e2a30a
[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.hyperion.internal.handler;
14
15 import static org.openhab.binding.hyperion.internal.HyperionBindingConstants.*;
16
17 import java.awt.Color;
18 import java.io.IOException;
19 import java.math.BigDecimal;
20 import java.net.UnknownHostException;
21 import java.util.List;
22 import java.util.Optional;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
25
26 import org.openhab.binding.hyperion.internal.connection.JsonTcpConnection;
27 import org.openhab.binding.hyperion.internal.protocol.ColorCommand;
28 import org.openhab.binding.hyperion.internal.protocol.CommandUnsuccessfulException;
29 import org.openhab.binding.hyperion.internal.protocol.EffectCommand;
30 import org.openhab.binding.hyperion.internal.protocol.HyperionCommand;
31 import org.openhab.binding.hyperion.internal.protocol.ServerInfoCommand;
32 import org.openhab.binding.hyperion.internal.protocol.v1.ActiveEffect;
33 import org.openhab.binding.hyperion.internal.protocol.v1.ActiveLedColor;
34 import org.openhab.binding.hyperion.internal.protocol.v1.ClearAllCommand;
35 import org.openhab.binding.hyperion.internal.protocol.v1.ClearCommand;
36 import org.openhab.binding.hyperion.internal.protocol.v1.Effect;
37 import org.openhab.binding.hyperion.internal.protocol.v1.Transform;
38 import org.openhab.binding.hyperion.internal.protocol.v1.TransformCommand;
39 import org.openhab.binding.hyperion.internal.protocol.v1.V1Info;
40 import org.openhab.binding.hyperion.internal.protocol.v1.V1Response;
41 import org.openhab.core.config.core.Configuration;
42 import org.openhab.core.library.types.HSBType;
43 import org.openhab.core.library.types.PercentType;
44 import org.openhab.core.library.types.StringType;
45 import org.openhab.core.thing.ChannelUID;
46 import org.openhab.core.thing.Thing;
47 import org.openhab.core.thing.ThingStatus;
48 import org.openhab.core.thing.ThingStatusDetail;
49 import org.openhab.core.thing.binding.BaseThingHandler;
50 import org.openhab.core.types.Command;
51 import org.openhab.core.types.RefreshType;
52 import org.openhab.core.types.UnDefType;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 import com.google.gson.Gson;
57 import com.google.gson.JsonParseException;
58
59 /**
60  * The {@link HyperionHandler} is responsible for handling commands, which are
61  * sent to one of the channels.
62  *
63  * @author Daniel Walters - Initial contribution
64  */
65 public class HyperionHandler extends BaseThingHandler {
66
67     private final Logger logger = LoggerFactory.getLogger(HyperionHandler.class);
68
69     private JsonTcpConnection connection;
70     private ScheduledFuture<?> refreshFuture;
71     private ScheduledFuture<?> connectFuture;
72     private Gson gson = new Gson();
73
74     private static final ServerInfoCommand SERVER_INFO_COMMAND = new ServerInfoCommand();
75
76     private String address;
77     private int port;
78     private int refreshInterval;
79     private int priority;
80
81     private Runnable refreshJob = new Runnable() {
82         @Override
83         public void run() {
84             try {
85                 V1Response response = sendCommand(SERVER_INFO_COMMAND);
86                 if (response.isSuccess()) {
87                     handleServerInfoResponse(response);
88                 }
89             } catch (IOException e) {
90                 logger.debug("Could not connect to server.");
91                 updateOnlineStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
92             } catch (JsonParseException e) {
93                 logger.debug("{}", e.getMessage(), e);
94             } catch (CommandUnsuccessfulException e) {
95                 logger.debug("Server rejected the command: {}", e.getMessage());
96             }
97         }
98     };
99
100     private Runnable connectionJob = new Runnable() {
101         @Override
102         public void run() {
103             try {
104                 if (!connection.isConnected()) {
105                     connection.connect();
106                     updateOnlineStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
107                 }
108             } catch (IOException e) {
109                 logger.debug("Could not connect to server.");
110                 updateOnlineStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
111             }
112         }
113     };
114
115     public HyperionHandler(Thing thing) {
116         super(thing);
117     }
118
119     protected void handleServerInfoResponse(V1Response response) {
120         V1Info info = response.getInfo();
121         if (info != null) {
122             // update Color
123             List<ActiveLedColor> activeColors = info.getActiveLedColor();
124             updateColor(activeColors);
125
126             // update effect
127             List<ActiveEffect> activeEffects = info.getActiveEffects();
128             List<Effect> effects = info.getEffects();
129             updateEffect(activeEffects, effects);
130
131             // update transform
132             List<Transform> transform = info.getTransform();
133             updateTransform(transform);
134         }
135     }
136
137     private void updateTransform(List<Transform> transform) {
138         Optional<Transform> defaultTransform = transform.stream() // convert list to stream
139                 .findFirst();
140
141         if (defaultTransform.isPresent()) {
142             double luminanceGain = defaultTransform.get().getLuminanceGain();
143             if (luminanceGain >= 0.0 && luminanceGain <= 1.0) {
144                 PercentType luminanceGainPercentType = new PercentType((int) (luminanceGain * 100));
145                 updateState(CHANNEL_BRIGHTNESS, luminanceGainPercentType);
146             }
147         } else {
148             updateState(CHANNEL_BRIGHTNESS, UnDefType.NULL);
149         }
150     }
151
152     private void updateEffect(List<ActiveEffect> activeEffects, List<Effect> effects) {
153         Optional<ActiveEffect> effect = activeEffects.stream() // convert list to stream
154                 .findFirst();
155         if (effect.isPresent()) {
156             String path = effect.get().getScript();
157             Optional<Effect> effectDescription = effects.stream() // convert list to stream
158                     .filter(effectCandidate -> effectCandidate.getScript().equals(path)).findFirst();
159             if (effectDescription.isPresent()) {
160                 String effectName = effectDescription.get().getName();
161                 updateState(CHANNEL_EFFECT, new StringType(effectName));
162             }
163         } else {
164             updateState(CHANNEL_EFFECT, UnDefType.NULL);
165         }
166     }
167
168     private void updateColor(List<ActiveLedColor> activeColors) {
169         Optional<ActiveLedColor> color = activeColors.stream() // convert list to stream
170                 .findFirst();
171         if (color.isPresent()) {
172             List<Integer> rgbVals = color.get().getRGBValue();
173             int r = rgbVals.get(0);
174             int g = rgbVals.get(1);
175             int b = rgbVals.get(2);
176             HSBType hsbType = HSBType.fromRGB(r, g, b);
177             updateState(CHANNEL_COLOR, hsbType);
178         } else {
179             updateState(CHANNEL_COLOR, UnDefType.NULL);
180         }
181     }
182
183     @Override
184     public void initialize() {
185         logger.debug("Initializing Hyperion thing handler.");
186         try {
187             Configuration config = thing.getConfiguration();
188             address = (String) config.get(PROP_HOST);
189             port = ((BigDecimal) config.get(PROP_PORT)).intValue();
190             refreshInterval = ((BigDecimal) config.get(PROP_POLL_FREQUENCY)).intValue();
191             priority = ((BigDecimal) config.get(PROP_PRIORITY)).intValue();
192
193             connection = new JsonTcpConnection(address, port);
194             connectFuture = scheduler.scheduleWithFixedDelay(connectionJob, 0, refreshInterval, TimeUnit.SECONDS);
195             refreshFuture = scheduler.scheduleWithFixedDelay(refreshJob, 0, refreshInterval, TimeUnit.SECONDS);
196         } catch (UnknownHostException e) {
197             logger.debug("Could not resolve host: {}", e.getMessage());
198             updateOnlineStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
199         }
200     }
201
202     @Override
203     public void dispose() {
204         logger.debug("Disposing of Hyperion thing handler.");
205         if (refreshFuture != null) {
206             refreshFuture.cancel(true);
207         }
208         if (connectFuture != null) {
209             connectFuture.cancel(true);
210         }
211         if (connection != null && connection.isConnected()) {
212             try {
213                 connection.close();
214             } catch (IOException e) {
215                 // do nothing
216             }
217         }
218     }
219
220     @Override
221     public void handleCommand(ChannelUID channelUID, Command command) {
222         try {
223             if (command instanceof RefreshType) {
224                 if (refreshFuture.isDone()) {
225                     refreshFuture = scheduler.scheduleWithFixedDelay(refreshJob, 0, refreshInterval, TimeUnit.SECONDS);
226                 } else {
227                     logger.debug("Previous refresh not yet completed");
228                 }
229             } else if (CHANNEL_BRIGHTNESS.equals(channelUID.getId())) {
230                 handleBrightness(command);
231             } else if (CHANNEL_COLOR.equals(channelUID.getId())) {
232                 handleColor(command);
233             } else if (CHANNEL_CLEAR.equals(channelUID.getId())) {
234                 handleClear(command);
235             } else if (CHANNEL_EFFECT.equals(channelUID.getId())) {
236                 handleEffect(command);
237             }
238         } catch (IOException e) {
239             logger.debug("Unable to send command: {}", command);
240         } catch (CommandUnsuccessfulException e) {
241             logger.debug("Server rejected the command: {}", e.getMessage());
242         }
243     }
244
245     private void handleEffect(Command command) throws IOException, CommandUnsuccessfulException {
246         if (command instanceof StringType) {
247             String effectName = command.toString();
248
249             Effect effect = new Effect(effectName);
250             EffectCommand effectCommand = new EffectCommand(effect, priority);
251             sendCommand(effectCommand);
252         } else {
253             logger.debug("Channel {} unable to process command {}", CHANNEL_EFFECT, command);
254         }
255     }
256
257     private void handleBrightness(Command command) throws IOException, CommandUnsuccessfulException {
258         if (command instanceof PercentType) {
259             PercentType percent = (PercentType) command;
260             Transform transform = new Transform();
261             transform.setLuminanceGain(percent.doubleValue() / 100);
262             TransformCommand transformCommand = new TransformCommand(transform);
263             sendCommand(transformCommand);
264         } else {
265             logger.debug("Channel {} unable to process command {}", CHANNEL_BRIGHTNESS, command);
266         }
267     }
268
269     private void handleColor(Command command) throws IOException, CommandUnsuccessfulException {
270         if (command instanceof HSBType) {
271             HSBType color = (HSBType) command;
272             Color c = new Color(color.getRGB());
273             int r = c.getRed();
274             int g = c.getGreen();
275             int b = c.getBlue();
276
277             ColorCommand colorCommand = new ColorCommand(r, g, b, priority);
278             sendCommand(colorCommand);
279         } else {
280             logger.debug("Channel {} unable to process command {}", CHANNEL_COLOR, command);
281         }
282     }
283
284     private void handleClear(Command command) throws IOException, CommandUnsuccessfulException {
285         if (command instanceof StringType) {
286             String cmd = command.toString();
287             if ("ALL".equals(cmd)) {
288                 ClearAllCommand clearCommand = new ClearAllCommand();
289                 sendCommand(clearCommand);
290             } else {
291                 int priority = Integer.parseInt(cmd);
292                 ClearCommand clearCommand = new ClearCommand(priority);
293                 sendCommand(clearCommand);
294             }
295         }
296     }
297
298     public V1Response sendCommand(HyperionCommand command) throws IOException, CommandUnsuccessfulException {
299         String commandJson = gson.toJson(command);
300         String jsonResponse = connection.send(commandJson);
301         V1Response response = gson.fromJson(jsonResponse, V1Response.class);
302         if (!response.isSuccess()) {
303             throw new CommandUnsuccessfulException(gson.toJson(command));
304         }
305         return response;
306     }
307
308     private void updateOnlineStatus(ThingStatus status, ThingStatusDetail detail, String message) {
309         ThingStatus current = thing.getStatus();
310         ThingStatusDetail currentDetail = thing.getStatusInfo().getStatusDetail();
311         if (!current.equals(status) || !currentDetail.equals(detail)) {
312             updateStatus(status, detail, message);
313         }
314     }
315 }