2 * Copyright (c) 2010-2022 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.epsonprojector.internal.handler;
15 import static org.openhab.binding.epsonprojector.internal.EpsonProjectorBindingConstants.*;
17 import java.math.BigDecimal;
18 import java.util.List;
19 import java.util.Optional;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.epsonprojector.internal.EpsonProjectorCommandException;
26 import org.openhab.binding.epsonprojector.internal.EpsonProjectorCommandType;
27 import org.openhab.binding.epsonprojector.internal.EpsonProjectorDevice;
28 import org.openhab.binding.epsonprojector.internal.EpsonProjectorException;
29 import org.openhab.binding.epsonprojector.internal.configuration.EpsonProjectorConfiguration;
30 import org.openhab.binding.epsonprojector.internal.enums.AspectRatio;
31 import org.openhab.binding.epsonprojector.internal.enums.Background;
32 import org.openhab.binding.epsonprojector.internal.enums.ColorMode;
33 import org.openhab.binding.epsonprojector.internal.enums.Gamma;
34 import org.openhab.binding.epsonprojector.internal.enums.Luminance;
35 import org.openhab.binding.epsonprojector.internal.enums.PowerStatus;
36 import org.openhab.binding.epsonprojector.internal.enums.Switch;
37 import org.openhab.core.io.transport.serial.SerialPortManager;
38 import org.openhab.core.library.types.DecimalType;
39 import org.openhab.core.library.types.OnOffType;
40 import org.openhab.core.library.types.PercentType;
41 import org.openhab.core.library.types.StringType;
42 import org.openhab.core.thing.Channel;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.thing.ThingStatus;
46 import org.openhab.core.thing.ThingStatusDetail;
47 import org.openhab.core.thing.ThingTypeUID;
48 import org.openhab.core.thing.binding.BaseThingHandler;
49 import org.openhab.core.types.Command;
50 import org.openhab.core.types.RefreshType;
51 import org.openhab.core.types.State;
52 import org.openhab.core.types.UnDefType;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
57 * The {@link EpsonProjectorHandler} is responsible for handling commands, which are
58 * sent to one of the channels.
60 * @author Pauli Anttila, Yannick Schaus - Initial contribution
61 * @author Michael Lobstein - Improvements for OH3
64 public class EpsonProjectorHandler extends BaseThingHandler {
65 private static final int DEFAULT_POLLING_INTERVAL_SEC = 10;
67 private final Logger logger = LoggerFactory.getLogger(EpsonProjectorHandler.class);
68 private final SerialPortManager serialPortManager;
70 private @Nullable ScheduledFuture<?> pollingJob;
71 private Optional<EpsonProjectorDevice> device = Optional.empty();
73 private boolean isPowerOn = false;
74 private int maxVolume = 20;
75 private int curVolumeStep = -1;
76 private int pollingInterval = DEFAULT_POLLING_INTERVAL_SEC;
78 public EpsonProjectorHandler(Thing thing, SerialPortManager serialPortManager) {
80 this.serialPortManager = serialPortManager;
84 public void handleCommand(ChannelUID channelUID, Command command) {
85 String channelId = channelUID.getId();
86 if (command instanceof RefreshType) {
87 Channel channel = this.thing.getChannel(channelUID);
88 if (channel != null) {
89 updateChannelState(channel);
92 EpsonProjectorCommandType epsonCommand = EpsonProjectorCommandType.getCommandType(channelId);
93 sendDataToDevice(epsonCommand, command);
98 public void initialize() {
99 EpsonProjectorConfiguration config = getConfigAs(EpsonProjectorConfiguration.class);
100 ThingTypeUID thingTypeUID = thing.getThingTypeUID();
102 if (THING_TYPE_PROJECTOR_SERIAL.equals(thingTypeUID)) {
103 device = Optional.of(new EpsonProjectorDevice(serialPortManager, config));
104 } else if (THING_TYPE_PROJECTOR_TCP.equals(thingTypeUID)) {
105 device = Optional.of(new EpsonProjectorDevice(config));
107 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
110 maxVolume = config.maxVolume;
111 pollingInterval = config.pollingInterval;
112 device.ifPresent(dev -> dev.setScheduler(scheduler));
113 updateStatus(ThingStatus.UNKNOWN);
114 schedulePollingJob();
118 * Schedule the polling job
120 private void schedulePollingJob() {
123 pollingJob = scheduler.scheduleWithFixedDelay(() -> {
124 List<Channel> channels = this.thing.getChannels();
125 for (Channel channel : channels) {
126 // only query power & lamp time when projector is off
127 if (isPowerOn || (channel.getUID().getId().equals(CHANNEL_TYPE_POWER)
128 || channel.getUID().getId().equals(CHANNEL_TYPE_LAMPTIME))) {
129 updateChannelState(channel);
132 }, 0, (pollingInterval > 0) ? pollingInterval : DEFAULT_POLLING_INTERVAL_SEC, TimeUnit.SECONDS);
136 * Cancel the polling job
138 private void cancelPollingJob() {
139 ScheduledFuture<?> pollingJob = this.pollingJob;
140 if (pollingJob != null) {
141 pollingJob.cancel(true);
142 this.pollingJob = null;
147 public void dispose() {
153 private void updateChannelState(Channel channel) {
155 if (!isLinked(channel.getUID()) && !channel.getUID().getId().equals(CHANNEL_TYPE_POWER)) {
159 EpsonProjectorCommandType epsonCommand = EpsonProjectorCommandType.getCommandType(channel.getUID().getId());
161 State state = queryDataFromDevice(epsonCommand);
164 if (isLinked(channel.getUID())) {
165 updateState(channel.getUID(), state);
167 // the first valid response will cause the thing to go ONLINE
168 if (state != UnDefType.UNDEF) {
169 updateStatus(ThingStatus.ONLINE);
172 } catch (IllegalArgumentException e) {
173 logger.warn("Unknown channel {}, exception: {}", channel.getUID().getId(), e.getMessage());
178 private State queryDataFromDevice(EpsonProjectorCommandType commandType) {
179 EpsonProjectorDevice remoteController = device.get();
182 if (!remoteController.isConnected()) {
183 remoteController.connect();
186 if (!remoteController.isReady()) {
187 logger.debug("Refusing command {} while not ready", commandType.toString());
191 switch (commandType) {
193 Switch autoKeystone = remoteController.getAutoKeystone();
194 return autoKeystone == Switch.ON ? OnOffType.ON : OnOffType.OFF;
196 AspectRatio aspectRatio = remoteController.getAspectRatio();
197 return new StringType(aspectRatio.toString());
199 Background background = remoteController.getBackground();
200 return new StringType(background.toString());
202 int brightness = remoteController.getBrightness();
203 return new DecimalType(brightness);
205 ColorMode colorMode = remoteController.getColorMode();
206 return new StringType(colorMode.toString());
208 int ctemp = remoteController.getColorTemperature();
209 return new DecimalType(ctemp);
211 int contrast = remoteController.getContrast();
212 return new DecimalType(contrast);
214 int density = remoteController.getDensity();
215 return new DecimalType(density);
217 int err = remoteController.getError();
218 return new DecimalType(err);
220 String errString = remoteController.getErrorString();
221 return new StringType(errString);
223 int fleshColor = remoteController.getFleshColor();
224 return new DecimalType(fleshColor);
226 Switch freeze = remoteController.getFreeze();
227 return freeze == Switch.ON ? OnOffType.ON : OnOffType.OFF;
229 Gamma gamma = remoteController.getGamma();
230 return new StringType(gamma.toString());
232 int hKeystone = remoteController.getHorizontalKeystone();
233 return new DecimalType(hKeystone);
235 int hPosition = remoteController.getHorizontalPosition();
236 return new DecimalType(hPosition);
238 Switch hReverse = remoteController.getHorizontalReverse();
239 return hReverse == Switch.ON ? OnOffType.ON : OnOffType.OFF;
243 int lampTime = remoteController.getLampTime();
244 return new DecimalType(lampTime);
246 Luminance luminance = remoteController.getLuminance();
247 return new StringType(luminance.toString());
249 Switch mute = remoteController.getMute();
250 return mute == Switch.ON ? OnOffType.ON : OnOffType.OFF;
252 PowerStatus powerStatus = remoteController.getPowerStatus();
253 if (isLinked(CHANNEL_TYPE_POWERSTATE)) {
254 updateState(CHANNEL_TYPE_POWERSTATE, new StringType(powerStatus.toString()));
257 if (powerStatus == PowerStatus.ON || powerStatus == PowerStatus.WARMUP) {
262 return OnOffType.OFF;
267 return new StringType(remoteController.getSource());
269 int tint = remoteController.getTint();
270 return new DecimalType(tint);
272 int vKeystone = remoteController.getVerticalKeystone();
273 return new DecimalType(vKeystone);
275 // Each volume step falls within several percentage values, only change the UI if the polled step is
276 // different than the step of the current percent. Without this logic the UI would snap back to the
277 // closest whole % value for that step. e.g., UI set to 51% would snap back to 50% on the next
279 int volumeStep = remoteController.getVolume(maxVolume);
280 if (curVolumeStep != volumeStep) {
281 curVolumeStep = volumeStep;
282 return new PercentType(
283 BigDecimal.valueOf(Math.round(curVolumeStep / (double) maxVolume * 100.0)));
287 int vPosition = remoteController.getVerticalPosition();
288 return new DecimalType(vPosition);
290 Switch vReverse = remoteController.getVerticalReverse();
291 return vReverse == Switch.ON ? OnOffType.ON : OnOffType.OFF;
293 logger.warn("Unknown '{}' command!", commandType);
294 return UnDefType.UNDEF;
296 } catch (EpsonProjectorCommandException e) {
297 logger.debug("Error executing command '{}', {}", commandType, e.getMessage());
298 return UnDefType.UNDEF;
299 } catch (EpsonProjectorException e) {
300 logger.debug("Couldn't execute command '{}', {}", commandType, e.getMessage());
305 return UnDefType.UNDEF;
308 private void sendDataToDevice(EpsonProjectorCommandType commandType, Command command) {
309 EpsonProjectorDevice remoteController = device.get();
312 if (!remoteController.isConnected()) {
313 remoteController.connect();
316 if (!remoteController.isReady()) {
317 logger.debug("Refusing command '{}' while not ready", commandType.toString());
321 switch (commandType) {
323 remoteController.setAutoKeystone((command == OnOffType.ON ? Switch.ON : Switch.OFF));
326 remoteController.setAspectRatio(AspectRatio.valueOf(command.toString()));
329 remoteController.setBackground(Background.valueOf(command.toString()));
332 remoteController.setBrightness(((DecimalType) command).intValue());
335 remoteController.setColorMode(ColorMode.valueOf(command.toString()));
338 remoteController.setColorTemperature(((DecimalType) command).intValue());
341 remoteController.setContrast(((DecimalType) command).intValue());
344 remoteController.setDensity(((DecimalType) command).intValue());
347 logger.warn("'{}' is read only parameter", commandType);
350 logger.warn("'{}' is read only parameter", commandType);
353 remoteController.setFleshColor(((DecimalType) command).intValue());
356 remoteController.setFreeze(command == OnOffType.ON ? Switch.ON : Switch.OFF);
359 remoteController.setGamma(Gamma.valueOf(command.toString()));
362 remoteController.setHorizontalKeystone(((DecimalType) command).intValue());
365 remoteController.setHorizontalPosition(((DecimalType) command).intValue());
368 remoteController.setHorizontalReverse((command == OnOffType.ON ? Switch.ON : Switch.OFF));
371 remoteController.sendKeyCode(command.toString());
374 logger.warn("'{}' is read only parameter", commandType);
377 remoteController.setLuminance(Luminance.valueOf(command.toString()));
380 remoteController.setMute((command == OnOffType.ON ? Switch.ON : Switch.OFF));
383 if (command == OnOffType.ON) {
384 remoteController.setPower(Switch.ON);
387 remoteController.setPower(Switch.OFF);
392 logger.warn("'{}' is read only parameter", commandType);
395 remoteController.setSource(command.toString());
398 remoteController.setTint(((DecimalType) command).intValue());
401 remoteController.setVerticalKeystone(((DecimalType) command).intValue());
404 int newVolumeStep = (int) Math.round(((PercentType) command).doubleValue() / 100.0 * maxVolume);
405 if (curVolumeStep != newVolumeStep) {
406 curVolumeStep = newVolumeStep;
407 remoteController.setVolume(curVolumeStep, maxVolume);
411 remoteController.setVerticalPosition(((DecimalType) command).intValue());
414 remoteController.setVerticalReverse((command == OnOffType.ON ? Switch.ON : Switch.OFF));
417 logger.warn("Unknown '{}' command!", commandType);
420 } catch (EpsonProjectorCommandException e) {
421 logger.debug("Error executing command '{}', {}", commandType, e.getMessage());
422 } catch (EpsonProjectorException e) {
423 logger.warn("Couldn't execute command '{}', {}", commandType, e.getMessage());
428 private void closeConnection() {
429 if (device.isPresent()) {
431 logger.debug("Closing connection to device '{}'", this.thing.getUID());
432 device.get().disconnect();
433 updateStatus(ThingStatus.OFFLINE);
434 } catch (EpsonProjectorException e) {
435 logger.debug("Error occurred when closing connection to device '{}'", this.thing.getUID(), e);