2 * Copyright (c) 2010-2023 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.hyperion.internal.handler;
15 import static org.openhab.binding.hyperion.internal.HyperionBindingConstants.*;
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;
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;
56 import com.google.gson.Gson;
57 import com.google.gson.JsonParseException;
60 * The {@link HyperionHandler} is responsible for handling commands, which are
61 * sent to one of the channels.
63 * @author Daniel Walters - Initial contribution
65 public class HyperionHandler extends BaseThingHandler {
67 private final Logger logger = LoggerFactory.getLogger(HyperionHandler.class);
69 private JsonTcpConnection connection;
70 private ScheduledFuture<?> refreshFuture;
71 private ScheduledFuture<?> connectFuture;
72 private Gson gson = new Gson();
74 private static final ServerInfoCommand SERVER_INFO_COMMAND = new ServerInfoCommand();
76 private String address;
78 private int refreshInterval;
81 private Runnable refreshJob = new Runnable() {
85 V1Response response = sendCommand(SERVER_INFO_COMMAND);
86 if (response.isSuccess()) {
87 handleServerInfoResponse(response);
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());
100 private Runnable connectionJob = new Runnable() {
104 if (!connection.isConnected()) {
105 connection.connect();
106 updateOnlineStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
108 } catch (IOException e) {
109 logger.debug("Could not connect to server.");
110 updateOnlineStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
115 public HyperionHandler(Thing thing) {
119 protected void handleServerInfoResponse(V1Response response) {
120 V1Info info = response.getInfo();
123 List<ActiveLedColor> activeColors = info.getActiveLedColor();
124 updateColor(activeColors);
127 List<ActiveEffect> activeEffects = info.getActiveEffects();
128 List<Effect> effects = info.getEffects();
129 updateEffect(activeEffects, effects);
132 List<Transform> transform = info.getTransform();
133 updateTransform(transform);
137 private void updateTransform(List<Transform> transform) {
138 Optional<Transform> defaultTransform = transform.stream() // convert list to stream
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);
148 updateState(CHANNEL_BRIGHTNESS, UnDefType.NULL);
152 private void updateEffect(List<ActiveEffect> activeEffects, List<Effect> effects) {
153 Optional<ActiveEffect> effect = activeEffects.stream() // convert list to stream
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));
164 updateState(CHANNEL_EFFECT, UnDefType.NULL);
168 private void updateColor(List<ActiveLedColor> activeColors) {
169 Optional<ActiveLedColor> color = activeColors.stream() // convert list to stream
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);
179 updateState(CHANNEL_COLOR, UnDefType.NULL);
184 public void initialize() {
185 logger.debug("Initializing Hyperion thing handler.");
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();
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());
203 public void dispose() {
204 logger.debug("Disposing of Hyperion thing handler.");
205 if (refreshFuture != null) {
206 refreshFuture.cancel(true);
208 if (connectFuture != null) {
209 connectFuture.cancel(true);
211 if (connection != null && connection.isConnected()) {
214 } catch (IOException e) {
221 public void handleCommand(ChannelUID channelUID, Command command) {
223 if (command instanceof RefreshType) {
224 if (refreshFuture.isDone()) {
225 refreshFuture = scheduler.scheduleWithFixedDelay(refreshJob, 0, refreshInterval, TimeUnit.SECONDS);
227 logger.debug("Previous refresh not yet completed");
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);
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());
245 private void handleEffect(Command command) throws IOException, CommandUnsuccessfulException {
246 if (command instanceof StringType) {
247 String effectName = command.toString();
249 Effect effect = new Effect(effectName);
250 EffectCommand effectCommand = new EffectCommand(effect, priority);
251 sendCommand(effectCommand);
253 logger.debug("Channel {} unable to process command {}", CHANNEL_EFFECT, command);
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);
265 logger.debug("Channel {} unable to process command {}", CHANNEL_BRIGHTNESS, command);
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());
274 int g = c.getGreen();
277 ColorCommand colorCommand = new ColorCommand(r, g, b, priority);
278 sendCommand(colorCommand);
280 logger.debug("Channel {} unable to process command {}", CHANNEL_COLOR, command);
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);
291 int priority = Integer.parseInt(cmd);
292 ClearCommand clearCommand = new ClearCommand(priority);
293 sendCommand(clearCommand);
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));
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);