]> git.basschouten.com Git - openhab-addons.git/blob
8e8651280743bd303bc72a03f05b1b365e81e6fa
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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;
14
15 import java.time.Duration;
16 import java.util.concurrent.ScheduledExecutorService;
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.epsonprojector.internal.configuration.EpsonProjectorConfiguration;
23 import org.openhab.binding.epsonprojector.internal.connector.EpsonProjectorConnector;
24 import org.openhab.binding.epsonprojector.internal.connector.EpsonProjectorSerialConnector;
25 import org.openhab.binding.epsonprojector.internal.connector.EpsonProjectorTcpConnector;
26 import org.openhab.binding.epsonprojector.internal.enums.AspectRatio;
27 import org.openhab.binding.epsonprojector.internal.enums.Background;
28 import org.openhab.binding.epsonprojector.internal.enums.ColorMode;
29 import org.openhab.binding.epsonprojector.internal.enums.ErrorMessage;
30 import org.openhab.binding.epsonprojector.internal.enums.Gamma;
31 import org.openhab.binding.epsonprojector.internal.enums.Luminance;
32 import org.openhab.binding.epsonprojector.internal.enums.PowerStatus;
33 import org.openhab.binding.epsonprojector.internal.enums.Switch;
34 import org.openhab.core.cache.ExpiringCache;
35 import org.openhab.core.io.transport.serial.SerialPortManager;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 /**
40  * Provide high level interface to Epson projector.
41  *
42  * @author Pauli Anttila - Initial contribution
43  * @author Yannick Schaus - Refactoring
44  * @author Michael Lobstein - Improvements for OH3
45  */
46 @NonNullByDefault
47 public class EpsonProjectorDevice {
48     private static final int[] MAP64 = new int[] { 0, 3, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47, 51, 55, 59, 63, 66,
49             70, 74, 78, 82, 86, 90, 94, 98, 102, 106, 110, 114, 118, 122, 126, 129, 133, 137, 141, 145, 149, 153, 157,
50             161, 165, 169, 173, 177, 181, 185, 189, 192, 196, 200, 204, 208, 212, 216, 220, 224, 228, 232, 236, 240,
51             244, 248, 252 };
52
53     private static final int[] MAP60 = new int[] { 0, 4, 8, 12, 16, 20, 25, 29, 33, 37, 41, 46, 50, 54, 58, 62, 67, 71,
54             75, 79, 83, 88, 92, 96, 100, 104, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 151, 155, 159, 163, 167,
55             172, 176, 180, 184, 188, 193, 197, 201, 205, 209, 214, 218, 222, 226, 230, 235, 239, 243, 247, 251 };
56
57     private static final int[] MAP49 = new int[] { 0, 5, 10, 15, 20, 25, 30, 35, 40, 46, 51, 56, 61, 66, 71, 76, 81, 87,
58             92, 97, 102, 107, 112, 117, 122, 128, 133, 138, 143, 148, 153, 158, 163, 168, 174, 179, 184, 189, 194, 199,
59             204, 209, 215, 220, 225, 230, 235, 240, 245, 250 };
60
61     private static final int[] MAP48 = new int[] { 0, 5, 10, 15, 20, 26, 31, 36, 41, 47, 52, 57, 62, 67, 73, 78, 83, 88,
62             94, 99, 104, 109, 114, 120, 125, 130, 135, 141, 146, 151, 156, 161, 167, 172, 177, 182, 188, 193, 198, 203,
63             208, 214, 219, 224, 229, 235, 240, 245, 250 };
64
65     private static final int[] MAP20 = new int[] { 0, 12, 24, 36, 48, 60, 73, 85, 97, 109, 121, 134, 146, 158, 170, 182,
66             195, 207, 219, 231, 243 };
67
68     private static final int[] MAP18 = new int[] { 0, 13, 26, 40, 53, 67, 80, 94, 107, 121, 134, 148, 161, 175, 188,
69             202, 215, 229, 242 };
70
71     private static final int[] MAP_COLOR_TEMP = new int[] { 0, 25, 51, 76, 102, 128, 153, 179, 204, 230 };
72     private static final int[] MAP_FLESH_COLOR = new int[] { 0, 36, 73, 109, 146, 182, 219 };
73
74     private static final int DEFAULT_TIMEOUT = 5 * 1000;
75     private static final int POWER_ON_TIMEOUT = 100 * 1000;
76     private static final int POWER_OFF_TIMEOUT = 130 * 1000;
77     private static final int LAMP_REFRESH_WAIT_MINUTES = 5;
78
79     private static final String ON = "ON";
80     private static final String ERR = "ERR";
81
82     private final Logger logger = LoggerFactory.getLogger(EpsonProjectorDevice.class);
83
84     private @Nullable ScheduledExecutorService scheduler = null;
85     private @Nullable ScheduledFuture<?> timeoutJob;
86
87     private EpsonProjectorConnector connection;
88     private ExpiringCache<Integer> cachedLampHours = new ExpiringCache<>(Duration.ofMinutes(LAMP_REFRESH_WAIT_MINUTES),
89             this::queryLamp);
90     private boolean connected = false;
91     private boolean ready = true;
92
93     public EpsonProjectorDevice(SerialPortManager serialPortManager, EpsonProjectorConfiguration config) {
94         connection = new EpsonProjectorSerialConnector(serialPortManager, config.serialPort);
95     }
96
97     public EpsonProjectorDevice(EpsonProjectorConfiguration config) {
98         connection = new EpsonProjectorTcpConnector(config.host, config.port);
99     }
100
101     public boolean isReady() {
102         return ready;
103     }
104
105     public void setScheduler(ScheduledExecutorService scheduler) {
106         this.scheduler = scheduler;
107     }
108
109     private synchronized @Nullable String sendQuery(String query, int timeout)
110             throws EpsonProjectorCommandException, EpsonProjectorException {
111         logger.debug("Query: '{}'", query);
112         String response = connection.sendMessage(query, timeout);
113
114         if (response.length() == 0) {
115             throw new EpsonProjectorException("No response received");
116         }
117
118         response = response.replace("\r:", "");
119         logger.debug("Response: '{}'", response);
120
121         if (ERR.equals(response)) {
122             throw new EpsonProjectorCommandException("Error response received for command: " + query);
123         }
124
125         if ("PWR OFF".equals(query) && ":".equals(response)) {
126             // When PWR OFF command is sent, next command can be sent 10 seconds after the colon is received
127             logger.debug("Refusing further commands for 10 seconds to power OFF completion");
128             ready = false;
129             ScheduledExecutorService scheduler = this.scheduler;
130             if (scheduler != null) {
131                 timeoutJob = scheduler.schedule(() -> {
132                     ready = true;
133                 }, 10, TimeUnit.SECONDS);
134             }
135         }
136
137         return response;
138     }
139
140     private String splitResponse(@Nullable String response)
141             throws EpsonProjectorCommandException, EpsonProjectorException {
142         if (response != null && !"".equals(response)) {
143             String[] pieces = response.split("=");
144
145             if (pieces.length < 2) {
146                 throw new EpsonProjectorCommandException("Invalid response from projector: " + response);
147             }
148
149             return pieces[1].trim();
150         } else {
151             throw new EpsonProjectorException("No response received");
152         }
153     }
154
155     protected void sendCommand(String command, int timeout)
156             throws EpsonProjectorCommandException, EpsonProjectorException {
157         sendQuery(command, timeout);
158     }
159
160     protected void sendCommand(String command) throws EpsonProjectorCommandException, EpsonProjectorException {
161         sendCommand(command, DEFAULT_TIMEOUT);
162     }
163
164     protected int queryInt(String query, int timeout, int radix)
165             throws EpsonProjectorCommandException, EpsonProjectorException {
166         String response = sendQuery(query, timeout);
167
168         String str = splitResponse(response);
169
170         // if the response has two number groups, get the first one (Aspect Ratio does this)
171         if (str.contains(" ")) {
172             String[] subStr = str.split(" ");
173             str = subStr[0];
174         }
175
176         return Integer.parseInt(str, radix);
177     }
178
179     protected int queryInt(String query, int timeout) throws EpsonProjectorCommandException, EpsonProjectorException {
180         return queryInt(query, timeout, 10);
181     }
182
183     protected int queryInt(String query) throws EpsonProjectorCommandException, EpsonProjectorException {
184         return queryInt(query, DEFAULT_TIMEOUT, 10);
185     }
186
187     protected int queryHexInt(String query, int timeout)
188             throws EpsonProjectorCommandException, EpsonProjectorException {
189         return queryInt(query, timeout, 16);
190     }
191
192     protected int queryHexInt(String query) throws EpsonProjectorCommandException, EpsonProjectorException {
193         return queryInt(query, DEFAULT_TIMEOUT, 16);
194     }
195
196     protected String queryString(String query) throws EpsonProjectorCommandException, EpsonProjectorException {
197         String response = sendQuery(query, DEFAULT_TIMEOUT);
198         return splitResponse(response);
199     }
200
201     public void connect() throws EpsonProjectorException {
202         connection.connect();
203         connected = true;
204     }
205
206     public void disconnect() throws EpsonProjectorException {
207         connection.disconnect();
208         connected = false;
209         ScheduledFuture<?> timeoutJob = this.timeoutJob;
210         if (timeoutJob != null) {
211             timeoutJob.cancel(true);
212             this.timeoutJob = null;
213             ready = true;
214         }
215     }
216
217     public boolean isConnected() {
218         return connected;
219     }
220
221     /*
222      * Power
223      */
224     public PowerStatus getPowerStatus() throws EpsonProjectorCommandException, EpsonProjectorException {
225         int val = queryInt("PWR?");
226         return PowerStatus.forValue(val);
227     }
228
229     public void setPower(Switch value) throws EpsonProjectorCommandException, EpsonProjectorException {
230         sendCommand(String.format("PWR %s", value.name()), value == Switch.ON ? POWER_ON_TIMEOUT : POWER_OFF_TIMEOUT);
231     }
232
233     /*
234      * Key code
235      */
236     public void sendKeyCode(String value) throws EpsonProjectorCommandException, EpsonProjectorException {
237         sendCommand(String.format("KEY %s", value));
238     }
239
240     /*
241      * Vertical Keystone
242      */
243     public int getVerticalKeystone() throws EpsonProjectorCommandException, EpsonProjectorException {
244         int vkey = queryInt("VKEYSTONE?");
245         for (int i = 0; i < MAP60.length; i++) {
246             if (vkey == MAP60[i]) {
247                 return i - 30;
248             }
249         }
250         return 0;
251     }
252
253     public void setVerticalKeystone(int value) throws EpsonProjectorCommandException, EpsonProjectorException {
254         value = value + 30;
255         if (value >= 0 && value <= 60) {
256             sendCommand(String.format("VKEYSTONE %d", MAP60[value]));
257         }
258     }
259
260     /*
261      * Horizontal Keystone
262      */
263     public int getHorizontalKeystone() throws EpsonProjectorCommandException, EpsonProjectorException {
264         int hkey = queryInt("HKEYSTONE?");
265         for (int i = 0; i < MAP60.length; i++) {
266             if (hkey == MAP60[i]) {
267                 return i - 30;
268             }
269         }
270         return 0;
271     }
272
273     public void setHorizontalKeystone(int value) throws EpsonProjectorCommandException, EpsonProjectorException {
274         value = value + 30;
275         if (value >= 0 && value <= 60) {
276             sendCommand(String.format("HKEYSTONE %d", MAP60[value]));
277         }
278     }
279
280     /*
281      * Auto Keystone
282      */
283
284     public Switch getAutoKeystone() throws EpsonProjectorCommandException, EpsonProjectorException {
285         String val = queryString("AUTOKEYSTONE?");
286         return val.equals(ON) ? Switch.ON : Switch.OFF;
287     }
288
289     public void setAutoKeystone(Switch value) throws EpsonProjectorCommandException, EpsonProjectorException {
290         sendCommand(String.format("AUTOKEYSTONE %s", value.name()), DEFAULT_TIMEOUT);
291     }
292
293     /*
294      * Freeze
295      */
296     public Switch getFreeze() throws EpsonProjectorCommandException, EpsonProjectorException {
297         String val = queryString("FREEZE?");
298         return val.equals(ON) ? Switch.ON : Switch.OFF;
299     }
300
301     public void setFreeze(Switch value) throws EpsonProjectorCommandException, EpsonProjectorException {
302         sendCommand(String.format("FREEZE %s", value.name()), DEFAULT_TIMEOUT);
303     }
304
305     /*
306      * Aspect Ratio
307      */
308     public AspectRatio getAspectRatio() throws EpsonProjectorCommandException, EpsonProjectorException {
309         int val = queryHexInt("ASPECT?");
310         return AspectRatio.forValue(val);
311     }
312
313     public void setAspectRatio(AspectRatio value) throws EpsonProjectorCommandException, EpsonProjectorException {
314         sendCommand(String.format("ASPECT %02X", value.toInt()));
315     }
316
317     /*
318      * Luminance
319      */
320     public Luminance getLuminance() throws EpsonProjectorCommandException, EpsonProjectorException {
321         int val = queryHexInt("LUMINANCE?");
322         return Luminance.forValue(val);
323     }
324
325     public void setLuminance(Luminance value) throws EpsonProjectorCommandException, EpsonProjectorException {
326         sendCommand(String.format("LUMINANCE %02X", value.toInt()));
327     }
328
329     /*
330      * Source
331      */
332     public String getSource() throws EpsonProjectorCommandException, EpsonProjectorException {
333         return queryString("SOURCE?");
334     }
335
336     public void setSource(String value) throws EpsonProjectorCommandException, EpsonProjectorException {
337         sendCommand(String.format("SOURCE %s", value));
338     }
339
340     /*
341      * Brightness
342      */
343     public int getBrightness() throws EpsonProjectorCommandException, EpsonProjectorException {
344         int brt = queryInt("BRIGHT?");
345         for (int i = 0; i < MAP48.length; i++) {
346             if (brt == MAP48[i]) {
347                 return i - 24;
348             }
349         }
350         return 0;
351     }
352
353     public void setBrightness(int value) throws EpsonProjectorCommandException, EpsonProjectorException {
354         value = value + 24;
355         if (value >= 0 && value <= 48) {
356             sendCommand(String.format("BRIGHT %d", MAP48[value]));
357         }
358     }
359
360     /*
361      * Contrast
362      */
363     public int getContrast() throws EpsonProjectorCommandException, EpsonProjectorException {
364         int con = queryInt("CONTRAST?");
365         for (int i = 0; i < MAP48.length; i++) {
366             if (con == MAP48[i]) {
367                 return i - 24;
368             }
369         }
370         return 0;
371     }
372
373     public void setContrast(int value) throws EpsonProjectorCommandException, EpsonProjectorException {
374         value = value + 24;
375         if (value >= 0 && value <= 48) {
376             sendCommand(String.format("CONTRAST %d", MAP48[value]));
377         }
378     }
379
380     /*
381      * Density
382      */
383     public int getDensity() throws EpsonProjectorCommandException, EpsonProjectorException {
384         int den = queryInt("DENSITY?");
385         for (int i = 0; i < MAP64.length; i++) {
386             if (den == MAP64[i]) {
387                 return i - 32;
388             }
389         }
390         return 0;
391     }
392
393     public void setDensity(int value) throws EpsonProjectorCommandException, EpsonProjectorException {
394         value = value + 32;
395         if (value >= 0 && value <= 64) {
396             sendCommand(String.format("DENSITY %d", MAP64[value]));
397         }
398     }
399
400     /*
401      * Tint
402      */
403     public int getTint() throws EpsonProjectorCommandException, EpsonProjectorException {
404         int tint = queryInt("TINT?");
405         for (int i = 0; i < MAP64.length; i++) {
406             if (tint == MAP64[i]) {
407                 return i - 32;
408             }
409         }
410         return 0;
411     }
412
413     public void setTint(int value) throws EpsonProjectorCommandException, EpsonProjectorException {
414         value = value + 32;
415         if (value >= 0 && value <= 64) {
416             sendCommand(String.format("TINT %d", MAP64[value]));
417         }
418     }
419
420     /*
421      * Color Temperature
422      */
423     public int getColorTemperature() throws EpsonProjectorCommandException, EpsonProjectorException {
424         int ctemp = queryInt("CTEMP?");
425         for (int i = 0; i < MAP_COLOR_TEMP.length; i++) {
426             if (ctemp == MAP_COLOR_TEMP[i]) {
427                 return i;
428             }
429         }
430         return 0;
431     }
432
433     public void setColorTemperature(int value) throws EpsonProjectorCommandException, EpsonProjectorException {
434         if (value >= 0 && value <= 9) {
435             sendCommand(String.format("CTEMP %d", MAP_COLOR_TEMP[value]));
436         }
437     }
438
439     /*
440      * Flesh Color
441      */
442     public int getFleshColor() throws EpsonProjectorCommandException, EpsonProjectorException {
443         int fclr = queryInt("FCOLOR?");
444         for (int i = 0; i < MAP_FLESH_COLOR.length; i++) {
445             if (fclr == MAP_FLESH_COLOR[i]) {
446                 return i;
447             }
448         }
449         return 0;
450     }
451
452     public void setFleshColor(int value) throws EpsonProjectorCommandException, EpsonProjectorException {
453         if (value >= 0 && value <= 6) {
454             sendCommand(String.format("FCOLOR %d", MAP_FLESH_COLOR[value]));
455         }
456     }
457
458     /*
459      * Color Mode
460      */
461     public ColorMode getColorMode() throws EpsonProjectorCommandException, EpsonProjectorException {
462         int val = queryHexInt("CMODE?");
463         return ColorMode.forValue(val);
464     }
465
466     public void setColorMode(ColorMode value) throws EpsonProjectorCommandException, EpsonProjectorException {
467         sendCommand(String.format("CMODE %02X", value.toInt()));
468     }
469
470     /*
471      * Horizontal Position
472      */
473     public int getHorizontalPosition() throws EpsonProjectorCommandException, EpsonProjectorException {
474         int hpos = queryInt("HPOS?");
475         for (int i = 0; i < MAP49.length; i++) {
476             if (hpos == MAP49[i]) {
477                 return i - 23;
478             }
479         }
480         return 0;
481     }
482
483     public void setHorizontalPosition(int value) throws EpsonProjectorCommandException, EpsonProjectorException {
484         value = value + 23;
485         if (value >= 0 && value <= 49) {
486             sendCommand(String.format("HPOS %d", MAP49[value]));
487         }
488     }
489
490     /*
491      * Vertical Position
492      */
493     public int getVerticalPosition() throws EpsonProjectorCommandException, EpsonProjectorException {
494         int vpos = queryInt("VPOS?");
495         for (int i = 0; i < MAP18.length; i++) {
496             if (vpos == MAP18[i]) {
497                 return i - 8;
498             }
499         }
500         return 0;
501     }
502
503     public void setVerticalPosition(int value) throws EpsonProjectorCommandException, EpsonProjectorException {
504         value = value + 8;
505         if (value >= 0 && value <= 18) {
506             sendCommand(String.format("VPOS %d", MAP18[value]));
507         }
508     }
509
510     /*
511      * Gamma
512      */
513     public Gamma getGamma() throws EpsonProjectorCommandException, EpsonProjectorException {
514         int val = queryHexInt("GAMMA?");
515         return Gamma.forValue(val);
516     }
517
518     public void setGamma(Gamma value) throws EpsonProjectorCommandException, EpsonProjectorException {
519         sendCommand(String.format("GAMMA %02X", value.toInt()));
520     }
521
522     /*
523      * Volume
524      */
525     public int getVolume() throws EpsonProjectorCommandException, EpsonProjectorException {
526         int vol = queryInt("VOL?");
527         for (int i = 0; i < MAP20.length; i++) {
528             if (vol == MAP20[i]) {
529                 return i;
530             }
531         }
532         return 0;
533     }
534
535     public void setVolume(int value) throws EpsonProjectorCommandException, EpsonProjectorException {
536         if (value >= 0 && value <= 20) {
537             sendCommand(String.format("VOL %d", MAP20[value]));
538         }
539     }
540
541     /*
542      * AV Mute
543      */
544     public Switch getMute() throws EpsonProjectorCommandException, EpsonProjectorException {
545         String val = queryString("MUTE?");
546         return val.equals(ON) ? Switch.ON : Switch.OFF;
547     }
548
549     public void setMute(Switch value) throws EpsonProjectorCommandException, EpsonProjectorException {
550         sendCommand(String.format("MUTE %s", value.name()));
551     }
552
553     /*
554      * Horizontal Reverse
555      */
556     public Switch getHorizontalReverse() throws EpsonProjectorCommandException, EpsonProjectorException {
557         String val = queryString("HREVERSE?");
558         return val.equals(ON) ? Switch.ON : Switch.OFF;
559     }
560
561     public void setHorizontalReverse(Switch value) throws EpsonProjectorCommandException, EpsonProjectorException {
562         sendCommand(String.format("HREVERSE %s", value.name()));
563     }
564
565     /*
566      * Vertical Reverse
567      */
568     public Switch getVerticalReverse() throws EpsonProjectorCommandException, EpsonProjectorException {
569         String val = queryString("VREVERSE?");
570         return val.equals(ON) ? Switch.ON : Switch.OFF;
571     }
572
573     public void setVerticalReverse(Switch value) throws EpsonProjectorCommandException, EpsonProjectorException {
574         sendCommand(String.format("VREVERSE %s", value.name()));
575     }
576
577     /*
578      * Background Select for AV Mute
579      */
580     public Background getBackground() throws EpsonProjectorCommandException, EpsonProjectorException {
581         int val = queryHexInt("MSEL?");
582         return Background.forValue(val);
583     }
584
585     public void setBackground(Background value) throws EpsonProjectorCommandException, EpsonProjectorException {
586         sendCommand(String.format("MSEL %02X", value.toInt()));
587     }
588
589     /*
590      * Lamp Time (hours) - get from cache
591      */
592     public int getLampTime() throws EpsonProjectorCommandException, EpsonProjectorException {
593         Integer lampHours = cachedLampHours.getValue();
594
595         if (lampHours != null) {
596             return lampHours.intValue();
597         } else {
598             throw new EpsonProjectorCommandException("cachedLampHours returned null");
599         }
600     }
601
602     /*
603      * Get Lamp Time
604      */
605     private @Nullable Integer queryLamp() {
606         try {
607             return Integer.valueOf(queryInt("LAMP?"));
608         } catch (EpsonProjectorCommandException | EpsonProjectorException e) {
609             logger.debug("Error executing command LAMP?", e);
610             return null;
611         }
612     }
613
614     /*
615      * Error Code
616      */
617     public int getError() throws EpsonProjectorCommandException, EpsonProjectorException {
618         return queryHexInt("ERR?");
619     }
620
621     /*
622      * Error Code Description
623      */
624     public String getErrorString() throws EpsonProjectorCommandException, EpsonProjectorException {
625         int err = queryInt("ERR?");
626         return ErrorMessage.forCode(err);
627     }
628 }