]> git.basschouten.com Git - openhab-addons.git/blob
f9171af5ee24ed57e1aba5666c2e72885b209d21
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.epsonprojector.internal.handler;
14
15 import static org.openhab.binding.epsonprojector.internal.EpsonProjectorBindingConstants.*;
16
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;
22
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;
55
56 /**
57  * The {@link EpsonProjectorHandler} is responsible for handling commands, which are
58  * sent to one of the channels.
59  *
60  * @author Pauli Anttila, Yannick Schaus - Initial contribution
61  * @author Michael Lobstein - Improvements for OH3
62  */
63 @NonNullByDefault
64 public class EpsonProjectorHandler extends BaseThingHandler {
65     private static final int DEFAULT_POLLING_INTERVAL_SEC = 10;
66
67     private final Logger logger = LoggerFactory.getLogger(EpsonProjectorHandler.class);
68     private final SerialPortManager serialPortManager;
69
70     private @Nullable ScheduledFuture<?> pollingJob;
71     private Optional<EpsonProjectorDevice> device = Optional.empty();
72
73     private boolean isPowerOn = false;
74     private int maxVolume = 20;
75     private int curVolumeStep = -1;
76     private int pollingInterval = DEFAULT_POLLING_INTERVAL_SEC;
77
78     public EpsonProjectorHandler(Thing thing, SerialPortManager serialPortManager) {
79         super(thing);
80         this.serialPortManager = serialPortManager;
81     }
82
83     @Override
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);
90             }
91         } else {
92             EpsonProjectorCommandType epsonCommand = EpsonProjectorCommandType.getCommandType(channelId);
93             sendDataToDevice(epsonCommand, command);
94         }
95     }
96
97     @Override
98     public void initialize() {
99         EpsonProjectorConfiguration config = getConfigAs(EpsonProjectorConfiguration.class);
100         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
101
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));
106         } else {
107             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
108         }
109
110         maxVolume = config.maxVolume;
111         pollingInterval = config.pollingInterval;
112         device.ifPresent(dev -> dev.setScheduler(scheduler));
113         updateStatus(ThingStatus.UNKNOWN);
114         schedulePollingJob();
115     }
116
117     /**
118      * Schedule the polling job
119      */
120     private void schedulePollingJob() {
121         cancelPollingJob();
122
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);
130                 }
131             }
132         }, 0, (pollingInterval > 0) ? pollingInterval : DEFAULT_POLLING_INTERVAL_SEC, TimeUnit.SECONDS);
133     }
134
135     /**
136      * Cancel the polling job
137      */
138     private void cancelPollingJob() {
139         ScheduledFuture<?> pollingJob = this.pollingJob;
140         if (pollingJob != null) {
141             pollingJob.cancel(true);
142             this.pollingJob = null;
143         }
144     }
145
146     @Override
147     public void dispose() {
148         cancelPollingJob();
149         closeConnection();
150         super.dispose();
151     }
152
153     private void updateChannelState(Channel channel) {
154         try {
155             if (!isLinked(channel.getUID()) && !channel.getUID().getId().equals(CHANNEL_TYPE_POWER)) {
156                 return;
157             }
158
159             EpsonProjectorCommandType epsonCommand = EpsonProjectorCommandType.getCommandType(channel.getUID().getId());
160
161             State state = queryDataFromDevice(epsonCommand);
162
163             if (state != null) {
164                 if (isLinked(channel.getUID())) {
165                     updateState(channel.getUID(), state);
166                 }
167                 // the first valid response will cause the thing to go ONLINE
168                 if (state != UnDefType.UNDEF) {
169                     updateStatus(ThingStatus.ONLINE);
170                 }
171             }
172         } catch (IllegalArgumentException e) {
173             logger.warn("Unknown channel {}, exception: {}", channel.getUID().getId(), e.getMessage());
174         }
175     }
176
177     @Nullable
178     private State queryDataFromDevice(EpsonProjectorCommandType commandType) {
179         EpsonProjectorDevice remoteController = device.get();
180
181         try {
182             if (!remoteController.isConnected()) {
183                 remoteController.connect();
184             }
185
186             if (!remoteController.isReady()) {
187                 logger.debug("Refusing command {} while not ready", commandType.toString());
188                 return null;
189             }
190
191             switch (commandType) {
192                 case AKEYSTONE:
193                     Switch autoKeystone = remoteController.getAutoKeystone();
194                     return autoKeystone == Switch.ON ? OnOffType.ON : OnOffType.OFF;
195                 case ASPECT_RATIO:
196                     AspectRatio aspectRatio = remoteController.getAspectRatio();
197                     return new StringType(aspectRatio.toString());
198                 case BACKGROUND:
199                     Background background = remoteController.getBackground();
200                     return new StringType(background.toString());
201                 case BRIGHTNESS:
202                     int brightness = remoteController.getBrightness();
203                     return new DecimalType(brightness);
204                 case COLOR_MODE:
205                     ColorMode colorMode = remoteController.getColorMode();
206                     return new StringType(colorMode.toString());
207                 case COLOR_TEMP:
208                     int ctemp = remoteController.getColorTemperature();
209                     return new DecimalType(ctemp);
210                 case CONTRAST:
211                     int contrast = remoteController.getContrast();
212                     return new DecimalType(contrast);
213                 case DENSITY:
214                     int density = remoteController.getDensity();
215                     return new DecimalType(density);
216                 case ERR_CODE:
217                     int err = remoteController.getError();
218                     return new DecimalType(err);
219                 case ERR_MESSAGE:
220                     String errString = remoteController.getErrorString();
221                     return new StringType(errString);
222                 case FLESH_TEMP:
223                     int fleshColor = remoteController.getFleshColor();
224                     return new DecimalType(fleshColor);
225                 case FREEZE:
226                     Switch freeze = remoteController.getFreeze();
227                     return freeze == Switch.ON ? OnOffType.ON : OnOffType.OFF;
228                 case GAMMA:
229                     Gamma gamma = remoteController.getGamma();
230                     return new StringType(gamma.toString());
231                 case HKEYSTONE:
232                     int hKeystone = remoteController.getHorizontalKeystone();
233                     return new DecimalType(hKeystone);
234                 case HPOSITION:
235                     int hPosition = remoteController.getHorizontalPosition();
236                     return new DecimalType(hPosition);
237                 case HREVERSE:
238                     Switch hReverse = remoteController.getHorizontalReverse();
239                     return hReverse == Switch.ON ? OnOffType.ON : OnOffType.OFF;
240                 case KEY_CODE:
241                     break;
242                 case LAMP_TIME:
243                     int lampTime = remoteController.getLampTime();
244                     return new DecimalType(lampTime);
245                 case LUMINANCE:
246                     Luminance luminance = remoteController.getLuminance();
247                     return new StringType(luminance.toString());
248                 case MUTE:
249                     Switch mute = remoteController.getMute();
250                     return mute == Switch.ON ? OnOffType.ON : OnOffType.OFF;
251                 case POWER:
252                     PowerStatus powerStatus = remoteController.getPowerStatus();
253                     if (isLinked(CHANNEL_TYPE_POWERSTATE)) {
254                         updateState(CHANNEL_TYPE_POWERSTATE, new StringType(powerStatus.toString()));
255                     }
256
257                     if (powerStatus == PowerStatus.ON || powerStatus == PowerStatus.WARMUP) {
258                         isPowerOn = true;
259                         return OnOffType.ON;
260                     } else {
261                         isPowerOn = false;
262                         return OnOffType.OFF;
263                     }
264                 case POWER_STATE:
265                     return null;
266                 case SOURCE:
267                     return new StringType(remoteController.getSource());
268                 case TINT:
269                     int tint = remoteController.getTint();
270                     return new DecimalType(tint);
271                 case VKEYSTONE:
272                     int vKeystone = remoteController.getVerticalKeystone();
273                     return new DecimalType(vKeystone);
274                 case VOLUME:
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
278                     // polling update.
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)));
284                     }
285                     return null;
286                 case VPOSITION:
287                     int vPosition = remoteController.getVerticalPosition();
288                     return new DecimalType(vPosition);
289                 case VREVERSE:
290                     Switch vReverse = remoteController.getVerticalReverse();
291                     return vReverse == Switch.ON ? OnOffType.ON : OnOffType.OFF;
292                 default:
293                     logger.warn("Unknown '{}' command!", commandType);
294                     return UnDefType.UNDEF;
295             }
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());
301             closeConnection();
302             return null;
303         }
304
305         return UnDefType.UNDEF;
306     }
307
308     private void sendDataToDevice(EpsonProjectorCommandType commandType, Command command) {
309         EpsonProjectorDevice remoteController = device.get();
310
311         try {
312             if (!remoteController.isConnected()) {
313                 remoteController.connect();
314             }
315
316             if (!remoteController.isReady()) {
317                 logger.debug("Refusing command '{}' while not ready", commandType.toString());
318                 return;
319             }
320
321             switch (commandType) {
322                 case AKEYSTONE:
323                     remoteController.setAutoKeystone((command == OnOffType.ON ? Switch.ON : Switch.OFF));
324                     break;
325                 case ASPECT_RATIO:
326                     remoteController.setAspectRatio(AspectRatio.valueOf(command.toString()));
327                     break;
328                 case BACKGROUND:
329                     remoteController.setBackground(Background.valueOf(command.toString()));
330                     break;
331                 case BRIGHTNESS:
332                     remoteController.setBrightness(((DecimalType) command).intValue());
333                     break;
334                 case COLOR_MODE:
335                     remoteController.setColorMode(ColorMode.valueOf(command.toString()));
336                     break;
337                 case COLOR_TEMP:
338                     remoteController.setColorTemperature(((DecimalType) command).intValue());
339                     break;
340                 case CONTRAST:
341                     remoteController.setContrast(((DecimalType) command).intValue());
342                     break;
343                 case DENSITY:
344                     remoteController.setDensity(((DecimalType) command).intValue());
345                     break;
346                 case ERR_CODE:
347                     logger.warn("'{}' is read only parameter", commandType);
348                     break;
349                 case ERR_MESSAGE:
350                     logger.warn("'{}' is read only parameter", commandType);
351                     break;
352                 case FLESH_TEMP:
353                     remoteController.setFleshColor(((DecimalType) command).intValue());
354                     break;
355                 case FREEZE:
356                     remoteController.setFreeze(command == OnOffType.ON ? Switch.ON : Switch.OFF);
357                     break;
358                 case GAMMA:
359                     remoteController.setGamma(Gamma.valueOf(command.toString()));
360                     break;
361                 case HKEYSTONE:
362                     remoteController.setHorizontalKeystone(((DecimalType) command).intValue());
363                     break;
364                 case HPOSITION:
365                     remoteController.setHorizontalPosition(((DecimalType) command).intValue());
366                     break;
367                 case HREVERSE:
368                     remoteController.setHorizontalReverse((command == OnOffType.ON ? Switch.ON : Switch.OFF));
369                     break;
370                 case KEY_CODE:
371                     remoteController.sendKeyCode(command.toString());
372                     break;
373                 case LAMP_TIME:
374                     logger.warn("'{}' is read only parameter", commandType);
375                     break;
376                 case LUMINANCE:
377                     remoteController.setLuminance(Luminance.valueOf(command.toString()));
378                     break;
379                 case MUTE:
380                     remoteController.setMute((command == OnOffType.ON ? Switch.ON : Switch.OFF));
381                     break;
382                 case POWER:
383                     if (command == OnOffType.ON) {
384                         remoteController.setPower(Switch.ON);
385                         isPowerOn = true;
386                     } else {
387                         remoteController.setPower(Switch.OFF);
388                         isPowerOn = false;
389                     }
390                     break;
391                 case POWER_STATE:
392                     logger.warn("'{}' is read only parameter", commandType);
393                     break;
394                 case SOURCE:
395                     remoteController.setSource(command.toString());
396                     break;
397                 case TINT:
398                     remoteController.setTint(((DecimalType) command).intValue());
399                     break;
400                 case VKEYSTONE:
401                     remoteController.setVerticalKeystone(((DecimalType) command).intValue());
402                     break;
403                 case VOLUME:
404                     int newVolumeStep = (int) Math.round(((PercentType) command).doubleValue() / 100.0 * maxVolume);
405                     if (curVolumeStep != newVolumeStep) {
406                         curVolumeStep = newVolumeStep;
407                         remoteController.setVolume(curVolumeStep, maxVolume);
408                     }
409                     break;
410                 case VPOSITION:
411                     remoteController.setVerticalPosition(((DecimalType) command).intValue());
412                     break;
413                 case VREVERSE:
414                     remoteController.setVerticalReverse((command == OnOffType.ON ? Switch.ON : Switch.OFF));
415                     break;
416                 default:
417                     logger.warn("Unknown '{}' command!", commandType);
418                     break;
419             }
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());
424             closeConnection();
425         }
426     }
427
428     private void closeConnection() {
429         if (device.isPresent()) {
430             try {
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);
436             }
437         }
438     }
439 }